section-schema-generator
v2.1.0
Published
CLI tool for React + Vite / Shopify Hydrogen projects. Write Shopify section schemas in JS/TS using export default and generate a consolidated settingSchema.json. Works with both CommonJS and ESM projects.
Maintainers
Readme
section-schema-generator
A CLI tool for React + Vite and Shopify Hydrogen projects.
Write your Shopify section schemas in JavaScript or TypeScript using export default — with full IntelliSense and type safety in your editor — and let this tool scan, validate, and generate a consolidated settingSchema.json that follows Shopify's native section schema format exactly.
Works with both CommonJS and ESM projects. No config needed.
Table of Contents
- Why use this?
- Install
- Quick Start
- Folder Structure
- Writing a Schema
- All Setting Types
- All Top-Level Keys
- Run Commands
- CLI Options
- Generated Output
- TypeScript Support
- Validation
- Backward Compatibility
- Architecture
- License
Why use this?
In a standard Shopify Liquid theme, section schemas live inside {% schema %} tags — raw JSON buried inside .liquid files. You get no autocomplete, no validation, no reuse.
This tool lets you write schemas as plain JavaScript objects:
// src/sections/hero/schema.js
export default {
name: "Hero",
settings: [
{ type: "text", id: "heading", label: "Heading", default: "Welcome" }
]
}Run one command → get a clean, validated settingSchema.json that your Hydrogen storefront can consume.
Install
npm install --save-dev section-schema-generatorThat's it. After install, the package automatically adds a generate-schema script to your package.json:
{
"scripts": {
"generate-schema": "section-schema"
}
}You will see this confirmation in your terminal:
✔ [section-schema-generator] Added "generate-schema" script to your package.json.
ℹ [section-schema-generator] Run it anytime with: npm run generate-schemaIf the script already exists, it is never overwritten.
Quick Start
Step 1 — Install the package
npm install --save-dev section-schema-generatorStep 2 — Create a section folder with a schema file
src/
sections/
hero/
index.jsx
schema.js ← create thisStep 3 — Write your schema
// src/sections/hero/schema.js
export default {
name: "Hero",
tag: "section",
settings: [
{
type: "text",
id: "heading",
label: "Heading",
default: "Welcome to our store"
},
{
type: "image_picker",
id: "background_image",
label: "Background image"
}
],
presets: [{ name: "Hero" }]
}Step 4 — Generate the JSON
npm run generate-schemaStep 5 — Done! Your src/settingSchema.json is generated:
[
{
"name": "Hero",
"tag": "section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Welcome to our store"
},
{
"type": "image_picker",
"id": "background_image",
"label": "Background image"
}
],
"presets": [{ "name": "Hero" }]
}
]Folder Structure
Flat layout — --depth=1 (default)
Each section is a direct subfolder of src/sections. This is the recommended layout for Shopify Hydrogen projects.
src/
sections/
hero/
index.jsx
schema.js ← CLI reads this
slideshow/
index.jsx
schema.js ← CLI reads this
footer/
index.jsx
schema.js ← CLI reads thisRun:
npm run generate-schema
# or
npx section-schemaNested layout — --depth=2
Sections grouped under page folders.
src/
sections/
home/
banner/
schema.js ← CLI reads this
carousel/
schema.js ← CLI reads this
product/
collection-grid/
schema.js ← CLI reads thisRun:
npx section-schema --depth=2
# or add it to your package.json script:
# "generate-schema": "section-schema --depth=2"In both layouts the output is always a flat JSON array — folder structure is only used to find schema files.
Writing a Schema
Basic Example
// src/sections/hero/schema.js
export default {
name: "Hero",
tag: "section",
class: "hero-section",
settings: [
{
type: "text",
id: "heading",
label: "Heading",
default: "Welcome to our store"
},
{
type: "textarea",
id: "subtext",
label: "Subtext"
},
{
type: "image_picker",
id: "background_image",
label: "Background image"
},
{
type: "color",
id: "overlay_color",
label: "Overlay color",
default: "#000000"
},
{
type: "range",
id: "overlay_opacity",
label: "Overlay opacity",
min: 0,
max: 100,
step: 5,
unit: "%",
default: 30
},
{
type: "url",
id: "button_link",
label: "Button link"
},
{
type: "text",
id: "button_label",
label: "Button label",
default: "Shop now"
}
],
presets: [
{ name: "Hero" }
]
}With Blocks
// src/sections/slideshow/schema.js
export default {
name: "Slideshow",
tag: "section",
class: "slideshow",
limit: 1,
settings: [
{
type: "header",
content: "Slideshow settings"
},
{
type: "checkbox",
id: "autoplay",
label: "Auto-rotate slides",
default: true
},
{
type: "range",
id: "autoplay_speed",
label: "Change slides every",
min: 3,
max: 9,
step: 2,
unit: "s",
default: 5
},
{
type: "select",
id: "slide_size",
label: "Slide size",
options: [
{ value: "small", label: "Small" },
{ value: "medium", label: "Medium" },
{ value: "large", label: "Large" }
],
default: "medium"
}
],
blocks: [
{
type: "slide",
name: "Slide",
limit: 6,
settings: [
{
type: "image_picker",
id: "image",
label: "Image"
},
{
type: "text",
id: "heading",
label: "Heading",
default: "Slide heading"
},
{
type: "select",
id: "heading_size",
label: "Heading size",
options: [
{ value: "h2", label: "Small" },
{ value: "h1", label: "Medium" },
{ value: "h0", label: "Large" }
],
default: "h1"
},
{
type: "richtext",
id: "subheading",
label: "Subheading"
},
{
type: "text",
id: "button_label",
label: "Button label"
},
{
type: "url",
id: "button_link",
label: "Button link"
},
{
type: "select",
id: "button_style",
label: "Button style",
options: [
{ value: "primary", label: "Primary" },
{ value: "secondary", label: "Secondary" },
{ value: "outline", label: "Outline" }
],
default: "primary"
}
]
}
],
max_blocks: 6,
presets: [
{
name: "Slideshow",
blocks: [
{ type: "slide" },
{ type: "slide" }
]
}
]
}With Section Groups (enabled_on / disabled_on)
// src/sections/announcement-bar/schema.js
export default {
name: "Announcement bar",
tag: "section",
class: "announcement-bar",
settings: [
{
type: "text",
id: "text",
label: "Announcement",
default: "Free shipping on orders over $50"
},
{
type: "url",
id: "link",
label: "Link"
},
{
type: "color",
id: "bg_color",
label: "Background color",
default: "#000000"
},
{
type: "color",
id: "text_color",
label: "Text color",
default: "#ffffff"
},
{
type: "checkbox",
id: "show_close",
label: "Show close button",
default: true
}
],
enabled_on: {
templates: ["*"],
groups: ["header"]
}
}With Locales
// src/sections/hero/schema.js
export default {
name: "t:sections.hero.name",
tag: "section",
settings: [
{
type: "text",
id: "heading",
label: "t:sections.hero.settings.heading.label",
default: "t:sections.hero.settings.heading.default"
},
{
type: "richtext",
id: "body",
label: "t:sections.hero.settings.body.label"
}
],
presets: [
{ name: "t:sections.hero.presets.hero.name" }
],
locales: {
en: {
sections: {
hero: {
name: "Hero",
presets: { hero: { name: "Hero" } },
settings: {
heading: { label: "Heading", default: "Welcome" },
body: { label: "Description" }
}
}
}
},
fr: {
sections: {
hero: {
name: "Héros",
presets: { hero: { name: "Héros" } },
settings: {
heading: { label: "Titre", default: "Bienvenue" },
body: { label: "Description" }
}
}
}
}
}
}With Product / Collection Pickers
// src/sections/featured-collection/schema.js
export default {
name: "Featured collection",
tag: "section",
settings: [
{
type: "text",
id: "title",
label: "Heading",
default: "Featured collection"
},
{
type: "collection",
id: "collection",
label: "Collection"
},
{
type: "range",
id: "products_to_show",
label: "Maximum products to show",
min: 2,
max: 25,
step: 1,
default: 4
},
{
type: "checkbox",
id: "show_view_all",
label: "Show \"View all\" button",
default: true
},
{
type: "header",
content: "Product card"
},
{
type: "checkbox",
id: "show_secondary_image",
label: "Show second image on hover",
default: false
},
{
type: "checkbox",
id: "show_vendor",
label: "Show vendor",
default: false
},
{
type: "select",
id: "image_ratio",
label: "Image ratio",
options: [
{ value: "adapt", label: "Adapt to image" },
{ value: "portrait", label: "Portrait" },
{ value: "square", label: "Square" }
],
default: "adapt"
}
],
presets: [
{ name: "Featured collection" }
]
}CommonJS Style (module.exports)
If your project uses CommonJS (no "type": "module" in package.json), use module.exports instead:
// src/sections/hero/schema.js
module.exports = {
name: "Hero",
tag: "section",
settings: [
{
type: "text",
id: "heading",
label: "Heading",
default: "Welcome"
}
],
presets: [{ name: "Hero" }]
}Both styles work. You can even mix them — one section can use export default and another can use module.exports in the same project.
All Supported Setting Types
| Type | Description |
|------|-------------|
| text | Single-line text input |
| textarea | Multi-line text input |
| richtext | Rich text editor (supports block-level HTML) |
| inline_richtext | Inline rich text (no block elements like <p>) |
| image_picker | Image selector from the Shopify media library |
| video | Shopify-hosted video selector |
| video_url | YouTube or Vimeo URL input |
| url | URL picker — internal pages or external links |
| checkbox | Boolean toggle (true / false) |
| radio | Radio button group (requires options array) |
| select | Dropdown select (requires options array) |
| range | Numeric slider (requires min, max, step) |
| number | Freeform numeric input |
| color | Color picker (hex value) |
| color_background | Solid color or gradient picker |
| color_scheme | Color scheme selector |
| color_scheme_group | Color scheme group selector |
| font_picker | Font selector from Shopify's font library |
| html | Raw HTML textarea |
| liquid | Liquid code textarea |
| article | Article resource picker |
| blog | Blog resource picker |
| collection | Single collection picker |
| collection_list | List of collections picker |
| page | Page resource picker |
| product | Single product picker |
| product_list | List of products picker |
| link_list | Navigation menu picker |
| metaobject | Single metaobject entry picker |
| metaobject_list | List of metaobject entries picker |
| header | Informational header divider (no id required) |
| paragraph | Informational paragraph text (no id required) |
All Top-Level Schema Keys
| Key | Type | Required | Description |
|-----|------|----------|-------------|
| name | string | ✅ Yes | Section name shown in the Shopify theme editor |
| tag | string | No | HTML element for the section wrapper ("section", "div", "aside", "article", "footer", "header") |
| class | string | No | CSS class added to the section wrapper element |
| limit | number | No | Max number of times this section can be added per template |
| settings | array | No | Array of setting objects (see setting types above) |
| blocks | array | No | Array of block type definitions (each block can have its own settings) |
| max_blocks | number | No | Maximum total block instances allowed |
| presets | array | No | Default configurations shown in the "Add section" panel |
| locales | object | No | Inline translation strings (keyed by locale code) |
| enabled_on | object | No | Restricts which templates or section groups can use this section |
| disabled_on | object | No | Prevents this section on specific templates or groups |
Run Commands
Generate the schema (normal)
npm run generate-schemaGenerate with npx (no script needed)
npx section-schemaCustom source folder
npx section-schema --src app/sectionsCustom output file
npx section-schema --out app/schema.jsonCustom source and output
npx section-schema --src app/sections --out app/schema.jsonNested page/section layout (depth 2)
npx section-schema --depth=2Everything custom
npx section-schema --src=app/sections --out=dist/schema.json --depth=2Add to package.json scripts manually
{
"scripts": {
"generate-schema": "section-schema",
"generate-schema:watch": "section-schema --src src/sections --out src/settingSchema.json"
}
}Then run:
npm run generate-schemaShow help
npx section-schema --helpOutput:
section-schema
Scans your project's section folders for schema.js / schema.ts / schema.json
files and generates a consolidated settingSchema.json array that follows
Shopify's native section schema format.
Usage:
section-schema [options]
Options:
--src <path> Path to the sections root directory (default: src/sections)
--out <path> Path to write the generated JSON file (default: src/settingSchema.json)
--depth <1|2> Folder scan depth:
1 = flat: src/sections/<section>/schema.js (default)
2 = nested: src/sections/<page>/<section>/schema.js
-h, --help Show this help messageCLI Options
| Flag | Default | Description |
|------|---------|-------------|
| --src <path> | src/sections | Path to the sections root directory |
| --out <path> | src/settingSchema.json | Path to write the generated JSON file |
| --depth <1\|2> | 1 | 1 = flat layout, 2 = page/section nested layout |
| -h, --help | — | Show usage help |
All paths resolve relative to wherever you run the command from (process.cwd()).
Generated Output
Every schema.js file becomes one entry in the output array. The output file is always overwritten on each run so it stays in sync with your source files.
Example — two sections:
src/sections/
hero/schema.js
slideshow/schema.jsGenerates src/settingSchema.json:
[
{
"name": "Hero",
"tag": "section",
"settings": [
{
"type": "text",
"id": "heading",
"label": "Heading",
"default": "Welcome to our store"
},
{
"type": "image_picker",
"id": "background_image",
"label": "Background image"
}
],
"presets": [
{ "name": "Hero" }
]
},
{
"name": "Slideshow",
"tag": "section",
"class": "slideshow",
"limit": 1,
"settings": [
{
"type": "checkbox",
"id": "autoplay",
"label": "Auto-rotate slides",
"default": true
}
],
"blocks": [
{
"type": "slide",
"name": "Slide",
"settings": [
{
"type": "image_picker",
"id": "image",
"label": "Image"
}
]
}
],
"presets": [
{ "name": "Slideshow" }
]
}
]TypeScript Support
Install jiti as a dev dependency:
npm install --save-dev jitiThen write your schemas in TypeScript:
// src/sections/hero/schema.ts
export default {
name: "Hero",
tag: "section" as const,
settings: [
{
type: "text" as const,
id: "heading",
label: "Heading",
default: "Welcome"
},
{
type: "image_picker" as const,
id: "bg_image",
label: "Background image"
}
],
presets: [{ name: "Hero" }]
}Or with a typed interface for full IntelliSense:
// src/sections/slideshow/schema.ts
interface Setting {
type: string
id?: string
label?: string
default?: unknown
[key: string]: unknown
}
interface Block {
type: string
name: string
settings?: Setting[]
}
interface SectionSchema {
name: string
tag?: string
class?: string
limit?: number
settings?: Setting[]
blocks?: Block[]
max_blocks?: number
presets?: { name: string }[]
}
const schema: SectionSchema = {
name: "Slideshow",
tag: "section",
class: "slideshow",
limit: 1,
settings: [
{ type: "checkbox", id: "autoplay", label: "Auto-rotate slides", default: true }
],
blocks: [
{
type: "slide",
name: "Slide",
settings: [
{ type: "image_picker", id: "image", label: "Image" }
]
}
],
presets: [{ name: "Slideshow" }]
}
export default schemaRun the same command:
npm run generate-schemaIf jiti is not installed, schema.ts files are skipped with a warning and the CLI never crashes:
⚠ Found "src/sections/hero/schema.ts" but no TypeScript loader is installed.
Install jiti to enable schema.ts support:
npm install --save-dev jitiValidation
The CLI validates every schema before writing the output. If validation fails, the run stops and every problem is listed clearly:
✖ Schema validation failed with 3 error(s):
• src/sections/hero/schema.js: schema is missing required "name" field.
• src/sections/slideshow/schema.js: duplicate setting id "heading" in settings.
• src/sections/slideshow/schema.js: blocks[0]: missing required "type" field.Fix the errors and re-run:
npm run generate-schemaWarnings (non-blocking) are printed but never stop the build. Example: using a setting type that was introduced in a newer version of Shopify and isn't yet in this package's known-types list — it gets a warning but still passes through to the output as-is.
Backward Compatibility
schema.json files still work. If you have existing JSON schema files, they are loaded as-is.
Priority order when multiple files exist in the same folder:
| Priority | File | Notes |
|----------|------|-------|
| 1st | schema.js | ESM or CJS — always preferred |
| 2nd | schema.ts | Requires jiti installed |
| 3rd | schema.json | Legacy fallback |
Architecture
bin/
section-schema.js # CLI entrypoint — args, help text, error handling, exit code
postinstall.js # Runs after npm install — adds generate-schema script automatically
lib/
generateSchema.js # Orchestrator — resolves paths, calls scan, writes output, logs
scanSections.js # Discovers all section folders, returns validated schema array
schemaLoader.js # Loads schema.js (ESM+CJS) / schema.ts (jiti) / schema.json
validator.js # Validates each schema object against Shopify's section schema spec
utils/
argsParser.js # Parses --src / --out / --depth / --help from process.argv
fileUtils.js # isDirectory, isFile, listSubdirectories, writeJsonFile
logger.js # ANSI-colored console output — success, error, warn, info
test/
run.js # 31 tests using node:assert only — no test framework neededEach module has a single responsibility. To add a new file type (e.g. schema.yaml), only schemaLoader.js needs to change. To add a new validation rule, only validator.js needs to change.
License
MIT
