JavaScript SDK
@shopfast-sdk/client for browsers and servers: the storefront client, the admin client and webhook verification.
@shopfast-sdk/client is the typed client for the Storefront API, the REST API and
webhooks. It has no dependencies and runs in browsers, Node 20+, Bun, Deno, edge runtimes and Next.js.
npm install @shopfast-sdk/client
Types are generated from the same contract the API validates against, and a schema snapshot is checked on every change to ShopFast, so an upgrade of the SDK never surprises you.
The storefront client
import { createStorefrontClient } from '@shopfast-sdk/client';
const shop = createStorefrontClient({
shop: 'your-store', // your handle, or the st_ id
key: 'sfpk_live_…', // a publishable key in browsers; a private key (sfsk_…) on your server
});
const info = await shop.shop.get(); // name, currency, status, SEO, links
const page = await shop.products.list({ collection: 'staff-picks', limit: 24 });
const product = await shop.products.get('nimbus-vpn');
const live = await shop.products.availability('nimbus-vpn'); // never cached
const found = await shop.search.query({ q: 'vpn' });
| Area | Methods |
|---|---|
shop |
get, policies |
pages, reviews |
list, get · list |
products |
list, iterate (every page), get, availability, reviews, recommendations |
collections, categories |
list, get (with a page of products) |
search |
query, suggest |
cart |
create, get, addLines, updateLine, update, applyCode, removeCode, checkoutOptions, checkout, checkoutLink |
orders |
get(sessionId, access) |
customer |
requestCode, signIn, signOut, get, orders, library, cancelSubscription, wallet |
support |
open, list, get, reply |
Lists
Lists answer { items, total, nextCursor }. Pass nextCursor back as after, or let iterate walk every page:
for await (const product of shop.products.iterate({ category: 'software' })) {
console.log(product.title, product.priceMinor);
}
Cart and checkout
const cart = await shop.cart.create({ lines: [{ productId: product.id, variantId: product.variants[0]?.id ?? null, quantity: 1 }] });
await shop.cart.applyCode(cart.id, 'LAUNCH10');
// Your own checkout page: what to ask for this cart, then start it and send the buyer to pay.
const options = await shop.cart.checkoutOptions(cart.id, { country: 'DE' });
try {
const session = await shop.cart.checkout(cart.id, {
email, name, country: 'DE', consent: true, provider: options.methods[0]?.provider ?? null, method: options.methods[0]?.method ?? null,
returnUrl: 'https://shop.example.com/checkout/complete',
});
redirect(session.paid ? `/orders/${session.sessionId}` : session.payUrl);
} catch (error) {
if (isShopFastError(error) && error.is('email-verification-required')) askForTheCode();
else throw error;
}
// Or the hosted checkout, back to your site after paying.
const { checkoutUrl } = await shop.cart.checkoutLink(cart.id, { returnUrl: 'https://shop.example.com/checkout/complete' });
See Headless checkout for your own checkout, the embedded one and the hosted one.
A signed-in buyer
await shop.customer.requestCode('[email protected]');
const session = await shop.customer.signIn('[email protected]', '123456');
const buyer = shop.withCustomer(session.token);
const library = await buyer.customer.library(); // keys, downloads, subscriptions
On your server
With a private key, pass the buyer’s address so limits and fraud checks see the buyer:
const shop = createStorefrontClient({ shop: 'your-store', key: process.env.SHOPFAST_STOREFRONT_KEY!, buyerIp: request.headers.get('x-forwarded-for') });
Errors, retries and caching
- Errors throw
ShopFastErrorwith the problem fields:status,type,title,detail,fieldsandretryAfter.isShopFastError(e)narrows it. - Reads retry on
429and503, honoringRetry-After. Writes retry only when you pass an idempotency key. cache: truekeeps shared answers with theirETagand revalidates them for free (304). Carts, accounts and orders are never kept.
Next.js
import { createNextStorefrontClient } from '@shopfast-sdk/client/next';
export const shop = createNextStorefrontClient({ shop: 'your-store', key: process.env.SHOPFAST_STOREFRONT_KEY!, revalidate: false });
Every fetch carries next: { tags } for your shop, so the storefront.updated webhook
refreshes exactly what changed:
// app/api/revalidate/route.ts
import { revalidateTag } from 'next/cache';
import { handleRevalidationWebhook } from '@shopfast-sdk/client/next';
export const POST = (request: Request) => handleRevalidationWebhook(request, { secret: process.env.SHOPFAST_WEBHOOK_SECRET!, revalidateTag });
The Next.js starter puts all of it together.
Webhooks
import { verifyWebhook } from '@shopfast-sdk/client';
const event = await verifyWebhook({ secret: process.env.SHOPFAST_WEBHOOK_SECRET!, headers: request.headers, body: await request.text() });
// throws WebhookVerificationError on a bad signature or a delivery older than 5 minutes
Pass two secrets during a rotation. The body must be the raw text, exactly as received.
The admin client
For your own store’s REST API with an API key or an OAuth token:
import { createAdminClient } from '@shopfast-sdk/client';
const admin = createAdminClient({ apiKey: process.env.SHOPFAST_API_KEY! });
const orders = await admin.request('GET', '/v1/store/orders', { query: { tab: 'paid', limit: 20 } });
await admin.request('POST', '/v1/store/products', { body: { name: 'Launch Pack', type: 'service', priceMinor: 2500 } });
Every write carries an Idempotency-Key (a fresh one unless you pass yours), so a retry after a timeout never runs
twice. Pass parse (a zod schema’s parse, say) to get your own type back.