Skip to main content
The @commercengine/storefront/sveltekit package provides a SvelteKit-specific wrapper around the core Storefront SDK. It gives you three accessors — publicStorefront(), serverStorefront(), and clientStorefront() — with automatic cookie-backed token management across server and client.

NPM Package

@commercengine/storefront

SvelteKit integration for Commerce Engine Storefront SDK. Import from @commercengine/storefront/sveltekit.

Installation

npm install @commercengine/storefront

Quick Start

1

Set Environment Variables

Add your store credentials to .env:
.env
PUBLIC_STORE_ID=your-store-id
PUBLIC_API_KEY=your-api-key
SvelteKit uses the PUBLIC_ prefix for client-exposed env vars (accessed via $env/static/public).
2

Create the Storefront Config

A shared config imported by both client and server storefronts:
src/lib/storefront-config.ts
import { Environment } from "@commercengine/storefront";
import type { SvelteKitStorefrontConfig } from "@commercengine/storefront/sveltekit";
import { env } from "$env/static/public";

export const storefrontConfig: SvelteKitStorefrontConfig = {
  storeId: env.PUBLIC_STORE_ID,
  apiKey: env.PUBLIC_API_KEY,
  environment: Environment.Staging, // or Environment.Production
  tokenStorageOptions: { prefix: "myapp_" },
};
3

Create the Client Storefront

src/lib/storefront.ts
import { createSvelteKitStorefront } from "@commercengine/storefront/sveltekit";
import { storefrontConfig } from "./storefront-config";

export const storefront = createSvelteKitStorefront(storefrontConfig);
4

Create the Server Storefront

Place this under $lib/server/ — SvelteKit enforces that files in this directory are never imported into client code (a build error is thrown if client code imports from it).
src/lib/server/storefront.ts
import { createSvelteKitServerStorefront } from "@commercengine/storefront/sveltekit/server";
import { storefrontConfig } from "$lib/storefront-config";

export const serverStorefront = createSvelteKitServerStorefront(storefrontConfig);
The @commercengine/storefront/sveltekit/server import must only be used in server-only files: +page.server.ts, +layout.server.ts, hooks.server.ts, and +server.ts. SvelteKit enforces this at build time.
5

Bootstrap in Root Layout

src/routes/+layout.svelte
<script>
  import { onMount } from "svelte";
  import { storefront } from "$lib/storefront";

  onMount(() => {
    storefront.bootstrap().catch(console.error);
  });
</script>

<slot />
bootstrap() is deduped and idempotent. If session cookies already exist (returning user), it is a no-op.

Accessor Rules

Use the right accessor for each context:
ContextAccessorImport From
Svelte component — public readsstorefront.publicStorefront()$lib/storefront
Svelte component — session flowsstorefront.clientStorefront()$lib/storefront
Client bootstrapstorefront.bootstrap()$lib/storefront
Universal load (+page.ts, +layout.ts)storefront.publicStorefront()$lib/storefront
Server load (+page.server.ts, +layout.server.ts)serverStorefront.serverStorefront(cookies)$lib/server/storefront
Server hooks (hooks.server.ts)serverStorefront.serverStorefront(event.cookies)$lib/server/storefront
Form actionsserverStorefront.serverStorefront(cookies)$lib/server/storefront
API routes (+server.ts)serverStorefront.serverStorefront(cookies)$lib/server/storefront
serverStorefront() requires passing cookies (from event.cookies or destructured from the load/action context) because SvelteKit has no global request context. This call is synchronous — no await needed. clientStorefront() throws if called on the server.

Key Patterns

Public Reads (Universal Load)

For public catalog data that doesn’t need a session, use publicStorefront() in universal load functions. These run on both server and client:
src/routes/+page.ts
import { storefront } from "$lib/storefront";

export async function load() {
  const sdk = storefront.publicStorefront();
  const { data, error } = await sdk.catalog.listProducts({ page: 1, limit: 20 });

  if (error) return { products: [], error: error.message };
  return { products: data?.products ?? [] };
}

Session-Aware Server Load

src/routes/account/+page.server.ts
import { serverStorefront } from "$lib/server/storefront";
import { redirect } from "@sveltejs/kit";

export async function load({ cookies }) {
  const sdk = serverStorefront.serverStorefront(cookies);
  const { data, error } = await sdk.customer.getCustomer();

  if (error) redirect(302, "/login");
  return { customer: data?.customer };
}

