Claude Code for Medusa: Open-Source E-Commerce Backend — Claude Skills 360 Blog
Blog / Backend / Claude Code for Medusa: Open-Source E-Commerce Backend
Backend

Claude Code for Medusa: Open-Source E-Commerce Backend

Published: May 20, 2027
Read time: 7 min read
By: Claude Skills 360

Medusa is an open-source headless commerce platform — @medusajs/js-sdk provides the storefront client. sdk.store.product.list({ limit: 12 }) fetches products. sdk.store.cart.create({}) creates a cart; sdk.store.cart.addLineItem(cartId, { variant_id, quantity }) adds items. sdk.store.paymentCollection.initiatePaymentSession(collectionId, { provider_id: "pp_stripe_stripe" }) starts Stripe payment. sdk.store.customer.register({ email, password, first_name }) creates accounts. sdk.store.order.list() fetches customer orders. Server-side: Medusa modules extend functionality with custom data models, event subscribers, and workflows. Modules.PRODUCT, Modules.ORDER, and Modules.CART are built-in. IOrderModuleService.createOrders() programmatically creates orders. The Admin REST API manages products, inventory, and fulfillments. Medusa runs on Node.js with PostgreSQL. Claude Code generates Medusa storefront clients, custom checkout flows, product catalog pages, and order management.

CLAUDE.md for Medusa

## Medusa Stack
- Version: @medusajs/js-sdk >= 2.6, @medusajs/medusa >= 2.6
- Client: const sdk = new Medusa({ baseUrl: MEDUSA_BACKEND_URL, publishableKey: NEXT_PUBLIC_MEDUSA_KEY })
- Products: const { products } = await sdk.store.product.list({ limit: 12, offset: 0 })
- Cart: const { cart } = await sdk.store.cart.create({}); const cartWithItem = await sdk.store.cart.addLineItem(cart.id, { variant_id, quantity })
- Customer: sdk.store.customer.register({ email, password, first_name, last_name })
- Auth: await sdk.store.customer.login({ email, password }) — sets cookie
- Order: await sdk.store.order.confirm(cartId)

SDK Client Setup

// lib/medusa/client.ts — typed Medusa storefront client
import Medusa from "@medusajs/js-sdk"

export const medusa = new Medusa({
  baseUrl: process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL!,
  publishableKey: process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY!,
  debug: process.env.NODE_ENV === "development",
  // Auth token stored in cookie by default in browser
  auth: {
    type: "session",
  },
})

// Server-side admin client (never expose this token client-side)
export const medusaAdmin = new Medusa({
  baseUrl: process.env.MEDUSA_BACKEND_URL!,
  auth: {
    type: "api-key",
    apiKey: process.env.MEDUSA_API_KEY!,
  },
})

Product Listing and Detail

// lib/medusa/products.ts — product queries
import { medusa } from "./client"
import type { StoreProduct, StoreProductVariant } from "@medusajs/types"

export type ProductListParams = {
  limit?: number
  offset?: number
  categoryId?: string
  collectionId?: string
  tags?: string[]
  search?: string
  regionId?: string
}

export async function listProducts(params: ProductListParams = {}) {
  const {
    limit = 12,
    offset = 0,
    categoryId,
    collectionId,
    tags,
    search,
    regionId,
  } = params

  const { products, count } = await medusa.store.product.list({
    limit,
    offset,
    category_id: categoryId ? [categoryId] : undefined,
    collection_id: collectionId ? [collectionId] : undefined,
    tags,
    q: search,
    region_id: regionId,
    fields: "+variants,+variants.calculated_price",
  })

  return {
    products: products as StoreProduct[],
    total: count ?? 0,
    limit,
    offset,
  }
}

export async function getProduct(handle: string, regionId?: string): Promise<StoreProduct | null> {
  const { products } = await medusa.store.product.list({
    handle,
    region_id: regionId,
    fields: "+variants,+variants.calculated_price,+images,+categories",
  })

  return products[0] as StoreProduct ?? null
}

