How to extend the CMS interface
Introduction
The CMS interface works just like any other part of your website: It consists of PHP controllers, templates, CSS stylesheets and JavaScript. Because it uses the same base elements, it is relatively easy to extend.
As an example, we're going to add a permanent "bookmarks" link list to popular pages into the main CMS menu. A page can be bookmarked by a CMS author through a simple checkbox.
For a deeper introduction to the inner workings of the CMS, please refer to our guide on CMS Architecture.
Override a CMS template
If you place a template with an identical name into your application template
directory (usually app/templates/
), it'll take priority over the built-in
one.
CMS templates are inherited based on their controllers, similar to subclasses of
the common Page
object (a new PHP class MyPage
will look for a template named MyPage
).
We can use this to create a different base template named LeftAndMain
(which corresponds to the LeftAndMain
PHP controller class).
Copy the template markup of the base implementation in the templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList
template from the silverstripe/admin
module
into a new app/templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList
template. It will automatically be picked up by
the CMS logic. Add a new section into the <ul class="cms-menu__list">
...
<ul class="cms-menu-list">
<!-- ... -->
<li class="bookmarked-link first">
<a href="$AdminURL('pages/edit/show/1')">Edit "My popular page"</a>
</li>
<li class="bookmarked-link last">
<a href="$AdminURL('pages/edit/show/99')">Edit "My other page"</a>
</li>
</ul>
...
Refresh the CMS interface with admin/?flush=all
, and you should see those
hardcoded links underneath the left-hand menu. We'll make these dynamic further down.
Include custom CSS in the CMS
In order to show the links a bit separated from the other menu entries,
we'll add some CSS, and get it to load
with the CMS interface. Paste the following content into a new file called
app/css/BookmarkedPages.css
:
.bookmarked-link.first {margin-top: 1em;}
Load the new CSS file into the CMS, by setting the LeftAndMain.extra_requirements_css
configuration value.
SilverStripe\Admin\LeftAndMain:
extra_requirements_css:
- app/css/BookmarkedPages.css
In order to let the frontend have the access to our css
files, we need to expose
them in the composer.json
:
"extra": {
"expose": [
"app/css"
]
}
Then run composer vendor-expose
. This command will publish all the css
files under the app/css
folder to their public-facing paths.
Note: don't forget to
flush
.
Create a "bookmark" flag on pages
Now we'll define which pages are actually bookmarked, a flag that is stored in
the database. For this we need to decorate the page record with an
Extension
. Create a new file called app/src/BookmarkedPageExtension.php
and insert the following code.
namespace App\Extension;
use SilverStripe\Core\Extension;
class BookmarkedPageExtension extends Extension
{
private static array $db = [
'IsBookmarked' => 'Boolean',
];
private static array $field_labels = [
'IsBookmarked' => 'Show in CMS bookmarks?',
];
}
By default, form fields in extension classes are automatically scaffolded in CMS edit forms. See scaffolding for more details about how that works.
If you need to update those form fields, you can implement the updateCMSFields()
extension hook method.
namespace App\Extension;
use SilverStripe\Core\Extension;
use SilverStripe\Forms\FieldList;
class BookmarkedPageExtension extends Extension
{
// ...
protected function updateCMSFields(FieldList $fields)
{
$fields->dataFieldByName('IsBookmarked')?->addExtraClass('special-css-class');
}
}
Enable the extension in your configuration file
SilverStripe\CMS\Model\SiteTree:
extensions:
- App\Extension\BookmarkedPageExtension
In order to add the field to the database, run sake db:build --flush
.
Refresh the CMS, open a page for editing and you should see the new checkbox.
Retrieve the list of bookmarks from the database
One piece in the puzzle is still missing: How do we get the list of bookmarked
pages from the database into the template we've already created (with hardcoded
links)? Again, we extend a core class: The main CMS UI controller called
LeftAndMain
.
Add the following code to a new file app/src/BookmarkedLeftAndMainExtension.php
;
namespace App\Extension;
use SilverStripe\Core\Extension;
class BookmarkedPagesLeftAndMainExtension extends Extension
{
public function getBookmarkedPages()
{
return Page::get()->filter('IsBookmarked', 1);
}
}
Enable the extension in your configuration file
SilverStripe\Admin\LeftAndMain:
extensions:
- App\Extension\BookmarkedPagesLeftAndMainExtension
As the last step, replace the hardcoded links with our list from the database.
Find the <ul>
you created earlier in the app/templates/SilverStripe/Admin/Includes/LeftAndMain_MenuList
template
and replace it with the following:
<ul class="cms-menu__list">
<!-- ... -->
<% loop $BookmarkedPages %>
<li class="bookmarked-link $FirstLast">
<li><a href="$AdminURL('pages/edit/show/$ID')">Edit "$Title"</a></li>
</li>
<% end_loop %>
</ul>
Extending the CMS actions
CMS actions follow a principle similar to the CMS fields: they are built in the
backend with the help of FormFields
and FormActions
, and the frontend is
responsible for applying a consistent styling.
The following conventions apply:
- New actions can be added by redefining
getCMSActions
, or adding an extension withupdateCMSActions
. - It is required the actions are contained in a
FieldList
(getCMSActions
returns this already). - Standalone buttons are created by adding a top-level
FormAction
(no such button is added by default). - Button groups are created by adding a top-level
CompositeField
withFormActions
in it. - A
MajorActions
button group is already provided as a default. - Drop ups with additional actions that appear as links are created via a
TabSet
andTabs
withFormActions
inside. - A
ActionMenus.MoreOptions
tab is already provided as a default and contains some minor actions. - You can override the actions completely by providing your own
getAllCMSFields
.
Let's walk through a couple of examples of adding new CMS actions in getCMSActions
.
First of all we can add a regular standalone button anywhere in the set. Here
we are inserting it in the front of all other actions. We could also add a
button group (CompositeField
) in a similar fashion.
$fields->unshift(FormAction::create('normal', 'Normal button'));
We can affect the existing button group by manipulating the CompositeField
already present in the FieldList
.
$fields->fieldByName('MajorActions')->push(FormAction::create('grouped', 'New group button'));
Another option is adding actions into the drop-up - best place for placing infrequently used minor actions.
$fields->addFieldToTab('ActionMenus.MoreOptions', FormAction::create('minor', 'Minor action'));
We can also easily create new drop-up menus by defining new tabs within the
TabSet
.
$fields->addFieldToTab('ActionMenus.MyDropUp', FormAction::create('minor', 'Minor action in a new drop-up'));
Empty tabs will be automatically removed from the FieldList
to prevent clutter.
To make the actions more user-friendly you can also use alternating buttons as detailed in the CMS Alternating Button how-to.
React-rendered UI
For sections of the admin that are rendered with React and Redux, please refer to the introduction on those concepts, as well as their respective How-To's in this section.
Implementing handlers
Your newly created buttons need handlers to bind to before they will do anything.
To implement these handlers, you will need to create a LeftAndMainExtension
and add
applicable controller actions to it:
namespace App\Extension;
use SilverStripe\Core\Extension;
class CustomActionsExtension extends Extension
{
private static $allowed_actions = [
'sampleAction',
];
public function sampleAction()
{
// Create the web
}
}
The extension then needs to be registered:
SilverStripe\Admin\LeftAndMain:
extensions:
- App\Extension\CustomActionsExtension
You can now use these handlers with your buttons:
$fields->push(FormAction::create('sampleAction', 'Perform Sample Action'));
Summary
In a few lines of code, we've customised the look and feel of the CMS.
While this example is only scratching the surface, it includes most building blocks and concepts for more complex extensions as well.