Layout Server Load

src/routes/+layout.server.ts
import { serverStorefront } from "$lib/server/storefront";

export async function load({ cookies }) {
  const sdk = serverStorefront.serverStorefront(cookies);
  const userId = await sdk.session.peekUserId();

  return { isLoggedIn: !!userId };
}

Form Actions (Mutations)

SvelteKit form actions can read and write cookies, making them ideal for cart and auth mutations:
src/routes/cart/+page.server.ts
import { serverStorefront } from "$lib/server/storefront";
import { fail } from "@sveltejs/kit";

export const actions = {
  addToCart: async ({ cookies, request }) => {
    const formData = await request.formData();
    const productId = formData.get("productId") as string;
    const variantId = formData.get("variantId") as string | null;
    const cartId = formData.get("cartId") as string;

    const sdk = serverStorefront.serverStorefront(cookies);
    const { data, error } = await sdk.cart.addDeleteCartItem(
      { id: cartId },
      { product_id: productId, variant_id: variantId, quantity: 1 }
    );

    if (error) return fail(400, { error: error.message });
    return { cart: data?.cart };
  },

  addToWishlist: async ({ cookies, request }) => {
    const formData = await request.formData();
    const productId = formData.get("productId") as string;

    const sdk = serverStorefront.serverStorefront(cookies);
    const { data, error } = await sdk.cart.addToWishlist({
      product_id: productId,
      variant_id: null,
    });

    if (error) return fail(400, { error: error.message });
    return { wishlist: data };
  },
};

API Routes

src/routes/api/wishlist/+server.ts
import { serverStorefront } from "$lib/server/storefront";
import { json, error } from "@sveltejs/kit";

export async function GET({ cookies }) {
  const sdk = serverStorefront.serverStorefront(cookies);
  const { data, error: apiError } = await sdk.cart.getWishlist();

  if (apiError) error(500, apiError.message);
  return json(data);
}

export async function POST({ cookies, request }) {
  const body = await request.json();
  const sdk = serverStorefront.serverStorefront(cookies);
  const { data, error: apiError } = await sdk.cart.addToWishlist({
    product_id: body.productId,
    variant_id: body.variantId ?? null,
  });

  if (apiError) error(500, apiError.message);
  return json(data);
}

Server Hooks

Use hooks for auth guards and injecting session data into event.locals:
src/hooks.server.ts
import { serverStorefront } from "$lib/server/storefront";
import type { Handle } from "@sveltejs/kit";

export const handle: Handle = async ({ event, resolve }) => {
  const sdk = serverStorefront.serverStorefront(event.cookies);
  const userId = await sdk.session.peekUserId();

  event.locals.userId = userId;

  if (event.url.pathname.startsWith("/account") && !userId) {
    return new Response(null, { status: 302, headers: { Location: "/login" } });
  }

  return resolve(event);
};
To make event.locals.userId type-safe, declare it in app.d.ts:
src/app.d.ts
declare global {
  namespace App {
    interface Locals {
      userId: string | null;
    }
  }
}

export {};

Client-Side Fetching (After Hydration)

In Svelte components, use the client accessors directly:
<script>
  import { onMount } from "svelte";
  import { storefront } from "$lib/storefront";

  let products = [];

  onMount(async () => {
    const sdk = storefront.publicStorefront();
    const { data } = await sdk.catalog.listProducts({ limit: 20 });
    products = data?.products ?? [];
  });
</script>

