Gui mockup
Articles » Rendering DataObjects as pages using controller actions

Rendering DataObjects as pages using controller actions

16 April, 2020

This is a quick guide on how to render a DataObject as a page. We’ll cover the following areas:

  1. Creating a page Link for each DataObject.
  2. Creating a controller action to handle the rendering of a DataObject.
  3. Creating a template that will render the DataObject.

Our example will be based on the following ProductsPage which ‘has_many’ Products.

class ProductsPage extends Page {
    private static $table_name = 'ProductsPage';
    private static $has_many = [
        'Products' => Product::class

    public function getCMSFields() {
        $fields = parent::getCMSFields();
        $fields->addFieldsToTab('Root.Products', [

        return $fields;


And here’s the ProductsPageController which is empty for now, but we’ll soon write our method to render each Product DataObject onto its own template here.

class ProductsPageController extends PageController {



Here’s what the Product DataObject looks like, a simple DataObject with only 2 database fields ‘Name’ and ‘Price’, and a ‘has_one’ relation that ties it back to the ProductsPage.

class Product extends DataObject {

    private static $table_name = "Product";

    private static $has_one = [
        'ProductPage' => ProductsPage::class

    private static $db = [
        'Name' => 'Text',
        'Price' => 'Varchar'

    public function getCMSFields() {
        $fields = parent::getCMSFields();

        $fields->addFieldsToTab('Root.Main', [
        return $fields;



Creating a page Link for each DataObject

Let’s add a method called ‘Link’ inside the Product DataObject class which will generate a unique link for each product.

public function Link() {
    return $this->ProductPage()->Link('product/'.$this->ID);


Now with what we have above and after rebuilding our database and flushing the cache with ‘dev/build?flush=all’, we can loop all Product DataObjects onto the template like so

<% loop $Products %>
    <div class="product">
        <a href="$Link">
<% end_loop %>

Notice the $Link used in the loop which is referencing the ‘Link’ method created earlier in the Product DataObject class.

Creating a controller action to handle the rendering of each DataObject

Now we can write our method to render each Product onto its own template in the ProductsPageController. We’ll call this method ‘product’ to match exactly what the $Link inside the loop is outputting (‘/product/productID’). We’ll also whitelist the method ‘product’ by adding it to the ‘$allowed_actions’ array shown below

class ProductsPageController extends PageController 
    private static $allowed_actions = [

    public function product(HTTPRequest $request) {
        $product = Product::get_by_id($request->param('ID'));

        if (!$product) {
            return $this->httpError(404, 'Product not found');

        return [
            'Product' => $product

Now flush the cache by appending ‘?flush=all’ to the end of our URL.

Creating a template to render each DataObject

The last step is to create a template that should render each Product. For this to work our template name has to follow this convention ‘’, so let’s name ours ‘’ and put the following inside it.

<% with $Product %>
    <span>ID: $ID</span>
    <span>Name: $Name</span>
    <span>Price: $Price</span>
<% end_with %>

Now introduce the new template by flushing the cache with “?flush=1” at the end of your URL and you should now be able to click on each Product and see it in its own template.


This guide is inspired by Silverstripe’s tutorial on Controller Actions and DataObjects as pages which covers the topic in deeper detail.

Post your comment


No one has commented on this page yet.

RSS feed for comments on this page | RSS feed for all comments