Shopify to NEXT theme migration
v2.2Updated Sep 4, 2026
A translation guide for Shopify Liquid developers moving an existing storefront to Next Commerce. Use it to map theme concepts and protect storefront behavior. Follow the storefront development workflow for setup, implementation, testing, and handoff.
What changes from Shopify
The platforms share a file-based theme model, reusable partials, assets, and merchant settings. The main differences are DTL syntax, explicit template inheritance, Spark's build pipeline, and the boundary between cached HTML and customer-specific state.
| Concept | Shopify | Next Commerce |
|---|---|---|
| Template language | Liquid | Django Template Language (DTL) |
| Template inheritance | Implicit via theme.liquid | Explicit {% extends %} / {% block %} |
| Partials | {% render 'snippet' %} | {% include 'partials/snippet.html' %} |
| CSS pipeline | Theme-specific tooling | Tailwind v4 through Spark's Makefile, or SCSS through ntk for Intro Bootstrap |
| Settings schema | settings_schema.json | JSON files in configs/ |
| Interactive UI / cart state | Sections blocks + Liquid cart | Web Components + GraphQL API (full-page cache requires client-side JS for per-user data) |
| Storefront API | GraphQL Storefront API | GraphQL Storefront API |
| Starter theme | Dawn | Spark (Tailwind v4) or Intro Bootstrap (Bootstrap 5) |
| CLI tool | Shopify CLI | Theme Kit (ntk) |
Important
Mapped domains cache storefront pages. Never render cart or customer state into shared HTML. Load customer-specific state in the browser instead. Read the caching section before porting a Shopify header, cart badge, or account control.
Tip
Shopify wraps pages with theme.liquid. NEXT templates state their layout with {% extends %} and place content in named {% block %} regions.
Directory structure mapping
Start with a complete NEXT starter or an existing store theme. Do not build a theme from an empty directory.
theme/
├── assets/
├── config/
├── layout/ ← theme.liquid
├── locales/
├── sections/
├── snippets/
└── templates/theme/
├── assets/ ← CSS, JS, images
├── configs/ ← settings JSON
├── layouts/ ← base layouts
├── locales/ ← i18n translations
├── partials/ ← reusable components
├── sass/ or css/ ← SCSS or local Tailwind source
└── templates/ ← page templatesInfo
Checkout is platform-managed. ntk pull may download a checkout/ directory, but ntk push skips it because the store does not accept checkout template uploads.
Styles have source and output files. Spark keeps Tailwind source in css/ and uploads compiled assets/main.css. Intro Bootstrap keeps SCSS in sass/, which ntk processes.
Shopify sections do not map one for one. Rebuild the composition with DTL layouts, blocks, partials, and Theme Settings.
Full-page caching and customer state
Shopify renders each page request for its visitor, so {{ cart.item_count }} is current. NEXT caches storefront pages for 5 minutes on mapped custom domains. The cache is keyed by URL, language, and currency. Visitors with the same locale receive the same cached HTML.
This has two consequences: product pricing is safe (it varies by currency, and the cache handles each combination separately), but per-user data is completely incompatible with page caching. One visitor's cart state gets baked into HTML and served to thousands of others.
Safe in DTL templates (cached per locale)
- Product details, pricing, images
- Category and collection data
- Blog posts, static pages
- Store name, logo, and address
- Theme settings (
settings.*) - Navigation menus
- SEO metadata
Requires GraphQL (per-user, client-side JS)
- Cart contents, count, totals
- User login state / profile
- Wishlists, saved items
- Order history
- Checkout state
- Any personalized data
Never do this in a cached template
Don't use {{ cart.num_items }}, {{ user.is_authenticated }}, or any cart/user object in layouts/base.html or any publicly-served template. One visitor's state gets cached and served to everyone in that locale.
Hydrate the header cart count from the GraphQL API after each page load. Reuse Spark's cart client and event contracts instead of adding another cart state implementation.
Development tip
Develop and verify on the store's .29next.store domain. A skip_cache query can help with debugging, but it is not deployment proof. Confirm the requested asset or page comes from the expected store domain in the browser Network panel.
Code in layouts/base.html appears across the storefront. Keep cart and customer state out of its server-rendered HTML.
Template syntax: Liquid and DTL
Output and filters
Both languages use double-curly-brace syntax for output, but filter syntax differs. DTL uses a colon with no spaces between the filter name and argument.
{{ product.title }}
{{ product.price | money }}
{{ 'hero.jpg' | asset_url }}
{{ product.title | upcase }}
{{ product.title | truncate: 50 }}{{ product.get_title }}
{{ product.price|currency }}
{{ 'hero.jpg'|asset_url }}
{{ product.get_title|upper }}
{{ product.get_title|truncatechars:50 }}Important
Do not add spaces around the pipe in DTL. Liquid uses {{ value | filter }}. DTL uses {{ value|filter }}.
Conditionals
{% if product.available %}
In stock
{% elsif product.coming_soon %}
Coming soon
{% else %}
Sold out
{% endif %}{% if product.is_available %}
In stock
{% elif product.coming_soon %}
Coming soon
{% else %}
Sold out
{% endif %}Note: elsif in Liquid becomes elif in DTL. Same logic, different keyword.
Loops
{% for product in collection.products %}
{{ product.title }}
{% if forloop.first %}First!{% endif %}
{% endfor %}{% for product in products %}
{{ product.get_title }}
{% if forloop.first %}First!{% endif %}
{% endfor %}Loop syntax is nearly identical. DTL also supports forloop.counter, forloop.last, and a built-in {% empty %} handler for when the iterable has no items.
Template inheritance
In Shopify, theme.liquid wraps all templates implicitly. In Next Commerce, templates explicitly declare which layout they extend and which blocks they fill:
{# templates/pages/about.html #}
{% extends "layouts/base.html" %}
{% block content %}
<h1>{{ page.title }}</h1>
<div class="page-body">{{ page.body|safe }}</div>
{% endblock %}The layout in layouts/base.html defines named blocks that child templates override. NEXT can use different layouts for different page types.
Comments
{% comment %}
Multi-line comments work fine
in Liquid with comment tags
{% endcomment %}
{%- # Inline comment (Liquid 2.0) -%}{# Single-line comment: correct #}
{# This is WRONG:
multi-line DTL comments render
as visible text on the page #}DTL comments must be single-line
Django template comments ({# ... #}) do not support line breaks. A comment that spans multiple lines will render its content as visible text on the storefront. Keep all {# #} comments on a single line.
Context variables and objects
The global objects available in templates differ between platforms. Here are the most common mappings:
| Shopify Liquid | Next Commerce DTL | Notes |
|---|---|---|
product | product | Similar structure, different property names |
product.title | product.get_title | Returns the localized title |
product.price | product.price | Use |currency filter for formatted output |
product.available | product.is_available | Boolean availability check |
product.images | product.images.all | Iterate over the QuerySet with {% for %} |
collection | category | Next Commerce uses "category" terminology |
collection.products | products | Products passed as separate context variable |
cart | GraphQL only | Cart data belongs in client-side code, not cached templates |
shop | store | Global store settings object |
settings | settings | Theme configuration values from configs/ |
customer | GraphQL only | Customer state requires client-side GraphQL |
request.locale | LANGUAGE_CODE | Current language/locale |
| Not applicable | request.CURRENCY_CODE | Current currency for template logic |
Info
Use the Template Tags Reference and Filter Reference for the complete API. The Template Objects reference maps each template path to its available variables.
Styling systems
Both starter themes compile to plain CSS. Their source directories and build commands differ.
| Track | Starter | Source | Build pipeline |
|---|---|---|---|
| SCSS | Intro Bootstrap | sass/ directory | ntk watch compiles and uploads changes |
| Tailwind v4 | Spark | css/input.css | make install-tailwind once, then make dev |
SCSS track (Intro Bootstrap)
ntk compiles each .scss file in sass/ to a matching .css file in assets/ before upload.
# Watch files, compile SCSS, and upload changes
ntk watch
# That's it. No extra build step required.Tailwind v4 track (Spark 1.3)
Spark's Makefile runs Tailwind and the platform compatibility checks. Keep css/input.css and the compiled assets/main.css together in each change.
Use make dev for normal development. Use make build only when you need a one-time local compile instead of the watcher.
# Fetch the Tailwind CLI once
make install-tailwind# Tailwind watcher and ntk watch
make dev
# Before release or handoff
make verify-theme
make releaseTailwind + DTL: never construct class names dynamically
Tailwind scans source files at build time. Template variables resolve later, so dynamically constructed class names do not appear in the compiled CSS.
{# BAD: Tailwind cannot see "blue" or "red" at build time #}
<div class="bg-{{ settings.color }}">
{# GOOD: wire settings to CSS custom properties #}
<style>:root { --primary: {{ settings.primary_color|default:"#1E293B" }}; }</style>
<div class="bg-[var(--primary)]">
{# GOOD: use static classes in conditionals #}
<div class="{% if large %}text-2xl{% else %}text-base{% endif %}">Interactive UI with web components and GraphQL
Shopify sections combine markup and schema. NEXT keeps merchant settings in configs/settings_schema.json and composes pages from DTL layouts, blocks, and partials.
Spark uses Web Components for add to cart, the side cart, quantity controls, and other live behavior. DTL renders the static shell. Browser code handles mutations and customer-specific updates.
| Shopify theme concept | NEXT migration target |
|---|---|
| Section schema | Theme Settings in configs/settings_schema.json |
| Snippet or section markup | DTL partial included from a template |
| Product form JavaScript | Spark form and Web Component contracts |
| Cart drawer state | Spark cart client plus spark:cart:* events |
Keep the native form action, CSRF token, and submit button when changing add-to-cart presentation. Spark's Web Component enhances that form with GraphQL while preserving its non-JavaScript fallback.
No non-ASCII in JS files
The platform processes theme JavaScript through the DTL engine. Use plain ASCII in assets/js/ to avoid encoding errors.
Tooling map
| Task | Shopify CLI | Next Commerce Theme Kit |
|---|---|---|
| Install | npm i -g @shopify/cli | pip install next-theme-kit |
| Create theme | shopify theme init | ntk init --name="My Custom Theme" --apikey="YOUR_API_KEY" --store="https://yourstore.29next.store" |
| Pull from store | shopify theme pull | ntk pull --theme_id=<id> |
| Push changes | shopify theme push | ntk push templates/index.html with explicit paths |
| Watch mode | shopify theme dev | make dev for Spark, ntk watch for SCSS themes |
| List themes | shopify theme list | ntk list |
| Sass compile | External (Webpack, etc.) | ntk sass (built-in for SCSS themes) |
| Preview unpublished | shopify theme dev local URL | https://{store}.29next.store/?preview_theme={theme_id} |
| Auth method | OAuth browser flow | API key from store admin OAuth App |
| Config file | .shopify/ directory | config.yml |
Tips
Push only the files you intend to change. Use explicit paths such as ntk push templates/index.html partials/header.html. Use a full push only for the first installation of a complete starter theme.
Theme Kit uploads storefront theme files only. It skips checkout/ and Spark's local css/ source directory.
Settings and configuration
Both platforms let merchants customize themes through a settings UI without editing code:
// config/settings_schema.json
[{
"name": "Colors",
"settings": [{
"type": "color",
"id": "primary_color",
"label": "Primary color",
"default": "#3C7DFF"
}]
}]// configs/settings_schema.json
{
"Colors": {
"primary_color": {
"type": "color",
"label": "Primary Color",
"default": "#3C7DFF"
}
}
}In templates, access settings via {{ settings.primary_color }}. The setting types and JSON schema are very similar between platforms.
Theme Settings rules
- Organize settings around merchant tasks, such as "Side Cart."
- Give a large group its own top-level section.
- Use merchant-facing labels instead of implementation names.
- Keep developer-only defaults in
settings_data.json. - Remember that display order follows the first appearance of a group in the file.
Dashboard-theme bridge for incentive features
A free shipping bar or free gift prompt needs both theme settings and a matching dashboard Offer. Theme settings control the display. The Offer controls the discount.
When adding these settings, include help text pointing merchants to Dashboard > Marketing > Offers to create the matching Offer.
Migration workflow
This guide covers the translation decisions. The storefront development workflow is the source for repository setup, store credentials, deployment, QA, and handoff.
Inventory the Shopify theme
List its templates, sections, snippets, settings, app embeds, tracking tags, cart behavior, and variant behavior. Record what is controlled by the theme and what comes from Shopify or an app.
Map each behavior to a NEXT owner
Use DTL for cached page structure, Theme Settings for merchant choices, and client-side code for cart or customer state. Treat checkout as a platform surface.
Port onto Spark
Start from Spark 1.3. Keep its existing scripts, template hooks, and form behavior until the replacement passes the same tests.
Protect Spark's runtime contracts
- Keep
{% pixels %}in the base layout so installed app trackers load. - Keep real
attr_*variant controls, product data, form actions, availability, and thespark:variant:changedevent contract on product pages. - Preserve CSRF, native form fallbacks, app hooks, and client-side cart hydration.
- Use static Tailwind classes or CSS custom properties. Do not build class names from template values.
Develop and verify
make install-tailwind
make dev
# Before release or handoff
make verify-theme
make releaseTest the store domain and confirm asset responses in the Network panel. Do not use skip_cache as proof that the deployed storefront is current.
Quick reference
| Pattern | Shopify Liquid | Next Commerce DTL |
|---|---|---|
| Output variable | {{ product.title }} | {{ product.get_title }} |
| Apply filter | {{ val | filter: arg }} | {{ val|filter:arg }} |
| If / else | {% if %}{% elsif %}{% endif %} | {% if %}{% elif %}{% endif %} |
| For loop | {% for x in list %}{% endfor %} | {% for x in list %}{% endfor %} |
| Empty loop | Check list.size | {% empty %} inside for block |
| Include partial | {% render 'name' %} | {% include 'partials/name.html' %} |
| Layout wrap | Implicit theme.liquid | {% extends 'layouts/base.html' %} |
| Content block | {{ content_for_layout }} | {% block content %}{% endblock %} |
| Comment | {% comment %}{% endcomment %} | {# single line only #} |
| Asset URL | {{ 'x.js' | asset_url }} | {{ 'x.js'|asset_url }} |
| Raw HTML | {{ content }} | {{ content|safe }} |
| Escape HTML | {{ val | escape }} | {{ val|escape }} |
| Format price | {{ price | money }} | {{ price|currency }} |
| Date format | {{ date | date: '%B %d' }} | {{ date|date:"F d" }} |
| Translate | {{ 'key' | t }} | {% t "key" %} |
| Cart / user data | {{ cart.item_count }} | Use client-side GraphQL, not cached template state |
Resources
Links and references
Storefront Development Workflow
Getting Started with Themes
Theme Kit Documentation
Template Tags Reference
Filter Reference
GraphQL Storefront API
Intro Bootstrap (GitHub)
Spark Theme (GitHub)
URLs & Template Paths
Theme Settings
Theme Kit (GitHub)
Internal guide for Shopify-to-NEXT migrations. Verified against Spark 1.3 and Theme Kit 1.3 on Sep 4, 2026.
Campaigns OS agency enablement guide
Build a NEXT campaign from approved HTML, Figma, a hybrid source, or a maintained starter template. Campaigns OS keeps commerce, source, build, and QA evidence tied to the same campaign.
Build a Spark storefront theme with agents
Use this workflow to turn an approved design into a new, unpublished Spark theme. next-theme-figma prepares the design handoff, next-theme-dev guides the build, and Theme Kit uploads the result for review.