[![Deploy with Vercel](https://vercel.com/button)]() saleor-storefront-paper-fin

apple-touch-icon-dark

Paper

A minimal, production-ready storefront template for Saleor.
Clean as a blank page — built to ship with agents and humans.


Learn about headless storefronts · Website · Docs · Discord

> [!TIP] > Questions or issues? Check our [Discord](https://saleor.io/discord) for help. --- ## Why Paper? **Ship faster, customize everything.** Paper is a new release—expect some rough edges—but every component is built with real-world e-commerce in mind. This is a foundation you can actually build on. ### 🛒 Open Checkout (v2) The checkout is where most storefronts fall apart or fall short. Paper's doesn't — and **checkout v2** aligns it with the rest of the stack: App Router, Server Components, server actions, and the same BFF session as the storefront (no client-side urql or browser Saleor tokens). ``` Storefront cart Checkout surface ───────────── ──────────────── src/lib/checkout.ts src/app/(checkout)/checkout/ cookie + mutations → CheckoutSessionLoader (RSC) @paper/session-bridge CheckoutApp → steps + payment buildCheckoutPath() /order/{hmac} (guest confirmation) ``` - **Server-first cart** — RSC loads checkout + `me` on entry; client context is a cache of server truth (`CheckoutDataProvider`). - **URL-driven steps** — `?step=contact|shipping|payment` updates shallowly (no full page refetch per click); browser Back walks the funnel. - **Dedicated confirmation** — `/order/{hmac}` after pay (URL frozen; status/tracking refresh from Saleor). `/order/{saleorId}` from email is redacted until we recognize you. Legacy `/checkout/complete?order=` redirects. See [`checkout-guest-order.md`](skills/saleor-paper-storefront/rules/checkout-guest-order.md). - **Extensible payments** — Registry (`INTEGRATED_GATEWAYS`) with Stripe + Dummy; add gateways via `checkout-payment-gateways` skill. - **Shared BFF auth** — Sign-in via `/api/auth/login`; session resolved server-side (`resolveSessionUser` — guest / authenticated / unavailable). - **Multi-step, mobile-first** — Focused forms, international address fields, composable step components. **Developer docs:** start at [`skills/saleor-paper-storefront/rules/paper-surfaces.md`](skills/saleor-paper-storefront/rules/paper-surfaces.md), then [`checkout-management.md`](skills/saleor-paper-storefront/rules/checkout-management.md). Forks on the old urql checkout: [`migrations/atomic/2026-06-checkout-v2/`](skills/saleor-paper-storefront/migrations/atomic/2026-06-checkout-v2/MIGRATION.md). ### 🌍 International by Default One codebase, many markets. Browse URLs are **`/{locale}/{channel}/…`** — e.g. `/en/us/products/hoodie` (English, US market, USD) and `/pl/pl/products/bluza` (Polish, Poland, PLN, with a **translated catalog slug** when set in Saleor) — with legacy `/{channel}/…` paths redirecting automatically. Each locale gets its own cached catalog payload, translated product copy from Saleor, per-channel pricing and currency, and hreflang/canonical metadata (including per-locale product/category handles). - **Region picker** — header control switches locale and channel together (language + market + currency), remapping catalog URLs to each language’s canonical slug - **Three string systems** — Saleor catalog translations, merchant-editable storefront content (CMS), and code-owned UI via **next-intl** (`messages/{locale}.json`) - **Seven built-in locales** — `en`, `pl`, `de`, `fr`, `fi`, `nb`, `ko` (extend via `LOCALE_DEFINITIONS` in `src/config/locale.ts`) - **Optional translated URL slugs** — Saleor Dashboard can set per-language handles for products, categories, collections, and pages; Paper resolves, redirects, links, and hreflang accordingly ([ADR 0004](docs/adr/0004-translatable-slugs.md)) **Storefront channels are explicit.** Saleor may have many channels (B2B, wholesale, internal regions); Paper only exposes the slugs you configure via `STOREFRONT_CHANNELS`. Disallowed channel URLs return 404. For a single-channel store, set `NEXT_PUBLIC_DEFAULT_CHANNEL` only—the footer channel selector is hidden automatically. **Developer docs:** [`docs/international-storefront.md`](docs/international-storefront.md) · ADRs [0001](docs/adr/0001-locale-channel-url-routing.md) / [0002](docs/adr/0002-cms-copy-vs-code-owned-ui-strings.md) / [0004](docs/adr/0004-translatable-slugs.md) · skills [`ui-locale-routing`](skills/saleor-paper-storefront/rules/ui-locale-routing.md) / [`ui-i18n`](skills/saleor-paper-storefront/rules/ui-i18n.md) ### 📱 Product Pages Done Right The hard parts are solved. Adapt the look, keep the logic. - **Partial Prerendering (PPR)** — Product name, attributes, and SEO stay in a static cached shell; variant gallery and add-to-cart stream in via Suspense when `searchParams` change. - **Multi-attribute variant selection** — Color + Size + Material? Handled. Complex variant matrices just work. - **Dynamic pricing** — Sale prices, variant-specific pricing, channel pricing—all reactive. - **Image gallery** — Next.js Image optimization, proper aspect ratios, keyboard navigation. ### ♿ Accessibility Built In Not an afterthought. Focus management on step transitions, keyboard navigation everywhere, semantic HTML, proper ARIA labels. Everyone deserves to shop. ### 🤖 AI-Ready Codebase Built for front-end developers _and_ AI agents. The codebase includes: - **`AGENTS.md`** — Architecture overview and quick reference for AI assistants - **[`skills/saleor-paper-storefront/`](skills/saleor-paper-storefront/)** — 21 task-specific rules covering GraphQL, caching, i18n, variant selection, checkout v2, and more - **[saleor/agent-skills](https://github.com/saleor/agent-skills)** — Universal Saleor API patterns; external skills via `pnpm skills:bootstrap` (`skills-lock.json`) - **Consistent patterns** — Predictable structure that AI tools can navigate and modify confidently Whether you're pair-programming with Cursor, Claude, or Copilot—the codebase is designed to help them help you. ### ⚡ Bleeding Edge Stack - **Next.js 16** with App Router and Server Components - **React 19** with the latest concurrent features - **TypeScript** in strict mode—your IDE will thank you - **Tailwind CSS** with design tokens (OKLCH colors, CSS variables) - **GraphQL Codegen** for type-safe Saleor API calls --- ## What's in the Box | Feature | Description | | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | | **Checkout (v2)** | RSC + server actions, shallow step URLs, payment registry (Stripe/Dummy), guest `/order/{key}` | | **Cart** | Slide-over drawer with real-time updates, quantity editing | | **Product Pages** | Multi-attribute variants, image gallery, sticky add-to-cart | | **Product Listings** | Category & collection pages with PPR (cached hero + dynamic filters), pagination | | **International** | `/{locale}/{channel}/` routing, region picker, Saleor translations + optional translated URL slugs, next-intl UI, hreflang SEO | | **Storefront content** | Merchant-editable copy layer (code or Saleor Models) — homepage, cart trust, checkout editorial | | **Navigation** | Dynamic menus from Saleor, mobile hamburger, breadcrumbs | | **SEO** | Per-locale metadata, JSON-LD, Open Graph images, hreflang with per-locale catalog handles | | **Caching** | Cache Components (PPR), named cacheLife tiers, per-locale catalog cache, webhooks | | **Saleor Cloud Paper app** | Saleor Cloud only — Dashboard extension for cache invalidation webhooks and _Preview in storefront_ | | **Customer Profile** | Account dashboard, address book, order history, password change, account deletion | | **Authentication** | Login, register, password reset, guest checkout | | **API Resilience** | Automatic retries, rate limiting, timeouts—handles flaky connections gracefully | --- ## Caching Architecture Paper uses **Cache Components** ([Next.js 16](https://nextjs.org/docs/app/getting-started/cache-components) — `"use cache"`, `cacheLife`, `cacheTag` behind `cacheComponents: true`) for optimal performance: static shells load instantly while dynamic content streams in. Learn more in the [directive docs](https://nextjs.org/docs/app/api-reference/directives/use-cache) or see [`skills/saleor-paper-storefront/rules/data-caching.md`](skills/saleor-paper-storefront/rules/data-caching.md) for project-specific patterns. The **display-cached, checkout-live** model ensures fast browsing with accurate checkout: ``` ┌─────────────────────────────────────────────────────────────────────┐ │ DATA FRESHNESS │ ├─────────────────────────────────────────────────────────────────────┤ │ │ │ Product Pages Cart / Checkout Payment │ │ ────────────── ────────────── ─────── │ │ │ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │ │ CACHED │────────▶│ LIVE │─────────▶│ LIVE │ │ │ │ 5 min │ Add │ Always │ Pay │ Always │ │ │ └───────────┘ to └───────────┘ └───────────┘ │ │ Cart │ │ Fast page loads Real-time prices Saleor validates │ │ │ └─────────────────────────────────────────────────────────────────────┘ ``` ### How It Works | Component | Freshness | Why | | ----------------------- | ------------------ | ------------------------------------------------------------------ | | **Product pages** | Cached (`catalog`) | Static shell + dynamic variant islands (PPR) | | **Category/Collection** | Cached (`catalog`) | Cached hero from params; filters/pagination stream in Suspense | | **Homepage featured** | Cached (`catalog`) | Sync page shell; product grid streams in nested Suspense | | **Navigation / footer** | Cached (`menus`) | Per-channel tags; per-locale menu payloads in cache keys | | **Storefront content** | Cached (`menus`) | Tag `storefront-content:{channel}:{locale}` | | **Cart drawer** | Always live | Saleor API with `cache: "no-cache"` | | **Checkout** | Always live | RSC entry + server actions (`cache: "no-cache"`), real-time totals | **cacheLife tiers** (see `src/lib/cache-life-profiles.ts`): | Profile | Fallback TTL | Used for | | ---------- | ------------ | ------------------------------------------- | | `catalog` | ~5 min | Products, categories, collections, homepage | | `menus` | ~1 hr | Header nav, footer menu | | `channels` | ~1 day | Footer channel metadata | Webhook `revalidateTag(tag, profile)` clears data immediately; TTL is the safety net when webhooks are missing. ### Locale Browse URLs are `/{locale}/{channel}/…`. Cached catalog fetches pass `localeSlug` — **separate cache entry per language**, same warm-path speed. Invalidation uses slug-scoped tags (`product:{slug}`) and revalidates every locale path via `buildPathsForAllLocales()`. GraphQL uses Saleor base language codes (`PL`, not `PL_PL`). See [`data-caching.md`](skills/saleor-paper-storefront/rules/data-caching.md) § Locale & Caching. ### PPR page patterns Cached GraphQL lives in **`src/lib/catalog/`**, **`src/lib/menus/`**, and **`src/lib/channels/`** — not in layout or page components. Pages are thin orchestrators with nested `` for dynamic islands. **PDP** — `params` only in the static shell; gallery and variant selection read `searchParams`: ``` ProductPage (sync) └── ProductShell → getProductData(slug, channel, locale) "use cache" ├── h1, attributes, JSON-LD, LCP preload ├── Suspense → VariantGalleryDynamic (searchParams) └── Suspense → VariantSectionDynamic (searchParams) ``` **PLP** (category, collection, all products) — cached hero/metadata from params; filter/sort/pagination in a dynamic grid: ``` Page ├── CategoryHero ← getCategoryData "use cache" └── Suspense → CategoryProducts (searchParams, always fresh fetch) ``` **Homepage** — sync `
` shell; featured collection grid in nested Suspense. **Loading UX** — route-level `loading.tsx` files (products, categories, collections) show skeletons during navigation. The main layout does not wrap `{children}` in `Suspense fallback={null}`. **Cache tags** (see `src/lib/cache-manifest.ts`): | Tag pattern | Invalidated when | | --------------------------------------- | -------------------------------- | | `product:{slug}` | Product updated (all locales) | | `category:{slug}` | Category updated (all locales) | | `collection:{slug}` | Collection updated (all locales) | | `page:{slug}` | CMS page updated (all locales) | | `navigation:{channel}` | Main menu changed for channel | | `footer-menu:{channel}` | Footer menu changed for channel | | `storefront-content:{channel}:{locale}` | Storefront Models page updated | | `channels` | Channel list metadata | Featured homepage products use tag `collection:featured-products` (same `catalog` profile as collections). ### Instant Updates with Webhooks **Saleor Cloud (recommended):** Install the [**Saleor Cloud Paper app**](https://github.com/saleor/saleor-paper-app) from Dashboard → Extensions. Available on Saleor Cloud only for now. It registers revalidation webhooks for products, categories, collections, pages, menus, and promotions; discovers cache tags via `/api/cache-info`; and adds _Preview in storefront_ on product pages in Dashboard. **Self-hosted / manual setup:** 1. Create webhooks in Saleor Dashboard → Configuration → Webhooks 2. Point to `https://your-store.com/api/revalidate` 3. Subscribe to product/category/collection/page events; for menus use `MENU_*` / `MENU_ITEM_*` and include `{ menu: { slug } }` for `navbar` and `footer` menus 4. Set `SALEOR_WEBHOOK_SECRET` env var **Manual revalidation** (requires `REVALIDATE_SECRET`): ```bash # Single product (all locale cache entries for slug) curl "https://your-store.com/api/revalidate?secret=xxx&tag=product:blue-hoodie" # Single product path (one locale; tag still clears all locales) curl "https://your-store.com/api/revalidate?secret=xxx&tag=product:blue-hoodie&path=/pl/default-channel/products/blue-hoodie" # CMS page (tag only — invalidates getPageData across channels) curl "https://your-store.com/api/revalidate?secret=xxx&tag=page:about-us" # Navigation for one channel (tag or tag + channel query) curl "https://your-store.com/api/revalidate?secret=xxx&tag=navigation:us" curl "https://your-store.com/api/revalidate?secret=xxx&tag=navigation&channel=us" # All tags for every storefront channel curl "https://your-store.com/api/revalidate?secret=xxx&all=1" ``` Without webhooks? TTL handles it—cached data expires per the `catalog` / `menus` / `channels` profiles above. ### Why This Is Safe - **Saleor is the source of truth**: `checkoutLinesAdd` calculates prices server-side - **Cart always fetches fresh**: Users see current prices before checkout - **Payment validates**: `checkoutComplete` uses real-time data > 📚 **Deep dive**: See [`skills/saleor-paper-storefront/rules/data-caching.md`](skills/saleor-paper-storefront/rules/data-caching.md) for the full architecture, Cache Components (PPR), webhook setup, and debugging guide. --- ## Image Pipeline Saleor already returns a compressed, format-converted thumbnail from a CDN. Re-encoding that through Next's image optimizer is paid work that buys nothing, so Paper asks Saleor for **several sizes at once** and hands the browser a real `srcset`: ```graphql thumbnail(size: 1024, format: WEBP) { url alt } thumbnail256: thumbnail(size: 256, format: WEBP) { url } thumbnail512: thumbnail(size: 512, format: WEBP) { url } ``` Product cards and the PDP gallery render those through [``](src/ui/atoms/saleor-image.tsx) as a plain ``, so they never hit `/_next/image` and are never billed as image transformations. Category and collection `backgroundImage(size, format)` use the same ladder. Anything else — `/public`, Model/Page `FILE` (original URL, no rungs) — has no Saleor resize; prefer a pre-encoded file and a plain ``, or let `SaleorImage` fall back to `next/image`. | | Saleor-native `srcset` | Pre-encoded `` or `next/image` | | ---------------------- | ---------------------------------------------------------- | --------------------------------------------------------------------- | | Used for | Product `thumbnail`, category/collection `backgroundImage` | `/public`, FILE uploads, slots far below the smallest requested size | | Transformations billed | none | none (plain ``) / one per `(src, width, quality)` (`next/image`) | | Served by | Saleor's media CDN | Vercel origin or the raw FILE host | Set `NEXT_PUBLIC_PAPER_IMAGE_PIPELINE=vercel` to route everything back through `next/image` without a code change — the escape hatch if your Saleor deployment doesn't front media with a CDN. Two things to know before going to production: - **Newly requested sizes start as redirects.** Until a thumbnail exists, Saleor returns a `/thumbnail/{id}/{size}/{format}/` proxy URL that 302s to storage, generating the file on first hit. Cached pages therefore embed redirect URLs right after you add a size, costing one extra round trip per image. It self-heals — once the file exists, the next GraphQL resolve returns the storage URL and the next re-render bakes it in — but on a large catalog, crawl it before cutting traffic over so shoppers don't pay for the warm-up. - **Every `thumbnail` selection needs `size:` and `format:`.** A bare `thumbnail { url }` resolves to 256px in the image's _original_ format, which puts the format conversion back on your bill. > 📚 **Deep dive**: [`skills/saleor-paper-storefront/rules/ui-images.md`](skills/saleor-paper-storefront/rules/ui-images.md) covers the Saleor thumbnail mechanics, the `sizes` vocabulary, and the anti-patterns. --- ## Quick Start > [!NOTE] > New to Saleor? Check out [saleor.io/start](https://saleor.io/start) to learn how storefronts work underneath. ### 1. Get a Saleor Backend Paper requires **Saleor 3.23 or later**. Older schemas are not supported. **Option A:** Free [Saleor Cloud](https://cloud.saleor.io/?utm_source=storefront&utm_medium=github) account (recommended) **Option B:** [Run locally with Docker](https://docs.saleor.io/docs/3.x/setup/docker-compose) ### 2. Clone & Configure ```bash # Using Saleor CLI (recommended) npm i -g @saleor/cli@latest saleor storefront create --url https://{YOUR_INSTANCE}/graphql/ # Or manually git clone https://github.com/saleor/storefront.git cd storefront cp .env.example .env pnpm install # Optional — wire agent skills for Cursor (see "For AI Agents" below) pnpm skills:bootstrap ``` Edit `.env` with your Saleor instance details: ```bash NEXT_PUBLIC_SALEOR_API_URL=https://your-instance.saleor.cloud/graphql/ NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel # Your Saleor channel slug ``` **Multi-channel** (recommended — explicit allowlist): ```bash STOREFRONT_CHANNELS=us,uk,eu NEXT_PUBLIC_DEFAULT_CHANNEL=us NEXT_PUBLIC_STOREFRONT_LOCALES=en,pl,de,fr,fi,nb,ja,ko # URL locale slugs SALEOR_APP_TOKEN=... # Server-side only — footer currency selector metadata ``` > **Finding your channel slug:** In Saleor Dashboard → Configuration → Channels → copy the slug > **Note:** `SALEOR_APP_TOKEN` alone no longer auto-discovers every Saleor channel. Set `STOREFRONT_CHANNELS` or opt in with `STOREFRONT_DISCOVER_CHANNELS=true` (see [Environment Variables](#environment-variables)). ### 3. Run ```bash pnpm dev ``` Open [localhost:3000](http://localhost:3000). That's it. --- ## Development ### Commands ```bash pnpm dev # Start dev server pnpm build # Production build pnpm run generate # Regenerate GraphQL types (storefront) pnpm run generate:checkout # Regenerate GraphQL types (checkout) ``` ### Project Structure ``` src/ ├── app/ # Next.js App Router │ ├── (storefront)/[locale]/[channel]/ # Browse, cart, account │ └── (checkout)/checkout/ # Checkout route (/checkout) ├── messages/ # next-intl UI strings (per locale) ├── session-bridge/ # @paper/session-bridge — storefront ↔ checkout handoff ├── checkout/ # Checkout UI, providers, payment registry (GraphQL via server actions) ├── graphql/ # GraphQL queries ├── gql/ # Generated types (don't edit) ├── lib/ # Server utilities & cached data layer │ ├── catalog/ # getCategoryData, getCollectionData, getFeaturedProducts │ ├── menus/ # getNavbarMenuItems, getFooterMenuItems │ ├── channels/ # getCachedChannelsList │ ├── cache-manifest.ts # Tag registry + cacheLife mapping │ └── cache-life-profiles.ts ├── ui/components/ # UI components │ ├── account/ # Customer profile & address book │ ├── pdp/ # Product detail page │ ├── plp/ # Product listing page │ ├── cart/ # Cart drawer │ └── ui/ # Primitives (Button, Badge, etc.) └── styles/brand.css # Design tokens ``` ### For AI Agents If you're working with AI coding assistants, point them to: - **`AGENTS.md`** — Architecture, commands, gotchas - **`skills/saleor-paper-storefront/`** — 21 project-specific rules (GraphQL, caching, i18n, checkout, etc.) - **`skills/saleor-paper-storefront/references/code-conventions.md`** — File naming, exports, imports - **[saleor/agent-skills](https://github.com/saleor/agent-skills)** — Universal Saleor patterns and optional community skills After clone, wire skills for Cursor discovery (repo-root `skills/` is not scanned automatically): ```shell pnpm skills:bootstrap ``` Symlinks the project skill into `.agents/skills/`, then runs `npx skills experimental_install` from `skills-lock.json`. Do **not** run `npx skills add . --skill saleor-paper-storefront` — it copies a drifting snapshot. ### Environment Variables ```env # Required NEXT_PUBLIC_SALEOR_API_URL=https://your-instance.saleor.cloud/graphql/ NEXT_PUBLIC_DEFAULT_CHANNEL=default-channel # Fallback channel; root "/" redirects here # Multi-channel (recommended) STOREFRONT_CHANNELS=us,uk,eu # Comma-separated allowlist — routes, revalidation, footer # Optional NEXT_PUBLIC_STOREFRONT_URL=http://localhost:3000/ # Canonical URLs, OG images, and redirect allowlist NEXT_PUBLIC_CHECKOUT_URL= # Optional checkout origin and redirect allowlist ALLOWED_EXTRA_ORIGINS= # Optional extra redirect origins - see ./docs/configuration/allowed-origins.md NEXT_PUBLIC_DEFAULT_LOCALE=en # Default URL locale slug NEXT_PUBLIC_STOREFRONT_LOCALES=en,pl,de,fr,fi,nb,ja,ko # Enabled locale slugs REVALIDATE_SECRET= # Manual cache invalidation (GET /api/revalidate) SALEOR_WEBHOOK_SECRET= # Webhook HMAC verification SALEOR_APP_TOKEN= # Server-side: footer channel metadata (never exposed to client) STOREFRONT_DISCOVER_CHANNELS=true # Opt-in: discover ALL active Saleor channels from API # (not recommended when Saleor has many channels; prefer STOREFRONT_CHANNELS) CONTENT_PROVIDER=saleor # Default: Saleor Models. Set `code` for code defaults only. # Cost controls — each trades money against freshness. See .env.example for the full reasoning. NEXT_PUBLIC_PAPER_IMAGE_PIPELINE=saleor # `vercel` routes catalog images back through /_next/image IMAGE_ALLOWED_HOSTS= # Extra hosts /_next/image may optimize (and bill you for) NEXT_IMAGE_MIN_CACHE_TTL=2678400 # 31 days; lower it if the catalogue is re-shot often SALEOR_MIN_REQUEST_DELAY_MS=0 # Runtime GraphQL throttle; defaults to 200 during `next build` ``` **Channel resolution order** (`getStorefrontChannelSlugs`): 1. `STOREFRONT_CHANNELS` — explicit allowlist _(recommended)_ 2. `STOREFRONT_DISCOVER_CHANNELS=true` + `SALEOR_APP_TOKEN` — all active channels from API 3. `NEXT_PUBLIC_DEFAULT_CHANNEL` only — single-channel storefront --- ## Payments The checkout architecture supports Saleor payment apps like [Adyen](https://docs.saleor.io/docs/3.x/developer/app-store/apps/adyen) and [Stripe](https://docs.saleor.io/docs/3.x/developer/app-store/apps/stripe). The heavy lifting is done—integrating your gateway requires minimal work compared to building from scratch. ### How payment config is split Paper deliberately keeps **secrets out of the storefront**. Stripe keys are never set in the storefront `.env`: | Where | What lives there | | --------------------------------- | ------------------------------------------------------------------------------------ | | **Saleor Stripe app** (Dashboard) | Stripe **secret key** + **publishable key** + webhook signing secret | | **Storefront `.env`** | Feature flags only (`NEXT_PUBLIC_ENABLE_STRIPE_PAYMENTS`, …) — no keys | | **Runtime** | The storefront receives the publishable key from Saleor's `paymentGatewayInitialize` | So "set up Stripe" means: configure the Saleor Stripe app with your keys, then flip the feature flags in the storefront. ### Set up Stripe with sandbox (test) keys You need a [Stripe account](https://dashboard.stripe.com/register) in **Test mode** and the [Saleor Stripe app](https://docs.saleor.io/docs/3.x/developer/app-store/apps/stripe) installed on your Saleor instance. 1. **Grab your Stripe test keys.** In the [Stripe Dashboard](https://dashboard.stripe.com/test/apikeys), toggle **Test mode** (top-right) and copy: - Publishable key — `pk_test_…` - Secret key — `sk_test_…` > Test mode is Stripe's sandbox: no real charges, and you pay with [test cards](https://docs.stripe.com/testing) (e.g. `4242 4242 4242 4242`, any future expiry, any CVC). 2. **Install & configure the Saleor Stripe app.** In **Saleor Dashboard → Apps** (or Extensions), install the Stripe payment app and open its configuration. Paste the **test** publishable and secret keys. The app registers the Stripe webhook for you; assign the configuration to the channel(s) you're testing. 3. **Enable Stripe in the storefront.** Add to `.env` (or `.env.local`): ```bash NEXT_PUBLIC_ENABLE_STRIPE_PAYMENTS=true ENABLE_STRIPE_PAYMENTS=true # server-side mirror for the transaction guard # Optional — wallet buttons (Apple Pay / Google Pay / Link); on by default when Stripe is enabled # NEXT_PUBLIC_ENABLE_STRIPE_EXPRESS_CHECKOUT=false ``` > In local development (`NODE_ENV === development`) Stripe auto-enables, so these flags are mainly for cloud/staging builds. Publishable keys still come from Saleor at runtime — never put `pk_…` / `sk_…` in the storefront env. 4. **Run a test order.** `pnpm dev`, add a product, go to checkout, and pay with a Stripe [test card](https://docs.stripe.com/testing). Use `4000 0027 6000 3184` to exercise the 3DS redirect flow. ### Test without Stripe (Dummy Payment) To validate the full checkout flow without any Stripe setup, use the Dummy Payment app (auto-enabled in development): ```bash ALLOW_DUMMY_PAYMENT=true NEXT_PUBLIC_ALLOW_DUMMY_PAYMENT=true ``` > **Developer docs:** payment registry, submit patterns, and adding a gateway → [`skills/saleor-paper-storefront/rules/checkout-payment-gateways.md`](skills/saleor-paper-storefront/rules/checkout-payment-gateways.md). All payment env vars are listed in [`.env.example`](.env.example). --- ## Customization Paper works as a reference implementation and as a starting point for your own storefront. Start here: - **Colors & typography** → `src/styles/brand.css` - **Components** → `src/ui/components/` - **Checkout flow** → `src/checkout/views/saleor-checkout/` The design token system uses CSS custom properties—swap the entire color palette by editing a few lines. --- ## Next Steps Known gaps and planned improvements: - **Error / not-found pages** — localized shells for global error boundaries (see [`docs/international-storefront.md`](./docs/international-storefront.md) Backlog) - **Filtering logic iteration** — fetching attributes from API for dynamic product filters - **Draft Mode** — unpublished Model preview via the Paper app (not implemented yet) --- ## License [FSL-1.1-ALv2](./LICENSE) (Functional Source License, Version 1.1, ALv2 Future License) — use it, modify it, ship it. Build your storefront, run your business. The license converts to Apache 2.0 after two years. Want to offer it as a managed service? [Let's talk](https://saleor.io/contact). ---

Built with 🖤 by the Saleor team