
Building Storefronts
Wire a Medusa v2 storefront to custom and built-in store/admin APIs using the JS SDK and React Query patterns.
Overview
building-storefronts is an agent skill for the Build phase that integrates Medusa storefront frontends with the JS SDK and React Query, including custom API routes via `sdk.client.fetch()`.
Install
npx skills add https://github.com/medusajs/medusa-agent-skills --skill building-storefrontsWhat is this skill?
- Never hardcode SDK import paths—locate the project’s `@medusajs/js-sdk` instance first
- Mandatory Medusa JS SDK for all API calls (publishable key + auth headers); avoid raw `fetch()`
- Built-in routes via typed SDK methods; custom API routes via `sdk.client.fetch()`
- React Query integration with query-key, error-handling, and optimistic-update guidance
- Documents that API routes and endpoints are interchangeable terms in Medusa docs
Adoption & trust: 2k installs on skills.sh; 185 GitHub stars; 3/3 security scanners passed (skills.sh audits).
What problem does it solve?
Your Medusa storefront calls APIs with plain fetch and fails on missing publishable keys or admin auth headers.
Who is it for?
Indie ecommerce builders on Medusa v2 adding or fixing Next.js/React storefront features against custom modules.
Skip if: Greenfield Medusa backend module design, non-React stacks without adaptation, or shops not using `@medusajs/js-sdk`.
When should I use this skill?
Implementing or fixing Medusa storefront data layers, custom endpoints, or React Query hooks.
What do I get? / Deliverables
Frontend code consistently uses the project SDK for store and admin traffic, with React Query hooks aligned to Medusa query-key and error-handling practices.
- SDK-backed data hooks for store/admin routes
- Custom route clients using `sdk.client.fetch()`
- React Query keys and error handling aligned to Medusa patterns
Recommended Skills
Journey fit
Storefront integration is core product construction for Medusa merchants, so Build/frontend is the canonical shelf. Frontend subphase covers SDK discovery, `sdk.client.fetch` for custom routes, and React Query data layers—not Medusa backend module authoring.
How it compares
Medusa-specific frontend integration guidance—not a generic REST client generator or Shopify theme skill.
Common Questions / FAQ
Who is building-storefronts for?
Solo developers building Medusa-powered ecommerce frontends who need correct SDK usage and React Query patterns from a coding agent.
When should I use building-storefronts?
Use it during Build while implementing storefront pages, custom Medusa API routes, cart/checkout data fetching, or optimistic UI updates.
Is building-storefronts safe to install?
The skill guides code that calls your Medusa APIs and may handle publishable keys; review the Security Audits panel on this Prism page and never commit secrets into the repo.
SKILL.md
READMESKILL.md - Building Storefronts
# Frontend SDK Integration ## Contents - [Frontend SDK Pattern](#frontend-sdk-pattern) - [Locating the SDK](#locating-the-sdk) - [Using sdk.client.fetch()](#using-sdkclientfetch) - [React Query Pattern](#react-query-pattern) - [Query Key Best Practices](#query-key-best-practices) - [Error Handling](#error-handling) - [Optimistic Updates](#optimistic-updates) This guide covers how to integrate Medusa custom API routes with frontend applications using the Medusa SDK and React Query. **Note:** API routes are also referred to as "endpoints" - these terms are interchangeable. ## Frontend SDK Pattern ### Locating the SDK **IMPORTANT:** Never hardcode SDK import paths. Always locate where the SDK is instantiated in the project first. Look for `@medusajs/js-sdk` The SDK instance is typically exported as `sdk`: ```typescript import { sdk } from "[LOCATE IN PROJECT]" ``` ### Using sdk.client.fetch() **⚠️ CRITICAL: ALWAYS use the Medusa JS SDK for ALL API requests - NEVER use regular fetch()** **Why this is critical:** - **Store API routes** require the publishable API key in headers - **Admin API routes** require authentication headers - **Regular fetch()** without these headers will cause errors - The SDK automatically handles all required headers for you **When to use what:** - **Existing endpoints** (built-in Medusa routes): Use existing SDK methods like `sdk.store.product.list()`, `sdk.admin.order.retrieve()` - **Custom endpoints** (your custom API routes): Use `sdk.client.fetch()` for custom routes **⚠️ CRITICAL: The SDK handles JSON serialization automatically. NEVER use JSON.stringify() on the body.** Call custom API routes using the SDK: ```typescript import { sdk } from "[LOCATE SDK INSTANCE IN PROJECT]" // ✅ CORRECT - Pass object directly const result = await sdk.client.fetch("/store/my-route", { method: "POST", body: { email: "user@example.com", name: "John Doe", }, }) // ❌ WRONG - Don't use JSON.stringify const result = await sdk.client.fetch("/store/my-route", { method: "POST", body: JSON.stringify({ // ❌ DON'T DO THIS! email: "user@example.com", }), }) ``` **Key points:** - **The SDK handles JSON serialization automatically** - just pass plain objects - **NEVER use JSON.stringify()** - this will break the request - No need to set Content-Type headers - SDK adds them - Session/JWT authentication is handled automatically - Publishable API key is automatically added ### Built-in Endpoints vs Custom Endpoints **⚠️ CRITICAL: Use the appropriate SDK method based on endpoint type** ```typescript import { sdk } from "[LOCATE SDK INSTANCE IN PROJECT]" // ✅ CORRECT - Built-in endpoint: Use existing SDK method const products = await sdk.store.product.list({ limit: 10, offset: 0 }) // ✅ CORRECT - Custom endpoint: Use sdk.client.fetch() const reviews = await sdk.client.fetch("/store/products/prod_123/reviews") // ❌ WRONG - Using regular fetch for ANY endpoint const products = await fetch("http://localhost:9000/store/products") // ❌ Error: Missing publishable API key header! // ❌ WRONG - Using regular fetch for custom endpoint const reviews = await fetch("http://localhost:9000/store/products/prod_123/reviews") // ❌ Error: Missing publishable API key header! // ❌ WRONG - Using sdk.client.fetch() for built-in endpoint when SDK method exists const products = await sdk.client.fetch("/store/products") // ❌ Less type-safe than using sdk.store.product.list() ``` **Why this matters:** - **Store routes** require `x-publishable-api-key` header - SDK adds it automatically - **Admin routes** require `Authorization` and session cookie headers - SDK adds them automatically - **Regular fetch()** doesn't include these headers → API returns authentication/authorization errors - Using existing SDK methods provides **better type safety** and autocomplete ## React Query Pattern Use `useQuery` for GET requests and `useMutation` for POST/DELETE: ```typescript import { sdk } from "[LOCATE SDK IN