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-0001vsWHL-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
- Go to Settings → Stores.
- Click New store.
-
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.
- Click Create. The new store shows up in the switcher within seconds.
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.
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
| Symptom | Likely cause | Fix |
|---|---|---|
| Order numbers conflict between stores | Same prefix on two stores | Each store needs a unique prefix. Rename in Settings → Stores. |
An integration key gets 404 not_found for an order you can see in the UI | Key is pinned to a different store | Switch stores in the UI to confirm; mint a key under the right store. |
| A staff member can’t see store B | No grant for that store | Owner / admin: Settings → Team → member → Store access. |
| Webhook fired but the receiver doesn’t know which store it’s for | Receiver ignoring store_id on the payload | Branch on payload.store_id on your side. |
| Reports show only one store’s numbers | Default filter is the active store | Click the store chip and choose All stores. |
Related
- Team & roles — per-member store access grants.
- Integration API — keys are minted per-store.
- Email pipeline — per-store from-address and reply-to.
- SMS pipeline — per-store sender numbers.
- Audit log — every event is stamped with its store id.