Theme settings
Define and access theme-level settings: global brand controls editors configure from the admin
Theme settings let editors configure global controls (brand colours, typography, social links, default behaviours) from the admin's theme configuration page. Defined once in config/settings_schema.json, accessed everywhere via the settings global.
This page covers both defining and accessing settings. Template-level settings (per-page options) are covered in Templates.
Theme settings vs template settings
| Aspect | Theme settings | Template settings |
|---|---|---|
| Defined in | config/settings_schema.json | {% schema %} inside a template |
| Structure | Groups with a fields array | A settings array |
| Scope | Global: applies across all pages | Per-page: applies only to pages using that template |
| Accessed via | settings global function | page.theme.settings on the document |
| Use case | Brand colours, fonts, social links | Hero style, sidebar toggle, layout options |
| Edited in admin | Theme configuration page | Page editor, per document |
Use theme settings for values that should stay consistent site-wide. Use template settings for options that vary per page.
Defining theme settings
1. Create the settings schema file
your-theme/
├── blocks/
├── config/
│ └── settings_schema.json
├── layouts/
├── templates/
└── assets/2. Define the groups array
The file is a JSON array of groups. Every group has a name.
The first group is reserved for theme metadata: your theme's name, version, author, and documentation links. It has no type and no fields:
[
{
"name": "theme_settings",
"theme_name": "Remarkable Theatre",
"theme_version": "1.0.0",
"theme_author": "Remarkable Theatre",
"theme_documentation_url": "https://remarkable-theatre.example/docs",
"theme_support_url": "https://remarkable-theatre.example/support"
}
]Every other group is configurable. A configurable group sets "type": "group" and lists its controls in a fields array. Each field uses default for its starting value:
{
"name": "global",
"type": "group",
"fields": [
{
"type": "text",
"name": "site_title",
"label": "Site title",
"default": "Remarkable Theatre"
}
]
}3. Add a colour palette group
{
"name": "colours",
"type": "group",
"fields": [
{
"type": "color",
"name": "color_primary",
"label": "Primary colour",
"default": "#1a1a2e"
},
{
"type": "color",
"name": "color_secondary",
"label": "Secondary colour",
"default": "#e94560"
},
{
"type": "color",
"name": "color_background",
"label": "Background colour",
"default": "#ffffff"
},
{
"type": "color",
"name": "color_text",
"label": "Body text colour",
"default": "#333333"
}
]
}4. Add typography and social media groups
{
"name": "typography",
"type": "group",
"fields": [
{
"type": "select",
"name": "heading_font",
"label": "Heading font family",
"options": [
{ "label": "System default", "value": "system-ui" },
{ "label": "Georgia", "value": "Georgia, serif" },
{ "label": "Helvetica", "value": "Helvetica, Arial, sans-serif" }
],
"default": "system-ui"
},
{
"type": "select",
"name": "body_font",
"label": "Body font family",
"options": [
{ "label": "System default", "value": "system-ui" },
{ "label": "Georgia", "value": "Georgia, serif" }
],
"default": "system-ui"
},
{
"type": "number",
"name": "base_font_size",
"label": "Base font size (px)",
"default": 16
}
]
}{
"name": "social",
"type": "group",
"fields": [
{ "type": "url", "name": "facebook_url", "label": "Facebook page URL" },
{ "type": "url", "name": "twitter_url", "label": "X (Twitter) profile URL" },
{ "type": "url", "name": "instagram_url", "label": "Instagram profile URL" },
{ "type": "url", "name": "youtube_url", "label": "YouTube channel URL" }
]
}A group can also be repeatable: set "type": "array" with the same fields shape, and editors can add multiple entries (for example, a list of footer links).
All groups appear in the admin on the theme configuration page.
Accessing settings in templates
Theme-level: settings global
The settings function returns an object grouped the same way as the schema: each group's name is a key, and its fields sit beneath it:
{% assign theme_settings = settings %}
<body style="
--color-primary: {{ theme_settings.colours.color_primary }};
--color-secondary: {{ theme_settings.colours.color_secondary }};
--color-background: {{ theme_settings.colours.color_background }};
--color-text: {{ theme_settings.colours.color_text }};
--font-heading: {{ theme_settings.typography.heading_font }};
--font-body: {{ theme_settings.typography.body_font }};
--font-size-base: {{ theme_settings.typography.base_font_size }}px;
">Or access individual settings inline through their group:
<span class="site-title">{{ settings.global.site_title }}</span>Template-level: page.theme.settings
Template settings come from the document object: page.theme.settings for a page, event.theme.settings for an event, and so on. The variable matches the template's collection.
{% assign header_style = page.theme.settings.header_style %}
{% assign show_breadcrumbs = page.theme.settings.show_breadcrumbs %}
{% if show_breadcrumbs %}
{% render 'breadcrumbs', page: page %}
{% endif %}
<header class="page__header page__header--{{ header_style }}">
<h1>{{ page.title }}</h1>
</header>Patterns
Inject brand colours as CSS custom properties
In layouts/default.liquid, settings flow into all templates as variables:
<head>
<meta charset="utf-8">
<title>{{ page.title | default: settings.global.site_title }}</title>
{{ content_for_header }}
{{ "theme.css" | asset_url | stylesheet_tag }}
<style>
:root {
--color-primary: {{ settings.colours.color_primary | default: '#1a1a2e' }};
--color-secondary: {{ settings.colours.color_secondary | default: '#e94560' }};
--color-bg: {{ settings.colours.color_background | default: '#ffffff' }};
--color-text: {{ settings.colours.color_text | default: '#333333' }};
--font-heading: {{ settings.typography.heading_font | default: 'system-ui' }};
--font-body: {{ settings.typography.body_font | default: 'system-ui' }};
}
</style>
</head>Per-page hero variant
{% assign hero_variant = page.theme.settings.hero_variant %}
{% assign overlay_opacity = page.theme.settings.overlay_opacity %}
{% if page.image %}
<section class="hero hero--{{ hero_variant | default: 'full-width' }}">
<img
src="{{ page.image | image_url: width: 1600, height: 900, fit: 'cover' }}"
alt="{{ page.image.alt | default: page.title }}"
loading="eager"
>
{% if hero_variant == 'overlay' %}
<div class="hero__overlay" style="opacity: {{ overlay_opacity | default: 0.5 }};">
<h1 class="hero__title">{{ page.title }}</h1>
</div>
{% endif %}
</section>
{% endif %}
<div class="page__body">
{% stageblocks page %}
</div>
{% schema %}
{
"settings": [
{
"type": "select",
"name": "hero_variant",
"label": "Hero style",
"options": [
{ "label": "Full width", "value": "full-width" },
{ "label": "Overlay", "value": "overlay" },
{ "label": "Side by side", "value": "side-by-side" }
],
"defaultValue": "full-width"
},
{
"type": "number",
"name": "overlay_opacity",
"label": "Overlay opacity (0 to 1)",
"defaultValue": 0.5
}
],
"blocks": ["text-section", "image-gallery", "call-to-action"]
}
{% endschema %}Conditional rendering based on settings
{% assign social = settings.social %}
{% if social.facebook_url != blank or social.twitter_url != blank or social.instagram_url != blank %}
<nav class="social-links" aria-label="Social media">
<ul>
{% if social.facebook_url != blank %}
<li><a href="{{ social.facebook_url }}" rel="noopener">Facebook</a></li>
{% endif %}
{% if social.twitter_url != blank %}
<li><a href="{{ social.twitter_url }}" rel="noopener">X (Twitter)</a></li>
{% endif %}
{% if social.instagram_url != blank %}
<li><a href="{{ social.instagram_url }}" rel="noopener">Instagram</a></li>
{% endif %}
{% if social.youtube_url != blank %}
<li><a href="{{ social.youtube_url }}" rel="noopener">YouTube</a></li>
{% endif %}
</ul>
</nav>
{% endif %}Troubleshooting
"Invalid settings_schema.json": JSON syntax error. Validate with a JSON linter.
"settings_schema.json should be an array of objects": The root must be [], not {}.
"settings_schema.json entries should include a name property": Every group needs a name.
Settings return empty values: The setting name in the schema must match the property name used in the template. Names are case-sensitive.
settings returns an empty object: The theme has no config/settings_schema.json, or the file failed validation. Check the upload validation log.
Template settings not available on page.theme.settings: The template needs a valid {% schema %} with a settings array. Validate the schema JSON.
Related
- Templates: define template-level settings.
- Block schema reference: every field type usable in settings.
- Theme directory structure: where the settings file lives.
- Liquid filters:
asset_url,image_url, and other filters used in settings-driven markup.