Shopify theme development produces more than files that happen to render in a browser. It produces a storefront interface that can be explained, checked, and restored. The lifecycle starts with requirement classification, then moves through directory contracts, Liquid data boundaries, JSON templates, sections and blocks, app extensions, development themes, code review, release acceptance, and a traceable recovery path. Every boundary below is written as a check that design, development, content, merchant, and on-call roles can execute together. The budgets are team acceptance thresholds, not promises about platform capability or business outcomes.
1. Classify the requirement before choosing a file
1.1 Describe the visible user result
Begin with what a visitor sees on a specific page, then select the implementation layer. A product-card price presentation, a collection filter entry point, or a cart empty state usually belongs to theme presentation. A cross-page business rule, sensitive data access, asynchronous synchronization, or checkout restriction needs an app, function, or service boundary. Putting every problem into Liquid makes templates carry untestable responsibilities and lets a visual change create an unexplained business change.
A useful requirement record includes the page type, data source, interaction, no-script result, language and market conditions, acceptance evidence, and recovery action. Do not write “make this component more flexible.” State which fields a merchant can configure in the theme editor, what a visitor sees when JavaScript is unavailable, what happens when a field is empty, and which data change the theme must not own.
| Requirement signal | Preferred layer | Visible theme result | Keep outside the theme |
|---|---|---|---|
| Change layout, copy, or display order | Theme section, block, or snippet | Configurable area, stable empty state, responsive HTML | Order writes and cross-system sync |
| Read a Shopify product or collection object | Liquid and template context | Object-driven output with explicit defaults | Hard-coded stock or prices |
| Let a merchant compose modules | JSON template, schema, and section | Addable, removable, sortable modules | Treating editor settings as permissions |
| Inject an app-owned interface | Theme App Extension | Addressable app block and settings | Copying private app files into the theme |
| Change checkout rules or calculate business results | App, Function, or service layer | Theme presents a confirmed result | Faking price, discount, or eligibility in Liquid |
| Retry, audit, or access sensitive cross-system data | Backend integration with separate credentials | Theme presents safe result or error | Exposing tokens, queues, or private data in the browser |
1.2 Write one boundary decision for each request
The boundary record needs an owner and a counterexample. A “member label” can be presentation when it reads an authorized value already in the page context; it needs a different boundary when it requires a live query against private customer information. Each request should have one primary implementation layer. Other layers should be documented as inputs or outputs, rather than giving both a theme and an app ownership of the same fact.
During review, answer four questions line by line: Who owns this field? When does it change? What is displayed for an empty or failed value? Who can correct a mismatch? If the answer depends on an undefined global variable, the request is not ready for implementation. Keep the decision and its acceptance examples beside the code version so a later upgrade can distinguish a code change from a changed business assumption.
2. Establish a readable repository contract
2.1 Give each directory one responsibility
Directory names are collaboration interfaces. layout owns the page shell, global head output, and global resource entry. templates owns page-type composition. sections owns modules that a template or editor can place. snippets owns reusable output fragments that are not standalone layouts. assets owns CSS, JavaScript, and media references. config owns theme settings and defaults. With this separation, a reviewer can infer impact from a path; with a blurred structure, every change requires runtime guesswork.
Do not make one giant section represent the entire home page, and do not put every rule into one snippet. Organize files around one user task or one display contract. A section can compose several blocks, but each block should have one settings shape and an explicit empty state. Templates should compose rather than quietly mutate an object or perform an app synchronization job.
| Directory or object | Appropriate responsibility | Review evidence | Common boundary failure |
|---|---|---|---|
layout | HTML shell, global head, global resource entry | Source output and resource list | Put a page-only query in the global shell |
templates | Page type and section order | JSON structure and route examples | Copy component logic into every template |
sections | Configurable modules and block containers | Schema, empty state, mobile sample | Depend on invisible global state |
snippets | Small reusable output fragments | Inputs, output HTML, call sites | Hide side effects inside a fragment |
assets | Styles and scripts required by a page | Load condition, failure fallback, size record | Load every script on every page |
config | Setting definitions and defaults | Types, defaults, migration note | Store secrets or order facts in settings |
| App extension directory | App-owned extension resources | Extension config, install, removal test | Edit extension resources as theme files |
2.2 Make names searchable interfaces
Names should describe a page role or action, such as product-card, cart-line, or media-gallery, rather than new-final-v2. A variable name should tell the reviewer whether it is an object, a setting, a derived display value, or a state. Keep loop variables local to the loop and document inputs when passing values across snippets.
The repository also needs a short README describing the local preview entry point, the source of theme settings, the versions of the checking tools, app-owned directories, and changes that need design or content review. A README does not replace code comments; it lets a new contributor reproduce a page without access to real customer data.
3. Liquid: presentation, filtering, and data boundaries
3.1 Confirm the object before reading fields
Liquid composes Shopify objects that are available in the page context. First confirm the object type, whether a field can be absent, and whether a template or market changes its availability. Do not use a product title as a stable ID, build a URL from a title, or hard-code a country or language in a branch. When displaying a price, preserve the currency context supplied by the platform rather than simulating conversion with string replacement.
A resilient fragment treats “no object,” “field unavailable,” and “empty field” as separate states and gives each a readable fallback. A product card with no image can still show its title, price, and action link. A recommendation module with no result can collapse without leaving an empty container. This also makes no-script acceptance possible because navigation and primary information do not depend on one asynchronous request.
| Data condition | Safe Liquid action | Visitor fallback | Evidence to retain |
|---|---|---|---|
| Root object is missing | Check existence first | Hide the dependent module and keep page structure | Route, template, fixture |
| Field is unavailable | Use explicit default copy or omit the field | Explain that information is unavailable | Field list and screenshot |
| List is empty | Render an empty state or omit the container | Offer a useful next navigation | Empty-list fixture |
| Market or language differs | Read context or configured result | Preserve local format and direction | Market, language, direction set |
| Business value needs calculation | Display only a confirmed input result | Explain that confirmation is pending | Calculation owner and source |
3.2 Bound loops, filters, and side effects
List the small field set required by the page before writing a loop. Unbounded nested loops, repeated filters, and duplicated calculations across snippets make rendering cost hard to explain. Let an upstream object or service own sorting, pagination, and stable identity when it supports those operations; keep the theme transformation light and visible.
Liquid output should not perform invisible writes or own retries. When JavaScript adds interaction, let HTML provide an initial usable state and let the script enhance it. If a script fails, is blocked, or arrives late, a visitor should still browse, select, and receive an error explanation. This boundary also limits the blast radius when an app script conflicts with theme behavior.
4. JSON templates, sections, blocks, and schema
4.1 Treat editor settings as a typed contract
A schema is a settings table that a merchant can understand, not a random list of variables. Each setting needs a label, default, allowed range or options, empty behavior, and a representative page sample. A block setting should describe that block’s content and display behavior; a section setting should describe the container. Once a setting name, default, or type is used, maintain it as a compatibility interface.
JSON templates declare which sections form a page and how their settings are composed. Validate a minimal template first, then combine more complex layouts. When several sections depend on the same resource, check for duplicate loading. When a section is removed, confirm that navigation, the main heading, and forms still have a usable path.
| Design item | Acceptable contract | Acceptance question | Failure fallback |
|---|---|---|---|
| Setting type | Matches the input purpose and renders its default | Is empty, long, or special-character input safe? | Hide optional decoration and retain text |
| Block count | Has a reasonable limit or clear reorder behavior | Do first, last, single, and many blocks behave? | Use a simplified layout |
| Section order | Declared explicitly by the JSON template | Does moving one section break heading order? | Return to the minimal template |
| Optional media | Has dimensions, alt text, and missing state | Does failure still leave useful information? | Text and reserved structure |
| Dynamic field | Has a traceable source and default | Can a missing field be mistaken for a fact? | Omit the field and keep the action |
| Settings migration | Rename or removal has a note | Does an old configuration open without an error? | Use a compatible default |
4.2 Keep section and block composition independent
A section is a layout container; a block is a movable content unit. A block should not assume a neighboring section exists or use DOM queries to guess a sibling’s order. When information must be shared, use a stable setting, a documented context object, or an explicit service result, and test the missing case separately.
Test the editor preview against the public theme preview. The editor may show selection outlines, temporary sample content, or unsaved settings, while the public page should show only saved configuration. Record whether each acceptance result came from the editor, a preview, or a public theme so temporary editor state is not mistaken for a visitor result.
5. App extensions and theme-file boundaries
5.1 Use an app block for app-owned interface
When an interface belongs to an app, prefer a Theme App Extension app block or app embed block, with extension configuration, so the app owns its resources and lifecycle. The theme should provide a place to render, pass only necessary settings, and keep surrounding layout usable. After an app is disabled, removed, or returns no data, the theme should hide an empty block without copying app code into the theme.
Extension settings must state who can change them, which fields are stored, and what appears when permission is missing. Do not put app tokens, private endpoints, or retry queues in theme settings. When an app needs server data, the theme should consume an authorized rendering result and handle timeout, empty result, and version mismatch with visible copy.
| Situation | Theme responsibility | App extension responsibility | Acceptance and exit evidence |
|---|---|---|---|
| Place a recommendation module | Provide an app-block layout slot | Fetch and render the extension result | Empty result does not push core content |
| Load extension resources | Keep an entry only where needed | Own extension assets and initialization | Resource list and removal page |
| Store merchant settings | Provide understandable container settings | Maintain its own configuration contract | Old and empty setting samples |
| Request fails | Preserve title, navigation, and primary action | Alerting, diagnosis, and retry policy | Failure screen and recovery steps |
| App is removed | Hide the empty block without copied files | Remove its extension state | No residual script or style |
5.2 Diagnose app and theme conflicts
Start with a minimal reproduction: the same product, market, theme copy, and page, changing only one app or one block. Record the route, template, resource order, console errors, DOM changes, and time. If disabling an app makes the problem disappear, that does not prove the theme is innocent; check whether the theme depended on an injected global variable or selector.
The order is isolate, collect evidence, degrade, repair, and regress. Disable a disputed block or resource first and confirm that core pages remain usable; do not upgrade the theme, app, and browser script together. For style conflicts, narrow selectors and loading scope. For script conflicts, prevent duplicate initialization and unchecked globals. For data conflicts, return to one source of truth and pause uncertain output.
6. CLI and development-theme workflow
6.1 Work on a development theme first
Start each task on a separate development theme. Keep page samples, settings snapshots, test products, markets, and languages there; it is temporary and hidden rather than a durable unpublished theme, and it is not a second long-lived business system. When a version needs durable review, push it to an unpublished theme with a documented access boundary. At the beginning record the theme name, baseline version, owner, and access scope. At the end save the preview address, checking output, and difference summary.
Use Shopify CLI according to the current official documentation and the installed team version. Use the documented theme development, checking, push, and release actions and do not embed unverified flags in scripts. Confirm the target theme before any action that can change shared state, and save the terminal result afterwards. A shared-theme action requires a review record and recovery preparation.
| Stage | CLI or platform action | Evidence to retain | Stop condition |
|---|---|---|---|
| Create isolation | Connect and select a development theme | Theme identifier, operator, start time | Target or permission is unclear |
| Local iteration | Start a theme preview and change a small scope | Preview address, route, difference | Preview is not reproducible |
| Static check | Run Theme Check | Output, tool version, locations | Unexplained error or warning |
| Preview acceptance | Test representative devices and languages | Screenshots, keyboard path, console | Core action or no-script state fails |
| Shared review | Push to a dedicated review space | Difference summary and review | No recovery version or owner |
| Release action | Release the approved version | Version name, time, result | Unaudited difference appears |
6.2 Make preview data repeatable
Fixtures should not depend on one contributor’s private draft. Prepare at least one product with media and variants, an empty collection, a multilingual page, an app-block failure, and a path with JavaScript unavailable. Fixtures reproduce interfaces and must not contain real customer information, access tokens, or private order details.
Walk the preview by page type rather than clicking files one at a time. Record routes for home, collection, product, search, cart, content, and error states. When a failure appears, retain the minimum reproduction steps and change one variable at a time. Reviewers can then tell whether a fix changed the original failure model.
7. Preview and acceptance matrix
7.1 Cover pages, states, and direction
Acceptance is not “the home page looks fine.” The matrix should cover page type, data state, language direction, input device, and app switch. Put fragile combinations first: a product without media, an empty collection, a long title, a right-to-left language, a narrow keyboard path, blocked scripts, and an app returning no result. Each row needs an expectation, actual result, evidence, and recovery action.
| Dimension | Representative values | Observe | Passing evidence |
|---|---|---|---|
| Page | Home, collection, product, search, cart, content | Template, heading order, primary action | Route and screenshot |
| Data | Normal, empty, missing, long text | Defaults, truncation, wrapping | Fixture and result |
| Language | Chinese, English, right-to-left language | Copy, direction, number and date | Language setting and screenshot |
| Device | Narrow, desktop, touch, keyboard | Overflow, focus, touch target | Viewport record |
| Script | Normal, delayed, failed, disabled | Core content and action availability | No-script image and log |
| App | Enabled, empty, timeout, removed | Block space, conflict, residue | Before and after switch |
7.2 Use a reproducible acceptance order
Confirm the URL and template first, then the page heading and navigation, then content, forms, media, and interaction, and finally resources, console output, and error logs. Change one variable at a time. If editor settings, app configuration, and test product change together, the outcome cannot be attributed.
Use three results: pass, conditional pass, and blocking. A conditional result must name the affected page, owner, due time, and recovery action; “optimise later” is not evidence. Blocking signals include broken links, a form that cannot submit, an incomplete keyboard path, mixed-language copy, exposed sensitive information, failed theme checks, or an unexplained resource spike.
8. Theme Check and continuous checks
8.1 Turn static warnings into actionable work
Theme Check is valuable because it locates risks in Liquid, schema, templates, and theme conventions down to a file and line. Save its version and complete output rather than a green summary. For each warning state whether it is a real defect, an accepted exception, or a structure to rewrite; an exception needs an owner and a review condition.
Static checks do not replace browser and representative-data acceptance. A template can be syntactically clean and still fail on an empty object, a right-to-left language, long copy, or app removal. CI should keep static checks, fixture rendering, link checks, and the human matrix as adjacent but separate gates so a failure identifies the layer that needs recovery.
| Check gate | Trigger | What it checks | Failure action |
|---|---|---|---|
| Format and syntax | Every change | Liquid, JSON, and schema structure | Repair and rerun |
| Theme Check | Every review version | Rules, deprecated-risk signals, locations | Explain or pause review |
| Fixture rendering | When a theme version is formed | Page states, empty values, error states | Keep the last usable version |
| Browser regression | Before shared preview | Console, keyboard, responsive behavior | Isolate the failed page |
| Links and metadata | Before release review | URL, title, language, structure | Block the release action |
| Post-release observation | After release | Key pages and error signals | Apply the recovery decision |
8.2 Avoid a false green CI result
CI fixtures should use fixed inputs and an explicit version, not a personal theme, external network, or temporary app response. Compare readable differences in HTML structure, links, resources, and key text instead of only one total hash. When a difference appears, the reviewer should see whether it is an expected heading change or an accidental removal of a form, focus path, or alt attribute.
When the checking tool changes, run old and new rules in an isolated change, list new warnings, and schedule code changes separately. Do not put a tool upgrade and a theme refactor into one inseparable batch. If the result fails, you can then restore the rule version or the theme difference without guessing.
9. Git, review, and version evidence
9.1 Make each commit explain one change
A commit message should state the page scope, user result, data dependency, and recovery path. Separate spacing, Liquid data correction, app-block integration, and asset splitting when possible so a reviewer can read by risk. Do not format the entire repository as a side effect, and do not mix generated large differences with hand-authored code.
The review description should list changed files, unchanged boundaries, test routes, language and device combinations, known limits, and the action that restores the last usable version. A screenshot proves one view, not a state matrix. A command output proves one run, not a failure recovery. A theme version name or tag should map to the review record one to one.
| Review question | Evidence required | Passing standard | Rejection signal |
|---|---|---|---|
| Which pages are affected? | File-to-template mapping | Scope is enumerable | “The whole store should be fine” |
| What data is read? | Object and field list | Source, default, owner clear | Hard-coded business fact |
| Which apps are involved? | App block and resource list | Can disable, remove, and degrade | Copied app code |
| Is it accessible? | Keyboard, focus, label evidence | Core path can complete | Mouse-only demonstration |
| Can it be restored? | Version, difference, and steps | Action is executable | No known-good version |
| Which facts are uncertain? | Limit and review note | Uncertainty is explicit | Assumption written as guarantee |
9.2 Protect theme settings and content
Code version and editor settings can change independently. Record the code version, JSON templates, settings snapshot, and representative pages together; a Git difference alone is incomplete. If a merchant changes a heading, media item, or section order in the editor, verify that recovery code will not overwrite it. If the settings shape changes, provide a compatibility path for old settings.
Permissions are also version evidence. Separate theme developer, reviewer, merchant, and app-owner responsibilities; grant only the scope required for the task. Remove shared-theme access when a contributor leaves, record the change, and never store personal credentials or temporary passwords in the repository.
10. Performance budgets and sustainable rendering
10.1 Use page budgets to constrain resources
A performance budget finds resource creep; it is not a fixed guarantee across networks, devices, or visitors. Record budgets by page and resource type, keep measurement conditions stable, and inspect LCP resource priority, server-rendered essential content, long tasks, media dimensions, and third-party scripts together. One over-budget result needs an explanation, scope, and action rather than an unexplained score.
| Page resource | Team acceptance budget | Measurement | Action when exceeded |
|---|---|---|---|
| Critical HTML | No more than 170 KB compressed | Record response and route | Remove duplicate fragments and retest |
| Critical CSS | No more than 24 KB | Record critical style entry | Split non-critical styles |
| Initial theme JavaScript | No more than 120 KB | Record the page load set | Defer non-critical interaction |
| Main above-fold media | No more than 220 KB | Record source, size, and format | Change size or defer loading |
| Third-party scripts | No more than 3 entry points by default | Record owner and execution time | Disable, defer, or consolidate |
| Font requests | No more than 2 initially | Record use and fallback | Use system font or fewer variants |
| Long task | No single task over 50 ms | Record browser performance | Split initialization and listeners |
10.2 Make every budget change explainable
For each resource answer which page needs it, who owns it, and how it degrades when disabled. Essential content should be server-rendered instead of waiting for a script to assemble the title, price, navigation, or form. Do not lazy-load the genuine LCP resource by default; when it is truly the above-fold resource, use an appropriate fetchpriority hint only after measuring the page. Do not load an app script across the store because one app requests it; let an app block create an entry only on pages where it is placed, and check that the element exists before initialization. Avoid one large bundle for every page and do not make unused polyfills default dependencies.
Media needs dimensions, alt text, and a loading decision. Decorative backgrounds must not block primary content. A product hero image should reserve layout space so a late response does not push an action away. Caching and a CDN can assist transfer, but they do not replace source dimensions, format, or scope review. When a page is slow, identify the resource and page instead of hiding a long-tail problem in one aggregate score.
11. Accessibility and internationalisation acceptance
11.1 Walk the core task with keyboard and screen reader
Core tasks include opening navigation, reaching main content, selecting a variant, adding to cart, changing quantity, closing a dialog, and reading an error. Start each task with a keyboard. Focus should follow the visual sequence, remain visible, and not be covered by a sticky element. Buttons, links, inputs, and dialogs need correct semantics and names; do not attach a click handler to a decorative element with no interaction semantics.
With a screen reader, verify the page title, landmark names, form labels, error association, and status changes. Dynamic updates need an appropriate announcement without interrupting every decorative change. Hidden content must be truly unreachable; when a panel opens, focus should go somewhere predictable, and when it closes, focus should return to the trigger. Alt text should describe an image’s purpose rather than its filename.
| Test item | Chinese page sample | English or right-to-left sample | Passing evidence |
|---|---|---|---|
| Page title and language | Title, lang, spoken Chinese | English title, language, direction | DOM and reading record |
| Main navigation | Keyboard expansion and skip link | Same path with translated labels | Focus path |
| Product selection | Variant name, state, error | Variant names and state changes | Form recording and DOM |
| Cart action | Quantity, remove, empty state | Quantity, remove, empty state | Completed keyboard path |
| Dialog | Open, focus boundary, return | Same behavior with longer labels | Before and after focus |
| Image and icon | Purposeful alt, decorative hiding | Localized purpose text | Attribute check |
| Dynamic error | Field association and announcement | Error text in the locale | Screen-reader output |
11.2 Treat translation and direction as layout inputs
Translation is not a token-for-token replacement. English may be longer, right-to-left languages change arrows and spacing, and numbers, dates, currencies, and product names have their own formatting. Put buttons, labels, errors, empty states, and alt text in the language inventory; do not hide Chinese-only strings in JavaScript or images.
Use long copy, a missing translation, mixed numerals, and a right-to-left direction during acceptance. Check navigation overflow, currency placement, icon direction, and focus order. When a translation is missing, use an explicit fallback language and record it; never expose a translation key. If an app block does not support the current language, retain the layout and explanation instead of exposing a script error.
12. SEO, structured information, and semantic HTML
12.1 Let the template own page semantics
Each page type should define its title, description, canonical, language association, and one primary heading. The theme should emit one semantic structure; layout, section, and app should not each emit a copy of the same head information. Product structured information may use only visible, traceable fields. If a value is missing, omit or degrade it rather than inventing a price, stock state, review, or eligibility.
Semantic HTML improves accessibility and maintenance. Use navigation elements for navigation, a clear main region, buttons for actions, links for location changes, and labeled form controls. Do not use heading tags only for larger type or skip levels for visual effect. Perform SEO acceptance with page states, languages, and app switches because duplicate head output, a wrong language, and residual resources often arise from composition.
| Page type | Check | Common error | Fallback |
|---|---|---|---|
| Home | Main heading, description, navigation regions | Sections repeat the heading | Keep one page heading |
| Collection | Collection name, pagination, filter semantics | Empty collection emits a bad link | Render useful empty navigation |
| Product | Product fields, media, variant state | Hidden value enters structured data | Omit the missing field |
| Search | Query state, result count, empty state | Query becomes a generic page title | Use safe query semantics |
| Content | Heading order, author region, links | App duplicates the canonical | Let the page template own it |
12.2 Check multilingual URLs and navigation
Language paths and link labels should match the page language. Do not add an English service entry to a Chinese page merely to fill a link count, and do not disguise a language switcher as an ordinary content link. Check URL, canonical, and language associations for every locale path; language versions should reference one another instead of sending every locale to a default page.
For related reading, see Shopify theme architecture and operations, Shopify Liquid theme customisation, Theme App Extension governance, Shopify performance engineering, and Shopify multilingual localisation. These pages cover operations, Liquid structure, extension boundaries, performance diagnosis, and localisation inputs; they do not replace this theme lifecycle guide.
13. Release checklist and responsibility matrix
13.1 Complete the release checklist item by item
Execute the checklist in order and attach evidence to every row. Confirm the version and target theme first, then inspect static output, templates, and schema, then run the page matrix, keyboard and screen-reader checks, performance budgets, language checks, and app-conflict tests. Finally save approval, the version name, and the last restorable version. A checklist is not complete when its evidence is missing.
| Order | Check | Evidence | Blocking condition |
|---|---|---|---|
| 1 | Target theme and version | Name, version, operator | Target or scope unclear |
| 2 | Difference and directory boundary | File list and affected pages | Unaudited file appears |
| 3 | Liquid and schema | Theme Check output | Unexplained error |
| 4 | Templates and empty states | Page matrix and fixtures | Empty object breaks core path |
| 5 | App extensions | Enable, disable, removal records | Residual or global script |
| 6 | Accessibility | Keyboard, reading, error state | Core task cannot complete |
| 7 | Performance budget | Resource and long-task record | Over budget without action |
| 8 | Language and semantics | Locale path, headings, labels | Mixed copy or wrong direction |
| 9 | Recovery preparation | Last usable version and steps | Cannot restore safely |
| 10 | Approval and observation | Review, time, owner | No accountable role |
13.2 Make ownership traceable with RACI
RACI should not make everyone “jointly accountable.” Give each activity one A. R roles execute, C roles provide input, and I roles need the result. For an app extension, the app owner is responsible for its configuration and failure path; the theme owner is responsible for layout degradation, resource scope, and semantic HTML. The merchant approves the business result and content, not the internal diagnosis.
| Activity | Theme developer | Technical reviewer | Merchant or content owner | App owner | On-call lead |
|---|---|---|---|---|---|
| Requirement boundary and page samples | R | C | A | C | I |
| Liquid, schema, and templates | R | A | C | I | I |
| App-block configuration | C | C | A | R | I |
| Accessibility and language matrix | R | A | C | C | I |
| Performance budget and resource list | R | A | I | C | I |
| Version approval and release | R | C | A | C | I |
| Incident isolation and recovery | C | C | I | R | A |
| Follow-up record and repair | R | A | I | C | C |
14. Version control, upgrades, and compatibility
14.1 Build a compatibility table before an upgrade
List theme code, JSON templates, setting keys, app blocks, script entries, language resources, and manual editor changes before upgrading. Mark each item as safe to carry unchanged, requiring conversion, requiring new acceptance, or ready for removal. Do not overwrite the old theme with new files and do not treat “the editor opens” as the only compatibility test.
| Asset | Before-upgrade check | After-upgrade validation | Failure action |
|---|---|---|---|
| JSON template | Section names and order | Page type retains primary path | Restore old template and convert |
| Schema setting | Key, type, default | Editor opens and saves | Use a compatible default |
| Liquid snippet | Input object and empty state | Normal and missing-field fixtures | Isolate the fragment and recover |
| App block | Extension configuration and assets | Enable, disable, and remove | Disable the block, retain layout |
| CSS and script | Entry, selector, initialisation | Console and budget checks | Split or defer the resource |
| Language resource | Key and fallback language | Long copy and direction | Restore the old translation path |
| Merchant content | Settings snapshot and media | Editor and public preview | Restore the settings snapshot |
14.2 Record facts that code cannot represent
Theme code does not represent every store state. Record app versions, editor settings, representative products and markets, permission changes, and known exceptions so reviewers know what recovery restores and what it does not. Do not claim code recovery can undo later merchant edits, and do not use a timestamp as proof that one version is trustworthy.
Run a small page test after an upgrade, then expand to the full matrix. If only one page fails, keep the last usable version and the minimal failure fixture. If navigation, cart, form, language, or sensitive information fails, stop expanding the scope and return to the last usable version first.
15. Failure case: an app script breaks theme navigation
15.1 Reproduce and preserve evidence
A cross-border store added a recommendation app block. On a narrow viewport the menu close control lost focus, so a keyboard user could not return to the trigger. The app script also initialised on every page even though the block existed only on the product page. The home page looked normal, while the product page logged a duplicate-initialisation warning. “One browser is incompatible” is not a sufficient diagnosis; preserve the same theme copy, language, product, and app switch as a comparison.
Reproduce it by opening navigation in a development theme without the block, then adding the block only to the product page. Use a keyboard to open the menu, trigger the recommendation, close the menu, and open it again. Repeat with long English copy and a right-to-left test language, then disable the app entry. Record the focused element, resource order, DOM attributes, console output, and screenshot for each step. This separates theme focus handling, app initialisation, and language layout.
15.2 Recover and repair in a bounded order
Disable the app block first and confirm that navigation, cart, and product selection remain usable. Keep the last usable theme version as the safety reference. Repair focus within the menu so it returns to the trigger, and let app initialisation check for the block before running. Scope the resource entry to pages that need it. The app owner handles recommendation timeout and empty results; the theme owner handles empty-block layout and keyboard degradation; the on-call lead decides when the block can return.
16. Failure timeline and recovery boundary
16.1 Separate observation, action, and decision
A timeline should separate what was observed, what was done, and what was decided. Do not write a later hypothesis as if it was known at the time. Record one event per row with the theme version and app switch. After recovery, retain the failed difference as read-only regression evidence rather than making it the default version again.
| Time | Event | Signal observed | Decision and evidence |
|---|---|---|---|
| 09:00 | Create development theme | Page matrix passes | Save version and fixtures |
| 09:25 | Add product-page app block | Product resource set grows | Record difference and owner |
| 09:40 | Begin keyboard regression | Focus disappears after close | Mark blocking and capture screen |
| 09:55 | Disable app block | Navigation returns | Isolation is proven, root cause is not |
| 10:10 | Inspect initialisation entry | Every page has an entry | Prepare page-scoped repair |
| 10:35 | Repair focus and resource condition | Core path passes | Save Theme Check and matrix |
| 11:00 | Retest language and empty result | No residual resource, stable focus | App owner reviews |
| 11:25 | Review restorable version | Last usable version remains | On-call lead chooses scope |
16.2 Use the recovery decision table
Recovery restores usable pages; it does not erase evidence. First judge the failure scope and data boundary, then choose to disable a block, restore a resource, restore a theme version, or pause an app. Be conservative when navigation, cart, forms, privacy, or language paths are affected; isolate an optional decoration when the core path is safe.
| Signal | Scope | Immediate action | Condition before repair continues |
|---|---|---|---|
| Decorative style is misaligned | One optional section | Disable the section or restore style | Core path and focus pass |
| App block is empty | One product module | Disable the block and retain product facts | Empty state is explained |
| Menu or cart keyboard path fails | Several core pages | Restore the last usable theme version | Keyboard steps pass again |
| Language path is mixed | One or more markets | Pause language change and restore links | Canonical and language matrix pass |
| Sensitive field is visible | Any public page | Remove output and isolate the version | Permission and source review pass |
| Resource delays primary content | Affected page set | Defer or disable the third-party entry | Budget and error log are clear |
| Theme check cannot be explained | Changed version | Do not widen the scope | Rule, version, and difference reproduce |
17. Maintenance rhythm and an executable runbook
17.1 Maintain in small, observable steps
Choose one page family and one observable result for each maintenance task. Save a baseline, change the smallest scope, and run the same matrix. Changing templates, apps, translation, and assets together makes attribution impossible. Before removing a snippet, search call sites and setting keys and write a migration note. Before removing an app block, confirm that removal leaves no script, style, or empty container.
The runbook should state the first diagnostic step, isolation switch, last usable version, evidence location, notification roles, and post-recovery checks. It is for on-call readers and should not require deep Liquid expertise. After an incident, update the runbook and fixture so a similar problem appears earlier in the next check.
17.2 Reconfirm changing facts on a regular cadence
Shopify platform behavior, theme tools, app-extension configuration, and browser behavior change. Regularly recheck the official documentation links, installed CLI version, Theme Check rules, extension resources, language inventory, and performance budgets. When a fact depends on version or region, record the review date and owner. Never treat an old command, field, or screenshot as a permanent contract.
Keep maintenance results in three states: continue using, review required, and isolate. Preserve evidence for each state. This guide helps a team build checks, but the actual theme version still needs validation in its own development theme, representative data, and permitted scope. Related performance, Liquid, and extension boundaries are covered by companion topics on theme operations, Liquid architecture, and Theme App Extension governance.
Frequently asked questions
FAQ 1: Should Shopify theme development start with Liquid or a JSON template?
Start with the user task, page type, and data boundary, then choose the order of Liquid and JSON work. If page composition and editor capability are undefined, writing Liquid first hardens the wrong responsibility. If objects, empty states, and output semantics are clear, build a minimal section first, then compose it with a JSON template and acceptance fixtures.
FAQ 2: Can an app block be copied directly into theme files?
No. An app extension owns its configuration and lifecycle. The theme should provide a placement, layout degradation, and necessary settings, not copy private app code. Test enable, empty result, timeout, disable, and removal; residual scripts or styles need the app owner to correct the extension boundary.
FAQ 3: Must the theme be fully usable without JavaScript?
Core reading, navigation, form semantics, and primary actions should have an understandable base state, with an error explanation or alternative path when enhancement fails. A dynamic recommendation may not be reproducible without a script, but product facts, primary links, and focus access must not disappear. Keep the no-script page as its own matrix fixture.
FAQ 4: Is a performance budget a guarantee of Shopify page speed?
No. A budget is a team threshold measured under fixed conditions to control resources and long tasks. It cannot guarantee behavior on every network, device, app response, or visitor context. Record page, data, device, resource, and measurement time; when a budget is exceeded, identify the resource, choose split, defer, replace, or isolate, and retest under the same conditions.
FAQ 5: When should the last usable theme version be restored?
Restore it when core navigation, cart, form, keyboard path, language URL, sensitive information, or primary content across many pages is affected and a safe isolated module cannot be proven quickly. Save the failed difference and logs first. After recovery, retest representative pages, app switches, languages, and settings to confirm the fault is gone without creating a new empty state.
First-party source register
Use these sources to verify theme architecture, CLI, Theme Check, performance, app extensions, accessibility, and version control. Documentation behavior, fields, rules, and tool output can change with versions; verify them in the development theme and scope used for the work.