How-to library · Developer docs

How-to library

Task-shaped pages: a title that starts with a verb, the situation in a few lines, the steps, the working code in both engines, and why this way rather than another. Found from a search, done, and left.

Add a per-site feature flag and read it in a template

Site data is a per-site store of values an administrator edits in the admin without a deploy. A flag is one key; read it with _siteData.get(), and read the field you mean when the value is a structure.

Context

Context. A new checkout, a promotion banner, a beta navigation: something that should be switchable per site, by an administrator, without a deploy. That is site data: a key-value store on the site, edited in the admin, read in templates through _siteData. A value is either a single value or, for an entry of type JSON, a structure that comes back as an array. Keys are literal names, so features.newCheckout is one key called exactly that, not newCheckout inside features.

Steps

  1. In the admin, add a site data entry: key features, type JSON, value {"newCheckout": true, "betaNav": false}. Or one plain key per flag, features.newCheckout with value 1.
  2. Read it in the template. For a structure, test the field, never the whole value: a structure is never empty, so !empty() on it turns every flag on.
  3. Branch on the flag. Keep both branches until the flag is removed, and remove the flag once the feature is permanent.

Template code

Template code
{* a JSON structure *}
{$flags = $_siteData->get('features')}
{$newCheckout = is_array($flags) && !empty($flags.newCheckout)}

{* one plain key per flag *}
{$betaNav = $_siteData->get('features.betaNav') == '1'}

{if $newCheckout}{include 'checkout/steps-v2.html'}{else}{include 'checkout/steps.html'}{/if}
{# a JSON structure #}
{% set flags = _siteData.get('features') %}
{% set newCheckout = flags is iterable and flags.newCheckout|default(false) %}

{# one plain key per flag #}
{% set betaNav = _siteData.get('features.betaNav') == '1' %}

{% if newCheckout %}{{ include('checkout/steps-v2.html') }}{% else %}{{ include('checkout/steps.html') }}{% endif %}

Why this and not that

Not an environment check: a flag is a decision an administrator makes, not a property of the server. Not a constant in the template: changing it is a deploy. Not a switch on a global component: that works, and suits an editor-facing toggle such as a banner, but site data is the place for configuration, and it is not versioned content. Everything a template prints from site data is public, so never put a secret in it.

Go deeper

Was this page helpful?

Your answer goes to the docs team.