Spree TypeScript SDK (@spree/sdk)
Before writing code
Fetch live docs:
- Fetch https://spreecommerce.org/docs/developer/sdk/quickstart for the SDK's current API surface.
- Inspect the SDK's source / README on GitHub (likely under https://github.com/spree/sdk or as part of the
storefrontrepo) for resource names and parameter shapes. - Check the npm registry page for
@spree/sdkfor the current version aligned to your Spree backend release. - Read the v5.4 announcement (https://spreecommerce.org/announcing-spree-commerce-5-4/) for the SDK's release context.
- For the Next.js usage pattern, fetch https://spreecommerce.org/docs/developer/storefront/nextjs/quickstart.
Conceptual Architecture
Why an SDK?
Hand-writing API v3 clients works, but @spree/sdk gives you:
- TypeScript types generated against the same OpenAPI spec the backend ships
- Zod validation of responses so runtime drift is caught
- Resource-builder pattern (
spree.products.list(...)) that mirrors the API - Auth handling — token storage, refresh, cookies
- Idempotency-key plumbing for safe retries
- Error mapping from HTTP responses to typed exceptions
Installation
npm install @spree/sdk
# or
pnpm add @spree/sdk
Pin to the version matching your backend Spree release:
{ "dependencies": { "@spree/sdk": "5.4.x" } }
When upgrading Spree, bump both together.
Client Initialization
import { createSpreeClient } from '@spree/sdk';
const spree = createSpreeClient({
apiUrl: process.env.SPREE_API_URL!, // https://your-spree.com
publishableKey: process.env.NEXT_PUBLIC_SPREE_PUBLISHABLE_KEY!, // pk_live_…
// For admin calls (server-side only):
// adminApiKey: process.env.SPREE_ADMIN_API_KEY,
});
(Verify the actual factory name and option shape against the live @spree/sdk docs.)
Resource Builder Pattern
The SDK exposes resources mirroring the API:
// Read
const products = await spree.products.list({ limit: 20, expand: ['default_variant', 'images'] });
const product = await spree.products.find('prod_…', { expand: ['variants'] });
const taxons = await spree.taxons.list();
// Cart
const cart = await spree.cart.create();
const cart2 = await spree.cart.addItem({ variantId: 'var_…', quantity: 1 });
// Checkout
await spree.checkout.update({ email: 'guest@example.com', shippingAddress: { ... } });
const session = await spree.checkout.createPaymentSession({ paymentMethodId: 'pm_…' });
const order = await spree.checkout.complete();
// Customer
await spree.auth.signIn({ email, password });
const me = await spree.account.find();
// Admin (server-side only)
await spree.admin.orders.update('ord_…', { internalNote: '...' });
Method names are illustrative — verify against live SDK docs.
Zod Schemas
Responses are parsed through Zod schemas:
import { ProductSchema } from '@spree/sdk/schemas';
const product = await spree.products.find('prod_…');
// product is fully typed and validated; runtime mismatches throw
This catches API shape drift (e.g., your backend is on 5.4.1 but the SDK expects 5.4.3 conventions).
Server-Only Auth Pattern
Never expose the admin API key to the browser. In Next.js:
// app/api/account/route.ts (Server Action / Route Handler)
import { cookies } from 'next/headers';
import { createSpreeClient } from '@spree/sdk';
export async function GET() {
const token = cookies().get('spree_jwt')?.value;
const spree = createSpreeClient({
apiUrl: process.env.SPREE_API_URL!,
publishableKey: process.env.NEXT_PUBLIC_SPREE_PUBLISHABLE_KEY!,
userToken: token,
});
const me = await spree.account.find();
return Response.json(me);
}
User JWTs go in httpOnly cookies set server-side. Cart tokens go in httpOnly cookies. Public publishable key (pk_…) is fine in browser code (that's its purpose).
Idempotency Keys
const order = await spree.checkout.complete({
idempotencyKey: crypto.randomUUID(),
});
Generate per logical operation; resend on transient failure to avoid double-charging.
Error Handling
import { SpreeError, ValidationError } from '@spree/sdk';
try {
await spree.cart.addItem({ variantId: 'var_…', quantity: 99 });
} catch (err) {
if (err instanceof ValidationError) {
err.fieldErrors; // { quantity: 'exceeds inventory' }
} else if (err instanceof SpreeError) {
err.code; // e.g. 'invalid_variant'
err.requestId; // for support
}
}
Typing Customizations
If your Spree backend exposes custom Metafields or extension fields, extend the SDK's types via module augmentation:
// types/spree.d.ts
declare module '@spree/sdk' {
interface Product {
metafields: {
my_app: {
launch_date?: string;
editor_pick?: boolean;
};
};
}
}
Version Pinning
The SDK is generated against a specific OpenAPI spec version. Mismatches cause Zod validation failures. Strategy:
- Pin
@spree/sdkto the exact backend Spree minor (5.4.x). - Upgrade in lockstep with backend deploys.
- CI: run a smoke test against your staging backend before promoting.
Implementation Guidance
Recommended Project Layout (Next.js)
src/
├── lib/
│ ├── spree.server.ts # createSpreeClient with adminApiKey — server-only
│ └── spree.public.ts # createSpreeClient with publishableKey — safe for browser
├── app/
│ ├── (storefront)/ # public pages
│ └── api/ # Server Actions / Route Handlers
└── types/
└── spree.d.ts # type augmentations
Never import spree.server.ts from a Client Component — Next.js will yell, but it's also a credential leak.
Caching API Responses
Use Next.js fetch cache or React cache():
import { cache } from 'react';
export const getProducts = cache(async () => {
return await spree.products.list({ limit: 20 });
});
For ISR / revalidate:
const products = await spree.products.list({}, { next: { revalidate: 60 } });
(Verify whether @spree/sdk exposes fetch options pass-through.)
Webhook Verification Sidekick
The SDK may expose webhook signature verification helpers:
import { verifyWebhookSignature } from '@spree/sdk/webhooks';
const event = verifyWebhookSignature({
payload: rawBody,
signature: request.headers['x-spree-webhook-signature'],
secret: process.env.SPREE_WEBHOOK_SECRET!,
});
Verify in the live SDK docs.
Pagination
let cursor: string | undefined;
do {
const page = await spree.products.list({ limit: 100, starting_after: cursor });
for (const product of page.data) { /* process */ }
cursor = page.has_more ? page.data[page.data.length - 1].id : undefined;
} while (cursor);
Common Pitfalls
- Importing the admin client from a Client Component — exposes the admin key.
- Pinning to
^5.4.0instead of5.4.x— minor releases can change generated schema shapes. - Storing JWT in localStorage — XSS-vulnerable; use httpOnly cookies.
- Not awaiting Zod parse errors — silent stringy responses break runtime.
- Using the SDK in browser code with admin key — Next.js' server-only enforcement is your friend; opt into it.
- Ignoring Idempotency-Key — duplicate checkouts on retry.
Always verify resource names, method signatures, and option shapes against the live @spree/sdk README and the SDK's TypeScript declarations — the SDK is the youngest part of the Spree ecosystem and is iterating fast.