Theme Settings
Declare the settings a theme accepts in its manifest. The admin form, the validation and the saved file follow. The view reads the result as one variable.
Overview
A theme is data, not a class: it declares what it can be configured with, never how that is edited. The panel validates each field by its declared type and writes the values into the theme's own directory.
That is the boundary of the contract. A theme adds a field and gets a control, but does not decide the form's plumbing. A value outside the schema is never stored.
Structure
Two Files, Two Owners
The Shape of the Schema
$manifest['settings'] = [
// key => the group's heading and icon. The label is a LOCALE KEY of this theme.
'groups' => [
'topbar' => ['label' => 'grp_topbar', 'icon' => 'bi-megaphone'],
],
// key => a descriptor. The key is the POST name, the config key and the name the
// view reads, all at once: renaming it orphans whatever was already saved.
'fields' => [
'topbar_enabled' => ['type' => 'switch', 'group' => 'topbar', 'label' => 'set_topbar_enabled', 'default' => false],
],
];
Step by Step
Declare a Group
- Add an entry to the groups map with a heading and an icon class.
- Put the heading in the theme's own locale file, both languages, and reference it by key. An untranslated key prints as itself.
- A field whose group is not declared still appears, ungrouped.
Declare a Field
- Pick the type from the list below. It decides the control, the validation and what is stored.
- Give it a real default, not an empty placeholder. That is the value on a fresh install and after every reset.
- Add
descfor a help sentence, anddependswhen the field only makes sense while another is on. - Reload the configure page; the control is there, no other file touched.
Read It in a View
- Read it off the settings variable by key. It is a variable and not a function so that it works inside a condition.
- For a colour, read the property value and its
_rgbtwin wherever a translucent version is needed. - From PHP, ask the theme object for one setting by key, without loading the whole map.
Take Over the Admin Form
- Add an
admin-settings.phpto the theme directory; it replaces the generated form entirely. - Build the markup with the form builder, saved values and schema you are handed. Return the HTML as a string.
- Keep field names identical to the schema keys. A control named anything else is posted and discarded.
Reference
Field Types
| Type | Control | Stored as | Validation on save |
|---|---|---|---|
switch, checkbox | Checkbox; desc is the label beside it | Boolean | Set means true, absent means false |
select | Dropdown built from options | The chosen key | Must be a key of options, else the default |
number | Number input | Integer | Cast to integer; non-numeric becomes zero |
color | Colour picker plus a typeable hex box | Hex string with the leading hash | Three to eight hex digits, else the default |
textarea | Textarea sized by rows | String, or a language map when multilingual | Tags stripped unless html is on, then allowlist-filtered |
| anything else, or a missing type | Text input | String, or a language map when multilingual | Same as a textarea |
What a Descriptor Accepts
['rail' => 'opt_rail', 'card' => 'opt_card']. Also the allowlist the saved value is checked against.
['topbar_enabled' => true]. The row collapses until every condition matches, on load and as the operator types.
The Settings API
// The manifest's settings block, exactly as declared (groups + fields).
public function settingsSchema(): array;
// One value: the saved one if the key was ever saved, otherwise the schema default,
// otherwise null. This is how the platform reads a single theme capability.
public function setting(string $key): mixed;
// The saved values only (config.php). Empty until the first save.
public function savedConfig(): array;
// Every schema field merged with its value : what the view receives as $setting.
// A multilingual value is reduced to the ACTIVE language here, and a colour field
// also produces a "{key}_rgb" entry.
public function allSettings(): array;
// A hex colour to the comma-separated triplet an rgba() expression needs. Short forms
// are expanded and a short value is padded, so an empty string yields three zeroes
// rather than an error. This is what produces the "{key}_rgb" entries above.
public static function hexToRgb(string $hex): string;
Example
A group with four fields, the saved file it produces and the view that reads it back. One reason to show them together: the schema key, the file key and the view name are the same string.
return [
'engine' => 'smarty',
'status' => 'ready',
'meta' => [
'name' => 'Acme',
'version' => '1.0.0',
'author' => 'Acme Ltd',
'image' => 'cover.png',
],
'settings' => [
'groups' => [
'brand' => ['label' => 'grp_brand', 'icon' => 'bi-palette'],
'topbar' => ['label' => 'grp_topbar', 'icon' => 'bi-megaphone'],
'checkout' => ['label' => 'grp_checkout', 'icon' => 'bi-cart3'],
],
'fields' => [
// A brand colour. `from_logo` is its index in the palette the panel extracts
// from the site logo, so the "pick colours from the logo" button fills it.
// The default is a hash plus six hex digits (the picker's own format) —
// replace the placeholder with this theme's real colour. Leave it empty and
// the derived primary_color_rgb reads as three zeroes, which paints every
// translucent surface built from it black.
'primary_color' => [
'type' => 'color',
'group' => 'brand',
'label' => 'set_primary_color',
'from_logo' => 0,
'default' => '#RRGGBB',
],
// The gate. Everything below it depends on this one.
'topbar_enabled' => [
'type' => 'switch',
'group' => 'topbar',
'label' => 'set_topbar_enabled',
'desc' => 'set_topbar_enabled_desc',
'default' => false,
],
// Per-language, and allowed to carry markup: an announcement usually has a
// link in it. The stored value is a map; the view still gets a string.
'topbar_text' => [
'type' => 'textarea',
'group' => 'topbar',
'label' => 'set_topbar_text',
'default' => '',
'rows' => 3,
'multilang' => true,
'html' => true,
'depends' => ['topbar_enabled' => true],
],
// The keys are what gets stored; the values are locale keys of this theme.
'checkout_sidebar' => [
'type' => 'select',
'group' => 'checkout',
'label' => 'set_checkout_sidebar',
'options' => [
'rail' => 'opt_checkout_rail',
'card' => 'opt_checkout_card',
'stack' => 'opt_checkout_stack',
],
'default' => 'rail',
],
'popup_width' => [
'type' => 'number',
'group' => 'checkout',
'label' => 'set_popup_width',
'default' => 500,
],
],
],
];
return [
// Stored with the leading hash, exactly as the picker posted it. Note that EVERY
// schema field is written on every save, whether the operator touched it or not.
'primary_color' => '#RRGGBB',
'topbar_enabled' => true,
// Multilingual: stored per language, one entry per ACTIVE language at save time.
'topbar_text' => [
'en' => 'Free migration this month.',
'tr' => 'Bu ay ücretsiz taşıma.',
],
'checkout_sidebar' => 'card',
'popup_width' => 520,
];
{* A boolean reads directly inside a condition, which is why this is a VARIABLE and
not a function: a registered function cannot be called from {if}. *}
{if $setting.topbar_enabled}
{* Markup was kept on save, so it is printed unfiltered here. *}
<div class="topbar">{$setting.topbar_text nofilter}</div>
{/if}
{* A select is just its stored key: use it as a class rather than branching four times. *}
<div class="checkout-layout-{$setting.checkout_sidebar}">
{* A colour field also produces an _rgb twin, for a translucent version of the same colour. *}
<style>
:root {
--acme-brand: {$setting.primary_color};
--acme-brand-rgb: {$setting.primary_color_rgb};
}
</style>
Pitfalls
The key is the form name, the stored key and the name the view reads. A value under the old name is never read again and never cleaned up. Every installation that had configured the theme falls back to the default.
It holds values and nothing else. Name and version stay in the manifest, where the theme listing and the upgrade check read them. Shipping one in a theme package hands your test configuration to everyone who installs it.
PHP asking for the raw setting gets the whole map. The view gets the active language resolved. Code confusing the two works in exactly one place.
The dependency hides the row; it does not disable the value. A view reading a dependent setting must check the field it depends on too. Otherwise it shows something the operator believes is off.
Related Articles
Vielen Dank für Ihre Rückmeldung!
Unser Support-Team ist rund um die Uhr für Sie da, wenn Sie oben nicht fündig werden.