Skip to main content

This site requires you to update your browser. Your browsing experience maybe affected by not having the most up to date version.

9th January 2015

In this tutorial we’re going to focus on the Travel Guides section of our website for this topic. As we can see in the designs, there are two templates required for this section -- a list view, or “hub” page, and a detail page. This is one of the most common patterns in web content, used throughout the web; think of news listings, image galleries, even a Twitter timeline.. To accomplish this in SilverStripe, we’ll use a very common convention.

In this tutorial we’re going to focus on the Travel Guides section of our website for this topic. As we can see in the designs, there are two templates required for this section -- a list view, or “hub” page, and a detail page. This is one of the most common patterns in web content, used throughout the web; think of news listings, image galleries, even a Twitter timeline.. To accomplish this in SilverStripe, we’ll use a very common convention.

The idea is simple. One page type manages the list view, and usually contains very little native content. The primary function of this page is to provide a list of its child pages, providing a brief summary for each one, along with a link to its detail view. A second page type will represent the detail view for any given child page, which will typically have a custom template and content fields that make up its identity.

Creating the page types

By convention, not enforcement, the list view and the detail view are named with the suffixes Holder and Page, respectively, and the name of our content type takes the prefix. In this case, our Travel Guides section features a type of content that we’ll call “Article,” so our page types will be named ArticleHolder and ArticlePage.

In the last episode, we created our first custom page type, HomePage. Let’s repeat that process, and create a page type called ArticleHolder. To recap, we need a PHP file called ArticleHolder.php in our mysite/code directory that contains a pair of empty classes, like so:

class ArticleHolder extends Page {


class ArticleHolder_Controller extends Page_Controller {


Likewise, we’ll do the same thing for ArticlePage.

class ArticlePage extends Page {


class ArticlePage_Controller extends Page_Controller {


We’ll also need templates for these pages. In your theme directory, copy the contents of static/article-holder.html to templates/Layout/, and static/article-page.html to templates/Layout/

Notice that our designer has made things easier for us by only including what belongs in our $Layout template. We still have to add our common variables, though. Before we go any further, let’s add $Title, $Content, and $Breadcrumbs to their appropriate places.

Also, while we’re at it, let’s tidy up a bit and move the breadcrumbs and title section (Marked <div class="parallax colored-bg pattern-bg">) to its own include template, which we’ll call Don’t forget to update your templates/Layout/ file, as well.

Since we’ve created two new page types and two new templates, we’ll need to build the database and flush the template cache before proceeding. We can do this all at once by visiting /dev/build?flush in the browser. Make sure you see some blue text showing that SiteTree.ClassName was updated and the new page types displayed in green.

Let’s go into the CMS and add these new page types to the site tree. This is probably a good time to build out the primary navigation of our site. Delete all of the pages except “Home” and “About Us.” Then, build out the structure with the following Titles and page types:

  • Home (HomePage)
  • Find a Rental (Page)
  • List Your Rental (Page)
  • Regions (Page)
  • Travel Guides (ArticleHolder)
  • About Us (Page)

Note that in order to get the “About Us” page to the bottom, you will have to use drag-and-drop.

Now that we have our new ArticleHolder page in place, create some child pages underneath it. These should use the page type ArticlePage. Feel free to title them any way you like.

Let’s go back to our site, and visit the Travel Guides section. This looks good, but obviously, we don’t want static content here. We want these articles to reflect all of the child pages in this section.

To accomplish this, we’ll use a template method that is afforded to all pages in the SiteTree, called $Children. This method returns a list of all the pages whose parent is the current page. Because it returns a list, we’ll need a <% loop %> control.

    <% loop $Children %>
    <div class="item col-md-6">
      <div class="image">
        <a href="blog-detail.html"> 
          <span class="btn btn-default">Read More</span>
        <img src="" alt="" />
      </div> <div class="tag"><i class="fa fa-file-text"></i></div>
     <div class="info-blog">
       <ul class="top-info">
         <li><i class="fa fa-calendar"></i> July 30, 2014</li>
         <li><i class="fa fa-comments-o"></i> 2</li>
         <li><i class="fa fa-tags"></i> Properties, Prices, best deals</li>
      <h3><a href="blog-detail.html">How to get your dream property for the best price?>/a></h3>
      <p>Sed rutrum urna id tellus euismod gravida. Praesent placerat, mauris ac pellentesque fringilla, tortor libero condimen. Aliquam fermem tum nulla felis, sed molestie libero porttitor in.</p>
    <% end_loop %>

When we step into a loop, the scope of all of our variables is set to the current iteration of the loop. That is, accessing the $Title property inside the loop will not return the property of the current page, Travel Guides, but rather the titles of each child page.

Let’s go through the loop and replace any static content with variables that we know to exist on the child pages.