{#each products as product}
  <div>{product.name}</div>
{/each}
Session-bound operations (cart, wishlist, account):
<script>
  import { storefront } from "$lib/storefront";

  async function addToWishlist(productId: string) {
    const sdk = storefront.clientStorefront();
    await sdk.cart.addToWishlist({
      product_id: productId,
      variant_id: null,
    });
  }
</script>

Static Prerendering

For prerendered pages, use publicStorefront() in a universal load function:
src/routes/products/[slug]/+page.ts
import { storefront } from "$lib/storefront";

export const prerender = true;

export async function load({ params }) {
  const sdk = storefront.publicStorefront();
  const { data, error } = await sdk.catalog.getProductDetail({
    product_id_or_slug: params.slug,
  });

  if (error || !data) return { product: null };
  return { product: data.product };
}
publicStorefront() never creates sessions, never reads/writes cookies, and is safe for prerender and build-time contexts.

Hosted Checkout + SvelteKit

If you use Commerce Engine Hosted Checkout, follow this pattern to keep the storefront SDK session and checkout session in sync.
1

Add onTokensUpdated to Storefront Config

This callback forwards token updates from the storefront SDK to the checkout iframe.
src/lib/storefront-config.ts
import { Environment } from "@commercengine/storefront";
import type { SvelteKitStorefrontConfig } from "@commercengine/storefront/sveltekit";
import { env } from "$env/static/public";

export const storefrontConfig: SvelteKitStorefrontConfig = {
  storeId: env.PUBLIC_STORE_ID,
  apiKey: env.PUBLIC_API_KEY,
  environment: Environment.Staging,
  tokenStorageOptions: { prefix: "myapp_" },
  onTokensUpdated: (accessToken, refreshToken) => {
    if (typeof window !== "undefined") {
      void import("@commercengine/checkout").then(({ getCheckout }) => {
        getCheckout().updateTokens(accessToken, refreshToken);
      });
    }
  },
};
2

Bootstrap + Init Checkout in Root Layout

Call both storefront.bootstrap() and initCheckout() in the root layout. The checkout is initialized with authMode: "provided" so the storefront SDK owns the session.
src/routes/+layout.svelte
<script>
  import { onMount, onDestroy } from "svelte";
  import { storefront } from "$lib/storefront";

  onMount(async () => {
    await storefront.bootstrap();

    const sdk = storefront.clientStorefront();
    const accessToken = await sdk.getAccessToken();
    const refreshToken = await sdk.session.peekRefreshToken();

    const { initCheckout } = await import("@commercengine/checkout");
    initCheckout({
      storeId: import.meta.env.PUBLIC_STORE_ID,
      apiKey: import.meta.env.PUBLIC_API_KEY,
      authMode: "provided",
      accessToken: accessToken ?? undefined,
      refreshToken: refreshToken ?? undefined,
      onTokensUpdated: ({ accessToken, refreshToken }) => {
        void sdk.setTokens(accessToken, refreshToken);
      },
    });
  });

  onDestroy(() => {
    if (typeof window !== "undefined") {
      import("@commercengine/checkout").then(({ destroyCheckout }) => {
        destroyCheckout();
      });
    }
  });
</script>

<slot />
Install @commercengine/checkout separately if you use hosted checkout. The authMode: "provided" setting tells checkout to use your storefront SDK tokens instead of managing its own.
3

Use Checkout in Components

Then use the checkout store in any Svelte component — no provider needed:
src/components/CartButton.svelte
<script>
  import { checkout } from "@commercengine/checkout/svelte";
</script>

<button onclick={() => $checkout.openCart()} disabled={!$checkout.isReady}>
  Cart ({$checkout.cartCount})
</button>

Common Pitfalls

LevelIssueSolution
CRITICALUsing publicStorefront() for cart, auth, or order flowsUse serverStorefront(cookies) on the server or clientStorefront() in the browser
CRITICALImporting $lib/server/storefront in a .svelte componentServer storefront must only be used in +page.server.ts, +layout.server.ts, hooks.server.ts, or +server.ts
HIGHMissing bootstrap in root layoutAdd onMount bootstrap in +layout.svelte
HIGHForgetting to pass cookies to serverStorefront()Always pass cookies from the load/action/hook context — there is no global cookie access
HIGHUsing @commercengine/ssr-utils directlyUse the first-party wrapper @commercengine/storefront/sveltekit instead
MEDIUMUsing server load when universal load sufficesUse +page.ts (universal) for public catalog reads — they work on both server and client

Best Practices

Shared Config

Export storefrontConfig from storefront-config.ts and import it in both client and server storefront modules. Keep configuration in one place.

Server Boundaries

Place server storefront in $lib/server/ — SvelteKit enforces this boundary at build time with a compile error if client code imports from it.

Universal vs Server Load

Use universal +page.ts with publicStorefront() for public catalog reads. Use +page.server.ts with serverStorefront(cookies) only when you need session data.

Form Actions for Mutations

Auth, cart updates, and order creation should go through form actions using serverStorefront(cookies) so cookies are read and written correctly.

Cross-References

SDK Installation

Core SDK setup and framework detection guide.

Token Management

Full token lifecycle, cookie hydration, and returning-user flow.

Authentication Guide

OTP, email, and social login patterns for storefronts.

Hosted Checkout (Svelte)

Svelte integration for Commerce Engine Checkout.