> ## Documentation Index
> Fetch the complete documentation index at: https://spreecommerce.org/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Extending Spree without Ruby

> Configure Spree with environment variables and connect your own services, scripts, admin screens and AI agents through webhooks, the Admin API, the TypeScript SDKs, the MCP server and dashboard plugins — no Ruby required.

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](#when-to-use-ruby-instead).

## Quick reference

| What you want to do | Use |
| - | - |
| Point Spree at your database, email server, file storage, search or error tracking | [Environment variables](#configure-with-environment-variables) |
| Sync orders, customers or stock to an ERP, CRM or warehouse | [Webhooks](#webhooks), then the [Admin API](#the-admin-api) for anything the payload leaves out |
| Run a nightly import, a scheduled report or a data pipeline | [The Admin API](#the-admin-api) with a secret key |
| Build a storefront or a mobile app | [The Store API](https://spreecommerce.org/docs/api-reference/store-api/introduction.md) through [`@spree/sdk`](https://spreecommerce.org/docs/developer/sdk/quickstart) |
| Let staff ask an assistant to cancel an order or update stock | [The MCP server](#the-mcp-server) |
| Add a screen, a column or a widget to the admin | [Dashboard plugins](#dashboard-plugins) |
| Store your own data on products, orders or customers | [Custom fields](https://spreecommerce.org/docs/developer/core-concepts/metafields.md), [metadata](https://spreecommerce.org/docs/developer/customization/metadata.md) or [external references](https://spreecommerce.org/docs/developer/core-concepts/external-references.md) |
| Block an operation or change how Spree calculates something | Ruby — see [When to use Ruby instead](#when-to-use-ruby-instead) |

## 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.

| Area | Variables |
| - | - |
| Database and secrets | `DATABASE_URL`, `SECRET_KEY_BASE`, the three `ACTIVE_RECORD_ENCRYPTION_*` keys |
| URLs | `SPREE_HOST`, `CDN_HOST` |
| Web server and background jobs | `PORT`, `WEB_CONCURRENCY`, `RAILS_MAX_THREADS`, `SOLID_QUEUE_IN_PUMA`, `JOB_CONCURRENCY`, `JOB_THREADS` |
| Email | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USERNAME`, `SMTP_PASSWORD`, `SMTP_FROM_ADDRESS` |
| File storage | `AWS_*` for Amazon S3, `CLOUDFLARE_*` for Cloudflare R2 |
| Search | `MEILISEARCH_URL`, `MEILISEARCH_API_KEY` switch product search to Meilisearch |
| Error tracking and logs | `SENTRY_DSN`, `RAILS_LOG_LEVEL` |
| SSL | `RAILS_ASSUME_SSL`, `RAILS_FORCE_SSL` |

[Environment Variables](https://spreecommerce.org/docs/developer/deployment/environment_variables.md) 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](#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:

> [tab] Admin SDK
>
> ```typescript theme={"theme":"night-owl"}
> import { createAdminClient } from '@spree/admin-sdk'
>
> const client = createAdminClient({
>   baseUrl: 'https://store.example.com',
>   secretKey: process.env.SPREE_SECRET_KEY,
> })
>
> const endpoint = await client.webhookEndpoints.create({
>   url: 'https://erp.example.com/webhooks/spree',
>   subscriptions: ['order.placed', 'order.canceled'],
>   active: true,
> })
>
> // Returned only once. Store it to verify signatures.
> endpoint.secret_key
> ```

> [tab] cURL
>
> ```bash theme={"theme":"night-owl"}
> curl -X POST 'https://store.example.com/api/v3/admin/webhook_endpoints' \
>   -H 'X-Spree-API-Key: sk_xxx' \
>   -H 'Content-Type: application/json' \
>   -d '{
>     "url": "https://erp.example.com/webhooks/spree",
>     "subscriptions": ["order.placed", "order.canceled"],
>     "active": true
>   }'
> ```

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:

```typescript theme={"theme":"night-owl"}
import { constructWebhookEvent } from '@spree/sdk/webhooks'

const event = constructWebhookEvent(rawBody, request.headers, process.env.SPREE_WEBHOOK_SECRET!)

if (event.name === 'order.placed') {
  await erp.createSalesOrder(event.data) // typed as an Order
}
```

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](https://spreecommerce.org/docs/developer/core-concepts/events.md#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](https://spreecommerce.org/docs/developer/core-concepts/webhooks.md) for the payload format, the headers, retries and the delivery log, and [Delivery guarantees](https://spreecommerce.org/docs/developer/core-concepts/events.md#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](https://spreecommerce.org/docs/api-reference/admin.yaml), 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:

```bash theme={"theme":"night-owl"}
spree api-key create --type secret --scopes read_orders,write_fulfillments
```

Then call the API:

> [tab] Admin SDK
>
> ```typescript theme={"theme":"night-owl"}
> const { data: orders } = await client.orders.list({
>   payment_status_eq: 'paid',
>   fulfillment_status_eq: 'unfulfilled',
>   sort: '-completed_at',
> })
> ```

> [tab] cURL
>
> ```bash theme={"theme":"night-owl"}
> curl -G 'https://store.example.com/api/v3/admin/orders' \
>   -H 'X-Spree-API-Key: sk_xxx' \
>   -d 'q[payment_status_eq]=paid' \
>   -d 'q[fulfillment_status_eq]=unfulfilled' \
>   -d 'sort=-completed_at'
> ```

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](https://spreecommerce.org/docs/api-reference/admin-api/authentication.md) for every scope, [Querying](https://spreecommerce.org/docs/api-reference/admin-api/querying.md) for filters and pagination, and [`spree api`](https://spreecommerce.org/docs/developer/cli/admin-api) to call the API from scripts and the terminal.

## The TypeScript SDKs

Spree publishes typed clients for its APIs:

| Package | API | For |
| - | - | - |
| [`@spree/sdk`](https://spreecommerce.org/docs/developer/sdk/quickstart) | Store API | Storefronts, mobile apps, and verifying webhooks |
| [`@spree/admin-sdk`](https://spreecommerce.org/docs/developer/sdk/admin/quickstart) | Admin API | Integrations, scripts and admin tools |
| `@spree/seller-sdk` | Seller API | Tools for sellers on a marketplace |

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](https://spreecommerce.org/docs/developer/mcp/overview.md), [the tool reference](https://spreecommerce.org/docs/developer/mcp/tools.md) and [Permissions and consent](https://spreecommerce.org/docs/developer/mcp/permissions.md).

## 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](https://spreecommerce.org/docs/developer/dashboard/plugins/overview.md).

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](https://spreecommerce.org/docs/developer/dashboard/customization/quickstart.md) and [Backend integration](https://spreecommerce.org/docs/developer/dashboard/customization/backend.md).

## 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 need to | Use | Why not from outside |
| - | - | - |
| Block an operation — refuse a cart, cap a quantity, veto a cancellation | [A workflow hook](https://spreecommerce.org/docs/developer/customization/workflows.md) | A webhook arrives after the operation has happened |
| Run code while an operation happens, in the same request | [A workflow hook](https://spreecommerce.org/docs/developer/customization/workflows.md) or [a subscriber](https://spreecommerce.org/docs/developer/core-concepts/events.md#reacting-inside-spree) | A webhook is delivered later, by a background job |
| Add a checkout step or requirement | [The checkout registry](https://spreecommerce.org/docs/developer/customization/checkout.md) | Requirements are evaluated inside the cart |
| Calculate tax or delivery rates your own way, or change search | [A provider](https://spreecommerce.org/docs/developer/customization/quickstart.md#providers) | Spree calls the provider while it calculates |
| Add a model, a database column or an association | [A decorator](https://spreecommerce.org/docs/developer/customization/decorators.md) or your own model | Custom fields and metadata cover most data. A new table needs Ruby |
| Add an API endpoint | [API customization](https://spreecommerce.org/docs/developer/customization/api.md) | Endpoints are Rails controllers |

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.

> [!NOTE]
>
> Ruby code runs only once the project builds the backend from its own source instead of the prebuilt image. Switch with [`spree eject`](https://spreecommerce.org/docs/developer/cli/quickstart#spree-eject).

## Related

* [Environment Variables](https://spreecommerce.org/docs/developer/deployment/environment_variables.md) — every configuration variable
* [Customization quickstart](https://spreecommerce.org/docs/developer/customization/quickstart.md) — every way to customize Spree, in order of recommendation
* [Events](https://spreecommerce.org/docs/developer/core-concepts/events.md) — what Spree announces, and delivery guarantees
* [Webhooks](https://spreecommerce.org/docs/developer/core-concepts/webhooks.md) — endpoints, payloads, signatures and retries
* [Admin SDK](https://spreecommerce.org/docs/developer/sdk/admin/quickstart.md) — the typed Admin API client
* [MCP Server](https://spreecommerce.org/docs/developer/mcp/overview.md) — connecting AI agents to a store


## Related topics

- [Quickstart](https://spreecommerce.org/docs/developer/customization/quickstart.md)
- [Decorators](https://spreecommerce.org/docs/developer/customization/decorators.md)
- [Installing Spree](https://spreecommerce.org/docs/developer/getting-started/quickstart.md)
- [Developing Spree](https://spreecommerce.org/docs/developer/contributing/developing-spree.md)
- [Customizing the Spree API](https://spreecommerce.org/docs/developer/customization/api.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.