1ecomm headless commerce  /  @1ecomm/cli

One command, one store ID, a shop that sells.

1ecomm init asks for a store ID, checks it against the live platform before it asks anything else, and writes a themed storefront wired to real catalog, real stock and real prices.

Frameworks React, Vue Components dt-ui, 24 palettes API keys required none

Getting started

You need Node 20.19 or newer and a store ID. Nothing else — no account file, no key, no config to prepare.

# scaffold, install and run, in one go
$ npm create @1ecomm/storefront@latest my-shop

# or drive the CLI directly
$ npx @1ecomm/cli init my-shop

The store ID lives in your 1Ecomm workspace under Applications. It is a UUID, it identifies a store rather than a person, and it is not a secret — it is designed to sit in a config file you commit.

Before you start

A store becomes discoverable once it has one active application. If the CLI says the store is not configured, that is usually the missing piece rather than a typo.

The conversation

Six questions. The first one is answered by the platform, not by you — which is the whole design.

Store ID, checked live

The store ID is asked first and validated before the second question is asked. A wrong ID costs you one prompt instead of a scaffolded project that installs, builds, and only then fails in the browser.

Terminal showing the 1ecomm storefront prompt asking for a Store ID, with the hint 'from your 1Ecomm workspace, under Applications'.
The first question. Paste the UUID and press Enter.

The platform answers back

Two checks run before anything is written. The bootstrap call says the store exists and what it is permitted to do; one authenticated catalog read says what actually works. They disagree often enough to be worth the round trip.

Terminal showing two green OK lines — the store resolved on api.1ecomm.com with capabilities catalog, cart, checkout-preparation, and the catalog reachable with published products — followed by a Framework list offering React, Next.js, Vue and Angular.
catalog, cart, checkout-preparation is this store's capability list, read from the platform. It decides which routes you are offered further down.

Pick a framework

React and Vue are built. Next.js and Angular appear in the list as coming soon and cannot be selected — declared rather than silently missing, so you know the list is complete.

Pick a colour, rendered as itself

All 24 dt-ui palettes, drawn as true-colour swatches in your terminal. The colour you see in the list is the colour the storefront paints.

Terminal showing the Colour prompt with all 24 dt-ui palette names listed vertically — indigo, cobalt, midnight, ocean, lagoon, teal, jade, forest, moss, lime, citrus, honey, sunset, ember, coral, rose, blossom, orchid, violet, royal, plum, slate, graphite, sand.
Twenty-four palettes, crossed with light, dark and follow-the-system.
  • indigo
  • cobalt
  • midnight
  • ocean
  • lagoon
  • teal
  • jade
  • forest
  • moss
  • lime
  • citrus
  • honey
  • sunset
  • ember
  • coral
  • rose
  • blossom
  • orchid
  • violet
  • royal
  • plum
  • slate
  • graphite
  • sand

Routes you are allowed to have

Optional routes are offered only where the store can serve them. On the store below, Order lookup is greyed out and the reason is printed: the store has no orders capability. You are not given a page that can only fail.

Terminal showing the full flow: store validated, framework list, 24 colour swatches rendered in their own colours, an Appearance prompt offering Follow the system, Light and Dark, and an Optional routes multi-select where Checkout is ticked and Order lookup is disabled with the reason 'store cannot place orders'.
The palette list drawn in true colour, and at the bottom the capability gate: Order lookup is unavailable: store cannot place orders.

Install and run

Choose a package manager, let it install, and the CLI prints exactly what to do next in the manager you picked. Every omitted route is named rather than silently dropped.

Terminal showing a completed non-interactive run: store validated, catalog reachable, 20 files written to my-shop, one line noting src/routes/OrderLookup.tsx was omitted because the store cannot serve it, and a Next block listing cd my-shop, npm install, npm run dev.
A scripted run. 20 files, with the omitted route reported, not hidden.

What you get

A storefront, not a catalog demo. Every screenshot below is a generated project running against api.1ecomm.com — the products, prices, images and stock are the store's own.

