Skip to main content
Your team doesn’t have to write Ruby to build on Spree. The backend runs from a prebuilt Docker image, and everything below works against that image as it is:
  • Environment variables configure the application: database, hosts, email, file storage, search, background jobs, error tracking and more.
  • Webhooks tell your services when something happens.
  • The Admin API, called with a scoped secret key, lets them read and change the store.
  • The TypeScript SDKs give typed clients for both.
  • The MCP server lets an AI agent read and change the store on a person’s behalf.
  • Dashboard plugins add your own screens to the admin, written in React.
Ruby is still the right tool for a few jobs. See When to use Ruby instead.

Quick reference

Configure with environment variables

The whole application is configured through environment variables, so the prebuilt image runs in any environment without a code change. Set them in the project’s .env for local development, or in your hosting provider’s settings in production. Environment Variables lists every variable. Store settings, such as markets, currencies, payment methods, delivery methods, taxes, staff and roles, are data rather than configuration: change them in the dashboard or through the Admin API.

Webhooks

A webhook sends an HTTP POST to your service when an event happens in the store — an order is placed, a payment is completed, a product sells out. Your service can be written in any language. Create an endpoint in the dashboard under Settings → Webhooks, or through the Admin API:
Every request is signed with HMAC-SHA256. Always check the signature before you trust a payload. In JavaScript or TypeScript, @spree/sdk/webhooks checks it and returns a typed event:
In another language, check that the X-Spree-Webhook-Timestamp header is a Unix time within 300 seconds of now, then compute the HMAC-SHA256 of {timestamp}.{body} with the endpoint’s secret key over the raw request body, encode the digest as lowercase hexadecimal, and compare that string with the X-Spree-Webhook-Signature header in constant time. Reject the request if either check fails. The SDK does both. Things to know:
  • A failed delivery is retried, so your service can receive the same event more than once. Every attempt carries the same event id. Pass it as an idempotency key to whatever the event triggers, so a second delivery does no extra work. Don’t use the record’s ID instead: order.placed and order.canceled share an order ID, and keying on it would drop the cancellation. Use the record’s ID only for work that must happen once per record. See Delivery guarantees.
  • Payloads carry what a customer could see. For back-office fields, fetch the record from the Admin API with the id in the payload.
  • A webhook tells you something happened. It can’t stop it or change it.
See Webhooks for the payload format, the headers, retries and the delivery log, and Delivery guarantees for making sure every placed order reaches your service.

The Admin API

The Admin API is the back office as a REST API: products, orders, customers, stock, fulfillments, settings and more. It is described by an OpenAPI specification, so you can generate a client for any language from it. Integrations authenticate with a secret API key. Each key carries scopes — read_orders, write_fulfillments, read_all and so on — and can only reach what its scopes allow. Give each integration its own key with the fewest scopes it needs. Create a key in the dashboard under Settings → API Keys, or with the Spree CLI:
Then call the API:
Writes through the Admin API run the same workflows as the dashboard. Cancelling an order through the API checks the same rules, refunds the same way, sends the same emails and publishes the same events as cancelling it by hand. Secret keys belong on a server. Never put one in a browser, a mobile app or a public repository. See Admin API authentication for every scope, Querying for filters and pagination, and spree api to call the API from scripts and the terminal.

The TypeScript SDKs

Spree publishes typed clients for its APIs: The types are generated from the API itself, so your editor knows the fields of every record and the filters every list accepts.

The MCP server

Your store serves an MCP server at /api/v3/admin/mcp, with nothing extra to deploy. Connect Claude, Cursor or your own agent to it, and the agent can work in the back office in plain language. The server reads and writes:
  • Reads: search products, orders and customers, read a record in full, run reports.
  • Writes: cancel an order, capture a payment, update stock, curate a collection, start an import, and more.
Each write runs the same workflow the dashboard runs, so validations, emails and webhooks behave exactly as they would if a person clicked the button. The agent’s client asks for confirmation before a write runs. An agent acts for the person who connected it, and only within the permissions that person approved. It is never offered API keys, webhooks, roles or staff accounts. Any Admin API action your own app adds becomes a tool too, once it is documented in your OpenAPI specification. The MCP server is for requests a person makes in words. For anything scheduled or automatic, use the Admin API with a secret key. See MCP Server, the tool reference and Permissions and consent.

Dashboard plugins

The admin dashboard is a React application. You extend it in TypeScript: add pages and navigation entries, place components in slots on existing screens, add table columns, translations and themes. For your own project, register them in src/plugins.ts in your dashboard app — no package needed. To share an extension between projects or publish it, package it as a dashboard plugin. Your screens talk to the Admin API through the same typed client the dashboard uses, so they follow the signed-in person’s permissions. See Dashboard customization and Backend integration.

When to use Ruby instead

The options above work from outside Spree’s process. Write Ruby when the change has to happen inside it: You can mix both. A common setup keeps validation in a small Ruby hook and does everything else — syncing, notifications, reporting — from a TypeScript service over webhooks and the Admin API.
Ruby code runs only once the project builds the backend from its own source instead of the prebuilt image. Switch with spree eject.