@spree/sdk
v1.0.1
Published
Official TypeScript SDK for Spree Commerce Store API
Downloads
1,558
Maintainers
Readme
@spree/sdk
Official TypeScript SDK for Spree Commerce API v3.
Installation
npm install @spree/sdk
# or
yarn add @spree/sdk
# or
pnpm add @spree/sdkQuick Start
import { createClient } from '@spree/sdk';
// Initialize the client
const client = createClient({
baseUrl: 'https://api.mystore.com',
publishableKey: 'spree_pk_xxx',
});
// Browse products (Store API)
const products = await client.products.list({
limit: 10,
expand: ['variants', 'media'],
});
// Get a single product
const product = await client.products.get('spree-tote');
// Authentication
const { token, user } = await client.auth.login({
email: '[email protected]',
password: 'password123',
});
// Create a cart and add items
const cart = await client.carts.create();
await client.carts.items.create(cart.id, {
variant_id: 'var_abc123',
quantity: 2,
}, { spreeToken: cart.token });
// Update cart and complete
await client.carts.update(cart.id, {
email: '[email protected]',
}, { spreeToken: cart.token });
await client.carts.complete(cart.id, { spreeToken: cart.token });Client Architecture
All Store API resources are available directly on the client:
client.products.list() // Products
client.carts.create() // Create a cart
client.carts.get(cartId) // Get a cart by ID
client.carts.items.create(cartId, params) // Cart line items
client.carts.complete(cartId, opt) // Complete cart
client.carts.list(opt) // List active carts
client.customers.create(params) // Registration
client.customer.get(opt) // Account
client.customer.orders.list() // Order historyAuthentication
The SDK supports multiple authentication modes:
1. Publishable Key Only (Guest/Public Access)
const client = createClient({
baseUrl: 'https://api.mystore.com',
publishableKey: 'spree_pk_xxx',
});
// Public endpoints work without user authentication
const products = await client.products.list();2. Publishable Key + JWT (Authenticated Customer)
// Login to get tokens
const { token, user } = await client.auth.login({
email: '[email protected]',
password: 'password123',
});
// Use token for authenticated requests
const orders = await client.customer.orders.list({}, { token });
// Refresh token when needed
const newTokens = await client.auth.refresh({ token });3. Register New Customer
const { token, user } = await client.customers.create({
email: '[email protected]',
password: 'password123',
password_confirmation: 'password123',
first_name: 'John',
last_name: 'Doe',
});Guest Checkout
For guest checkout, use the token returned when creating a cart:
// Create a cart (guest)
const cart = await client.carts.create();
// Use spreeToken for all cart operations
const options = { spreeToken: cart.token };
// Add items
await client.carts.items.create(cart.id, {
variant_id: 'var_abc123',
quantity: 1,
}, options);
// Update cart with email and addresses
await client.carts.update(cart.id, {
email: '[email protected]',
}, options);
// Complete the order
await client.carts.complete(cart.id, options);API Reference
Store
// Get current store information
const store = await client.store.get();Products
// List products with filtering
const products = await client.products.list({
page: 1,
limit: 25,
name_cont: 'shirt',
sort: 'price asc',
expand: ['variants', 'media', 'categories'],
});
// Get single product by ID or slug
const product = await client.products.get('spree-tote', {
expand: ['variants', 'media'],
});
// Get product with prior price (EU Omnibus Directive compliance)
const product = await client.products.get('spree-tote', {
expand: ['prior_price'],
});
console.log(product.prior_price); // { amount: "9.99", currency: "USD", display_amount: "$9.99", ... }
// Get available filters (price range, availability, options, categories)
const filters = await client.products.filters({
category_id: 'ctg_abc123', // Optional: scope filters to a category
});Categories
// List categories with filtering
const categories = await client.categories.list({
depth_eq: 1, // Top-level categories only
});
// Get single category by ID or permalink
const category = await client.categories.get('clothing/shirts', {
expand: ['ancestors', 'children'], // For breadcrumbs and subcategories
});
// List products in a category
const categoryProducts = await client.categories.products.list('clothing/shirts', {
page: 1,
limit: 12,
expand: ['media', 'default_variant'],
});Carts
All cart operations use explicit cart IDs (cart_xxx). The cart is authorized
via spreeToken header (guest) or JWT token (authenticated user).
// Create a new cart
const cart = await client.carts.create();
// Get a cart by ID
const cart = await client.carts.get('cart_xxx', { spreeToken: cart.token });
// Delete / abandon a cart
await client.carts.delete('cart_xxx', { spreeToken: cart.token });
// Associate guest cart with authenticated user
await client.carts.associate('cart_xxx', {
token: jwtToken, // User's JWT token
});
// List all active carts for authenticated user
const { data: carts } = await client.carts.list({ token: jwtToken });
// Update cart (email, addresses, customer note)
await client.carts.update('cart_xxx', {
email: '[email protected]',
shipping_address: {
first_name: 'John',
last_name: 'Doe',
address1: '123 Main St',
city: 'New York',
postal_code: '10001',
phone: '+1 555 123 4567',
country_iso: 'US',
state_abbr: 'NY',
},
}, options);
// Complete the order
await client.carts.complete('cart_xxx', options);Cart Items (Line Items)
const options = { spreeToken: cart.token };
// Add item
await client.carts.items.create('cart_xxx', {
variant_id: 'var_123',
quantity: 2,
}, options);
// Update item quantity
await client.carts.items.update('cart_xxx', lineItemId, {
quantity: 3,
}, options);
// Remove item
await client.carts.items.delete('cart_xxx', lineItemId, options);Discount Codes
const options = { spreeToken: cart.token };
// Apply a discount code
await client.carts.discountCodes.apply('cart_xxx', 'SAVE20', options);
// Remove a discount code
await client.carts.discountCodes.remove('cart_xxx', 'SAVE20', options);Gift Cards
const options = { spreeToken: cart.token };
// Apply a gift card
const cart = await client.carts.giftCards.apply('cart_xxx', 'GC-ABCD-1234', options);
// Remove a gift card (ID from cart.gift_card.id)
await client.carts.giftCards.remove('cart_xxx', 'gc_abc123', options);
// Check cart warnings (e.g. items removed due to stock changes)
if (cart.warnings.length > 0) {
cart.warnings.forEach(w => console.log(w.code, w.message));
}Fulfillments
const options = { spreeToken: cart.token };
// Fulfillments are included in the cart response.
// Select a delivery rate
await client.carts.fulfillments.update('cart_xxx', fulfillmentId, {
selected_delivery_rate_id: 'rate_xxx',
}, options);Store Credits
const options = { spreeToken: cart.token };
// Apply store credit (applies maximum available by default)
await client.carts.storeCredits.apply('cart_xxx', undefined, options);
// Apply specific amount of store credit
await client.carts.storeCredits.apply('cart_xxx', 25.00, options);
// Remove store credit
await client.carts.storeCredits.remove('cart_xxx', options);Payments
const options = { spreeToken: cart.token };
// Payment methods and payments are included in the cart response.
// Each payment method includes `session_required` flag:
// - true -> use paymentSessions (Stripe, Adyen, PayPal, etc.)
// - false -> use payments.create (Check, Cash on Delivery, Bank Transfer, etc.)
// Create a payment for a non-session payment method
// (e.g. Check, Cash on Delivery, Bank Transfer, Purchase Order)
const payment = await client.carts.payments.create('cart_xxx', {
payment_method_id: 'pm_xxx',
amount: '99.99', // Optional, defaults to order total minus store credits
metadata: { // Optional, write-only metadata
purchase_order_number: 'PO-12345',
},
}, options);Payment Sessions
Payment sessions provide a unified, provider-agnostic interface for payment processing. They work with any payment gateway (Stripe, Adyen, PayPal, etc.) through a single API.
const options = { spreeToken: cart.token };
// Create a payment session (initializes a session with the payment gateway)
const session = await client.carts.paymentSessions.create('cart_xxx', {
payment_method_id: 'pm_xxx',
amount: '99.99', // Optional, defaults to order total
external_data: { // Optional, provider-specific data
return_url: 'https://mystore.com/checkout/complete',
},
}, options);
// The session contains provider-specific data (e.g., Stripe client_secret)
console.log(session.external_data.client_secret);
// Get a payment session
const existing = await client.carts.paymentSessions.get(
'cart_xxx', session.id, options
);
// Update a payment session (e.g., after order total changes)
await client.carts.paymentSessions.update('cart_xxx', session.id, {
amount: '149.99',
}, options);
// Complete the payment session (after customer confirms payment on the frontend)
const completed = await client.carts.paymentSessions.complete(
'cart_xxx',
session.id,
{ session_result: 'success' },
options
);
console.log(completed.status); // 'completed'Orders
Completed orders can be looked up by ID or number:
// Get a completed order by ID or number
const order = await client.orders.get('R123456789', {
expand: ['items', 'fulfillments'],
}, { spreeToken: orderToken });For order history, use the customer orders resource:
const options = { token: jwtToken };
// List order history for authenticated customer
const orders = await client.customer.orders.list({}, options);
// Get a specific order from history
const order = await client.customer.orders.get('or_xxx', {
expand: ['items', 'fulfillments'],
}, options);Markets
// List all markets
const { data: markets } = await client.markets.list();
// [{ id: "mkt_xxx", name: "North America", currency: "USD", default_locale: "en", ... }]
// Get a single market
const market = await client.markets.get('mkt_xxx');
// Resolve which market applies for a country
const market = await client.markets.resolve('DE');
// => { id: "mkt_xxx", name: "Europe", currency: "EUR", default_locale: "de", ... }
// List countries in a market
const { data: countries } = await client.markets.countries.list('mkt_xxx');
// Get a country in a market (with states for address forms)
const country = await client.markets.countries.get('mkt_xxx', 'DE', {
expand: ['states'],
});Geography
// List countries available for checkout
const { data: countries } = await client.countries.list();
// Get country by ISO code (with states)
const usa = await client.countries.get('US', { expand: ['states'] });
console.log(usa.states); // Array of statesCustomer Account
const options = { token: jwtToken };
// Get profile
const profile = await client.customer.get(options);
// Update profile
await client.customer.update({
first_name: 'John',
last_name: 'Doe',
}, options);Customer Addresses
const options = { token: jwtToken };
// List addresses
const { data: addresses } = await client.customer.addresses.list({}, options);
// Get address by ID
const address = await client.customer.addresses.get('addr_xxx', options);
// Create address
await client.customer.addresses.create({
first_name: 'John',
last_name: 'Doe',
address1: '123 Main St',
city: 'New York',
postal_code: '10001',
country_iso: 'US',
state_abbr: 'NY',
}, options);
// Update address
await client.customer.addresses.update('addr_xxx', { city: 'Brooklyn' }, options);
// Delete address
await client.customer.addresses.delete('addr_xxx', options);
// Set as default billing or shipping address
await client.customer.addresses.update('addr_xxx', { is_default_billing: true }, options);
await client.customer.addresses.update('addr_xxx', { is_default_shipping: true }, options);Password Resets
// Request a password reset email
// Always returns { message: string } (202 status) — prevents email enumeration
await client.passwordResets.create({
email: '[email protected]',
redirect_url: 'https://myshop.com/reset-password', // optional, validated against store's allowed origins
});
// Reset password with token from email
// Returns AuthTokens (JWT + user) — auto-login on success
// Token expires in 15 minutes
const { token, user } = await client.passwordResets.update('reset_token_xxx', {
password: 'newPassword123',
password_confirmation: 'newPassword123',
});Customer Credit Cards
const options = { token: jwtToken };
// List saved credit cards
const { data: cards } = await client.customer.creditCards.list({}, options);
// Get credit card by ID
const card = await client.customer.creditCards.get('cc_xxx', options);
// Delete credit card
await client.customer.creditCards.delete('cc_xxx', options);Wishlists
const options = { token: jwtToken };
// List wishlists
const { data: wishlists } = await client.wishlists.list({}, options);
// Get wishlist by ID
const wishlist = await client.wishlists.get('wl_xxx', {
expand: ['wishlist_items'],
}, options);
// Create wishlist
const newWishlist = await client.wishlists.create({
name: 'Birthday Ideas',
is_private: true,
}, options);
// Update wishlist
await client.wishlists.update('wl_xxx', {
name: 'Updated Name',
}, options);
// Delete wishlist
await client.wishlists.delete('wl_xxx', options);Wishlist Items
const options = { token: jwtToken };
// Add item to wishlist
await client.wishlists.items.create('wl_xxx', {
variant_id: 'var_123',
quantity: 1,
}, options);
// Update item quantity
await client.wishlists.items.update('wl_xxx', 'wi_xxx', {
quantity: 2,
}, options);
// Remove item from wishlist
await client.wishlists.items.delete('wl_xxx', 'wi_xxx', options);Nested Resources
The SDK uses a resource builder pattern for nested resources:
| Parent Resource | Nested Resource | Available Methods |
|-----------------|-----------------|-------------------|
| carts | items | create, update, delete |
| carts | discountCodes | apply, remove |
| carts | giftCards | apply, remove |
| carts | fulfillments | update |
| carts | payments | create |
| carts | paymentSessions | create, get, update, complete |
| carts | storeCredits | apply, remove |
| policies | — | list, get |
| passwordResets | — | create, update |
| customer | addresses | list, get, create, update, delete |
| customer | creditCards | list, get, delete |
| customer | giftCards | list, get |
| customer | storeCredits | list, get |
| customer | orders | list, get |
| markets | countries | list, get |
| categories | products | list |
| wishlists | items | create, update, delete |
Example:
// Cart resources take cartId as first argument
await client.carts.items.create(cartId, params, options);
await client.carts.discountCodes.apply(cartId, code, options);
await client.carts.fulfillments.update(cartId, fulfillmentId, params, options);
await client.carts.payments.create(cartId, params, options);
await client.carts.paymentSessions.create(cartId, params, options);
await client.carts.storeCredits.apply(cartId, amount, options);
// Other nested resources follow the same pattern
await client.customer.addresses.list({}, options);
await client.customer.orders.list({}, options);
await client.markets.countries.list(marketId);
await client.categories.products.list(categoryId, params, options);
await client.wishlists.items.create(wishlistId, params, options);Localization & Currency
Client-level defaults
Set locale, currency, and country when creating the client:
const client = createClient({
baseUrl: 'https://api.mystore.com',
publishableKey: 'spree_pk_xxx',
locale: 'fr',
currency: 'EUR',
country: 'FR',
});
// All requests use fr/EUR/FR automatically
const products = await client.products.list();Update defaults at any time:
client.setLocale('de');
client.setCurrency('EUR');
client.setCountry('DE');Per-request overrides
Pass locale and currency headers with any request to override defaults:
const products = await client.products.list({}, {
locale: 'fr',
currency: 'EUR',
country: 'FR',
});Error Handling
import { SpreeError } from '@spree/sdk';
try {
await client.products.get('non-existent');
} catch (error) {
if (error instanceof SpreeError) {
console.log(error.code); // 'record_not_found'
console.log(error.message); // 'Product not found'
console.log(error.status); // 404
console.log(error.details); // Validation errors (if any)
}
}TypeScript Support
The SDK includes full TypeScript support with generated types from the API serializers:
import type {
Product,
Cart,
Order,
Variant,
Category,
LineItem,
Address,
Customer,
PaginatedResponse,
} from '@spree/sdk';
// All responses are fully typed
const products: PaginatedResponse<Product> = await client.products.list();
const category: Category = await client.categories.get('clothing');
const cart: Cart = await client.carts.get('cart_xxx');Available Types
All types are exported as unprefixed names (e.g., Product, Order). Legacy Store* prefixed aliases (e.g., StoreProduct) are still available for backward compatibility.
Core Types
Product- Product dataVariant- Variant dataCart- Cart data (usescart_prefixed IDs)Order- Completed order data (usesor_prefixed IDs)LineItem- Line item in cartCategory- CategoryCountry- Country with statesState- State/provinceAddress- Customer addressCustomer- Customer profileMarket- Market configuration (currency, locales, countries)
Commerce Types
Payment- Payment recordPaymentMethod- Payment methodPaymentSession- Provider-agnostic payment sessionFulfillment- Fulfillment recordDeliveryRate- Delivery rate optionDeliveryMethod- Delivery methodCreditCard- Saved credit cardGiftCard- Gift cardDiscount- Discount applied to a cart or order
Product Types
Media- Product media (images, videos)Price- Price dataPriceHistory- Prior price data (for EU Omnibus Directive compliance)OptionType- Option type (e.g., Size, Color)OptionValue- Option value (e.g., Small, Red)DigitalLink- Digital download linkCustomField- Custom field data
Wishlist Types
Wishlist- WishlistWishlistItem- Wishlist item
Client Types
Client- Main client interfaceStoreClient- Store API client classClientConfig- Client configurationRequestOptions- Per-request optionsRetryConfig- Retry behavior configuration
Utility Types
PaginatedResponse<T>- Paginated API responseListResponse<T>- List API responseAuthTokens- JWT tokens from loginAddressParams- Address input parametersUpdateCartParams- Cart update parameters (email, addresses, etc.)CreatePaymentParams- Direct payment creation parameters (for non-session payment methods)CreatePaymentSessionParams- Payment session creation parametersUpdatePaymentSessionParams- Payment session update parametersCompletePaymentSessionParams- Payment session completion parametersProductFiltersResponse- Product filters responseCheckoutRequirement- Checkout requirement ({ step, field, message })
Custom Fetch
You can provide a custom fetch implementation:
import { createClient } from '@spree/sdk';
const client = createClient({
baseUrl: 'https://api.mystore.com',
publishableKey: 'spree_pk_xxx',
fetch: customFetchImplementation,
});Webhooks
The SDK includes webhook signature verification and types via the @spree/sdk/webhooks subpath:
import { verifyWebhookSignature, type WebhookEvent } from '@spree/sdk/webhooks';
import type { Order } from '@spree/sdk';
// Verify a webhook signature (works with any framework)
const isValid = verifyWebhookSignature(
rawBody, // raw request body string
request.headers.get('x-spree-webhook-signature')!, // HMAC signature
request.headers.get('x-spree-webhook-timestamp')!, // unix timestamp
process.env.SPREE_WEBHOOK_SECRET! // endpoint secret key
);
// Type your webhook handlers using SDK types
type OrderEvent = WebhookEvent<Order>;
function handleOrderCompleted(event: OrderEvent) {
const order = event.data; // fully typed Order
console.log(order.number, order.email, order.display_total);
}Webhook payloads use the same V3 serializers as the REST API, so all SDK types (Order, Cart, Payment, Fulfillment, etc.) work directly as the data field type.
For Next.js projects, the Spree Storefront includes a ready-made webhook route handler with signature verification and event routing.
Development
Setup
cd sdk
npm installScripts
| Command | Description |
|---------|-------------|
| npm test | Run tests once |
| npm run test:watch | Run tests in watch mode |
| npm run test:coverage | Run tests with coverage report |
| npm run typecheck | Type-check with tsc --noEmit |
| npm run build | Build CJS + ESM bundles with tsup |
| npm run dev | Build in watch mode |
| npm run console | Interactive REPL for testing the SDK |
Testing
Tests use Vitest with MSW (Mock Service Worker) for API mocking at the network level.
# Run all tests
npm test
# Run in watch mode during development
npm run test:watch
# Run with coverage
npm run test:coverageTest files live in tests/ and follow the structure:
tests/mocks/handlers.ts- MSW request handlers with fixture datatests/mocks/server.ts- MSW server instancetests/setup.ts- Server lifecycle (listen/reset/close)tests/helpers.ts-createTestClient()and constantstests/*.test.ts- Test suites per resource (auth, products, orders, etc.)
To add tests for a new endpoint, add an MSW handler in handlers.ts and create a corresponding test file.
Releasing
This package uses Changesets for version management and publishing.
After making changes:
npx changesetThis prompts you to select a semver bump type (patch/minor/major) and write a summary. A changeset file is created in .changeset/.
How releases work:
- Changeset files are committed with your PR
- When merged to
main, a GitHub Action creates a "Version Packages" PR that bumps the version and updates the CHANGELOG - When that PR is merged, the package is automatically published to npm
Manual release (if needed):
npm run version # Apply changesets and bump version
npm run release # Build and publish to npmLicense
MIT