A generated storefront home page in the indigo palette: header with navigation and a cart icon, a hero reading Northwind Supply with a Shop everything button, a Shop by category grid of four photographic tiles, a Latest product grid of eight real products at $49 each, a Why shop with us incentives row, a Questions accordion and a newsletter sign-up.
React, indigo, light. Hero, category tiles, product grid, incentives, FAQ and newsletter — composed from dt-ui components and real catalog data.
The same storefront structure generated as a Vue project in the ember palette and dark appearance: dark ground, red accent, identical sections with the same live catalog.
Vue, ember, dark. A different framework and a different palette, from the same command. Theming is two attributes on <html>, not a rebuild.
The category listing page showing filter pills for All, Casual, Formal, T-Shirts and Polo Shirts above a grid of product cards.
Category listing, filtered by the store's own categories.
The storefront at phone width, showing the compact navigation row beneath the header and the hero stacked for a narrow screen.
At 390px, with the compact navigation the wide header hides.
The cart page showing an order summary card with a product line, subtotal, shipping, taxes and total, a quantity control with minus, one, plus and Remove, a note that tax is an estimate until an address is entered, and a Checkout button.
The cart. Quantities and removal go through the platform — the totals shown are the server's, not arithmetic done in the browser.
The checkout page showing email, first name, last name, address and city fields, a country select, and a payment section listing Bank Transfer, with a note that the store is set up for checkout preparation but cannot place orders yet.
Checkout preparation. Which fields a country needs is the platform's rule — the form asks for a state or postal code only where that country requires one.

Why there is no API key

Most scaffolders ask you to paste a key. This one does not, because the platform already answers the question:

GET /v1/headless/stores/{storeId}/config

 { storeId, apiUrl, publishableKey, apiVersion, capabilities[] }

The publishable key, the API URL and the capability list are all discovered from the store ID. So the only thing written into your project is this:

// headless-config.json — the whole file
{
  "storeId": "01f5b02f-d7c0-42cd-b880-59f78ea70aa3",
  "bootstrapUrl": "https://api.1ecomm.com"
}

Nothing secret reaches the generated project, there is no key to rotate in a committed file, and a test asserts that no publishable key appears in any generated file.

Routes follow capabilities

A store's application carries a capability list. The CLI reads it and scaffolds accordingly, because a checkout page on a store that cannot check out is worse than no page.

capabilityYou getWithout it
catalog Home, category listing, product pages The CLI refuses — a storefront with no catalog has nothing to show
cart Cart route, header count, add to bag Browse-only storefront; no cart link in the header
checkout-preparation Checkout route with address, delivery and payment The cart says checkout is not configured, and links nowhere
orders Order lookup route, and the place-order button No /orders route is written at all
Capabilities can change later

If an application loses a capability after you scaffolded, 1ecomm doctor is what tells you — it compares the routes in your project against what the store can serve today.

The variant gate

This is the part most hand-written storefronts get wrong, so the generated one handles it for you.

A product with options cannot be added to a cart without a variantId. The platform refuses, correctly, with a message that names nothing:

POST /v1/headless/carts/current/items
      { productId, quantity }          ← no variantId

 HTTP 400  "Cart mutation could not be completed"

So the product page resolves the variants first, and the button says which choice is still outstanding rather than letting you press it and fail.

A product page for Leggings at $49 with a Color row where Green is selected and a Size row where nothing is selected. The primary button is disabled and reads 'Choose size'.
Green chosen, size not. The button is disabled and names what is missing — it does not read "Add to bag" and then fail.

The gate distinguishes four states, and says a different thing in each:

  • Loading options — the variants are still in flight, so the button cannot be pressed yet.
  • Choose colour and size — nothing picked; every outstanding axis is named.
  • Out of stock — a real combination, with no stock.
  • That combination is unavailable — no such variant exists.
Found by running it

The first of those states exists because of a real defect: while the variants were still loading the list was empty, an empty list means "nothing to choose", and the button rendered a working Add to bag for a product that required a variant. A shopper quick enough to press it got the 400 above.

Flags and scripting

Every prompt has a flag, so the whole thing runs unattended. Without a TTY the CLI refuses rather than hangs — a CI job that waits forever is worse than one that fails.

$ 1ecomm init my-shop \
    --store-id 01f5b02f-d7c0-42cd-b880-59f78ea70aa3 \
    --framework react \
    --palette jade \
    --appearance system \
    --package-manager pnpm \
    -y --install --no-start
flagValuesDefault under -y
--store-ida UUIDrequired
--frameworkreact · vuereact
--paletteany of the 24indigo
--appearancelight · dark · systemsystem
--package-managernpm · pnpm · yarn · bunnpm
--bootstrap-urlplatform originapi.1ecomm.com
--install / --no-installinstall without askinginstall
--start / --no-startrun the dev server after installno-start

Every flag is checked before the first question, so a mistyped palette costs you nothing. Exit codes are real: 0 on success, 1 on refusal, 130 on Ctrl-C.

Terminal showing 1ecomm --help: usage for init and doctor, the full list of init options, a Notes section explaining that there is no API key to paste and that routes are gated by capabilities, and three examples.
1ecomm --help. The notes section is the part worth reading twice.

1ecomm doctor

Run it in a project that has stopped working. It catches the failure the CLI cannot prevent: a capability revoked after you scaffolded.

