> ## Documentation Index
> Fetch the complete documentation index at: /docs/llms.txt
> Use this file to discover all available pages before exploring further.

# React Quickstart

> Build a direct-to-Crave storefront with React, Vite, and the Storefront SDK.

Build a browser storefront with React and `@craveup/storefront-sdk`. Public catalog reads are anonymous. Cart mutations use the short-lived capability returned when the cart is created; no API key or BFF is required.

## Prerequisites

* Node.js 20+ and a package manager
* A published Crave merchant and location
* The public Crave API origin for your environment
* The exact deployed browser origin registered with Crave

## 1. Create the app and install the SDK

```bash theme={null}
npm create vite@latest my-storefront -- --template react-ts
cd my-storefront
npm install --save-exact @craveup/storefront-sdk@2.0.1
```

## 2. Configure public identifiers

```env filename=".env" theme={null}
VITE_CRAVEUP_API_URL=https://api.craveup.com
VITE_LOCATION_ID=loc_your_location_id
VITE_MERCHANT_SLUG=your-restaurant-slug
```

These values identify a public API and published resources. Do not put Crave admin, partner, payment-provider, or integration secrets in a `VITE_` variable.

## 3. Create a tab-scoped session store

```ts filename="src/lib/storefront.ts" theme={null}
import {
  createStorefrontClient,
  type StorefrontCartSession,
  type StorefrontSessionStore,
} from "@craveup/storefront-sdk";

const apiUrl = import.meta.env.VITE_CRAVEUP_API_URL;
const merchantSlug = import.meta.env.VITE_MERCHANT_SLUG;
if (!apiUrl || !merchantSlug)
  throw new Error("Missing Storefront configuration");

const apiOrigin = new URL(apiUrl).origin;
const storageKeyPart = (value: string) =>
  encodeURIComponent(value).replaceAll(".", "%2E");
const environmentNamespace = storageKeyPart(apiOrigin);
const scope = `${environmentNamespace}.${storageKeyPart(merchantSlug)}`;
const storageKey = (kind: "cart" | "receipt", resourceId: string) =>
  `storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`;
const cartKey = (locationId: string) => storageKey("cart", locationId);
const receiptKey = (receiptId: string) => storageKey("receipt", receiptId);

function readCartSession(locationId: string): StorefrontCartSession | null {
  const key = cartKey(locationId);
  const raw = sessionStorage.getItem(key);
  if (!raw) return null;

  try {
    const value = JSON.parse(raw) as Partial<StorefrontCartSession>;
    if (
      value.locationId !== locationId ||
      typeof value.cartId !== "string" ||
      typeof value.revision !== "number" ||
      !Number.isInteger(value.revision) ||
      value.revision < 0 ||
      (value.accessToken !== undefined && typeof value.accessToken !== "string")
    ) {
      throw new Error("Invalid cart session");
    }
    return value as StorefrontCartSession;
  } catch {
    sessionStorage.removeItem(key);
    return null;
  }
}

export const sessionStore: StorefrontSessionStore = {
  get(locationId) {
    return readCartSession(locationId);
  },
  set(session) {
    sessionStorage.setItem(
      cartKey(session.locationId),
      JSON.stringify(session),
    );
  },
  clear(locationId) {
    sessionStorage.removeItem(cartKey(locationId));
  },
};

export const receiptTokenStore = {
  get: (receiptId: string) => sessionStorage.getItem(receiptKey(receiptId)),
  set: (receiptId: string, token: string) =>
    sessionStorage.setItem(receiptKey(receiptId), token),
  clear: (receiptId: string) =>
    sessionStorage.removeItem(receiptKey(receiptId)),
};

export const storefront = createStorefrontClient({
  baseUrl: apiOrigin,
  sessionStore,
  getMerchantSlug: () => merchantSlug,
});
```

The SDK attaches `X-Cart-Token`, `If-Match`, and `Idempotency-Key` where required and updates the stored cart revision from response ETags. The canonical API environment, merchant, and resource scopes prevent capabilities from crossing storefronts. For signed-in customer flows, create the client after Clerk is ready and add `getAuthToken: () => getToken({ template: "crave-customer" })`; never persist that JWT yourself. Save a fragment-delivered receipt capability only with `receiptTokenStore.set(receiptId, token)` and remove it after terminal display.

## 4. Fetch the published merchant

```tsx filename="src/App.tsx" theme={null}
import { useEffect, useState } from "react";
import type { MerchantApiResponse } from "@craveup/storefront-sdk";
import { storefront } from "./lib/storefront";

export default function App() {
  const [merchant, setMerchant] = useState<MerchantApiResponse | null>(null);

  useEffect(() => {
    storefront.merchant
      .getBySlug(import.meta.env.VITE_MERCHANT_SLUG)
      .then(setMerchant);
  }, []);

  if (!merchant) return <p>Loading...</p>;
  return <h1>{merchant.name}</h1>;
}
```

## 5. Start a cart and add an item

```ts theme={null}
const locationId = import.meta.env.VITE_LOCATION_ID;
const session = await storefront.orderingSessions.start(locationId, {
  marketplaceId: "web",
  fulfillmentMethod: "takeout",
});

const cart = await storefront.cart.addItem(locationId, session.cart.id, {
  productId: "prod_margherita",
  quantity: 1,
  selections: [],
  itemUnavailableAction: "remove_item",
});
```

Keep the cart capability in tab-scoped storage. On `CART_CONFLICT`, refresh the cart and ask the user to retry; do not replay the mutation automatically. Clear the cart capability after claim, deletion, expiry, or terminal checkout handling. Clerk owns logout and customer-session cleanup.

`marketplaceId` is an optional source-attribution label, such as `"web"`; it is
not a location ID. The route already scopes the cart to `locationId`.

## 6. Register the browser origin

Register the exact deployed origin—including scheme, hostname, and any non-default port—with Crave before testing. Register stable preview origins separately; wildcard origins are not accepted.

## Next steps

> [card] [Display Menu](/docs/guides/display-menu.md)
>
> Fetch the published menu and product data.

> [card] [Manage Cart](/docs/guides/manage-cart.md)
>
> Handle capabilities, revisions, and idempotent mutations.

> [card] [Checkout Flow](/docs/guides/checkout-flow.md)
>
> Submit the canonical order and poll its authoritative order-first outcome.

> [card] [SDK Reference](/docs/getting-started/storefront-sdk.md)
>
> See the complete direct API client.

<Snippet file="need-help.mdx" />


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.