---
title: Architecture
description: How Spree fits together — the catalog, the cart, the order, and fulfillment — and the two APIs you build against.
---

## Overview

Spree is a commerce engine you drive through a REST API. It holds the catalog, works out what a customer owes, takes the money, and tracks what was delivered. You build the storefront, the mobile app, Point of Sale or an internal tool on top of it.

There is no built-in storefront you have to accept, however we do provide a [reference one built in Next.js](../storefront/nextjs/quickstart.md). 
Everything a customer-facing app needs is on the Store API, and everything a back office needs is on the Admin API. Both ship as fully typed TypeScript clients.

## The shape of a purchase

Four things carry a purchase from browsing to delivery. Each one has a job the others don't:


  - [Product](products.md) — What you sell. A product has variants — the actual buyable items, each with its own price and stock.
  - [Cart](carts.md) — What a customer is assembling. It changes constantly and is usually abandoned.
  - [Order](orders.md) — What they committed to. A permanent financial record that must not change quietly.
  - [Fulfillment](fulfillments.md) — What actually ships. One parcel, from one location, with one tracking number.


```mermaid
flowchart LR
    Product --> Variant
    Variant --> Cart
    Cart -->|complete| Order
    Order --> Fulfillment
    Order --> Payment

    style Cart fill:#e3f2fd,stroke:#0077ff
    style Order fill:#e8f5e9,stroke:#2e7d32
```

**A cart and an order are separate records**, and that distinction shapes most of the API. A cart tolerates half-finished states — no address yet, no payment chosen. An order has to keep saying what the customer actually agreed to pay, so once it exists, today's prices and promotions can't quietly rewrite it. Completing a cart creates the order.

## How the pieces relate

```mermaid
erDiagram
    Store ||--o{ Product : "sells"
    Store ||--o{ Order : "records"

    Product ||--o{ Variant : "has many"
    Product }o--o{ Category : "filed under"
    Product }o--|| DeliveryProfile : "ships by"

    Variant ||--o{ Price : "priced in each currency"
    Variant ||--o{ StockLevel : "stocked at locations"
    StockLevel }o--|| StockLocation : "held at"

    Cart ||--o{ LineItem : "has many"
    Cart ||--o| Order : "completes into"
    LineItem }o--|| Variant : "of"

    Order ||--o{ LineItem : "has many"
    Order ||--o{ Fulfillment : "shipped as"
    Order ||--o{ Payment : "paid by"
    Order ||--o{ TaxLine : "taxed by"
    Order ||--o{ Discount : "reduced by"
    Order ||--o{ Fee : "surcharged by"
    Order }o--|| Customer : "placed by"

    Fulfillment }o--|| DeliveryMethod : "via"
    Fulfillment }o--|| StockLocation : "ships from"
    Payment }o--|| PaymentMethod : "via"
```

Three things in that diagram are worth calling out, because they're where Spree differs from what you might expect:

**Money added or removed is never one mixed list.** Tax, discounts and fees are three separate kinds of row. "What tax did we charge?" is a direct question with a direct answer instead of a filter over a mixed pile. See [Order totals](order-totals.md).

**Delivery is described by a profile, not a category.** A [delivery profile](fulfillments.md) says how a product travels — physically shipped, or digital — and which locations and methods serve it.

**Stock lives per location.** A variant can have a stock level at each [stock location](inventory.md), so availability is a real question about real warehouses rather than a single number.

## The two APIs

```mermaid
flowchart TB
    Storefront["Storefront / mobile app"] -->|"@spree/sdk"| StoreAPI["Store API"]
    BackOffice["Dashboard / integrations"] -->|"@spree/admin-sdk"| AdminAPI["Admin API"]
    StoreAPI --> Spree[(Spree)]
    AdminAPI --> Spree
    Spree --> Webhooks["Webhooks & events"]
    Webhooks --> External["Your systems"]
```

| API | For | Authentication |
|---|---|---|
| [**Store API**](../../api-reference/store-api/introduction.md) | Anything a customer touches — browsing, cart, checkout, their account | [Publishable key](../../api-reference/store-api/authentication.md), plus a customer token once signed in |
| [**Admin API**](../../api-reference/admin-api/introduction.md) | Anything staff or systems touch — catalog, orders, settings | [Secret key](../../api-reference/admin-api/authentication.md) with scopes, or a staff token |

The split is about exposure, not convenience. A publishable key is safe in browser code because it can only reach what a shopper is allowed to see. A secret key never belongs in a browser.

Both APIs share the same filtering, pagination and error shapes, so what you learn on one carries to the other. Both return **prefixed IDs** — `prod_86Rf07xd4z`, `cart_k5nR8xLq` — so an ID tells you what it points at.

> **NOTE:** Money is always a **string**: `"135.60"`, never `135.60`. JavaScript can't represent every decimal exactly — `0.1 + 0.2` gives `0.30000000000000004` — which is not something you want inside a price. Every amount also comes formatted for its currency as `display_total`, `display_price` and so on. Render the `display_` one.

## Building on top

Spree assumes you'll extend it, and gives you three ways in that survive an upgrade:

| Approach | Use it for | Guide |
|---|---|---|
| **Events** | Reacting inside your own app when something happens — indexing a record, calling an API with your credentials | [Events](events.md) |
| **Webhooks** | Telling another system something happened — an ERP, a warehouse, an email tool | [Webhooks](webhooks.md) |
| **Custom fields** | Storing your own data on a product, order or customer without changing the schema | [Custom Fields](metafields.md) |
| **Providers** | Plugging in a real service for tax, delivery rates, payments or search | [Providers](../providers/overview.md) |

For merchant-managed data, reach for [custom fields](metafields.md) before anything heavier — they're queryable and editable in the dashboard without touching code.

## Multi-store, multi-market, multi-seller

One Spree installation can run more than one business:

- **[Stores](stores.md)** are separate businesses — their own catalog, orders, customers and settings. Data does not cross between them.
- **[Channels](channels.md)** are the ways one store sells: a website, a mobile app, a retail till.
- **[Markets](markets.md)** are the regions a store sells into, each with its currency, locale and tax treatment.
- **[Sellers](sellers.md)** let one store list goods from many vendors, for a marketplace.

## What you install

| Package | What it is |
|---|---|
| [`@spree/sdk`](../sdk/quickstart.md) | Store API client — for your storefront |
| [`@spree/admin-sdk`](../sdk/admin/quickstart.md) | Admin API client — for back-office and integrations |
| [`@spree/cli`](../cli/quickstart.md) | Command line for local setup and calling the Admin API |
| [`@spree/dashboard`](../dashboard/overview.md) | The admin interface, a React app you own and extend |

> **INFO:** Spree runs on PostgreSQL, MySQL or SQLite. Check [database configuration](../deployment/database.md) for more details.

## Where to go next

Two ways in, depending on how you prefer to learn.


  - [Build something](../tutorial/introduction.md) — Add one feature end to end — a model with its own API, a screen in the dashboard, a page on the storefront, and the tests around it. Start here if you learn by doing.
  - [Read the reference](products.md) — Every part of the platform, one page each: products and pricing, carts and orders, payments, fulfillment, promotions. Start here if you already know what you need.


Building a storefront rather than extending the backend? Go straight to the [Spree Next.js Storefront](../storefront/nextjs/quickstart.md).
