Overview

{{COMPANY_SHORT_NAME}} has two layers of tenancy. A workspace is your business / organization — it owns billing, your team, your CRM, and the master settings. A store sits underneath the workspace and partitions the operational data: its own orders, its own invoices, its own integration keys, its own shipping carriers, its own webhook deliveries.

Most operators run a single store. Multi-store is for the cases where you genuinely need to keep books separate — a second brand, a second physical location, a wholesale arm that ships under a different name from your retail arm.

What working correctly looks like

  • The store switcher in the top-left of the shell shows the active store name; clicking it lists every store you have access to.
  • Switching stores changes the order list, invoices, products, and integrations — but not the client list (clients are workspace-wide).
  • New orders use the store’s own number prefix (e.g. RX-2026-0001 vs WHL-2026-0001).
  • An integration key minted under store A only writes to store A — attempting to look up an order from store B returns 404.
  • Reports default to the active store; the “All stores” toggle is one click away for cross-store roll-ups.
  • RLS denies cross-store reads even when an admin manually crafts a query.

Add a store

  1. Go to Settings → Stores.
  2. Click New store.
  3. Fill in:
    • Display name — what the team sees.
    • Slug — short identifier used in URLs and in audit log entries (e.g. retail, wholesale).
    • Number prefix — what appears before each order/invoice number (e.g. RX-). Required and unique within the workspace.
    • Default currency — ISO 4217 (USD, EUR…). Cannot be changed once orders exist.
  4. Click Create. The new store shows up in the switcher within seconds.
Number prefixes are forever

Number prefixes appear on customer-facing invoices and on every payment provider receipt. Pick a prefix you’re willing to print. Renaming the display name later is fine; the prefix stays.

What’s scoped where

Knowing which side of the workspace/store line a setting lives on is the difference between “it works for everyone” and “why is this missing in store B?”

Per-store

  • Order numbering — each store has its own counter and prefix.
  • Invoice numbering — same as orders, separate sequence.
  • Integration keys (pck_int_*) — pinned at mint time. See Integration API.
  • Webhook endpoints — can be per-store or org-wide; default is org-wide. Set per-store from Settings → Webhooks while that store is active.
  • Shipping carrier config — FedEx / USPS / UPS account credentials, return labels, default service.
  • Email from-address & reply-to — per-store sender so an email from your retail brand looks different from your wholesale brand. See Email pipeline.
  • SMS sender numbers — assigned per-store, per-purpose. See SMS pipeline.
  • Products / SKUs / inventory — products live under one store; share by duplicating across stores.

Per-org (across all stores)

  • Clients (CRM) — shared by design. A buyer who orders from your retail store and your wholesale store is one client record, with two separate order histories.
  • Team members & roles — one team across the whole workspace.
  • Billing / subscription — one Stripe subscription per workspace.
  • Audit log — one chain across the workspace, with the store id stamped on each row.
  • Translation settings — OpenAI key, glossary, monthly cost cap.
Clients are intentionally workspace-wide

The shared CRM means a buyer’s lifetime value, communication history, and preferences follow them across your stores. If you genuinely need a buyer to look like two different people, you need two workspaces, not two stores.

The store switcher

The switcher is the dropdown in the top-left of the app shell, next to the {{COMPANY_SHORT_NAME}} mark.

  • Lists every store you have access to (admins see all; staff only see the ones they’ve been granted).
  • The active store id is stored in your session cookie and is sent on every API call.
  • Switching is a full-page navigation; in-flight forms are discarded so you can’t accidentally submit a wholesale draft into your retail store.
  • Press Cmd+K to open the command palette and type “switch store” to switch by name.

Isolation at the database

The store boundary is enforced in Postgres, not in the UI. Every row in the operational tables (orders, invoices, payments, products, integration_keys, webhook_endpoints, etc.) carries an org_id and a store_id column, and every table has a Row-Level Security policy of the shape:

create policy _store_isolation
  on .
for all using ( org_id = core.fn_current_org_id() and store_id = core.fn_current_store_id() );

This means even a manually-crafted query through the JS SDK can’t read another store’s data. Cross-store reports are written through dedicated security definer functions that explicitly opt-in to spanning stores, with a hard org filter.

Per-store reports

Every report (orders, revenue, inventory, AR aging, fulfillment SLA) defaults to the currently-active store. To see across all stores:

  1. Open any report.
  2. Click the Store: <active> chip at the top of the filter bar.
  3. Pick All stores — or hold Shift to multi-select.
  4. The chart re-renders; rows include a Store column.

The export buttons (CSV, PDF) honor the same filter so you can ship a per-store P&L or a consolidated one with one click.

When to add a second store

The honest answer: usually you don’t need to. Adding a store doubles your operational surface area — two sets of integrations, two sets of carrier configs, two number sequences to keep straight. Add one only when at least one of these is true.

  • Separate brand — different name on the invoice, different from-address on the email, different sender number on the SMS.
  • Separate legal entity — you keep books that need to roll up separately for tax / accounting.
  • Separate location — physically separate inventory pools that you don’t want to commingle.
  • Wholesale vs retail — different price books, different fulfillment SLA, different number ranges.

If you’re unsure, run with one store and add a second later. Migrating an order between stores is a manual operation and we charge for it; better to start right.

Settings & permissions

  • Settings → Stores — create, archive, rename. Owner / admin only.
  • Settings → Team — per-member store access. A staff member can be granted access to one store and not the others. Owners and admins always see all stores.
  • Settings → Workspace — org-wide settings (billing, audit retention, translation). Visible only at the workspace level — the URL doesn’t include a store id.

API + automation

The Integration API and the Public read API both pin to a single store at the key level.

  • An pck_int_* key writes to exactly one (workspace, store) pair. There is no “write to any store” key. See Integration API → Mint a key.
  • A pck_pub_* read key is similarly pinned. To pull data across stores, mint one key per store.
  • Outbound webhooks fire from the store the event happened in; the payload includes store_id so a single endpoint can receive from multiple stores and route accordingly.

Troubleshooting

SymptomLikely causeFix
Order numbers conflict between storesSame prefix on two storesEach store needs a unique prefix. Rename in Settings → Stores.
An integration key gets 404 not_found for an order you can see in the UIKey is pinned to a different storeSwitch stores in the UI to confirm; mint a key under the right store.
A staff member can’t see store BNo grant for that storeOwner / admin: Settings → Team → member → Store access.
Webhook fired but the receiver doesn’t know which store it’s forReceiver ignoring store_id on the payloadBranch on payload.store_id on your side.
Reports show only one store’s numbersDefault filter is the active storeClick the store chip and choose All stores.