The theme check reads a whole package in one pass and reports every problem at once. The same check gates a developer's release, the marketplace submission and the platform's own themes (a test runs it on every official theme). A theme with any error cannot be released or installed; warnings are what a reviewer reads first; an informational note is advice.
Every finding has a stable code (js.eval, section.unknown-block), the path it was found at, a message written for the developer and, where it can, a line number. The workspace groups findings by the code's first word — manifest, package, layout, template, section, block, setting, settings, presets, preset, liquid, snippet, asset, css, js, security, performance, locale, a11y, listing, docs, group, config — and re-runs on every save.
Manifest
| Code |
Severity |
Rule |
manifest.missing |
error |
every theme needs abizvo-theme.json at its root |
manifest.json |
error |
it must be valid JSON |
manifest.schema-version |
error |
schemaVersion must be 1 |
manifest.theme-api |
error |
themeApi must be 1.0 |
manifest.key |
error |
2–40 lowercase letters, digits and hyphens, starting with a letter |
manifest.name |
error |
required, at most 40 characters |
manifest.version |
error |
major.minor.patch |
manifest.developer |
error |
required |
manifest.category |
error |
one of the thirteen categories |
manifest.pages |
error / warning |
exactly one starter page is the home; each needs a name, a valid address and a template the theme has; none at all is a warning |
theme.apps.unknown |
error |
apps.recommended or apps.required names an app key the platform does not have — an official app, or one the host's app store holds a released version of; a key nobody released would install nothing and recommend a blank |
manifest.features |
warning |
the theme has JavaScript but does not declare features.javascript |
Package and structure
| Code |
Severity |
Rule |
package.path |
error |
a path outside the ten folders, with .., absolute, too deep, or with an extension the platform does not serve |
package.duplicate |
error |
two files at one path (compared exactly) |
package.file-size, package.file-count, package.size, package.text-size |
error |
10 MB per file, 1,500 files, 60 MB, 512 KB per text file |
package.misplaced |
error |
a non-.liquid file in layouts, sections, blocks or snippets; a non-.json in templates or locales |
layout.missing |
error |
layouts/theme.liquid |
layout.header, layout.content |
error |
a layout must output {{ content_for_header }} and {{ content_for_layout }} |
layout.title |
warning |
a <title> of the layout's own |
template.missing |
error |
home, page and 404 templates |
template.recommended |
warning |
contact and gallery templates |
template.name |
error |
a template file not named for a type, or with a bad suffix |
group.name |
error |
a -group.json not named header, footer, announcement, overlay or aside |
group.missing |
warning |
no header or footer group |
settings.missing |
error |
config/settings_schema.json |
settings.json, settings.data-json, presets.json, template.json, group.json, locale.json |
error |
a config, template, group or locale file that is not valid JSON of the right shape |
config.unknown |
warning |
a config file the platform does not read |
listing.screenshots, docs.missing |
warning |
no screenshots/, no documentation/ |
Liquid
| Code |
Severity |
Rule |
liquid.parse |
error |
a layout, section, block or snippet Fluid cannot parse |
liquid.schema-twice, liquid.stylesheet-twice, liquid.javascript-twice |
error |
a second {% schema %}, {% stylesheet %} or {% javascript %} in one file |
section.name, block.name, snippet.name |
error |
a file name outside letters, digits, hyphens, underscores |
section.schema-missing, block.schema-missing |
error |
a section or theme block without a {% schema %} |
section.schema-json, block.schema-json |
error |
a schema that is not valid JSON |
snippet.missing |
error |
{% render 'x' %} names a snippet the theme lacks |
asset.missing |
error |
'x' \| asset_url names an asset the theme lacks |
Schemas
| Code |
Severity |
Rule |
settings.definition, setting.definition |
error |
a setting declaration that fails the rules on Settings: unknown type, bad id, a reserved id (size…), no label, no options, a bad range, an invalid default, responsive on a type that cannot be, a scheme group without a definition, a header or paragraph without content |
settings.duplicate-id, setting.duplicate-id |
error |
two settings sharing an id in one owner |
settings.schemes |
error |
more than one color_scheme_group |
setting.scheme-group-place |
error |
a color_scheme_group anywhere but Theme settings |
section.name-missing, block.name-missing |
error |
a schema without a name |
section.block-duplicate, section.block-name |
error |
two local blocks of one type; a local block without a name |
section.unknown-block, block.unknown-block |
error |
a named theme block the theme has no file for |
section.max-blocks |
error |
max_blocks outside 1–250 |
section.placement |
error |
enabled_on / disabled_on naming a template type or group that does not exist |
section.no-presets |
warning |
a section that cannot be added from the picker |
section.category |
info |
no category for the picker |
preset.name, presets.name |
error |
a preset or look without a name |
preset.setting, preset.value, preset.block, presets.value |
error |
a preset setting a setting that does not exist, an invalid value, a block that cannot go there; a look with an invalid value |
Data
Every template, group and settings_data.json is normalized against the theme's own schemas exactly as a site's saved data is at save and publish — every code on the Sections and blocks page (section.unknown, template.too-many-sections, block.not-accepted, block.limit, block.max, block.depth, block.children, template.layout, template.schema-version, setting.value, settings.value…). In a theme package, an informational note about a template (a value for a setting that does not exist) is raised to an error: a shipped file has no excuse.
Security
| Code |
Severity |
Rule |
security.remote-script |
error |
a <script src> from another site in any Liquid file |
security.inline-handler |
error |
an inline event handler (onclick=…) |
security.inline-script |
error |
an inline <script> with code (JSON and JSON-LD blocks are allowed) |
security.script-url |
error |
an address that runs script: javascript:, vbscript:, or data: holding HTML or SVG |
security.active-element |
error |
<base>, <object>, <embed>, a refresh <meta>, or a frame with srcdoc |
security.svg-script |
error |
an SVG with a <script>, an on… handler or a javascript: link |
security.svg-foreign |
error |
an SVG with <foreignObject>, <iframe>, <embed>, <object> or an external href |
security.mime |
error |
a picture whose bytes are not the image its name says |
CSS — read as tokens
Every .css asset and every {% stylesheet %} is tokenized by the platform's own CSS lexer, never matched as text, so a comment mentioning @import finds nothing.
| Code |
Severity |
Rule |
css.import |
error |
@import loads another stylesheet at runtime; put it in assets/ and link it |
css.remote |
error / warning |
url(http:…) or url(//…) is an error; url(https:…) is a warning to review as a dependency |
css.javascript |
error |
a javascript: address |
css.script |
error |
expression() or behavior |
a11y.focus |
warning |
outline: none (or 0) in a stylesheet with no :focus-visible rule |
JavaScript — read as a syntax tree
Every .js and .mjs asset and every {% javascript %} is parsed with Acornima (a .mjs as a module; otherwise as a script, then as a module). A pattern match is never the evidence: every finding is a node of the tree, so a string that merely mentions eval finds nothing and window["ev" + "al"] is flagged. Unlike a site owner's generated effects, which run against an allowlist, a theme gets the whole DOM — so this is a denylist of the things no theme has a reason to do, the most dangerous refused outright and the rest flagged for the human review every marketplace release goes through.
| Code |
Severity |
Rule |
js.size |
error |
a script over 512 KB |
js.parse |
error |
not valid JavaScript |
js.eval, js.function, js.importscripts |
error |
eval, Function, importScripts run text as code |
js.miner |
error |
a name of a known cryptocurrency miner |
js.dynamic-import |
error |
import() loads code at runtime |
js.remote-import |
error |
an import from another site |
js.worker |
error |
new Worker / new SharedWorker |
js.script-markup |
error |
a string literal containing <script |
js.inject-script |
error |
document.createElement('script') |
js.with |
error |
with |
js.cookie |
error |
document.cookie — it holds the visitor's session |
js.beacon |
error |
navigator.sendBeacon |
js.credentials |
error |
the browser's credential store |
js.document-write |
error |
document.write / writeln |
js.string-timer |
error |
setTimeout / setInterval given a string |
js.redirect |
error |
assigning location / href to a computed address or another site |
js.form-action |
error |
assigning a form's action to a computed value |
js.storage |
warning |
localStorage, sessionStorage, indexedDB |
js.network |
warning |
fetch, XMLHttpRequest, WebSocket, EventSource |
js.computed-global |
warning |
a member of window, globalThis, self or document reached by a name computed at runtime |
js.html-sink |
warning |
innerHTML, outerHTML, insertAdjacentHTML |
js.frame-reach |
warning |
window.opener, window.parent, window.top |
js.inject-iframe |
warning |
document.createElement('iframe') |
js.post-message-any |
warning |
postMessage(…, '*') |
js.remote-src |
warning |
assigning an element's src to another site |
Locales
| Code |
Severity |
Rule |
locale.name |
error |
a locale file not named for its language |
locale.missing |
warning |
no locale file at all |
locale.key-missing |
warning |
'x.y' \| t with no text in the default locale (a plural key counts when it has other) |
locale.french |
info |
no fr.json |
| Code |
Severity |
Rule |
performance.css |
warning |
above 150 KB of CSS |
performance.js |
warning / error |
above 150 KB of script; above 400 KB refused |
performance.image |
warning |
one image above 800 KB |
performance.size |
warning |
a package above 25 MB |
performance.requests |
warning |
more than 60 assets |
Accessibility
| Code |
Severity |
Rule |
a11y.lang |
warning |
<html> without lang |
a11y.alt |
warning |
an <img> written by hand without alt — use image_tag, which writes the owner's alt text |
a11y.new-tab |
warning |
a link opening a new tab without rel="noopener" |
a11y.tabindex |
warning |
a positive tabindex |
a11y.focus |
warning |
outline: none with no :focus-visible style |
Reading findings in the workspace
Each save in the workspace re-runs the check and shows the report beside the file tree: the counts, then every finding under its category with its path and line. Release is refused while any error stands, with the count in the refusal. A finding's message says what to change; the code is what to search this page for.
The render also reports: a missing asset, a missing translation, a section that failed, an unknown form kind, a content_for outside a section. Those come back from the preview as render.* findings and never reach a visitor — a section that fails renders as nothing on the public page.
What the check does not do
It does not render every look and template, measure contrast, count H1s or read the sample content. Those are the premium theme standards, held by tests and by the human review rather than by the check.