  • Replace the title with $Title
  • Replace the link to blog-detail.html with $Link

Everything else can be left static for now.

Note that any pages that have Show in menus unchecked will be hidden. To show all children, regardless of their Show in menus state, we can use $AllChildren.

There’s a lot left undone on this page that we’ll leave as static, especially in the sidebar, but we’ll be picking it apart over the course of several tutorials.

Refresh the page and see that we’re generating some dynamic content. Try clicking on a link and see that it takes you to a detail page for the article. The detail page is still hardcoded. Let’s fill in the $Title, and $Content variables where they belong.

Applying hierarchical constraints

Lest we forget, ultimately what we’re building is an experience for a content editor. It’s easy to forget this as a developer when you’re just focused on getting frontend functionality to work as it’s supposed to. One of the hallmarks of a great CMS developer is attention to the user experience in the CMS.

Let’s take a moment to assume the role of a content author, and imagine that we want to create a new article. Going through this process, we notice two glaring usability hazards:

  • The user can add an ArticlePage anywhere in the site tree
  • When adding a child page to Travel Guides, the user has to know to use the ArticlePage page type.

SilverStripe offers an API for hierarchical constraints that will enforce that pattern that we require. Let’s start by ensuring that only ArticlePage can be created underneath Travel Guides. Add the following to your ArticleHolder class:

    private static $allowed_children = array ('ArticlePage');

$allowed_children is just what it sounds like. It’s a list of the page types that are allowed to be created under this page type. Note that a scalar value is also accepted here if you have only one page type.

There’s a bit of an oddity we need to cover when modifying private static class variables. In SilverStripe, private statics are functionally the same as updates to the config YAML files, which is to say we could have just as easily applied our $allowed_children setting in our config file like so:

    - ArticlePage

Whichever way you choose is a matter of preference. Historically, these types of common settings on pages have been set as static variables, as the config API wasn’t introduced until version 3.0, so for consistency, it makes sense to use a static variable in this case, but again, it is functionally identical.

Because of this overlap, just like in the config API, private static variable changes do not apply until we flush the cache. If it seems odd that you have to flush the cache after making a simple change to your class definition, that’s a good sign. You’re normal. This is an idiosyncrasy of the SilverStripe framework, and without getting into the specifics in this tutorial, suffice it to say it’s just one of those things you have to be aware of.

Let’s flush the cache, by appending ?flush to the URL, and go back into the CMS. Try adding an incorrect page type under Travel Guides. Notice that the dropdown is forcing the ArticlePage page type on us.

But what about pages at the root level? Because there is no page type for the root, we can’t specify $allowed_children. For this, we can use the boolean variable $can_be_root on our ArticlePage class, and flush the cache.

