Everything you need to build, preview, and publish store templates.
Create a project — dependencies are installed for you:
npx selldoes create my-theme # choose Theme (or npx selldoes create for prompts)
cd my-themeGenerate an API key in Dashboard → Profile → API Keys, then:
npx selldoes login --api-key sk_...
npx selldoes dev --store my-store # live preview, hot reload
# in src/home.tsx — your first page
import { useStore, useProducts, ProductGrid, ProductCard } from "selldoes/theme"
export default function HomePage() {
const store = useStore()
const { products } = useProducts({ limit: 8 })
return (
<div style={{ fontFamily: "system-ui, sans-serif", padding: 32 }}>
<h1>{store.name}</h1>
<ProductGrid products={products} renderItem={(p) => <ProductCard product={p} />} />
</div>
)
}Publish it:
npx selldoes build
npx selldoes publish
npx selldoes apply --store my-store # apply to one of your stores| Command | Description |
|---|---|
selldoes create [name] | Scaffolds a new theme (or plugin) project — interactive, like create-next-app. |
selldoes login --api-key sk_... | Authenticates with an API key and remembers your default store. |
selldoes init [template-id] | Downloads an existing theme as a local project. |
selldoes dev [--store slug] | Bundles src/*.tsx and serves a live preview on http://localhost:4173 with hot reload. The preview talks to your real store through the same bridge the storefront uses. |
selldoes build | Validates the project and bundles each page to dist/ with the manifest. |
selldoes publish [--public] | Publishes dist/ to Selldoes (alias: upload). Re-uploading updates the same theme. |
selldoes apply --store slug | Snapshots the uploaded theme into a store: sections, theme settings, and template switch. |
selldoes whoami / logout | Account information / clears saved credentials. |
selldoes update | Checks npm and updates the CLI — asks first; --check only reports, --yes skips the prompt. |
Each file in src/ is a page of your template. The storefront loads the entry for the current URL:
| File | Page |
|---|---|
| src/home.tsx | Storefront home |
| src/product.tsx | Product detail (/store/slug/product/sku) |
| src/category.tsx | Category listing (/store/slug/category/slug) |
| src/cart.tsx | Cart |
| src/checkout.tsx | Checkout |
| src/thankyou.tsx | Thank-you page |
| src/login.tsx | Login |
| src/profile.tsx | Customer profile |
| src/pages.tsx | Info pages and 404 |
| src/wishlist.tsx | Wishlist |
Beyond the built-in pages above, a template can declare its own page types in manifest.json. Custom page types become real storefront routes at /store/<slug>/<pageType> (e.g. portal, blog) and appear as editable tabs in the store's appearance customizer. Add a matching src/<pageType>.tsx file for a custom-coded page, or provide sections in sections.json for a sections-only page:
{
"pages": {
"home": { "custom": true, "entry": "pages/home.js" },
"portal": { "custom": true, "entry": "pages/portal.js", "label": "Portal" }
}
}Page types without a src/ file fall back to the standard section-based rendering (tier-1), so you can mix custom code pages with the visual editor's sections freely. Every template carries optional sections.json (the section layout per page type) and theme.json (fonts, colors, widths) snapshots that are applied when the template is applied to a store. Store owners can edit sections.json-defined layouts in the appearance customizer, including custom page types.
| Hook | Returns |
|---|---|
useStore() | The store (name, slug, currency, theme, templateId...). |
useSections(pageType) | The tier-1 section layout for a page type. |
useTheme() | The store's theme settings object. |
useInitialProduct() | The product passed by the host on product pages (first paint). |
useProducts({ limit, category, q, sortBy }) | { data, loading, error } — data.products is a list. |
useProductDetail(sku) | Full product detail (attributes, images, ratings). |
useCategories() | Categories with product counts. |
useRelatedProducts(sku, { matchBy, limit }) | Related products. |
useReviews(sku) | Product reviews. |
useCart() | Client-side cart: { items, add, remove, updateQuantity, clear, total, count }. |
useCustomer() | Customer profile through the host bridge (null when logged out). |
apiFetch(path, opts) | Low-level bridge fetch. Only /api/store/* paths are allowed. |
Price, ProductCard, ProductGrid, ProductGallery, CategoryList, AddToCartButton, CartDrawer, Breadcrumbs, Container, Section. All are plain styled React components — style them with inline styles or your own CSS.
{
"name": "My Template",
"version": "1.0.0",
"apiVersion": "1",
"mode": "full-page",
"pages": {
"home": { "custom": true, "entry": "pages/home.js" }
},
"shell": { "hideToolbar": true, "hideHeader": true, "hideFooter": true }
}mode: "full-page" means custom pages take over the entire viewport — the store shell (toolbar, header, categories bar, footer) is suppressed on those pages. shell flags control which shell parts are hidden. Custom pages are client-rendered and are not indexed by search engines; keep important pages (e.g. category/product) in section mode if SEO matters.
Templates receive the same settings the store owner manages in the appearance customizer, so you can build any header/footer style and keep the settings working:
const store = useStore() // headerSettings, footerSettings, productSettings, layouts, colors
const theme = useTheme() // { logoUrl, favicon, faviconBgColor, storeName, pageWidth, siteBgColor, ... }
const home = useSections("home") // home sections; the "header" section carries logo/favicon/colors
const headerSettings = store.headerSettings || {}
const logo = headerSettings.logo || theme.logoUrl
<header style={{ background: headerSettings.bgColor || "#ffffff" }}>
{logo ? <Image src={logo} alt="" width={0} height={0} className="w-auto" style={{ height: headerSettings.logoHeight || 32 }} unoptimized /> : <h1>{store.name}</h1>}
</header>Key settings: headerSettings (logo, logoHeight, margins, headerHeight, bgColor, textColor, favicon), footerSettings (footerLayout rows/columns/blocks), productSettings(trust badges, variants, reviews), theme (logoUrl, favicon, siteBgColor, pageWidth, all page layout choices). Render your own shell by declaringshell: { hideToolbar: true, hideHeader: true, hideFooter: true } in the manifest.
Your template is bundled to static JavaScript and served from the Selldoes CDN inside an iframe sandboxed with allow-scripts only. It never executes on the server and cannot touch the host page, cookies, or the network directly:
host → template { source: "selldesk", type: "init", payload } // store, sections, theme, product
template → host { source: "selldesk", type: "fetch", id, url } // request data
host → template { source: "selldesk", type: "fetch:result", id, ok, status, data }The host only proxies /api/store/* paths and only for its own store. The local dev server implements the identical protocol, which is why preview == production.
Uploads are validated server-side (manifest schema, entry existence, size caps). Templates start as draft. Toggle --public on upload (or in Dashboard → Templates) to submit for review; an admin approves them into the marketplace. Private templates stay visible only to you and remain fully functional — they're the same pipeline.
Can my template fetch arbitrary APIs? No — network access is limited to the store's own API through the bridge. This is a sandbox, like Shopify's Liquid.
Can I edit my template's tier-1 sections in the visual editor?Yes. Applying a template snapshots its sections into the store, and the appearance editor works on them as usual — for any page type that isn't custom-coded. Custom page types declared in the manifest appear as tabs in the customizer automatically.
Do template updates flow to stores that applied them? No — applying snapshots. Re-apply to pick up a newer version.
Is my code ever executed on your servers? Never. Templates are static client-side bundles only.
Selldoes's flagship templates (minimal, lootku) are code templates: React components that run inside the app itself — server-rendered, SEO-friendly, and fully editable in the appearance customizer. They live in src/templates/<id>/ and follow the same API as the SDK: every hook used by a code template exists in selldoes/theme with the same name and shape.
src/templates/<id>/
config.ts — CodeTemplateConfig (metadata, pageTypes, customPageTypes, sections.defines)
index.ts — TemplatePages component map (default) + pageRegistry (named)
pages/ — one folder per page type
index.ts — pageRegistry: { type, label, Page } per pages/<type>/
home/index.tsx
cart/index.tsx + cart/default.tsx, cart/drawer.tsx, ...
sections/ — the template's section registry + defs
shell/ — header/nav/footer components
theme.ts — design tokensCode templates import store data only from @/lib/template-api — the in-app mirror of this SDK:useStore(), useTheme(), useSections(pageType),useCategories(), useProducts(), useProductDetail(sku),useCart(), useCustomer(), useWishlist(),useStoreLink(). Templates never import from other templates — shared components live insrc/templates/shared/, and visually-neutral sections (gallery, marquee, newsletter, ...) are shared once and re-themed per template via withDefaults().
Custom page types by convention. Drop a pages/<type>/index.tsx folder into the template and register one line inpageRegistry — the page type immediately becomes a storefront route (/store/<slug>/<pageType>) and an editable tab in the appearance customizer live preview (config.ts derives customPageTypes from the registry automatically). Coded custom pages render their component; sections-only custom pages render as a section stack — the same semantics as manifest-declared page types in SDK templates.
Shell sections are not auto-global. The toolbar/header/categories-bar/footer sections render only on the Home page. To show them on other pages, link them (the link button in the editor sidebar) — linked sections are synced into every page's sections and render in position; unlinked shells stay on Home only, in the store and the live preview.