Theme validation
Every rule theme check applies with its verbatim error string, the limits, the remote check, the validator-lag note, and a CI recipe.
On this page
This page is the reference for anyone who met a ✗ line from theme check or from a push. The validator theme check runs locally is the same file the server runs at push, so every rule here applies verbatim in both places.
How it reports#
✗ templates/home.liquid: <script> is not allowed (themes ship no JavaScript; JSON-LD data blocks are the only exception)
✗ disallowed file extension: assets/app.js
2 error(s) block pushing. Fix them, then run `dukkan theme check` again. https://developer.dukkan.one/docs/themes/checkOn success:
✓ Bundle valid — 14 files, hash 3f9a1c2b7d4e…The command exits 1 on any error and 0 on success. Dotfiles, dot-directories and node_modules are skipped silently; symlinks are skipped with the warning ! skipped PATH (symlink).
Path rules#
| Error | Cause |
|---|---|
bundle is empty |
no files |
bundle has too many files (N) |
more than 400 files |
path length out of range: "…" |
an empty path or one longer than 200 characters |
absolute path not allowed: PATH |
starts with / |
backslash not allowed: PATH |
contains \ |
NUL byte in path |
contains a NUL byte |
path traversal / empty segment not allowed: PATH |
contains .., . or an empty segment |
dotfiles not allowed: PATH |
a segment starts with a dot |
disallowed file extension: PATH |
an extension outside .liquid .json .css .svg .png .jpg .jpeg .webp .woff2 |
Size rules#
| Error | Limit |
|---|---|
file too large: PATH |
a file over 1 MB |
bundle too large (N bytes) |
a total over 5 MB |
theme exceeds 400 files / theme exceeds the total bundle size limit |
the CLI's directory reader stops early, before validation |
Content rules#
.liquid and .css files are scanned as text:
| Error | What it catches |
|---|---|
PATH: <script> is not allowed (themes ship no JavaScript; JSON-LD data blocks are the only exception) |
any <script> except exactly <script type="application/ld+json"> |
PATH: inline event handlers (onclick=...) are not allowed |
any on*= attribute |
PATH: javascript: URLs are not allowed |
javascript: |
PATH: data:text/html URLs are not allowed |
data:text/html |
PATH: <iframe> is not allowed |
<iframe> |
PATH: the \raw` filter is not allowed (breaks output escaping)` |
` |
PATH: "<" is not allowed in CSS (prevents </style> breakout) |
any < inside a CSS file |
PATH: section wrapper markers are platform-emitted and not allowed in theme code |
data-dukkan-section or data-section-id= in Liquid |
Structure rules#
| Error | Cause |
|---|---|
missing required template layout/theme.liquid |
no layout |
missing required config/settings_schema.json |
no schema |
config/settings_schema.json is not valid JSON |
invalid JSON |
config/settings_schema.json: … |
a schema error; see Schema errors |
snippets/sections.liquid is a reserved platform path |
a platform-reserved file exists in your bundle |
theme declares sections but ships no snippets/section-body.liquid |
a schema with sections but no body dispatcher |
Schema errors#
They appear prefixed with config/settings_schema.json::
| Error | Cause |
|---|---|
path.to.key: message |
a structural violation (extra key, disallowed type, string over its limit, schema_version not 1) |
duplicate group id "x" |
a repeated group id |
duplicate setting id "x" in group "g" |
a repeated setting id |
setting id "x" in group "g" is reserved |
pages, translations or a reserved object key used as a global id |
duplicate section type "x" |
a repeated section type |
duplicate block type "b" in section "s" |
a repeated block type |
select "x" in … must declare options |
a select without options |
setting "x" in … declares options but is not a select |
options on a non-select |
setting "x" in … declares source but is not a picker |
source on a non-product/category |
setting "x" in … declares localized but is not text |
localized on a non-text/textarea/richtext |
setting "x" in … has min greater than max |
min > max |
default … (where) |
a default that does not match its type |
The full specification is in Settings schema.
The remote check#
npx @dukkan.one/theme-cli theme check --remoteIt sends the bundle to POST /theme-api/v1/theme/check, which re-runs the rules above on the server and then performs a smoke render of templates/home.liquid in the same sandbox that gates publication, with a dummy store, one product and one instance of every section type you declare. A failure comes back as a render failed: <code> line, where <code> is one of:
| Code | Meaning |
|---|---|
timeout |
the render exceeded 1.5 seconds of engine work or 3 seconds overall |
render_error |
a Liquid error (unknown tag, unknown filter, include of a missing file) |
output_cap |
output over 1 MB |
template_missing |
templates/home.liquid is absent |
worker_unavailable / busy |
server pressure; retry |
At push the render output is checked too: a section wrapper without an id, an unknown id or a repeated id rejects the push even when the text scan passed.
Validator lag in the published release#
Warning
The published @dukkan.one/theme-cli@0.1.0 bundles a validator copy older than the current schema: it locally rejects band and newer additions the server accepts. If you see a schema error on a key documented here, the remote check is the judge. Until a newer release ships, either leave band off or run the CLI build from the platform repository.
In the dukkan-themes repository the variable DUKKAN_CLI=/path/to/app/cli/dist/index.js points the repo tooling at an unreleased CLI build; see the CLI reference.
CI recipe#
name: theme-check
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npx @dukkan.one/theme-cli@0.1.0 theme check .The local check needs no sign-in; --remote needs a CLI token, so keep it for a manual run or a step that reads its credentials from a secret.