A merchant wants to experiment with design elements in the theme editor - button colors, font choices, border thickness, opacity. They're not comfortable editing Liquid code; they want to click, preview, save. The question for the developer: how do you expose the right knobs to the theme editor without giving merchants a way to break the theme?
The theme-editor control surface
Shopify themes have a specific file that controls which settings merchants see in the theme editor: config/settings_schema.json.
This schema defines:
- Theme settings - global controls (site typography, brand colors, button defaults)
- Section settings - per-section controls (image, heading, call-to-action per hero section)
- Block settings - nested controls within sections (individual feature blocks)
A well-designed settings schema gives merchants meaningful control without overwhelming them with technical knobs.
The file structure
config/settings_schema.json defines what the merchant sees:
[
{
"name": "Colors",
"settings": [
{
"type": "color",
"id": "color_button_primary",
"label": "Primary button color",
"default": "#2b5ce6"
},
{
"type": "color",
"id": "color_button_text",
"label": "Button text color",
"default": "#ffffff"
}
]
},
{
"name": "Typography",
"settings": [
{
"type": "font_picker",
"id": "font_heading",
"label": "Heading font",
"default": "helvetica_n4"
}
]
}
]
config/settings_data.json holds the merchant's saved values. This file isn't edited by developers directly - it's updated every time the merchant saves settings in the theme editor.
Liquid templates consume the settings via settings.color_button_primary, settings.font_heading, etc.
The correct answer for "where to expose design tokens"
The file that controls merchant-editable design is config/settings_schema.json. Adding a new setting there makes it appear in the theme editor's global settings. Merchants click, preview, save; the change propagates across every section that references the setting.
This is distinct from:
config/settings_data.json- the saved values, not the schemasections/*.liquid- per-section settings defined in schema tags at the bottom of each section fileassets/*.css- compiled CSS, not editable through the editortemplates/*.json- page template structure, edited through the theme editor's customization mode
Setting types that fit design tokens
Shopify's settings schema supports many types. For design tokens, the useful ones:
color- hex color pickercolor_background- background with optional transparencycolor_scheme- references a color scheme defined elsewherefont_picker- select from Shopify's font library (Google Fonts + system fonts)range- numeric slider (for border thickness, opacity percentage, image border radius)select- dropdown (for size options like "small/medium/large")checkbox- boolean toggletext- short text input (for custom CSS class names, if you really must)
For the scenario at the top - button colors, fonts, border thickness, opacity - the right setting types are color, font_picker, and range respectively.
Consuming settings in Liquid and CSS
In Liquid, settings are accessed via the global settings variable:
<button style="background-color: {{ settings.color_button_primary }}; color: {{ settings.color_button_text }};">
Add to cart
</button>
For bulk CSS styling, many themes compile settings into CSS custom properties in the layout:
{% stylesheet %}
:root {
--color-primary-button: {{ settings.color_button_primary }};
--color-primary-button-text: {{ settings.color_button_text }};
--font-heading: {{ settings.font_heading.family }}, {{ settings.font_heading.fallback_families }};
--border-radius: {{ settings.border_radius }}px;
}
{% endstylesheet %}
Then component CSS uses the custom properties:
.button-primary {
background-color: var(--color-primary-button);
color: var(--color-primary-button-text);
border-radius: var(--border-radius);
font-family: var(--font-heading);
}
This separation means design-system changes in the theme editor propagate everywhere in one place.
Color schemes for complex theming
Modern Shopify themes support color schemes - named combinations of background, text, button, and accent colors that apply to sections. A merchant can define "Light scheme" and "Dark scheme" and apply either to any section.
Color schemes live in the schema too but have their own structure:
{
"type": "color_scheme_group",
"id": "color_schemes",
"definition": [
{
"type": "color",
"id": "background",
"label": "Background"
},
{
"type": "color",
"id": "text",
"label": "Text"
}
],
"role": {
"background": { "solid": "background" },
"text": "text"
}
}
This is the pattern Dawn and modern OS 2.0 themes use. Merchants pick a scheme per section; the theme renders with the scheme's colors consistently.
Font pickers and the font_url filter
For fonts, the font_picker setting type returns a font object with a family name and loading URL. Themes use the font_url filter to load the right stylesheet:
{{ settings.font_heading | font_face: font_display: 'swap' }}
This emits the proper @font-face declaration for the chosen font. Merchants can pick any of Shopify's library fonts (Google Fonts + system) and the theme adapts automatically.
Settings organization for non-technical merchants
A well-organized settings schema:
- Groups related settings under headers ("Colors", "Typography", "Buttons")
- Uses clear labels ("Primary button color" not "color_button_primary")
- Includes help text explaining the effect
- Orders settings from most-impactful to least
- Provides sensible defaults
A schema with 100 ungrouped settings is unusable even by technical merchants. A schema with 15 well-organized settings lets non-technical merchants confidently experiment.
What to expose vs hide
Not every theme value belongs in the settings schema. Good candidates:
- Brand colors
- Typography choices
- Layout parameters (max content width, spacing scale)
- Button styles
- Section-level toggles (show/hide features)
Bad candidates:
- Things requiring CSS knowledge (box-shadow syntax, custom positioning)
- Things that would break layout at wrong values (minimum padding below which content overlaps)
- Implementation details (cache timeouts, third-party IDs)
The boundary: if a wrong value breaks the theme meaningfully, it doesn't belong in merchant-editable settings.
What ships with a well-designed theme settings schema
A theme that supports non-technical merchant self-service has:
config/settings_schema.jsonwith grouped, labeled, defaulted settings- Design tokens consumed via CSS custom properties
- Settings exposed through the theme editor in an intuitive order
- Defaults that produce a usable theme without any customization
- Documentation (or help text in the editor) explaining what each setting does
- Versioning discipline - renaming a setting's ID in code breaks merchant's saved values
Merchants who can self-serve design tokens stay happier with their theme. The developer's work is making the right set of knobs available without offering footguns.