export function getVariantPrice(variant: StoreProductVariant): string {
  const price = (variant as any).calculated_price
  if (!price) return "Price unavailable"

  const amount = price.calculated_amount
  const currency = price.currency_code?.toUpperCase() ?? "USD"

  return new Intl.NumberFormat("en-US", {
    style: "currency",
    currency,
    minimumFractionDigits: 0,
  }).format(amount / 100)
}

Cart Management

// lib/medusa/cart.ts — cart state management
import { medusa } from "./client"
import type { StoreCart } from "@medusajs/types"

const CART_ID_KEY = "medusa_cart_id"

// Get or create a cart
export async function getOrCreateCart(regionId: string, email?: string): Promise<StoreCart> {
  const storedId = typeof window !== "undefined"
    ? localStorage.getItem(CART_ID_KEY)
    : null

  if (storedId) {
    try {
      const { cart } = await medusa.store.cart.retrieve(storedId, {
        fields: "+items,+items.variant,+items.variant.product,+payment_collection,+shipping_address",
      })
      return cart as StoreCart
    } catch {
      // Cart expired or not found — create new one
      localStorage.removeItem(CART_ID_KEY)
    }
  }

  const { cart } = await medusa.store.cart.create({
    region_id: regionId,
    email,
  })

  if (typeof window !== "undefined") {
    localStorage.setItem(CART_ID_KEY, cart.id)
  }

  return cart as StoreCart
}

export async function addToCart(
  cartId: string,
  variantId: string,
  quantity = 1,
): Promise<StoreCart> {
  const { cart } = await medusa.store.cart.addLineItem(cartId, {
    variant_id: variantId,
    quantity,
  })
  return cart as StoreCart
}

export async function updateCartItem(
  cartId: string,
  lineItemId: string,
  quantity: number,
): Promise<StoreCart> {
  if (quantity === 0) {
    const { cart } = await medusa.store.cart.deleteLineItem(cartId, lineItemId)
    return cart as StoreCart
  }

  const { cart } = await medusa.store.cart.updateLineItem(cartId, lineItemId, { quantity })
  return cart as StoreCart
}

export async function applyDiscount(cartId: string, code: string): Promise<StoreCart> {
  const { cart } = await medusa.store.cart.update(cartId, {
    promo_codes: [code],
  })
  return cart as StoreCart
}

export async function addShippingAddress(cartId: string, address: {
  first_name: string
  last_name: string
  address_1: string
  city: string
  country_code: string
  postal_code: string
  phone?: string
}): Promise<StoreCart> {
  const { cart } = await medusa.store.cart.update(cartId, {
    shipping_address: address,
    email: (address as any).email,
  })
  return cart as StoreCart
}

Checkout Flow

// lib/medusa/checkout.ts — Stripe checkout with Medusa
import { medusa } from "./client"

export async function initiateStripePayment(cartId: string): Promise<{
  clientSecret: string
  paymentCollectionId: string
}> {
  // Create payment collection for the cart
  const { payment_collection } = await medusa.store.paymentCollection.createForCart(cartId)

  // Initialize Stripe payment session
  const { payment_collection: updatedCollection } = await medusa.store.paymentCollection.initiatePaymentSession(
    payment_collection.id,
    { provider_id: "pp_stripe_stripe" },
  )

  const stripeSession = updatedCollection.payment_sessions?.find(
    s => s.provider_id === "pp_stripe_stripe",
  )

  if (!stripeSession?.data?.client_secret) {
    throw new Error("Failed to initialize Stripe payment")
  }

  return {
    clientSecret: stripeSession.data.client_secret as string,
    paymentCollectionId: payment_collection.id,
  }
}

export async function confirmOrder(cartId: string): Promise<string> {
  const { order } = await medusa.store.order.confirm(cartId)

  // Clear cart from localStorage
  if (typeof window !== "undefined") {
    localStorage.removeItem("medusa_cart_id")
  }

  return order.id
}

Next.js Product Page

