Storelib Developer HubSchema

Schema

The <schema> block is JSON. It says what the section is, what a creator can change about it, which blocks it may hold, and how Add Section offers it. The builder's settings panel for a section is generated from its schema and nothing else. There is no per-section panel code to write: declare a setting and its control appears.

JSON
{
  "schema_version": 1,
  "type": "features",
  "name": "Features",
  "category": "Content",
  "version": "1.0.0",
  "settings": [
    { "type": "header", "id": "hdr_content", "label": "Content" },
    { "type": "text", "id": "heading", "label": "Heading", "default": "Why us" },
    { "type": "range", "id": "columns", "label": "Columns", "default": 3, "min": 1, "max": 4, "step": 1, "responsive": true },
    { "type": "range", "id": "padding_top", "label": "Top padding", "default": 64, "min": 0, "max": 200, "step": 4, "unit": "px" },
    { "type": "color_scheme", "id": "color_scheme", "label": "Colour scheme", "default": "white" }
  ],
  "blocks": [
    {
      "type": "feature",
      "name": "Feature",
      "limit": 12,
      "settings": [
        { "type": "icon_picker", "id": "icon", "label": "Icon", "default": "star" },
        { "type": "text", "id": "title", "label": "Title", "default": "Fast shipping" },
        { "type": "textarea", "id": "text", "label": "Text", "default": "" }
      ]
    }
  ],
  "max_blocks": 12,
  "presets": [
    { "name": "Three features", "blocks": [{ "type": "feature" }, { "type": "feature" }, { "type": "feature" }] }
  ],
  "capabilities": []
}

Top level#

KeyRequiredMeaning
schema_versionno, defaults to 1The schema format this file is written in. A file from a newer engine is refused rather than misread.
typeyesThe section type. Must equal the file name, sections/<type>.storelib.
nameyesWhat the builder calls it.
categorynoGroups Add Section.
description, iconnoShown in Add Section.
versionnoThe section's own version, which migrations refer to.
settingsyesThe settings, in panel order.
blocksnoThe block types this section accepts. See Blocks.
max_blocksnoCap across all block types.
presetsnoWhat Add Section offers. No presets means the section is not offered.
allowed_templates, disabled_templatesnoThe pages it may be placed on, or may not. One or the other, never both.
capabilitiesnoWhat the section may reach: embed for a player frame, and the script capabilities. See JavaScript.
migrationsnoSetting renames across versions. See Changing a section safely.
supports_color_overrides, supports_typography_overridesnoWhich colour and type overrides the builder offers for this section. See Styling.

A key the engine does not recognise is kept and written back untouched. Nothing you add is stripped.

A setting#

KeyMeaning
typeOne of the control types below. Nothing else exists.
idLowercase letters, digits and _, starting with a letter, unique within the section. Stable for ever: it is the key the creator's content is stored under, so renaming one is a migration, not an edit.
labelWhat the panel shows.
infoA short line of help under the control.
placeholderShown in an empty text field.
defaultMust be a valid value for the type. Omitted means the control's own fallback.
optionsRequired for select and radio: [{ "label": "…", "value": "…" }]. The default must be one of the values.
min, max, step, unitFor range and number. A range needs min and max, and min below max.
responsivetrue, on a type that allows it, makes the value { desktop, tablet, mobile }.

header, paragraph and divider draw in the panel and hold no value. They take no default.

Every control type#

This list is generated from the builder's control registry, the same one the compiler validates against and the panel draws from. If a type is here, it exists; if it is not, it does not.