$ 1ecomm doctor

  ok    configuration found (headless-config.json)
  ok    store resolves at api.1ecomm.com (API v1)
  ok    capabilities: catalog, cart, checkout-preparation
  ok    catalog read (1 product returned)

Everything checks out.

It checks four things in order, and each answers a different question: is there a project, does the store resolve, what is it permitted to do, and does a real authenticated read actually succeed. It exits 1 when any check fails, so it works in a script.

Terminal showing 1ecomm doctor failing with 'fail store configuration — No headless-config.json with a storeId was found here. Run this inside a storefront project, or pass the directory as an argument.'
Run outside a project, it says so and how to fix it.
Terminal showing an init refusal: 'Headless store is not configured. No headless store is configured for that ID', followed by three bullet points about checking the workspace, needing one active application, and the ID being the store rather than the application.
A store that stops resolving, with the three things to check.

What it refuses, and why

Refusals are the part of a CLI you meet when something is wrong, so they carry the reason and the way out. Nothing is half-written: a refused init leaves no directory behind.

You didIt saysexit
Gave an unprovisioned store IDNot configured, plus three things to check1
Mistyped a paletteNames your typo and lists all 24 real palettes1
Asked for Angular"Not available yet", and which frameworks are1
Pointed at a non-empty directoryRefuses rather than merging into your files1
Used a name npm would rejectExplains the naming rule1
Ran with no TTY and no flagsRefuses immediately rather than hanging1
Could not reach the platformNames the network, not your store ID1
Terminal showing a refusal for an unknown palette: 'Unknown palette chartreuse.' followed by the full list of valid palette names.
A mistyped palette, caught before any question is asked or any file written.

When the platform is down

The generated storefront fails closed. It never falls back to sample products — a shop that quietly shows fake stock is worse than one that says it is unavailable.

The storefront showing 'This store is unavailable', the platform's own error message, the store ID and bootstrap URL, and a line suggesting npx 1ecomm doctor.
The platform's own message, the store ID it tried, and the command that diagnoses it.

Inside the project

It is ordinary code. Nothing is hidden behind the CLI, and there is no eject step.

my-shop/
├── headless-config.json     the store ID, and nothing else
├── index.html               data-dt-palette + data-dt-theme live here
└── src/
    ├── lib/commerce.ts      typed client: catalog, cart, checkout, orders
    ├── lib/adapt.ts         API shapes → dt-ui contracts, and the variant gate
    ├── store.tsx            bootstrap once, then fail closed
    ├── components/          header, footer, category tiles — yours to edit
    └── routes/              Home, Category, Product, Cart, Checkout

Changing the look

The palette and appearance are two attributes on <html>. Swap them and the whole storefront repaints — no rebuild, no component edits.

<html data-dt-palette="ember" data-dt-theme="dark">

The shell is yours

dt-ui has no header, footer or hero component yet, so those are written into the project rather than imported. They use only dt-ui's semantic tokens, so they repalette with everything else — and they are the first thing most merchants rebrand anyway.

How this was verified

Every screenshot above is a real run against the production API. Nothing here is a mockup.

Unit tests
62scaffolder, platform client, adapter, CLI process
Browser tests
211four real builds, live platform, 1 legitimate skip
Mutations caught
9each break turned the suites red; a control change did not

The end-to-end suite scaffolds four projects through the CLI, installs them from the public npm registry, builds them, serves each on its own port and drives them with Chromium. Nothing is linked and nothing is mocked: if the published packages cannot install and compile in a clean project, the suite fails before a single test runs.

One spec suite drives both frameworks. It contains no framework-specific code — it addresses everything through data-testid, which is why the React and Vue templates agree on them.

A gate never seen failing may assert nothing

Both suites were watched failing. The instructive case is the price conversion: Intl.NumberFormat coerces a numeric string, so an unconverted price renders correctly and no browser assertion can catch it. A unit test does — which is why both layers exist.

Limits, stated plainly

  • No order has been placed. The demo store has no orders capability, so the place-order path is gated out and unexercised.
  • Hosted payment is not implemented. A payment method that requires a provider's hosted page is detected and declined by the template, not completed.
  • React and Vue only. Next.js and Angular are declared in the prompt and disabled, rather than silently absent.
  • dt-ui has no storefront shell. Header, footer, hero, product overview, shopping cart, category filters and order history are composed in the project. They work and they repalette; they are not yet library components.
  • The jade palette fails WCAG AA contrast. The same markup passes on indigo and ember and fails on jade, in both frameworks. dt-ui ships 24 palettes and its own accessibility gate never varies the palette, so 23 have never been contrast-tested.