Control Panel Templates
The control panel is built using Twig templates, so extending it with new pages should feel familiar if you’ve worked with Twig on the front-end.
Plugins can define templates within the templates/
folder within their base source folder. Templates within there can be referenced using the plugin’s handle as the template path prefix.
For example if a plugin’s handle is foo
and it has a templates/bar.twig
template, that template could be accessed by going to /admin/foo/bar
, or from Twig by including/extending foo/bar
(or foo/bar.twig
).
Modules can have templates too, but they will need to manually define a template root before they are accessible.
Page Templates
To add a new full page to the control panel, create a template that extends the _layouts/cp.html template.
At a minimum, your template should set a title
variable and define a content
block:
{% extends "_layouts/cp.html" %}
{% set title = "Page Title"|t('plugin-handle') %}
{% block content %}
<p>Page content goes here</p>
{% endblock %}
Supported Variables
The following variables are supported by the _layouts/cp.html
template:
Variable | Description
-------- | -----------
title
| The page title.
bodyClass
| An array of class names that should be added to the <body>
tag.
fullPageForm
| Whether the entire page should be wrapped in one big <form>
element (see Form Pages).
crumbs
| An array of breadcrumbs (see Adding Breadcrumbs).
tabs
| An array of tabs (see Adding Tabs).
selectedTab
| The ID of the selected tab.
showHeader
| Whether the page header should be shown (true
by default).
mainAttributes
| A hash of HTML attributes that should be added to the <main>
tag.
Available Blocks
The _layouts/cp
template defines the following blocks, which your template can extend:
Block | Outputs
----- | -------
actionButton
| The primary Save button.
content
| The page’s main content.
contextMenu
| An optional context menus beside the page title (e.g. the revision menu on Edit Entry pages).
details
| The page’s right sidebar content.
footer
| The page’s footer content.
header
| The page’s header content, including the page title and other header elements.
pageTitle
| The page title (rendered within the header
block).
sidebar
| The page’s left sidebar content.
toolbar
| The page’s toolbar content.
Adding Breadcrumbs
To add breadcrumbs to your page, define a crumbs
variable, set to an array of the breadcrumbs.
Each breadcrumb should be represented as a hash with the following keys:
Key | Description
--- | -----------
label
| The breadcrumb label.
url
| The URL that the breadcrumb should link to.
For example, the following crumbs
array defines two breadcrumbs:
{% set crumbs = [
{
label: 'Plugin Name'|t('plugin-handle'),
url: url('plugin-handle'),
},
{
label: 'Products'|t('plugin-handle'),
url: url('plugin-handle/products'),
},
] %}
Adding Tabs
To add tabs to your page, define a tabs
variable, set to a hash of the tabs, indexed by tab IDs.
Each tab should be represented as a nested hash with the following keys:
Key | Description
--- | -----------
label
| The tab label.
url
| The URL or anchor that the tab should link to.
class
| A class name that should be added to the tab (in addition to tab
).
For example, the following tabs
hash defines two tabs, which will toggle the hidden
class of <div>
elements whose IDs match the anchors:
{% set tabs = {
content: {
label: 'Content'|t('plugin-handle'),
url: '#content',
},
settings: {
label: 'Settings'|t('plugin-handle'),
url: '#settings',
},
} %}
The first tab will be selected by default. You can force a different tab to be selected by default by setting a selectedTab
variable to the ID of the desired tab:
{% set selectedTab = 'settings' %}
Form Pages
If the purpose of your template is to present a form to the user, start by setting the fullPageForm
variable to true
at the top:
{% set fullPageForm = true %}
When you do that, everything inside the page’s <main>
element will be wrapped in a <form>
element, and a Save button and CSRF input will be added to the page automatically for you. It will be up to you to define the action
input, however.
{% block content %}
{{ actionInput('plugin-handle/controller/action') }}
<!-- ... -->
{% endblock %}
Your template can also define the following variables, to customize the form behavior:
Variable | Description
-------- | -----------
formActions
| An array of available Save menu actions for the form (see Alternate Form Actions).
mainFormAttributes
| A hash of HTML attributes that should be added to the <form>
tag.
retainScrollOnSaveShortcut
| Whether the page’s scroll position should be retained on the subsequent page load when the Ctrl/Command + S keyboard shortcut is used.
saveShortcutRedirect
| The URL that the page should be redirected to when the Ctrl/Command + S keyboard shortcut is used.
saveShortcut
| Whether the page should support a Ctrl/Command + S keyboard shortcut (true
by default).
Form Inputs
Craft’s _includes/forms.html template defines several macros that can be used to display your form elements.
Most input types have two macros: one for outputting just the input; and another for outputting the input as a “field”, complete with a label, author instructions, etc.
For example, if you just want to output a date input, but nothing else, you could use the date
macro:
{% import '_includes/forms' as forms %}
{{ forms.date({
id: 'start-date',
name: 'startDate',
value: event.startDate,
}) }}
However if you want to wrap the input with a label, author instructions, a “Required” indicator, and any validation errors, you could use the dateField
macro instead:
{% import '_includes/forms' as forms %}
{{ forms.dateField({
label: 'Start Date'|t('plugin-handle'),
instructions: 'The start date of the event.'|t('plugin-handle'),
id: 'start-date',
name: 'startDate',
value: event.startDate,
required: true,
errors: event.getErrors('startDate'),
}) }}
Alternate Form Actions
If you want your form to have a menu button beside the Save button with alternate form actions, define a formActions
variable, set to an array of the actions.
Each action should be represented as a hash with the following keys:
Key | Description
--- | -----------
action
| The controller action path that the form should submit to, if this action is selected.
confirm
| A confirmation message that should be presented to the user when the action is selected, before the form is submitted.
data
| A hash of any data that should be passed to the form’s submit
event.
destructive
| Whether the action should be considered destructive, which will cause it to be listed in red text at the bottom of the menu, after a horizontal rule.
label
| The action’s menu option label.
params
| A hash of additional form parameters that should be included in the submission if the action is selected.
redirect
| The URL that the controller action should redirect to if successful.
retainScroll
| Whether the page’s scroll position should be retained on the subsequent page load.
shift
| Whether the action’s keyboard shortcut should include the Shift key.
shortcut
| Whether the action should be triggered by a keyboard shortcut.
For example, the following formActions
array defines three alternative form actions:
{% set formActions = [
{
label: 'Save and continue editing'|t('plugin-handle'),
redirect: 'plugin-handle/edit/{id}',
retainScroll: true,
shortcut: true,
},
{
label: 'Save and add another'|t('plugin-handle'),
redirect: 'plugin-handle/new',
shortcut: true,
shift: true,
},
{
label: 'Delete'|t('plugin-handle'),
confirm: 'Are you sure you want to delete this?'|t('plugin-handle'),
redirect: 'plugin-handle',
destructive: true,
},
] %}
Note that when formActions
is defined, the saveShortcut
, saveShortcutRedirect
, and retainScrollOnSaveShortcut
variables will be ignored, as it will be up to the individual form actions to define that behavior.