TypeWhat it isHoldsResponsive
textA single line of text.textno
textareaSeveral lines of plain text.textno
richtextFormatted text. Stored as sanitised HTML; output with the raw filter.textno
numberA number typed in.a numberyes
toggleOn or off.true or falseno
checkboxOn or off, drawn as a checkbox. Prefer toggle for a single switch.true or falseno
selectOne of a list, drawn as a dropdown.one of its optionsno
radioOne of a short list, every option visible.one of its optionsno
rangeA number on a slider.a numberyes
colorA hex colour.a hex colourno
color_schemeOne of the theme's named colour schemes, by key: the built-in white, light, dark, black and highlight (Light, Soft, Dark, Brand and Accent in the editor) or one the creator added. The editor offers every scheme the theme has; see docs/theme/16-design-tokens.md.one of its optionsno
font_pickerA font family from the theme's font list.textno
alignmentLeft, centre or right.one of its optionsyes
image_pickerOne image from the media library. Stored as an asset reference, rendered as a URL.an assetno
image_gallerySeveral images, in order.a list of assetsno
video_pickerA video file from the media library, or a hosted video URL.an assetno
file_pickerAny file from the media library.an assetno
icon_pickerOne icon from Storelib's icon library, by name.textno
urlWhere something links to: a page of the site, a product, or any address.textno
page_pickerOne of the site's pages, by key.textno
product_pickerOne of the creator's products, by id.textno
community_pickerOne of the creator's communities, by id.textno
headerA heading in the sidebar. Holds no value.nothing; it only draws in the panelno
paragraphA line of help text in the sidebar. Holds no value.nothing; it only draws in the panelno
dividerA rule between groups of settings. Holds no value.nothing; it only draws in the panelno
A working declaration of each of the 25 types
JSON
[
  {
    "type": "text",
    "id": "heading",
    "label": "Heading",
    "default": "Welcome"
  },
  {
    "type": "textarea",
    "id": "body",
    "label": "Body"
  },
  {
    "type": "richtext",
    "id": "content",
    "label": "Content"
  },
  {
    "type": "number",
    "id": "columns",
    "label": "Columns",
    "min": 1,
    "max": 6,
    "default": 3
  },
  {
    "type": "toggle",
    "id": "show_badge",
    "label": "Show badge",
    "default": true
  },
  {
    "type": "checkbox",
    "id": "agree",
    "label": "Require agreement"
  },
  {
    "type": "select",
    "id": "layout",
    "label": "Layout",
    "options": [
      {
        "label": "Centre",
        "value": "center"
      },
      {
        "label": "Split",
        "value": "split"
      }
    ],
    "default": "center"
  },
  {
    "type": "radio",
    "id": "style",
    "label": "Button style",
    "options": [
      {
        "label": "Filled",
        "value": "filled"
      },
      {
        "label": "Outline",
        "value": "outline"
      }
    ],
    "default": "filled"
  },
  {
    "type": "range",
    "id": "padding",
    "label": "Padding",
    "min": 0,
    "max": 160,
    "step": 4,
    "unit": "px",
    "default": 64,
    "responsive": true
  },
  {
    "type": "color",
    "id": "background",
    "label": "Background",
    "default": "#ffffff"
  },
  {
    "type": "color_scheme",
    "id": "scheme",
    "label": "Colour scheme",
    "default": "white"
  },
  {
    "type": "font_picker",
    "id": "font",
    "label": "Heading font",
    "default": "Inter"
  },
  {
    "type": "alignment",
    "id": "align",
    "label": "Align",
    "default": "center"
  },
  {
    "type": "image_picker",
    "id": "image",
    "label": "Image"
  },
  {
    "type": "image_gallery",
    "id": "images",
    "label": "Images",
    "max": 12
  },
  {
    "type": "video_picker",
    "id": "video",
    "label": "Background video"
  },
  {
    "type": "file_picker",
    "id": "download",
    "label": "Downloadable file"
  },
  {
    "type": "icon_picker",
    "id": "icon",
    "label": "Icon",
    "default": "zap"
  },
  {
    "type": "url",
    "id": "button_url",
    "label": "Button link",
    "default": "/shop"
  },
  {
    "type": "page_picker",
    "id": "page",
    "label": "Page"
  },
  {
    "type": "product_picker",
    "id": "product",
    "label": "Product"
  },
  {
    "type": "community_picker",
    "id": "community",
    "label": "Community"
  },
  {
    "type": "header",
    "id": "hdr_content",
    "label": "Content"
  },
  {
    "type": "paragraph",
    "id": "note",
    "content": "Shown above the fold on every page."
  },
  {
    "type": "divider",
    "id": "div_1"
  }
]

Older type names#

Early sections used different names for some types. They still validate and mean the same thing, but new sections should use the names above.

Older nameMeans
linkurl
imageimage_picker
videovideo_picker
iconicon_picker
fontfont_picker
spacingrange

How a value is resolved#

A page stores only what the creator changed. Everything else is decided in one order, every time a section renders:

resolved = saved value  ??  schema default  ??  control fallback

For a block setting, the block's saved value comes first, then the block definition's default, then the fallback. The theme is not a step in this chain: it provides tokens that a template reads directly, and never overrides a setting by name.

Responsive values arrive as { desktop, tablet, mobile }, filled downwards. Set desktop only and tablet and mobile inherit it.

JSON
saved:    { "columns": { "desktop": 4, "mobile": 1 } }
resolved: { "columns": { "desktop": 4, "tablet": 4, "mobile": 1 } }

Read one breakpoint in the template as {{ settings.columns.desktop }}, or pass all three to CSS as custom properties and pick with container queries.

Saving strips defaults. A value equal to the default is not stored, and a responsive value equal at every breakpoint collapses to a plain one. That keeps pages small, and it is why changing a default reaches every instance that never overrode it.

Assets#

An image, video, file or gallery setting holds an asset reference, asset://<sha256>, never a URL. By the time the template runs, the reference has been turned into the URL this account serves the file from, so you print it directly:

.storelib
{% if settings.image != "" %}
  <img src="{{ settings.image }}" alt="{{ settings.image_alt }}" width="1200" height="800">
{% endif %}

A reference held anywhere else goes through the asset filter: {{ block.settings.clip | asset }}. A reference the account does not have resolves to an empty string, so an empty image means "no image chosen", never an error. Because no stored page knows where bytes live, a theme moves between accounts and storage can change without a page being touched.

Changing a section safely#

Setting ids and block types are the contract between a section file and every page that uses it. An update may freely:

  • add settings and block types; existing instances get the defaults,
  • change defaults; instances that never overrode them follow,
  • change markup and CSS.

It must not rename or remove an id without saying so. Declare the rename and the old value is read under the new name:

JSON
"version": "1.1.0",
"migrations": [
  { "from": "title", "to": "heading", "since": "1.1.0" }
]

A removed setting's saved value is kept as an unknown key, so putting the setting back restores it.

What the validator refuses#

Each refusal names the path, such as settings[3].default, and the line in the file:

  • a type that is not a control type, listing the ones that are,
  • a bad or duplicate id,
  • options missing on a select or radio, or a default that is not among them,
  • a default of the wrong type, such as default "big" is not a range,
  • responsive on a type that cannot be,
  • a range without min and max, or with min not below max,
  • a preset that sets a setting or adds a block the section does not declare,
  • a block type declared twice,
  • both allowed_templates and disabled_templates,
  • a schema_version newer than the engine,
  • more settings than the limit.
Schema | Storelib Developer Hub