// app/products/[handle]/page.tsx — Medusa product with add-to-cart
import { getProduct, getVariantPrice } from "@/lib/medusa/products"
import { notFound } from "next/navigation"
import type { Metadata } from "next"
import type { StoreProductVariant } from "@medusajs/types"

export async function generateMetadata({ params }: { params: { handle: string } }): Promise<Metadata> {
  const product = await getProduct(params.handle)
  return {
    title: product?.title ?? "Product",
    description: product?.description ?? undefined,
  }
}

export default async function ProductPage({ params }: { params: { handle: string } }) {
  const product = await getProduct(params.handle, process.env.MEDUSA_DEFAULT_REGION_ID)

  if (!product) notFound()

  return (
    <div className="max-w-6xl mx-auto px-4 py-12">
      <div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
        {/* Images */}
        <div className="space-y-3">
          {product.images?.map((img, i) => (
            <img
              key={img.id}
              src={img.url}
              alt={product.title}
              className="w-full rounded-2xl object-cover aspect-square"
            />
          ))}
        </div>

        {/* Details */}
        <div className="space-y-6">
          <div>
            <h1 className="text-3xl font-bold">{product.title}</h1>
            {product.subtitle && (
              <p className="text-muted-foreground mt-1">{product.subtitle}</p>
            )}
          </div>

          <p className="text-muted-foreground">{product.description}</p>

          {/* Variants */}
          {product.variants && product.variants.length > 0 && (
            <div className="space-y-3">
              {product.variants.map((variant: StoreProductVariant) => (
                <div key={variant.id} className="flex items-center justify-between p-3 rounded-xl border">
                  <span className="font-medium">{variant.title}</span>
                  <span className="font-bold">{getVariantPrice(variant)}</span>
                </div>
              ))}
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

export const revalidate = 300  // 5 minute ISR

For the Shopify Hydrogen alternative when a Shopify-powered storefront with existing products, fulfillment, and the Shopify ecosystem (apps, themes, payments) is needed — Hydrogen provides a React framework for Shopify while Medusa is database-agnostic and self-hosted, see the Shopify Hydrogen guide. For the WooCommerce/WordPress alternative when a large existing WordPress + WooCommerce store needs a headless frontend — WooCommerce’s REST API can power React storefronts, though Medusa’s code-first TypeScript API is more developer-friendly for greenfield projects. The Claude Skills 360 bundle includes Medusa skill sets covering storefront clients, cart management, and checkout flows. Start with the free tier to try e-commerce backend generation.

Keep Reading

Backend

Claude Code for Bun: Fast JavaScript Runtime and Toolkit

Build with Bun and Claude Code — Bun.serve for HTTP servers, Bun.file for fast file I/O, Bun.$ for shell commands, Bun.sql for SQLite and PostgreSQL, Bun.build for bundling, bun:test for testing, Bun.hash for hashing, bun.lock for deterministic installs, bun run for package.json scripts, hot reloading with --hot, bun init for project scaffolding, and compatibility with Node.js modules.

6 min read Jun 13, 2027
Backend

Claude Code for Express.js Advanced: Patterns for Production APIs

Advanced Express.js patterns with Claude Code — typed request handlers with RequestHandler generics, async error handling middleware, Zod validation middleware factory, rate limiting with express-rate-limit and Redis store, helmet security middleware, compression, dependency injection with tsyringe, file upload with multer and S3, pagination utilities, JWT middleware, and structured logging with pino.

6 min read Jun 8, 2027
Backend

Claude Code for KeystoneJS: Node.js CMS and App Framework

Build full-stack apps with KeystoneJS and Claude Code — config with lists, fields.text and fields.relationship for schema definition, access control with isAuthenticated and isAdmin functions, hooks with beforeOperation and afterOperation, GraphQL API auto-generation from schema, AdminUI for content management, session with statelessSessions, Prisma adapter for database, file storage with images and files fields, and custom REST endpoints.

6 min read Jun 7, 2027

Put these ideas into practice

Claude Skills 360 gives you production-ready skills for everything in this article — and 2,350+ more. Start free or go all-in.

Back to Blog

Get 360 skills free