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

# Build a Custom Storefront

> Scaffold and extend a direct, capability-safe Crave ordering experience.

## 1. Scaffold with the CLI

```bash theme={null}
npx @craveup/cli init
```

The CLI may authenticate you to discover or validate merchant resources, but it does not write a private credential into the generated browser app.

## 2. Configure public values

```env theme={null}
NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com
NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_456def
NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=downtown-pizza
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_xxx
```

The API origin is configuration, not a credential. Use the sandbox origin and
publishable key in non-production environments. These merchant and location
variables are standalone/local overrides. The hosted template resolves the
merchant from its controlled host/subdomain and the location from the
`[locationId]` route.

## 3. Create separate browser and server clients

Browser code supplies a tab-scoped cart `sessionStore` and `getAuthToken`. Server Components create a client with the same explicit `baseUrl` and perform anonymous published reads only.

```ts theme={null}
import { createStorefrontClient } from "@craveup/storefront-sdk";

export const storefront = createStorefrontClient({
  baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
  sessionStore,
  getAuthToken,
  getMerchantSlug: () => merchantSlug,
});
```

## 4. Follow the capability lifecycle

1. Load published merchant, location, and menu data anonymously.
2. Start an ordering session with a generated idempotency key.
3. Store the returned cart capability in versioned `sessionStorage`, scoped by canonical API environment, merchant, and location.
4. Let the SDK attach the capability, `If-Match`, and idempotency headers.
5. On `CART_CONFLICT`, reload the cart before asking the shopper to retry.
6. Clear the capability after claim, deletion, expiry, or terminal checkout handling.

## 5. Checkout

Ask the server which collection modes the current cart may use. This is a
preflight only; submission repeats every authority and exposure check.

```ts theme={null}
const collectionOptions = await storefront.checkout.getCollectionOptions(
  locationId,
  cartId,
  { revision: cart.revision },
);
```

Render `Pay online` only when the `ONLINE` option is available. Render `Pay at
store` only when `PAY_AT_STORE` is available, and use its server-returned
Square, Clover, or Toast label to explain where payment will be collected.

```ts theme={null}
const outcome = await storefront.checkout.submitOrder(
  locationId,
  cartId,
  {
    acceptedCatalogEvidence,
    acceptedDiscountEvidence,
    collectionMode: "ONLINE",
    customerConfirmationRevision,
    quoteFingerprint,
  },
  { idempotencyKey, revision: cart.revision, includeCustomerContext: true },
);
```

Use `includeCustomerContext` only when a signed-in shopper still has a guest
capability and the selected provider needs customer association. Omit it for
guest checkout; a claimed cart uses customer authentication automatically.
Render account-scoped Stripe Elements only when `outcome.status` is
`CUSTOMER_ACTION_REQUIRED`, then poll `checkout.getOrderSubmissionOutcome` with
the returned attempt id. Treat `PROCESSING` as retryable, render success only
for `PAID`, and surface `ACTION_REQUIRED` without creating another order.

For pay at store, submit the same payload with
`collectionMode: "PAY_AT_STORE"` and omit `savedPaymentMethodId`. An
`ACCEPTED` response confirms the order, not payment. Continue polling the same
attempt while the selected POS supplies native tender evidence. Never render a
POS browser payment form, provider-hosted checkout, saved card, or gift-card
input for pay at store. If evidence is delayed, show **Verifying payment — do
not charge again** rather than creating a second collection attempt.

## 6. Customer and receipt access

Clerk owns customer sign-in and session recovery. After Clerk is ready, supply its active-session `getToken({ template: "crave-customer" })` callback as `getAuthToken`, keep `getMerchantSlug`, and never persist the customer JWT yourself. Receipt links use a purpose-limited fragment capability: remove the fragment immediately, keep the token in tab-scoped storage keyed by API environment, merchant, and receipt ID, and pass it through `receipts.get`.

## 7. Deploy

* Configure the exact storefront origin in the API CORS allowlist.
* Keep provider and private integration credentials on the API service.
* Run tests, lint, type checking, a production build, and a static-bundle secret scan.
* Verify a real published catalog read and a sandbox ordering flow before promoting production.

See [Deployment](/docs/guides/deployment.md) and the [Storefront SDK](/docs/getting-started/storefront-sdk.md) for the full contract.


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