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.
Looking to support full-page interfaces and slideouts? Check out the new control panel screens API.
# Page Templates
To add a new full page to the control panel, create a template that extends the _layouts/cp.twig (opens new window) template.
At a minimum, your template should set a title
variable and define a content
block:
{% extends "_layouts/cp.twig" %}
{% 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.twig
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.twig (opens new window) 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.twig' 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.twig' 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.