    private static $can_be_root = false;

Now if we try to create an ArticlePage at the root level of the site tree, the action is disallowed.

Now if we go through the process of creating a new ArticlePage, it feels much more forgiving and streamlined.

Questions and Feedback

Hi there,

I seem to be missing the Article-Holder.html and Article-Page.html pages under my static folder. Where can I grab these from?


by Gemma at 02:40pm, 25 March 2015


Sorry about that! I've added them as a separate download to the lesson. Right at the top of the tutorial, you'll now see a download button.

by UncleCheese at 02:53pm, 25 March 2015

Hello UncleCheese,

Thanks for all your tutorial they are all very nice. I'm currently looking for a CMS to redo my website and I quite like this one.

So regarding to this tutorial I have one question. It's not clear here if there is a link between the content of the Holder page and the detail page. So say you want to have the first 2-3 sentences of your detail page in the holder page, you have to do a copy past? Maybe I don't get the point of the holder page (meaning that you actually have a different text, like a summary of the detail page).


by Rio Grande at 10:23am, 19 May 2015


Hi, rio,

Good question (and good name, if that's your real name!). I'm not sure I articulated it well, but what we're offering to the CMS user is the ability to customise the content that shows on the holder page. By default, it will just use the first sentence of the detail page, but often times content authors want to customise that content (a field we're calling $Teaser in this case), so we've offered that as an option, with a fallback. That way, they get the benefit of being able to customise, without the obligation to fill it out and risk leaving an empty space.

Does that make sense?

by UncleCheese at 04:26pm, 20 May 2015


When I add this to the ArticleHolder.php: private static $allowed_children = array (‘ArticlePage’);

I get this error after I flush the cache: Notice: Use of undefined constant ‘ArticlePage’ - assumed '‘ArticlePage’' in /Applications/MAMP/htdocs/tutorial/framework/core/manifest/ConfigStaticManifest.php(371) : eval()'d code on line 1

And when I go to admin, there are now no "add new page here" options for Travel Guides.

Any idea why this error is coming up?



by bob at 07:09am, 28 May 2015


Hi, Bob,

Are you using curly quotes like that in your code? That won't get parsed in PHP. Make sure you use straight quotes only. ' ArticlePage'

by UncleCheese at 04:04pm, 28 May 2015

Thanks. Good grab...not sure how the curly quotes got in there (I had just copy and pasted from the tutorial).

by Bob at 06:46am, 29 May 2015

Hi there, Is there an easy way to make the new page types only available to the relevant section. For example when I create a new page under the Article Holder page then the only option is Article Page. Which is great, but on any other page I can still choose Article Page. I know I could go through each "Page" type and add the restrictions. But, wondering if there is a better way to do this. Thanks Adam

by Adam at 04:39pm, 11 August 2015

Not sure if a scaler will still work in the $allowed_children. At least, it didn't work for me. SS 3.1.9

by Troy Dalmasso at 06:31pm, 4 September 2015


if I add private static $can_be_root = false; in the ArticlePage.php and also add : ArticleHolder: allowed_children: -ArticlePage

in the config.yml file

then could not create a ArticlePage as a root when should we set in variable in php file and config.yml? or each way is ok?


by flymoon at 12:07pm, 26 September 2015


Great question. Yeah, this is confusing topic, and I talk briefly about it in one of these lessons (it may even be this one).

99% of the time, these are interchangeable:

class MyClass extends DataObject {
    private static $foo = 'bar';
  foo: bar

The only general distinction I can make, as a matter of best practice, is that variables that are commonly used in DataObject definitions, such as $db, $has_one, $has_many, $allowed_children, etc., should still be defined as such. It's not necessary, but that tends to be the practice that developers follow. Should another developer ever get involved in your project, he or she would expect to see those variable definitions as private statics, but again, using YAML is programatically the same.

by UncleCheese at 10:42am, 30 September 2015

Is there a way to disable child pages to an ArticlePage page? For instance, at the moment, although the ArticlePage is the only pagetype available as a child for an ArticleHolder page, any pagetype can be used under an ArticlePage page.

I've briefly tried using one of the two:

private static $allowed_children = false; private static $allowed_children = array('');

neither of which work, and I assume that entering a 'fake' page type in the latter won't work either (I'd probably best check that one...!).

Apologies if this is something that crops up in a later tutorial, but it was just something I noticed happened while testing out the restrictions, and knowing end-users as I do, it's something they'll somehow do...

by Richard at 11:39am, 27 October 2015


You've almost got it. :)

private static $allowed_children = 'none';

by UncleCheese at 12:28pm, 27 October 2015

I followed your tutorial closely and realized that after creating ArticleHolder.php and ArticlePage.php you dev/build/?flush. I did that too but had only blue texts and no green text. I realized that might be the reason for nothing showing up when I create an ArticlePage type in my cms. How do I solve this. Thanks

by Samuel at 02:00am, 3 December 2015

Hi, Samuel,

The blue text is the expected result. You haven't created any new database fields, so there is no need for a new ArticlePage or ArticleHolder table. Rather, all you're going to get is a new entry in the ClassName field (an Enum) for SiteTree. When the page gets its own fields, at that point, it will spin off a dedicated table.

Not being able to create the page is also the expected result if you are in the root level. See the $can_be_root that we added above. This is to prevent the creation of orphaned ArticlePages. They need to be under an ArticleHolder.

by UncleCheese at 10:47am, 19 January 2016

hi, how can i create a pagetype to show chinese please? thanks!

by Ray at 06:33pm, 7 January 2016

Hi. How do I make the article holder as my home page?


by jess at 07:03pm, 27 February 2016

Hi, Jess,

SilverStripe will use any page with the URL /home/ as your home page.

by UncleCheese at 03:57pm, 9 March 2016

I did the whole tutorial except for the part with the loop: <% loop $Children %> which does not seem to work. I downloaded two files from The results I am getting does not match the video tutorial. I am not seeing the text "How to get your dream property for the best price?" Are there files that I am missing? Is there some kind of mismatch between the text tutorial and the video tutorial?

by Dmitry Sergeev at 09:52am, 6 December 2016

Stuck on something? Have something to share? Don't be shy!

Keep learning!

Working with Multiple Templates

Typically a site has more than one distinct template used across all pages. The home page, for instance, is likely to have a different layout than...

Adding Dynamic Content

In this tutorial, we’ll start using SilverStripe’s native template syntax to inject dynamic content into our site, such as navigation, page titles, CMS content, and...

Migrating static templates into your theme

In this tutorial, we’ll migrate a static site into your SilverStripe project. HTML, CSS, and JavaScript will provide a nice common ground and starting point for...