Skip to main content
A code storefront can serve the customer account itself: sign-in, order history, downloads, support. The account is then part of your theme, with your layout, your type and your colors, instead of a separate page the buyer is sent to. This page is about the Advanced lane. If your shop runs an Easy (visual) theme, the account is a block you place in the Builder and none of the code below applies.
The reference implementation ships with the Nova template, in src/components/account/ and src/commerce/account-route.ts. Start from those files rather than from a blank page.

Where the account lives

There is exactly one account route. The ten section names are overview, orders, downloads, subscriptions, rewards, referrals, favorites, support, settings and reseller.
Do not invent per-section paths such as /dashboard/orders. Order emails, password-free sign-in redirects and links customers already have are written against the query form, and it is identical on both Shoppex storefront lanes. A theme that routes its account differently breaks links it does not own.
Your storefront is a single-page app, so serve /dashboard from your own router and read the two parameters:
A section your theme does not implement should say so. Answering it with the overview makes a broken link look like it worked, and the customer keeps hunting for something that is not there.

How the session works

Sign-in is a single-use code sent by email. There is no password, and there is no redirect: the customer types the code into your page and the same page becomes their account.
1

The customer asks for a code

requestOtp(email) sends the address and nothing else.
2

Shoppex emails the code

The code is short lived. Its lifetime and retry limits are enforced on the server.
3

The customer types it back

verifyOtp(email, otp) completes the sign-in.
4

The edge sets the session cookie

The Shoppex storefront worker sets an HttpOnly cookie on your own domain, scoped to that host. Every later call carries it automatically.
Three consequences shape everything you write:

No token in JavaScript

Nothing hands your code a session token, and nothing should store one. The cookie is HttpOnly, so your scripts cannot read it even by accident.

No shop parameter

The shop is derived from the host the buyer is on. Never send a shop, a shop id or a slug with an account call.

Same origin only

Calls go to your own domain under /api/customer/*. Anything that changes data must come from your own pages; the edge refuses cross-site writes before they reach the API.
Use the SDK for every account call. A hand-rolled fetch client against /api/customer/* is how a storefront ends up sending a shop or a token it was never meant to choose, and it drops the response validation the SDK does for you.

The account calls

Load the Storefront SDK as you already do for the catalog and the cart. In a code storefront that is the CDN global (window.shoppex), wrapped by src/commerce/shoppex.ts in the official templates. In a bundled project, the same functions are named exports of @shoppexio/storefront. Every call answers with the same envelope: { success, data?, message?, code? }. message is a sentence to show the customer. code is the machine-readable reason, when the server sent one, and is the only value you should branch on.
Licences, subscriptions and tickets have no list call. All three arrive inside dashboard(), and the calls above act on one item whose id you already have. GET /api/customer/licenses is not an endpoint and answers 404 at the edge — the same is true for /subscriptions and /tickets.
dashboard(), orders() and order() are validated against the published response contract before you see them. A payload that does not match comes back as a failure rather than as half-read data, so a change on the platform side surfaces as an error instead of as a wrong number on a customer’s screen.

Sign a customer in

Decide what a signed-in page looks like by asking, not by remembering:
Only a 401 means “not signed in”. Every other failure — a 403, a 5xx, a contract mismatch, or a network error that carries no status at all — means the answer is unknown. Rendering the sign-in form there tells a buyer whose session is still live that they were signed out, and a reload contradicts you. Show the error and let them retry.Never do the opposite either and treat a failed check as a signed-in customer: that renders an account shell around data nobody fetched.

The overview

One call carries the whole overview. Read what you need out of it and count nothing yourself.
data.invoices holds the most recent orders, which is enough for a “recent orders” block without a second request.

Order history

Pagination is page based. Ask for a page and a size, and read has_more to decide whether a next page exists.
There is no cursor. A cursor parameter is ignored by the API, which then keeps answering with page one, so a list built on one silently stops advancing.
An order row carries uniqid, and that is the id the detail call takes:

Two details that bite

Field casing is not uniform. The dashboard payload is snake_case (total_display, created_at, line_items), while the order list and the order detail are camelCase (totalDisplay, createdAt, lineItems). Keep the names as they arrive. Renaming them in a shared helper is how one of the two surfaces quietly starts reading undefined. total and currency are not a pair. currency is the currency the buyer sees. The total field is a normalized figure in USD, and total_display / totalDisplay is the same total in the buyer’s currency. Render the display amount first:
The same rule applies to discount and discountDisplay. Line prices, subtotal and tax are already in the buyer’s currency.

What a storefront cannot do

The edge keeps a fixed list of account endpoints, bound to their method. Anything outside it answers 404 before it reaches the API, whatever the page calls it from. Your theme runs on the same origin as the customer’s session, so that list is the answer to what shop-authored code may do on a buyer’s behalf, and it is not something a theme can widen. Deliberately closed today: Link a customer to the hosted flow for anything on that list rather than trying to reach it from your theme.

Failures and empty states

Account pages fail in ways catalog pages do not: the session expires, the network drops mid-page, a shop turns a feature off. Three habits keep that honest.
  • Show the server’s sentence. message is written for the customer. A friendlier guess of your own is often simply wrong, and a customer who is told the wrong reason cannot fix anything.
  • Tell the three states apart. Loading, empty and failed are different. An error rendered as an empty list reads as “you have no orders”, which is the one thing it does not mean.
  • Never fill a gap. If the API did not send a value, do not compute a replacement. A number your storefront made up is worse than a missing one, because nobody can tell.

Where to go next

Code storefront development

Project structure, the commerce layer, and working locally.

Storefront SDK reference

Every SDK call, including the catalog, cart and checkout surfaces.