Build a theme in 15 minutes
From an empty folder to a live preview on your sandbox store with a correct two-file template.
This page is for anyone building a storefront theme for Dukkan stores. Themes are Liquid templates rendered on Dukkan's servers inside a hardened sandbox — zero theme JavaScript, an Arabic-first data model, RTL by default. Your tool is @dukkan.one/theme-cli, and by the end of this page you have a theme running in a live preview on your sandbox store.
1. Set up#
You need Node.js 18.17 or newer, a developer account, and an active sandbox store (create one here).
npx @dukkan.one/theme-cli theme init my-theme
cd my-theme
npx @dukkan.one/theme-cli loginlogin opens a device flow: it prints a /cli/authorize link and a code shaped DKN-XXXXXX; you confirm it in the portal and pick the sandbox store you work against. The resulting token reaches only that sandbox and can never touch a real store. Flow details and errors are in the CLI reference.
Warning
In the published 0.1.0 release, theme init writes a layout/theme.liquid in a legacy form the render engine does not support, so the page renders empty. Replace both files as shown in step 4 before your first theme dev.
2. The dev loop#
npx @dukkan.one/theme-cli theme devIt pushes the theme as your sandbox store's draft, then watches your files, pushes on every change, and prints a live preview URL on the sandbox storefront shaped https://dukkan.one/stores/YOUR_SANDBOX_SLUG?__preview=TOKEN. A preview link lives 24 hours and is revoked automatically by any push that changes the draft, at which point the CLI prints a fresh one.
3. Theme anatomy#
| Path | Purpose |
|---|---|
layout/theme.liquid |
The shell: <html>, <head> and the {% block content %} slot. Required. |
templates/home.liquid |
Home page, product list and search results. |
templates/category.liquid |
Category page. |
templates/product.liquid |
Product page. |
templates/cart.liquid |
Cart. |
config/settings_schema.json |
Settings the merchant edits in the visual editor. Required. |
locales/ar.json |
UI strings for the t filter; locales/en.json is optional. |
snippets/*.liquid |
Partials, included by full path. |
assets/*.css and images |
CSS, images and woff2 fonts only; never JavaScript. |
Checkout and order pages are platform-owned and never themed. Prices arrive as { amount_minor, currency } and are displayed with the money filter. The full rule set is in Bundle anatomy.
4. The correct minimal pair#
The layout declares a content block; every template declares that it renders inside the layout and fills the block:
<!doctype html>
<html dir="{{ request.direction }}" lang="{{ request.locale }}">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ store.name }}</title>
<style>{% include 'assets/theme.css' %}</style>
</head>
<body>
<header><a href="{{ store.url }}">{{ store.name }}</a></header>
{% block content %}{% endblock %}
<footer>{{ 'footer.powered_by' | t }}</footer>
</body>
</html>{% layout 'layout/theme.liquid' %}
{% block content %}
<main>
<h1>{{ store.name }}</h1>
<ul>
{% for product in products %}
<li>
<a href="{{ product.url }}">{{ product.name }}</a>
<span>{{ product.price | money }}</span>
{% unless product.available %}<em>{{ 'product.sold_out' | t }}</em>{% endunless %}
</li>
{% endfor %}
</ul>
</main>
{% endblock %}There is no content variable inside the layout; the contract is {% layout %} plus {% block %}, exactly as in every first-party Dukkan theme. Arabic strings never sit in templates — they live in locales/ar.json:
{
"nav": {
"home": "الرئيسية",
"cart": "السلة"
},
"product": {
"add_to_cart": "أضف إلى السلة",
"sold_out": "نفد المخزون"
},
"footer": {
"powered_by": "مدعوم من دكان"
}
}5. Check#
npx @dukkan.one/theme-cli theme check
npx @dukkan.one/theme-cli theme check --remotetheme check runs locally the same code the server runs at push: the extension allowlist, size and count limits, path safety, a valid settings_schema.json, and no <script> anywhere. --remote adds a smoke render in the server sandbox. The exit code is 1 on any error, so the command is CI-ready. The rule list with verbatim error strings is in Theme validation.
Next steps#
- Sections and blocks — let the merchant compose the page.
- Arabic first — RTL, fonts, numerals.
- Publish your theme to the marketplace