ConceptsArchitecture

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, ...)                       │
 └──────────────────────────────────────────────────────┘
  1. Control plane — the Orchard admin UI and the API. Store admins run their store here; your app talks to the /api/v1 surface here.
  2. 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).
  3. Storefronts — separate from Orchard. Your app (any framework) is the storefront; it consumes Orchard through the typed @orchard/sdk or 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