Next Commerce

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.

ConceptShopifyNext Commerce
Template languageLiquidDjango Template Language (DTL)
Template inheritanceImplicit via theme.liquidExplicit {% extends %} / {% block %}
Partials{% render 'snippet' %}{% include 'partials/snippet.html' %}
CSS pipelineTheme-specific toolingTailwind v4 through Spark's Makefile, or SCSS through ntk for Intro Bootstrap
Settings schemasettings_schema.jsonJSON files in configs/
Interactive UI / cart stateSections blocks + Liquid cartWeb Components + GraphQL API (full-page cache requires client-side JS for per-user data)
Storefront APIGraphQL Storefront APIGraphQL Storefront API
Starter themeDawnSpark (Tailwind v4) or Intro Bootstrap (Bootstrap 5)
CLI toolShopify CLITheme 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.

Shopify
theme/
├── assets/
├── config/
├── layout/        ← theme.liquid
├── locales/
├── sections/
├── snippets/
└── templates/
Next Commerce
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 templates

Info

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.

Shopify Liquid
{{ product.title }}
{{ product.price | money }}
{{ 'hero.jpg' | asset_url }}
{{ product.title | upcase }}
{{ product.title | truncate: 50 }}
Next Commerce DTL
{{ 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

Shopify Liquid
{% if product.available %}
  In stock
{% elsif product.coming_soon %}
  Coming soon
{% else %}
  Sold out
{% endif %}
Next Commerce DTL
{% 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

Shopify Liquid
{% for product in collection.products %}
  {{ product.title }}
  {% if forloop.first %}First!{% endif %}
{% endfor %}
Next Commerce DTL
{% 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

Shopify Liquid
{% comment %}
  Multi-line comments work fine
  in Liquid with comment tags
{% endcomment %}

{%- # Inline comment (Liquid 2.0) -%}
Next Commerce DTL
{# 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 LiquidNext Commerce DTLNotes
productproductSimilar structure, different property names
product.titleproduct.get_titleReturns the localized title
product.priceproduct.priceUse |currency filter for formatted output
product.availableproduct.is_availableBoolean availability check
product.imagesproduct.images.allIterate over the QuerySet with {% for %}
collectioncategoryNext Commerce uses "category" terminology
collection.productsproductsProducts passed as separate context variable
cartGraphQL onlyCart data belongs in client-side code, not cached templates
shopstoreGlobal store settings object
settingssettingsTheme configuration values from configs/
customerGraphQL onlyCustomer state requires client-side GraphQL
request.localeLANGUAGE_CODECurrent language/locale
Not applicablerequest.CURRENCY_CODECurrent 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.

TrackStarterSourceBuild pipeline
SCSSIntro Bootstrapsass/ directoryntk watch compiles and uploads changes
Tailwind v4Sparkcss/input.cssmake 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.

One time setup
# Fetch the Tailwind CLI once
make install-tailwind
Develop and verify
# Tailwind watcher and ntk watch
make dev

# Before release or handoff
make verify-theme
make release

Tailwind + 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 conceptNEXT migration target
Section schemaTheme Settings in configs/settings_schema.json
Snippet or section markupDTL partial included from a template
Product form JavaScriptSpark form and Web Component contracts
Cart drawer stateSpark 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

TaskShopify CLINext Commerce Theme Kit
Installnpm i -g @shopify/clipip install next-theme-kit
Create themeshopify theme initntk init --name="My Custom Theme" --apikey="YOUR_API_KEY" --store="https://yourstore.29next.store"
Pull from storeshopify theme pullntk pull --theme_id=<id>
Push changesshopify theme pushntk push templates/index.html with explicit paths
Watch modeshopify theme devmake dev for Spark, ntk watch for SCSS themes
List themesshopify theme listntk list
Sass compileExternal (Webpack, etc.)ntk sass (built-in for SCSS themes)
Preview unpublishedshopify theme dev local URLhttps://{store}.29next.store/?preview_theme={theme_id}
Auth methodOAuth browser flowAPI key from store admin OAuth App
Config file.shopify/ directoryconfig.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:

Shopify
// config/settings_schema.json
[{
  "name": "Colors",
  "settings": [{
    "type": "color",
    "id": "primary_color",
    "label": "Primary color",
    "default": "#3C7DFF"
  }]
}]
Next Commerce
// 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

  1. Organize settings around merchant tasks, such as "Side Cart."
  2. Give a large group its own top-level section.
  3. Use merchant-facing labels instead of implementation names.
  4. Keep developer-only defaults in settings_data.json.
  5. 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 the spark:variant:changed event 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 release

Test 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

PatternShopify LiquidNext 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 loopCheck list.size{% empty %} inside for block
Include partial{% render 'name' %}{% include 'partials/name.html' %}
Layout wrapImplicit 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


Internal guide for Shopify-to-NEXT migrations. Verified against Spark 1.3 and Theme Kit 1.3 on Sep 4, 2026.

On this page