Architecture
A mental model of how Orchard is put together and where your code fits. You don’t need this to make API calls — but it explains the guarantees you’re building on.
Three planes
Operators / store admins Shoppers
│ │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────┐
│ CONTROL PLANE │ │ STOREFRONTS │
│ Orchard admin + API │ │ (your app, any stack) │
│ │ │ via @orchard/sdk │
│ admin UI │ │ or raw /api/v1 │
│ /api/v1/* (public) │◀───┤ │
│ webhooks (outbound) │ └──────────────────────────┘
└────────────┬─────────────┘
│
▼
┌──────────────────────────────────────────────────────┐
│ DATA PLANE │
│ isolated per-tenant data + per-tenant integrations │
│ (Stripe, email, shipping, ...) │
└──────────────────────────────────────────────────────┘- Control plane — the Orchard admin UI and the API. Store admins run their
store here; your app talks to the
/api/v1surface here. - Data plane — each tenant’s data, fully isolated, plus that tenant’s own external integrations (Stripe for payments, an email provider, a shipping carrier, and so on — each tenant supplies their own credentials).
- Storefronts — separate from Orchard. Your app (any framework) is the
storefront; it consumes Orchard through the typed
@orchard/sdkor raw/api/v1, and never touches Orchard’s database directly.
Tenant isolation
Every store is isolated at the data layer with defense in depth: the application scopes every query to your tenant, and the database enforces it again underneath. The tenant is resolved server-side from your API key — there is no tenant parameter you (or an attacker with a leaked key) can change to reach another store’s data. A customer, product, or order in one store has no relationship to anything in another.
Where your app fits
You’re an external API consumer: you own and run your storefront repo and host it yourself; Orchard provides the commerce backend. Your footprint inside Orchard is small — a store record, the tenant’s Stripe + email credentials, and one or more API keys scoped to that tenant. See the storefront models.
The flows you’ll build on
Read (catalog, content, inventory). Your app calls /api/v1 with a key →
Orchard resolves the tenant from the key → returns that tenant’s data.
Checkout. Your app creates a checkout session → Orchard uses the tenant’s own Stripe account → returns a hosted Stripe Checkout URL → you redirect the shopper → the shopper pays on Stripe.
Order settled. Stripe notifies Orchard; Orchard records the order against the
correct tenant and runs that tenant’s post-purchase steps (confirmation email,
etc.). If you’ve subscribed to webhooks, Orchard then emits
order.paid to your endpoint.
You never hold payment card data. Card entry happens on Stripe-hosted Checkout; Orchard (and your app) only ever see references, never card numbers.
What this means for you
- You’re decoupled. Rewriting your storefront never touches Orchard, and vice versa.
- Money flows through the tenant’s own Stripe — Orchard orchestrates, it doesn’t custody funds.
- One API surface. Everything you need is
/api/v1(typed via the SDK). You don’t get — or need — direct database access.
Next
- Data model — the entities behind the API.
- Authentication — keys, scopes, tenant resolution.
- Developer quickstart — first call in five minutes.