@sdg.la/wishlist-stack-sdk
v0.10.1
Published
TypeScript SDK for the Wishlist Stack API
Downloads
347
Readme
@sdg.la/wishlist-stack-sdk
TypeScript/JavaScript SDK for the Wishlist Stack API.
Table of Contents
- Features
- Requirements
- Installation
- Quick Start
- Usage
- Error Handling
- Pagination
- API Reference
- TypeScript Types
- Related Packages
- License
Features
- TypeScript-first — Full type definitions with exported types for all requests and responses
- Universal — Works in Node.js, browsers, and edge runtimes (Cloudflare Workers, Deno, etc.)
- Flexible API — Choose between functional
createWishlistStackClient()or class-basednew WishlistStackClient() - Built-in error handling — Structured
WishlistStackApiErrorwith status codes and API error messages - Pagination support — All list endpoints support
pageandpageSizeparameters
Requirements
- Node.js 18+ (uses native
fetch) or provide a customfetchimplementation - Modern browsers with native
fetchsupport
Installation
npm install @sdg.la/wishlist-stack-sdkyarn add @sdg.la/wishlist-stack-sdkpnpm add @sdg.la/wishlist-stack-sdkQuick Start
import { createWishlistStackClient } from '@sdg.la/wishlist-stack-sdk';
const client = createWishlistStackClient({
apiKey: 'your-merchant-api-key',
customerAccessToken: 'customer-access-token', // required for authenticated endpoints
});
// Fetch all groups
const { groups } = await client.groups.getAll();
// Fetch all lists
const { lists } = await client.lists.getAll();Usage
Server / Node.js / Workers
import { createWishlistStackClient } from '@sdg.la/wishlist-stack-sdk';
const client = createWishlistStackClient({
apiKey: process.env.WISHLIST_STACK_API_KEY!,
baseUrl: process.env.WISHLIST_STACK_BASE_URL, // optional, defaults to production
customerAccessToken: '...customer_access_token...',
});
const { groups } = await client.groups.getAll();Browser
import { createWishlistStackClient } from '@sdg.la/wishlist-stack-sdk';
const client = createWishlistStackClient({
apiKey: 'YOUR_API_KEY',
baseUrl: 'https://wishlist.devteam.run', // optional default
customerAccessToken: '...optional...',
});
const { lists } = await client.lists.getAll();Note: If you use a strict Content Security Policy (CSP), you must allow your API domain in
connect-src.
Class API
If you prefer a class-based API, use WishlistStackSDK:
import { WishlistStackSDK } from '@sdg.la/wishlist-stack-sdk';
const client = new WishlistStackSDK({
apiKey: process.env.WISHLIST_STACK_API_KEY!,
baseUrl: process.env.WISHLIST_STACK_BASE_URL,
customerAccessToken: '...optional...',
});
await client.groups.getAll();
await client.lists.getById('list-id');Back-compat aliases are available on the class:
await client.getGroups(); // alias for client.groups.getAll()
await client.getGroupById('id'); // alias for client.groups.getById('id')
await client.getLists(); // alias for client.lists.getAll()Error Handling
Non-2xx responses throw WishlistStackApiError. Use the isWishlistStackApiError type guard for safe handling:
import { isWishlistStackApiError } from '@sdg.la/wishlist-stack-sdk';
try {
await client.groups.getAll();
} catch (error) {
if (isWishlistStackApiError(error)) {
console.log(error.status); // HTTP status code
console.log(error.apiErrors); // Array of { message, field? }
console.log(error.apiErrorMessages); // Array of error message strings
console.log(error.requestId); // Request ID for debugging
}
}Pagination
Endpoints that return lists support pagination via query parameters:
// Paginate groups
await client.groups.getAll({ page: 2, pageSize: 10 });
// Paginate lists within a group
await client.groups.getById('group-id', { page: 1, pageSize: 25 });
// Paginate items within a list
await client.lists.getById('list-id', { page: 1, pageSize: 25 });Pagination response structure:
{
page: number;
pageSize: number;
totalCount: number;
totalPages: number;
}API Reference
Groups
groups.getAll(query?)
Fetch all groups for the authenticated customer.
- Endpoint:
GET /api/groups - Parameters:
query?—{ page?: number; pageSize?: number, query?: string } - Returns:
Promise<GetGroupsResponse>
const { groups, pagination } = await client.groups.getAll({ page: 1, pageSize: 10, query: 'holiday' });{
"groups": [
{
"id": "cml8drad90001js09weonjojg",
"name": "Holiday Lists",
"description": "All my holiday wishlists",
"position": 1,
"shared": false,
"listCount": 2,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-18T12:00:00.000Z",
"featuredItems": [
{
"variantId": 46932429275374,
"image": {
"altText": "Product image",
"url": "https://cdn.shopify.com/s/files/1/example.jpg",
"width": 800,
"height": 800
}
}
]
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"totalCount": 1,
"totalPages": 1
}
}groups.getById(groupId, query?)
Fetch a single group by ID. Lists within the group are paginated.
- Endpoint:
GET /api/groups/{groupId} - Parameters:
groupId—stringquery?—{ page?: number; pageSize?: number, query?: string }
- Returns:
Promise<GetGroupResponse>
const group = await client.groups.getById('group-id', { page: 1, pageSize: 25, query: 'gift' });{
"id": "cml8drad90001js09weonjojg",
"name": "Holiday Lists",
"description": "All my holiday wishlists",
"position": 1,
"shared": false,
"listCount": 1,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-18T12:00:00.000Z",
"lists": [
{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": "Birthday gift ideas",
"position": 1,
"shared": false,
"itemCount": 2,
"featuredItems": [
{
"variantId": 46932429275374,
"image": {
"altText": "Product image",
"url": "https://cdn.shopify.com/s/files/1/example.jpg",
"width": 800,
"height": 800
}
}
]
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"totalCount": 1,
"totalPages": 1
}
}groups.create(body)
Create a new group.
- Endpoint:
POST /api/groups - Parameters:
body—{ name?: string; description?: string } - Returns:
Promise<CreateGroupResponse>
const group = await client.groups.create({ name: 'Favorites', description: 'My favorite items' });{
"id": "cml8drad90001js09weonjojg",
"name": "Favorites",
"description": "My favorite items",
"position": 1,
"shared": false,
"listCount": 0,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-10T08:00:00.000Z"
}groups.update(groupId, body)
Update a group's name or description.
- Endpoint:
PATCH /api/groups/{groupId} - Parameters:
groupId—stringbody—{ name?: string; description?: string }
- Returns:
Promise<UpdateGroupDetailsResponse>
await client.groups.update('group-id', { name: 'Renamed Group' });{
"id": "cml8drad90001js09weonjojg",
"name": "Renamed Group",
"description": "All my holiday wishlists",
"position": 1,
"shared": false,
"listCount": 2,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}groups.remove(groupId)
Remove a group. Lists assigned to the group are unassigned, not deleted.
- Endpoint:
DELETE /api/groups/{groupId} - Parameters:
groupId—string - Returns:
Promise<RemoveGroupResponse>
await client.groups.remove('group-id');{ "ok": true }groups.reorder(groupId, body)
Reorder lists within a group.
- Endpoint:
POST /api/groups/{groupId}/reorder - Parameters:
groupId—stringbody—{ listIds?: string[] }
- Returns:
Promise<ReorderGroupResponse>
await client.groups.reorder('group-id', { listIds: ['list-3', 'list-1', 'list-2'] });groups.share(groupId)
Mark a group as shared (public read-only). Also shares all lists within the group.
- Endpoint:
POST /api/groups/{groupId}/share - Parameters:
groupId—string - Returns:
Promise<MarkGroupSharedResponse>
await client.groups.share('group-id');{
"id": "cmlbe49qk0001ju094x1ya9co",
"name": "Holiday Lists",
"description": "All my holiday wishlists",
"position": 1,
"shared": true,
"listCount": 2,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}groups.unshare(groupId)
Revoke sharing for a group and all of its lists.
- Endpoint:
DELETE /api/groups/{groupId}/share - Parameters:
groupId—string - Returns:
Promise<RevokeGroupSharedResponse>
await client.groups.unshare('group-id');{
"id": "cmlbe49qk0001ju094x1ya9co",
"name": "Holiday Lists",
"description": "All my holiday wishlists",
"position": 1,
"shared": false,
"listCount": 2,
"createdAt": "2025-01-10T08:00:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}Lists
lists.getAll(query?)
Fetch all lists for the authenticated customer.
- Endpoint:
GET /api/lists - Parameters:
query?—{ page?: number; pageSize?: number, query?: string } - Returns:
Promise<GetListsResponse>
const { lists, pagination } = await client.lists.getAll({ page: 1, pageSize: 10 });{
"lists": [
{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": "Birthday gift ideas",
"position": 1,
"shared": false,
"itemCount": 3,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-20T14:00:00.000Z",
"featuredProducts": [
{
"variantId": 46932429275374,
"image": {
"altText": "Product image",
"url": "https://cdn.shopify.com/s/files/1/example.jpg",
"width": 800,
"height": 800
}
}
]
}
],
"pagination": {
"page": 1,
"pageSize": 10,
"totalCount": 1,
"totalPages": 1
}
}lists.getById(listId, query?)
Fetch a single list by ID. Items are hydrated with Shopify product data and paginated.
- Endpoint:
GET /api/lists/{listId} - Parameters:
listId—stringquery?—{ page?: number; pageSize?: number }
- Returns:
Promise<GetListResponse>
const list = await client.lists.getById('list-id', { page: 1, pageSize: 25 });{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": "Birthday gift ideas",
"position": 1,
"shared": false,
"itemCount": 1,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-20T14:00:00.000Z",
"items": [
{
"id": "cml8dveoj0002jf09vsqw10r2",
"userNote": "Love this one",
"quantity": 1,
"position": 1,
"product": {
"id": "gid://shopify/Product/123456789",
"title": "Classic T-Shirt",
"description": "A comfortable classic t-shirt",
"availableForSale": true,
"handle": "classic-t-shirt",
"productType": "Apparel",
"category": null,
"tags": ["summer", "casual"],
"onlineStoreUrl": "https://example.myshopify.com/products/classic-t-shirt",
"metafields": [],
"variant": {
"id": "gid://shopify/ProductVariant/46932429275374",
"title": "Medium / Blue",
"sku": "TSHIRT-M-BLUE",
"price": { "amount": "29.99", "currencyCode": "USD" },
"compareAtPrice": null,
"availableForSale": true,
"currentlyNotInStock": false,
"selectedOptions": [
{ "name": "Size", "value": "Medium" },
{ "name": "Color", "value": "Blue" }
],
"image": {
"altText": "Classic T-Shirt in Blue",
"url": "https://cdn.shopify.com/s/files/1/example.jpg",
"width": 800,
"height": 800
},
"metafields": []
},
"vendor": "Example Brand"
},
"properties": null
}
]
}lists.create(body)
Create a new list, optionally assigned to a group.
- Endpoint:
POST /api/lists - Parameters:
body—{ name?: string; description?: string; groupId?: string } - Returns:
Promise<CreateListResponse>
const list = await client.lists.create({ name: 'Gift Ideas', groupId: 'group-id' });{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": null,
"position": 1,
"shared": false,
"itemCount": 0,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-15T10:30:00.000Z"
}lists.update(listId, body)
Update a list's name, description, or group assignment.
- Endpoint:
PATCH /api/lists/{listId} - Parameters:
listId—stringbody—{ name?: string; description?: string; groupId?: string }
- Returns:
Promise<UpdateListDetailsResponse>
await client.lists.update('list-id', { name: 'Renamed List' });{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Renamed List",
"description": "Birthday gift ideas",
"position": 1,
"shared": false,
"itemCount": 3,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}lists.remove(listId)
Remove a list and all of its items.
- Endpoint:
DELETE /api/lists/{listId} - Parameters:
listId—string - Returns:
Promise<RemoveListResponse>
await client.lists.remove('list-id');{ "ok": true }lists.addItems(listId, body)
Add one or more items to a list.
- Endpoint:
POST /api/lists/{listId}/add - Parameters:
listId—stringbody—AddItemsToListBody
type AddItemsToListBody = {
items?: Array<{
variantId?: string;
quantity?: number;
note?: string;
properties?: Record<string, unknown> | null;
}>;
};- Returns:
Promise<AddItemsToListResponse>— the updated list with hydrated items (same shape asgetById)
await client.lists.addItems('list-id', {
items: [
{ variantId: 'gid://shopify/ProductVariant/123', quantity: 1 },
{ variantId: 'gid://shopify/ProductVariant/456', note: 'Size M' },
],
});lists.updateItem(listId, itemId, body)
Update an item on a list (variant, quantity, note, position, or custom properties). When variantId is provided, the item's product data is re-fetched from Shopify to reflect the new variant.
- Endpoint:
PATCH /api/lists/{listId}/items/{itemId} - Parameters:
listId—stringitemId—stringbody—{ variantId?: string; quantity?: number; note?: string; position?: number; properties?: Record<string, unknown> | null }
- Returns:
Promise<UpdateListItemResponse>— the updated hydrated item
await client.lists.updateItem('list-id', 'item-id', { quantity: 3, note: 'Updated note' });
// Change the variant stored on the item
await client.lists.updateItem('list-id', 'item-id', { variantId: '46932429275374' });{
"id": "cml8dveoj0002jf09vsqw10r2",
"userNote": "Updated note",
"quantity": 3,
"position": 1,
"product": {
"id": "gid://shopify/Product/123456789",
"title": "Classic T-Shirt",
"description": "A comfortable classic t-shirt",
"availableForSale": true,
"handle": "classic-t-shirt",
"productType": "Apparel",
"category": null,
"tags": ["summer", "casual"],
"onlineStoreUrl": "https://example.myshopify.com/products/classic-t-shirt",
"metafields": [],
"variant": {
"id": "gid://shopify/ProductVariant/46932429275374",
"title": "Medium / Blue",
"sku": "TSHIRT-M-BLUE",
"price": { "amount": "29.99", "currencyCode": "USD" },
"compareAtPrice": null,
"availableForSale": true,
"currentlyNotInStock": false,
"selectedOptions": [
{ "name": "Size", "value": "Medium" },
{ "name": "Color", "value": "Blue" }
],
"image": {
"altText": "Classic T-Shirt in Blue",
"url": "https://cdn.shopify.com/s/files/1/example.jpg",
"width": 800,
"height": 800
},
"metafields": []
},
"vendor": "Example Brand"
},
"properties": null
}lists.removeItem(listId, itemId)
Remove an item from a list.
- Endpoint:
DELETE /api/lists/{listId}/items/{itemId} - Parameters:
listId—stringitemId—string
- Returns:
Promise<RemoveItemFromListResponse>
await client.lists.removeItem('list-id', 'item-id');{ "ok": true }lists.reorderItems(listId, body)
Reorder items in a list. You can submit all items or just a subset. Each submitted item specifies its ID and desired 1-based position. Items not included in the request keep their relative order and fill the remaining slots.
- Endpoint:
POST /api/lists/{listId}/reorder - Parameters:
listId—stringbody—{ items: Array<{ id: string; position: number }> }
- Returns:
Promise<ReorderListItemsResponse>
await client.lists.reorderItems('list-id', {
items: [
{ id: 'item-3', position: 1 },
{ id: 'item-1', position: 2 },
{ id: 'item-2', position: 3 },
],
});{
"updated": [
{ "id": "item-3", "position": 1 },
{ "id": "item-1", "position": 2 },
{ "id": "item-2", "position": 3 }
]
}lists.share(listId)
Mark a list as shared (public read-only).
- Endpoint:
POST /api/lists/{listId}/share - Parameters:
listId—string - Returns:
Promise<MarkListSharedResponse>
await client.lists.share('list-id');{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": "Birthday gift ideas",
"position": 1,
"shared": true,
"itemCount": 3,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}lists.unshare(listId)
Revoke sharing for a list.
- Endpoint:
DELETE /api/lists/{listId}/share - Parameters:
listId—string - Returns:
Promise<RevokeListSharedResponse>
await client.lists.unshare('list-id');{
"id": "cml8drg8x0003js093j4ua6p8",
"name": "Gift Ideas",
"description": "Birthday gift ideas",
"position": 1,
"shared": false,
"itemCount": 3,
"createdAt": "2025-01-15T10:30:00.000Z",
"updatedAt": "2025-01-22T09:15:00.000Z"
}Shared
Public read-only endpoints for accessing shared lists and groups. These require only the merchant apiKey — no customer access token needed.
shared.getSharedList(listId)
Fetch a public shared list with hydrated product details.
- Endpoint:
GET /api/shared/list/{listId} - Parameters:
listId—string - Returns:
Promise<GetSharedListResponse>— same response shape aslists.getById
const list = await client.shared.getSharedList('list-id');shared.getSharedGroup(groupId)
Fetch a public shared group and its lists.
- Endpoint:
GET /api/shared/group/{groupId} - Parameters:
groupId—string - Returns:
Promise<GetSharedGroupResponse>— same response shape asgroups.getById
const group = await client.shared.getSharedGroup('group-id');TypeScript Types
The package exports all request, response, and domain types. Import what you need:
import type {
// Client
CreateWishlistStackClientOptions,
WishlistStackClient,
// Errors
WishlistStackApiError,
WishlistStackApiErrorDetails,
// Groups
GetGroupsResponse,
GetGroupResponse,
GroupSummary,
GroupDetail,
GroupDetailList,
CreateGroupBody,
UpdateGroupBody,
ReorderGroupBody,
// Lists
GetListsResponse,
GetListResponse,
ListSummary,
ListDetail,
CreateListBody,
UpdateListBody,
AddItemsToListBody,
UpdateListItemBody,
ReorderListItemsBody,
ReorderListItemsResponse,
// Shared
GetSharedListResponse,
GetSharedGroupResponse,
// Items & Products
HydratedWishlistItem,
HydratedProduct,
HydratedVariant,
Money,
SelectedOption,
Metafield,
// Common
Pagination,
PaginationParams,
OkResponse,
Image,
FeaturedItem,
} from '@sdg.la/wishlist-stack-sdk';Domain Types
Below are the key domain types returned by the SDK. Expand any section to see the full shape.
type Pagination = {
page: number;
pageSize: number;
totalCount: number;
totalPages: number;
};type GroupSummary = {
id: string;
name: string;
description: string | null;
position: number;
shared: boolean;
listCount: number;
featuredItems: FeaturedItem[];
createdAt: string;
updatedAt: string;
};type GroupDetail = {
id: string;
name: string;
description: string | null;
position: number;
shared: boolean;
listCount: number;
createdAt: string;
updatedAt: string;
listsCount: number;
lists: GroupDetailList[];
pagination: Pagination;
};
type GroupDetailList = {
id: string;
position: number;
name: string;
description: string | null;
isShared: boolean;
itemCount: number;
items: string[];
featuredItems: FeaturedItem[];
};type ListSummary = {
id: string;
name: string;
description: string | null;
position: number;
shared: boolean;
itemCount: number;
createdAt: string;
updatedAt: string;
featuredProducts?: FeaturedItem[];
};type ListDetail = {
id: string;
name: string;
description: string | null;
position: number;
shared: boolean;
itemCount: number;
createdAt: string;
updatedAt: string;
items: HydratedWishlistItem[];
};type HydratedWishlistItem = {
id: string;
userNote?: string | null;
quantity: number;
position: number;
product: HydratedProduct;
properties?: Record<string, unknown> | null;
};type HydratedProduct = {
id: string | number;
title: string;
description: string;
availableForSale: boolean;
handle: string;
productType: string | null;
category: string | null;
tags: string[];
onlineStoreUrl: string | null;
metafields: Metafield[];
variant: HydratedVariant;
vendor: string | null;
};
type HydratedVariant = {
id: string | number;
title: string;
sku?: string | null;
price: Money;
compareAtPrice?: Money | null;
availableForSale: boolean;
currentlyNotInStock: boolean;
selectedOptions: SelectedOption[];
image?: Image | null;
metafields: Metafield[];
};
type Money = { amount: string; currencyCode: string };
type SelectedOption = { name: string; value: string };
type Metafield = { key: string; value: string };type Image = {
altText?: string | null;
url: string;
width?: number | null;
height?: number | null;
};
type FeaturedItem = {
variantId: string | number;
image?: Image | null;
};type OkResponse = { ok: true };Related Packages
@sdg.la/wishlist-stack-hydrogen— Hydrogen/Remix integration with React hooks and middleware
