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 (opens new window) 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 (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' 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.