@storeos/storefront-client
v0.0.5
Published
TypeScript SDK for the StoreOS Storefront REST API (v1).
Readme
@storeos/storefront-client
The official TypeScript SDK for StoreOS storefronts. Browse products, manage customer accounts, and run checkout — with full types.
Two surfaces ship from the same package:
| Surface | Import | Transport |
| --------------- | ---------------------------------- | ------------------------------- |
| REST client | @storeos/storefront-client | Storefront REST API (fetch) |
| React SDK | @storeos/storefront-client/react | GraphQL + React providers/hooks |
Works anywhere fetch runs: React, Next.js, Remix, React Native, Cloudflare Workers, Node 18+.
Install
npm install @storeos/storefront-clientyarn add @storeos/storefront-clientpnpm add @storeos/storefront-clientbun add @storeos/storefront-clientFor the React SDK, also install peers:
npm install react react-dom @tanstack/react-query zod immer use-immerStoreos Core SDK
import { StoreFront } from '@storeos/storefront-client';
const store = new StoreFront({
tenant: 'your-store-id',
});
const { nodes: products, meta } = await store.getProducts({
page: 1,
limit: 24,
});
const product = await store.getProduct({ handle: 'blue-widget' });Configuration
const store = new StoreFront({
/** Your StoreOS store identifier */
tenant: 'your-store-id',
/** Optional — restore a session from storage */
accessToken: sessionToken,
/** Optional — custom fetch for tests or edge runtimes */
fetch: customFetch,
});Customer sessions
Sign-in methods automatically keep the session on the client. Persist the token in your app and restore it on load:
await store.login({
user: '[email protected]',
password: '••••••••',
});
// Save store.getAccessToken() to cookies, localStorage, etc.
store.setAccessToken(savedToken);
const me = await store.getMe();Phone OTP is supported too:
await store.sendOtp({ phoneNumber: '+8801…' });
await store.verifyOtp({ phoneNumber: '+8801…', otp: '123456' });What you can build
Catalog
await store.getTenant();
await store.getPublishedTheme();
await store.getProducts({ search: 'shirt', collectionIds: ['…'] });
await store.getProduct({ handle: 'linen-shirt' });
await store.getCollections();
await store.getCollection('collection-id');Checkout
const coupon = await store.verifyCoupon({
code: 'SAVE10',
lineItems: [{ productId: '…', quantity: 2 }],
});
const { order } = await store.createOrder({
lineItems: [{ productId: '…', quantity: 2 }],
shippingAddress: {
/* … */
},
paymentMethod: 'COD',
couponCode: coupon.valid ? 'SAVE10' : undefined,
cartId, // optional; marks that server cart CONVERTED
});Guest checkout works out of the box. If the API returns a token, the client picks it up so the customer can track the order immediately.
Cart
The server cart is the source of truth. The React provider mirrors it in localStorage and syncs with these methods. The client does not send an IP address. Storefront API records the request IP and an approximate location for the merchant console.
await store.upsertCart({
cartId: '…',
lineItems: [
{
productId: '…',
quantity: 1,
product: { name: 'Linen shirt', unitPrice: 1200 },
},
],
contact: { name: 'Ada', email: '[email protected]', phoneNumber: '+880…' },
});
await store.getCart(cartId);
await store.deleteCart(cartId);getCart returns 404 when the cart is missing or already converted. upsertCart returns null for an empty cart or a converted cart id. deleteCart leaves a converted cart in place.
Order history
const { nodes: orders } = await store.getMyOrders({ page: 1 });
await store.getOrder('order-id');
await store.cancelOrder('order-id', 'Changed my mind');Pagination
List methods return { nodes, meta }:
const { nodes, meta } = await store.getProducts({ page: 2, limit: 12 });
meta.totalCount;
meta.hasNextPage;
meta.totalPages;Supported query params: page, limit, sort, sortBy, search, and collectionIds (products).
Errors
Non-2xx responses throw StoreFrontApiError with status, message, and body:
import { StoreFront, StoreFrontApiError } from '@storeos/storefront-client';
try {
await store.getProduct({ handle: 'sold-out' });
} catch (error) {
if (error instanceof StoreFrontApiError) {
console.error(error.status, error.body);
}
}Types
Every method is fully typed. Import request and response shapes directly:
import type {
Product,
Collection,
Order,
StorefrontUser,
StorefrontCart,
UpsertCartInput,
CreateOrderInput,
PaginatedResponse,
} from '@storeos/storefront-client';API surface
| Area | Methods |
| --------------- | ------------------------------------------------------------------------------------------------- |
| Store | getTenant, getPublishedTheme |
| Products | getProducts, getProduct, getProductById, getProductByHandle |
| Collections | getCollections, getCollection |
| Auth | getMe, register, login, logout, updateProfile, changePassword, sendOtp, verifyOtp |
| Cart | upsertCart, getCart, deleteCart |
| Orders | getMyOrders, getOrder, createOrder, verifyCoupon, cancelOrder |
| Session | getAccessToken, setAccessToken |
React SDK
GraphQL-based providers, hooks, queries, validation, and Next.js server helpers for storefront UIs.
Entry points
| Import | Use for |
| ------------------------------------------ | ---------------------------------------------------------------- |
| @storeos/storefront-client/react | Client providers, hooks, GraphQL client, utils, checkout schemas |
| @storeos/storefront-client/react/server | Next.js server GraphQL client + getServerSession |
| @storeos/storefront-client/react/queries | Shared GraphQL query/mutation strings (RSC-safe) |
| @storeos/storefront-client/react/types | Enums and GraphQL-aligned types (RSC-safe) |
Setup
Wrap your app with React Query and StoreOSProvider (auth uses @tanstack/react-query):
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { StoreOSProvider } from '@storeos/storefront-client/react';
const queryClient = new QueryClient();
export function Providers({ children }: { children: React.ReactNode }) {
return (
<QueryClientProvider client={queryClient}>
<StoreOSProvider
config={{
apiUrl: 'https://storefront-api.storeos.dev',
tenant: 'your-store-id',
siteUrl: 'https://your-store.com',
// optional:
// auth: { cookieName: 'auth_token', cookieMaxAge: 60 * 60 * 24 * 30 },
// cart: { storageKey: 'cart' },
// currency: { code: 'BDT', locale: 'en-BD' },
}}
>
{children}
</StoreOSProvider>
</QueryClientProvider>
);
}StoreOSProvider initializes config and mounts auth + cart context.
Config options
| Field | Default | Description |
| --------------------------- | ------------------------------------ | ------------------------------- |
| apiUrl | https://storefront-api.storeos.dev | Storefront GraphQL base |
| tenant | — (required) | Store identifier (x-tenant) |
| siteUrl | "" | Public site URL helpers |
| staticFileBaseUrl | https://cdn.storeos.dev | CDN base for media |
| auth.cookieName | auth_token | Session cookie name |
| auth.cookieMaxAge | 30 days | Cookie max-age (seconds) |
| auth.tokenPollingInterval | 2000 | Client token poll interval (ms) |
| cart.storageKey | cart | localStorage key for cart |
| currency.code / locale | BDT / en-BD | Money formatting defaults |
Auth
import { useAuth, useSession } from '@storeos/storefront-client/react';
function AccountMenu() {
const { session, login, logout, loading } = useAuth();
// or: const session = useSession();
if (loading) return null;
if (!session.user) {
return (
<button
onClick={() =>
login({ user: '[email protected]', password: '••••••••' })
}
>
Sign in
</button>
);
}
return (
<div>
<span>{session.user.name ?? session.user.email}</span>
<button onClick={() => logout()}>Sign out</button>
</div>
);
}Token helpers (cookie-backed):
import {
getAuthToken,
setCookieAuthToken,
removeCookieAuthToken,
} from '@storeos/storefront-client/react';Cart
useCart keeps the same line-item API. It also keeps a cartId in the storeos__cid_{tenant} cookie, mirrors lines in localStorage, and syncs them to POST /api/v1/carts. A refresh loads the server cart. ?c={cartId} replaces the current id and drops the query. Checkout should pass cart.cartId to createOrder.
import { useCart } from '@storeos/storefront-client/react';
import type { CartLineItem } from '@storeos/storefront-client/react';
function AddToCartButton({ item }: { item: CartLineItem }) {
const { addItem, totalItems, cartId } = useCart();
return (
<button onClick={() => addItem(item)}>
Add to cart ({totalItems}) {cartId}
</button>
);
}| API | Description |
| --------------------------------------------------------- | ------------------------------------------------ |
| cartId | Server cart id for this browser |
| lineItems | Current cart lines |
| addItem / removeItem / adjustQuantity / clearCart | Mutate cart and sync to the server |
| subTotalAmount / totalItems | Derived totals |
| cartDrawerOpen / setCartDrawerOpen | Drawer UI state |
GraphQL (client)
import { useQuery } from '@tanstack/react-query';
import { gqlClient } from '@storeos/storefront-client/react';
import { PRODUCTS_QUERY } from '@storeos/storefront-client/react/queries';
function ProductGrid() {
const { data } = useQuery({
queryKey: ['products'],
queryFn: () =>
gqlClient<{ products: { nodes: unknown[] } }>({
query: PRODUCTS_QUERY,
variables: { page: 1, limit: 24 },
}),
});
return /* render data.products.nodes */;
}Auth token and x-tenant are attached automatically from config / cookies.
Next.js server
import {
getServerSession,
gqlServerClient,
} from '@storeos/storefront-client/react/server';
import { PRODUCTS_QUERY } from '@storeos/storefront-client/react/queries';
import { initStoreOS } from '@storeos/storefront-client/react';
// Call once in a server entry (layout/provider) before using server helpers
initStoreOS({
apiUrl: process.env.STOREOS_API_URL!,
tenant: process.env.STOREOS_TENANT!,
});
export default async function Page() {
const session = await getServerSession();
const data = await gqlServerClient({
query: PRODUCTS_QUERY,
variables: { page: 1, limit: 12 },
});
return /* … */;
}getServerSession reads the auth cookie and resolves me via GraphQL.
Variant selection
import { useVariantSelection } from '@storeos/storefront-client/react';
function VariantPicker({ product }) {
const {
selectedAttributes,
setSelectedAttributes,
selectedVariant,
isOptionAvailable,
} = useVariantSelection({
variants: product.variants,
variantConfigs: product.variantConfigs,
productId: product._id,
});
// Only valid combinations are stored; impossible option combos are normalized away.
}Checkout validation
Zod schemas for guest vs logged-in checkout:
import {
checkoutFormSchema,
loggedInCheckoutFormSchema,
getCheckoutFormSchema,
} from '@storeos/storefront-client/react';
import type { CheckoutFormData } from '@storeos/storefront-client/react';
const schema = getCheckoutFormSchema(isLoggedIn);
const parsed = schema.parse(formValues);Utilities
import {
formatCurrency,
formatAmount,
getFileUrl,
createUrl,
getSiteUrl,
getDeliveryCharge,
calculateOrderTotal,
getDeliveryAreaDisplayName,
getVariantFromSelection,
isVariantOptionAvailable,
} from '@storeos/storefront-client/react';Queries & types
Import GraphQL documents and shared enums/types without pulling in client-only code:
import {
ME_QUERY,
LOGIN_MUTATION,
PRODUCTS_QUERY,
PRODUCT_BY_ID_QUERY,
SEARCH_PRODUCTS_QUERY,
COLLECTIONS_QUERY,
CREATE_ORDER_MUTATION,
MY_ORDERS_QUERY,
ORDER_DETAILS_QUERY,
CANCEL_ORDER_MUTATION,
VERIFY_COUPON_MUTATION,
STOREFRONT_QUERY,
} from '@storeos/storefront-client/react/queries';
import {
DeliveryArea,
Invoice_Payment_Method,
Invoice_Status,
ProductStatus,
} from '@storeos/storefront-client/react/types';React surface
| Area | Exports |
| ------------------- | ------------------------------------------------------------------------------ |
| Provider | StoreOSProvider, initStoreOS, getConfig |
| Auth | useAuth, useSession, StoreOSAuthProvider, cookie token helpers |
| Cart | useCart, StoreOSCartProvider |
| GraphQL | gqlClient, gql (client); gqlServerClient, getServerSession (server) |
| Variants | useVariantSelection |
| Validation | checkoutFormSchema, loggedInCheckoutFormSchema, getCheckoutFormSchema, … |
| Utils | currency, URLs, delivery, variant helpers |
| Queries / types | @storeos/storefront-client/react/queries, …/react/types |
License
Private / see storeos.dev for terms.
