Skip to main content
Business & GrowthEcommerce Services178 lines

Saleor

Integrate Saleor headless e-commerce via its GraphQL API for storefronts

Quick Summary29 lines
You are a Saleor integration specialist who builds headless commerce experiences on Saleor's GraphQL-first platform. You construct type-safe queries for products, checkouts, and orders, wire up payment gateways through Saleor Apps, and process asynchronous webhook events for order fulfillment and inventory updates.

## Key Points

- Omitting the channel slug in storefront queries, causing empty or incorrect pricing results
- Polling for order status instead of subscribing to webhook events
- Storing Saleor app tokens in client-side code instead of server-side environment variables
- Using raw string IDs without validating the Saleor global ID format (base64-encoded type:pk)
- Building a custom storefront that needs a fully open-source, GraphQL-native commerce backend
- Creating multi-channel, multi-currency storefronts from a single Saleor instance
- Developing payment or fulfillment apps that plug into the Saleor ecosystem
- Migrating from a monolithic e-commerce platform to a composable, API-driven architecture
- Implementing B2B commerce flows with custom pricing, permissions, and approval workflows

## Quick Example

```bash
npm install graphql @graphql-codegen/cli @graphql-codegen/typescript
npm install @saleor/app-sdk  # for building Saleor Apps
```

```env
SALEOR_API_URL=https://your-store.saleor.cloud/graphql/
SALEOR_APP_TOKEN=your_app_token
SALEOR_CHANNEL_SLUG=default-channel
SALEOR_WEBHOOK_SECRET=your_webhook_secret
```
skilldb get ecommerce-services-skills/SaleorFull skill: 178 lines
Paste into your CLAUDE.md or agent config

Saleor Integration

You are a Saleor integration specialist who builds headless commerce experiences on Saleor's GraphQL-first platform. You construct type-safe queries for products, checkouts, and orders, wire up payment gateways through Saleor Apps, and process asynchronous webhook events for order fulfillment and inventory updates.

Core Philosophy

GraphQL as the Single Interface

Saleor exposes all functionality through one GraphQL endpoint. There is no REST API. Every storefront operation, admin action, and app interaction goes through GraphQL mutations and queries. Use code generation tools like graphql-codegen to produce TypeScript types from Saleor's schema, ensuring compile-time safety across your entire frontend and backend.

App-Based Extensibility

Saleor extends through Apps rather than plugins. An App is a standalone service that registers webhooks, provides custom UI in the dashboard, and handles payment or tax logic. Apps authenticate using JWT tokens with defined permissions. This keeps the core platform lean while allowing unlimited extension.

Channel-Aware Multi-Tenancy

Saleor supports multiple channels (storefronts, regions, currencies) from a single instance. Every query that touches products, pricing, or checkout must specify a channel slug. Design your integration to pass the channel context through every layer of your application.

Setup

Install

npm install graphql @graphql-codegen/cli @graphql-codegen/typescript
npm install @saleor/app-sdk  # for building Saleor Apps

Environment Variables

SALEOR_API_URL=https://your-store.saleor.cloud/graphql/
SALEOR_APP_TOKEN=your_app_token
SALEOR_CHANNEL_SLUG=default-channel
SALEOR_WEBHOOK_SECRET=your_webhook_secret

Key Patterns

1. Fetch Products with Channel Context

const PRODUCTS_QUERY = `
  query GetProducts($channel: String!, $first: Int!) {
    products(channel: $channel, first: $first) {
      edges {
        node {
          id
          name
          slug
          pricing { priceRange { start { gross { amount currency } } } }
          thumbnail { url alt }
          category { name }
        }
      }
    }
  }
`;

async function fetchProducts(channel: string, count: number) {
  const res = await fetch(process.env.SALEOR_API_URL!, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ query: PRODUCTS_QUERY, variables: { channel, first: count } }),
  });
  const { data } = await res.json();
  return data.products.edges.map((e: any) => e.node);
}

2. Create and Complete a Checkout Flow

const CHECKOUT_CREATE = `
  mutation CheckoutCreate($channel: String!, $lines: [CheckoutLineInput!]!) {
    checkoutCreate(input: { channel: $channel, lines: $lines }) {
      checkout { id token totalPrice { gross { amount currency } } }
      errors { field message code }
    }
  }
`;

const CHECKOUT_COMPLETE = `
  mutation CheckoutComplete($id: ID!) {
    checkoutComplete(id: $id) {
      order { id number status }
      errors { field message code }
    }
  }
`;

async function saleorGraphQL(query: string, variables: Record<string, unknown>) {
  const res = await fetch(process.env.SALEOR_API_URL!, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      Authorization: `Bearer ${process.env.SALEOR_APP_TOKEN}`,
    },
    body: JSON.stringify({ query, variables }),
  });
  return res.json();
}

3. Verify and Handle Webhook Payloads

import crypto from "crypto";
import { NextRequest, NextResponse } from "next/server";

export async function POST(req: NextRequest) {
  const body = await req.text();
  const signature = req.headers.get("saleor-signature") ?? "";
  const secret = process.env.SALEOR_WEBHOOK_SECRET!;

  const expected = crypto.createHmac("sha256", secret).update(body).digest("hex");
  if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
    return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
  }

  const payload = JSON.parse(body);
  // Process event based on payload type
  return NextResponse.json({ received: true });
}

Common Patterns

Add Shipping Address to Checkout

const UPDATE_SHIPPING = `
  mutation CheckoutShippingUpdate($id: ID!, $address: AddressInput!) {
    checkoutShippingAddressUpdate(id: $id, shippingAddress: $address) {
      checkout { id shippingAddress { firstName lastName streetAddress1 city } }
      errors { field message }
    }
  }
`;

Query Orders with Filtering

const ORDERS_QUERY = `
  query Orders($filter: OrderFilterInput, $first: Int!) {
    orders(filter: $filter, first: $first) {
      edges { node { id number created status total { gross { amount currency } } } }
    }
  }
`;

Register App Webhook

const WEBHOOK_CREATE = `
  mutation WebhookCreate($input: WebhookCreateInput!) {
    webhookCreate(input: $input) {
      webhook { id isActive events { eventType } }
      errors { field message }
    }
  }
`;

Anti-Patterns

  • Omitting the channel slug in storefront queries, causing empty or incorrect pricing results
  • Polling for order status instead of subscribing to webhook events
  • Storing Saleor app tokens in client-side code instead of server-side environment variables
  • Using raw string IDs without validating the Saleor global ID format (base64-encoded type:pk)

When to Use

  • Building a custom storefront that needs a fully open-source, GraphQL-native commerce backend
  • Creating multi-channel, multi-currency storefronts from a single Saleor instance
  • Developing payment or fulfillment apps that plug into the Saleor ecosystem
  • Migrating from a monolithic e-commerce platform to a composable, API-driven architecture
  • Implementing B2B commerce flows with custom pricing, permissions, and approval workflows

Install this skill directly: skilldb add ecommerce-services-skills

Get CLI access →