# Record an allowlisted Storefront analytics event
Source: https://docs.craveup.com/api-reference/analytics/record-an-allowlisted-storefront-analytics-event
/generated/storefront.openapi.json post /locations/{locationId}/analytics-events
# Add an item to a cart
Source: https://docs.craveup.com/api-reference/cart-items/add-an-item-to-a-cart
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/items
# Remove an item from a cart
Source: https://docs.craveup.com/api-reference/cart-items/remove-an-item-from-a-cart
/generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/items/{itemId}
# Update a cart item quantity
Source: https://docs.craveup.com/api-reference/cart-items/update-a-cart-item-quantity
/generated/storefront.openapi.json patch /locations/{locationId}/carts/{cartId}/items/{itemId}
# Accept the current authoritative cart changes
Source: https://docs.craveup.com/api-reference/carts/accept-the-current-authoritative-cart-changes
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/catalog-revalidation/accept
# Claim a guest cart for the signed-in customer
Source: https://docs.craveup.com/api-reference/carts/claim-a-guest-cart-for-the-signed-in-customer
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/claim
# Expire a cart
Source: https://docs.craveup.com/api-reference/carts/expire-a-cart
/generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}
# Get an authorized cart
Source: https://docs.craveup.com/api-reference/carts/get-an-authorized-cart
/generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}
# List cart recommendations
Source: https://docs.craveup.com/api-reference/carts/list-cart-recommendations
/generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/products
# Set cart delivery address
Source: https://docs.craveup.com/api-reference/carts/set-cart-delivery-address
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-delivery
# Set room service details
Source: https://docs.craveup.com/api-reference/carts/set-room-service-details
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-room
# Set table service details
Source: https://docs.craveup.com/api-reference/carts/set-table-service-details
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/set-table
# Start a cart at another merchant location
Source: https://docs.craveup.com/api-reference/carts/start-a-cart-at-another-merchant-location
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/location-switches
# Update cart fields
Source: https://docs.craveup.com/api-reference/carts/update-cart-fields
/generated/storefront.openapi.json patch /locations/{locationId}/carts/{cartId}
# Update cart gratuity
Source: https://docs.craveup.com/api-reference/carts/update-cart-gratuity
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/gratuity
# Update cart order time
Source: https://docs.craveup.com/api-reference/carts/update-cart-order-time
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/update-order-time
# Update the DoorDash courier tip
Source: https://docs.craveup.com/api-reference/carts/update-the-doordash-courier-tip
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/courier-tip
# Confirm an authoritative provider payment
Source: https://docs.craveup.com/api-reference/checkout/confirm-an-authoritative-provider-payment
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/payments
# Create a one-time checkout handoff
Source: https://docs.craveup.com/api-reference/checkout/create-a-one-time-checkout-handoff
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/checkout-handoffs
# Create or recover the authoritative cart payment session
Source: https://docs.craveup.com/api-reference/checkout/create-or-recover-the-authoritative-cart-payment-session
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/payment-session
# Exchange a one-time checkout handoff
Source: https://docs.craveup.com/api-reference/checkout/exchange-a-one-time-checkout-handoff
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/checkout-handoffs/exchange
# Poll checkout order state
Source: https://docs.craveup.com/api-reference/checkout/poll-checkout-order-state
/generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/order-result
# Submit a rating after successful payment
Source: https://docs.craveup.com/api-reference/checkout/submit-a-rating-after-successful-payment
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/rating
# Validate checkout and update customer contact details
Source: https://docs.craveup.com/api-reference/checkout/validate-checkout-and-update-customer-contact-details
/generated/storefront.openapi.json put /locations/{locationId}/carts/{cartId}/validate-and-update
# Revoke the customer session
Source: https://docs.craveup.com/api-reference/customer-authentication/revoke-the-customer-session
/generated/storefront.openapi.json delete /customer/logout
# Start merchant-bound customer login
Source: https://docs.craveup.com/api-reference/customer-authentication/start-merchant-bound-customer-login
/generated/storefront.openapi.json post /customer/auth/login
# Verify customer OTP
Source: https://docs.craveup.com/api-reference/customer-authentication/verify-customer-otp
/generated/storefront.openapi.json post /customer/auth/verify-otp
# Buy and activate a Square digital gift card
Source: https://docs.craveup.com/api-reference/customers/buy-and-activate-a-square-digital-gift-card
/generated/storefront.openapi.json post /customer/locations/{locationId}/gift-cards/sales
# Confirm a customer phone verification code
Source: https://docs.craveup.com/api-reference/customers/confirm-a-customer-phone-verification-code
/generated/storefront.openapi.json post /customer/phone-verification/confirmation
# Create a customer address
Source: https://docs.craveup.com/api-reference/customers/create-a-customer-address
/generated/storefront.openapi.json post /customer/addresses
# Delete a customer address
Source: https://docs.craveup.com/api-reference/customers/delete-a-customer-address
/generated/storefront.openapi.json delete /customer/addresses/{addressId}
# Delete a saved payment method
Source: https://docs.craveup.com/api-reference/customers/delete-a-saved-payment-method
/generated/storefront.openapi.json delete /customer/locations/{locationId}/saved-payments/{paymentId}
# Get Square digital gift-card sale settings
Source: https://docs.craveup.com/api-reference/customers/get-square-digital-gift-card-sale-settings
/generated/storefront.openapi.json get /customer/locations/{locationId}/gift-cards/sale-session
# Get the signed-in customer profile
Source: https://docs.craveup.com/api-reference/customers/get-the-signed-in-customer-profile
/generated/storefront.openapi.json get /customer
# List customer addresses
Source: https://docs.craveup.com/api-reference/customers/list-customer-addresses
/generated/storefront.openapi.json get /customer/addresses
# List saved payment methods
Source: https://docs.craveup.com/api-reference/customers/list-saved-payment-methods
/generated/storefront.openapi.json get /customer/locations/{locationId}/saved-payments
# Send a customer phone verification code
Source: https://docs.craveup.com/api-reference/customers/send-a-customer-phone-verification-code
/generated/storefront.openapi.json post /customer/phone-verification
# Update a customer address
Source: https://docs.craveup.com/api-reference/customers/update-a-customer-address
/generated/storefront.openapi.json patch /customer/addresses/{addressId}
# Apply a discount code
Source: https://docs.craveup.com/api-reference/discounts/apply-a-discount-code
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/discount
# Remove the applied discount
Source: https://docs.craveup.com/api-reference/discounts/remove-the-applied-discount
/generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/discount
# Calculate distance to a location
Source: https://docs.craveup.com/api-reference/locations/calculate-distance-to-a-location
/generated/storefront.openapi.json post /locations/{locationId}/distance
# Get a published location
Source: https://docs.craveup.com/api-reference/locations/get-a-published-location
/generated/storefront.openapi.json get /locations/{locationSlugOrId}
# Get available order times
Source: https://docs.craveup.com/api-reference/locations/get-available-order-times
/generated/storefront.openapi.json get /locations/{locationId}/time-intervals
# Get gratuity configuration
Source: https://docs.craveup.com/api-reference/locations/get-gratuity-configuration
/generated/storefront.openapi.json get /locations/{locationId}/gratuity
# Redirect to the hosted location storefront
Source: https://docs.craveup.com/api-reference/locations/redirect-to-the-hosted-location-storefront
/generated/storefront.openapi.json get /locations/{locationId}/redirect
# Get customer loyalty balances and ledger
Source: https://docs.craveup.com/api-reference/loyalty/get-customer-loyalty-balances-and-ledger
/generated/storefront.openapi.json get /customer/locations/{locationId}/loyalty/ledger
# Get loyalty earning and reward options
Source: https://docs.craveup.com/api-reference/loyalty/get-loyalty-earning-and-reward-options
/generated/storefront.openapi.json get /locations/{locationId}/carts/{cartId}/loyalty/quote
# Join the checkout's authoritative loyalty program
Source: https://docs.craveup.com/api-reference/loyalty/join-the-checkouts-authoritative-loyalty-program
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/loyalty/enrollment
# List customer loyalty claims
Source: https://docs.craveup.com/api-reference/loyalty/list-customer-loyalty-claims
/generated/storefront.openapi.json get /customer/loyalty/claims
# Remove the applied loyalty reward
Source: https://docs.craveup.com/api-reference/loyalty/remove-the-applied-loyalty-reward
/generated/storefront.openapi.json delete /locations/{locationId}/carts/{cartId}/loyalty/redeem
# Reserve and apply a loyalty reward
Source: https://docs.craveup.com/api-reference/loyalty/reserve-and-apply-a-loyalty-reward
/generated/storefront.openapi.json post /locations/{locationId}/carts/{cartId}/loyalty/redeem
# Submit a loyalty claim
Source: https://docs.craveup.com/api-reference/loyalty/submit-a-loyalty-claim
/generated/storefront.openapi.json post /customer/loyalty/claims
# List the published menu bundle
Source: https://docs.craveup.com/api-reference/menus/list-the-published-menu-bundle
/generated/storefront.openapi.json get /locations/{locationId}/menus
# Get a published merchant and its locations
Source: https://docs.craveup.com/api-reference/merchants/get-a-published-merchant-and-its-locations
/generated/storefront.openapi.json get /merchant/{merchantSlug}
# Check ordering availability without creating a cart
Source: https://docs.craveup.com/api-reference/ordering-sessions/check-ordering-availability-without-creating-a-cart
/generated/storefront.openapi.json get /locations/{locationId}/ordering-readiness
# Create or resume an ordering session
Source: https://docs.craveup.com/api-reference/ordering-sessions/create-or-resume-an-ordering-session
/generated/storefront.openapi.json post /locations/{locationId}/ordering-sessions
# Get an authorized public receipt
Source: https://docs.craveup.com/api-reference/orders/get-an-authorized-public-receipt
/generated/storefront.openapi.json get /receipts/{receiptId}
# Get customer order details
Source: https://docs.craveup.com/api-reference/orders/get-customer-order-details
/generated/storefront.openapi.json get /customer/orders/{orderId}
# List customer orders
Source: https://docs.craveup.com/api-reference/orders/list-customer-orders
/generated/storefront.openapi.json get /customer/orders
# API Overview
Source: https://docs.craveup.com/api-reference/overview/api-overview
Use the direct public Storefront API with capabilities, customer JWTs, and optimistic concurrency.
## Base URLs
```text theme={null}
Production: https://api.craveup.com/api/v1/storefront
Sandbox: https://dev-api-43233223.craveup.com/api/v1/storefront
```
## Authorization
The public API does not use a browser API key.
| Resource | Authorization |
| - | - |
| Published merchant, location, menu, product, order-time, gratuity | Anonymous |
| Guest cart | Purpose-limited `X-Cart-Token` capability |
| Customer profile, orders, addresses, saved payments, loyalty history | Merchant-bound bearer JWT |
| Receipt | Customer JWT or purpose-limited `X-Receipt-Token` capability |
Private admin and integration credentials belong only in trusted server or CLI environments.
## Start an ordering session
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/LOCATION_ID/ordering-sessions" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: session_01JEXAMPLE" \
-d '{"fulfillmentMethod":"takeout"}'
```
The response contains `cart` and, for a newly created guest cart, `cartAccessToken`. Save the token in tab-scoped storage. The response `ETag` contains the cart revision.
## Mutate the cart
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/LOCATION_ID/carts/CART_ID/items" \
-H "Content-Type: application/json" \
-H "X-Cart-Token: CART_CAPABILITY" \
-H 'If-Match: "cart-0"' \
-H "Idempotency-Key: item_01JEXAMPLE" \
-d '{"productId":"PRODUCT_ID","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}'
```
Use the returned `ETag` for the next mutation. A stale revision returns `409 CART_CONFLICT`; reload current cart state before offering a retry.
## Checkout
```text theme={null}
POST /locations/{locationId}/carts/{cartId}/payment-session
POST /locations/{locationId}/carts/{cartId}/payments
GET /locations/{locationId}/carts/{cartId}/order-result
POST /locations/{locationId}/carts/{cartId}/rating
GET /receipts/{receiptId}
```
The order result is one of `payment_pending`, `order_pending`, `completed`, or `failed`. Only `completed` is success.
## Customer authentication
```text theme={null}
POST /customer/auth/login
POST /customer/auth/verify-otp
GET /customer
GET /customer/orders
GET /customer/addresses
GET /customer/saved-payments
GET /customer/locations/{locationId}/loyalty/ledger
```
Login initiation includes only `merchantSlug` and the email/phone identifier. OTP verification
repeats both values and may include customer profile fields after identity proof, binding the
resulting session to one merchant.
## Error envelope
```json theme={null}
{
"code": "CART_CONFLICT",
"message": "The cart changed. Refresh it before retrying.",
"requestId": "req_123",
"details": {}
}
```
Common statuses are `400` validation, `401` invalid capability/session, `404` hidden or missing resource, `409` state/revision conflict, `429` rate limit, `503` dependency unavailable, and `500` internal error.
## SDK
```ts theme={null}
const crave = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
sessionStore,
getAuthToken,
});
```
Prefer the SDK for TypeScript storefronts because it owns capability headers, revisions, idempotency defaults, timeouts, and typed errors.
# Core Concepts
Source: https://docs.craveup.com/api-reference/overview/core-concepts
Understand the data model behind merchants, locations, menus, carts, and orders.
The Storefront API is built around five entities: **merchants**, **locations**, **menus**, **carts**, and **orders**. This page explains how they relate to each other and what data each one carries.
## Merchants
A merchant represents a restaurant brand. Each merchant has a name, logo, and one or more locations.
```ts theme={null}
const merchant = await storefront.merchant.getBySlug('downtown-pizza');
merchant.id; // "5b2e7f71-62cf-4bd5-b84e-df17f1f18927"
merchant.name; // "Downtown Pizza Co."
merchant.locations; // MerchantLocation[]
```
Use `merchant.getBySlug()` to resolve a brand slug into its list of locations. Each location in the response includes an `id`, display name, address, logo, and a `methodsStatus` object indicating which fulfillment methods are enabled.
## Locations
A location is a single restaurant site. All storefront operations — menus, carts, payments — are scoped to a location.
```ts theme={null}
const location = await storefront.locations.getById('f8af3da4-f70e-4521-a04f-716c0c04fb77');
location.restaurantDisplayName; // "Downtown Pizza — Market St"
location.addressString; // "123 Market St, San Francisco, CA 94105"
location.addressData; // { street, city, state, zipCode, country, lat, lng }
```
### Location identifiers
You can reference a location by either its UUID or slug. Both work in storefront location endpoints:
```
/api/v1/storefront/locations/f8af3da4-f70e-4521-a04f-716c0c04fb77/...
/api/v1/storefront/locations/downtown-pizza/...
```
### Fulfillment methods
Each location enables a subset of these fulfillment methods:
| Method | Value | Description |
| - | - | - |
| Pickup | `takeout` | Customer picks up at the counter |
| Delivery | `delivery` | Delivered to customer's address |
| Table-side | `table_side` | Served to a table number |
| Room service | `room_service` | Delivered to a hotel room |
Check `methodsStatus` on the merchant location response to determine which options to show:
```ts theme={null}
const loc = merchant.locations[0];
loc.methodsStatus.pickup; // true
loc.methodsStatus.delivery; // true
loc.methodsStatus.table; // false
loc.methodsStatus.roomService; // false
```
### Order times
Locations define when they accept orders. Fetch the available time slots before showing a schedule picker:
```ts theme={null}
const times = await storefront.locations.getOrderTimes('f8af3da4-f70e-4521-a04f-716c0c04fb77');
times.scheduleAllowed; // true — scheduled orders accepted
times.requireScheduledOrders; // false — ASAP is available
times.orderDays; // RegularOrderDay[] or SpecialOrderDay[]
```
## Menus, categories, and products
Each location has one or more **menus**. A menu contains **categories**, and each category links to **products**. The active menu depends on the time of day.
```
Merchant → Location → Menu → Category → Product → Modifier
```
### Products
A product represents a single orderable item:
| Field | Type | Description |
| - | - | - |
| `id` | `string` | Unique product ID |
| `name` | `string` | Display name |
| `description` | `string` | Short description |
| `price` | `string` | Base price as a decimal string (e.g., `"12.99"`) |
| `displayPrice` | `string` | Formatted price with currency symbol |
| `images` | `string[]` | Product image URLs |
| `modifiers` | `Modifier[]` | Available customizations |
| `availability` | `string` | Current availability status |
| `nutrition` | `object \| undefined` | Calorie count, dietary preferences, ingredients |
### Modifiers
Modifiers let customers customize a product (e.g., size, toppings, extras). Each modifier group has selection rules:
```ts theme={null}
interface Modifier {
id: string;
name: string; // "Choose your size"
rule: {
min: number; // 1 — must select at least one
max: number; // 1 — can select at most one
};
items: ModifierItem[]; // [{ id, name, price, maxQuantity }]
}
```
Modifier items can have nested child groups for multi-level customization (e.g., "Choose your protein" > "Choose your preparation").
## Carts
A cart holds the customer's selections and computes all pricing automatically. You create a cart by starting an **ordering session**:
```ts theme={null}
const session = await storefront.orderingSessions.start('loc_123', {
marketplaceId: 'web',
fulfillmentMethod: 'takeout',
});
const cartId = session.cart.id;
```
`marketplaceId` is optional source attribution, such as `"web"` or
`"mobile"`; it is not a location identifier.
### Cart lifecycle
| Status | Meaning |
| - | - |
| `OPEN` | Active — items can be added, removed, or updated |
| `LOCKED` | Payment is processing — no modifications allowed |
| `COMPLETED` | Payment confirmed — order sent to the restaurant |
| `EXPIRED` | Capability is no longer valid; start a new cart |
### Automatic pricing
Every time you modify the cart, the API recalculates all totals:
| Field | Description |
| - | - |
| `subTotal` | Sum of item prices before tax and fees |
| `taxTotal` | Tax based on the location's tax rates |
| `serviceFeeTotal` | Platform service fee |
| `fulfillmentMethodFeeTotal` | Delivery or fulfillment fee |
| `waiterTipTotal` | Customer gratuity |
| `orderTotalWithServiceFee` | Final total the customer pays |
Price fields are decimal strings. Format them using the cart currency and the customer's locale.
### Cart items
Each item in the cart tracks its product, quantity, modifier selections, special instructions, and computed totals:
```ts theme={null}
const cart = await storefront.cart.get('loc_123', cartId);
cart.items.forEach((item) => {
item.name; // "Margherita Pizza"
item.quantity; // 2
item.total; // "25.98"
item.selections; // CartModifierGroup[] — chosen modifiers
item.specialInstructions; // "Extra crispy"
});
```
### Fulfillment configuration
Before checkout, set the fulfillment method on the cart. Each method requires different data:
| Method | API call | Required data |
| - | - | - |
| Pickup | `cart.update(...)` | `{ fulfillmentMethod: 'takeout' }` |
| Delivery | `cart.setDelivery(...)` | Full address with `lat`/`lng` |
| Table-side | `cart.setTable(...)` | Numeric table string such as `"12"` |
| Room service | `cart.setRoom(...)` | Room number and last name |
## Payments
Crave processes payments through Stripe Connect. You create a `PaymentIntent` via the API and confirm it on the client with Stripe.js:
```ts theme={null}
const { clientSecret } = await storefront.checkout.createPaymentIntent('loc_123', cartId);
```
| Field | Description |
| - | - |
| `clientSecret` | Pass to `stripe.confirmPayment()` on the client |
## Orders
Orders are created automatically when a payment succeeds — you do not create them via the API. After payment, the order appears in the restaurant's merchant dashboard for fulfillment.
Poll `storefront.checkout.getOrderResult()` after payment. It distinguishes `payment_pending`, `order_pending`, `completed`, and `failed`. Signed-in customers can list and read only their own orders through `storefront.customer.orders`.
## Analytics events
Track key funnel events to measure storefront performance:
```ts theme={null}
await storefront.analyticsEvents.track('loc_123', {
cartId,
eventType: 'CHECKOUT_VIEW', // 'SCAN' | 'CART_VIEW' | 'CHECKOUT_VIEW'
});
```
Every public analytics event requires authorization for its cart. `ORDER_PLACED` is recorded by the
API during authoritative order creation and is not accepted from clients.
## Currencies and countries
The API supports these currencies and countries:
| Currency | Code | Countries |
| - | - | - |
| US Dollar | `usd` | US |
| British Pound | `gbp` | GB |
| UAE Dirham | `aed` | AE |
| Australian Dollar | `aud` | AU |
Currency is set at the merchant level and applies to all locations under that merchant.
## Next steps
Fetch menus, categories, and products for a location.
Add items, apply modifiers, and manage the cart lifecycle.
Collect customer details, set order time, and process payment.
Configure delivery, table-side, and room service.
# Get a published product
Source: https://docs.craveup.com/api-reference/products/get-a-published-product
/generated/storefront.openapi.json get /locations/{locationId}/products/{productId}
# Changelog
Source: https://docs.craveup.com/changelog
Track improvements to the Crave storefront documentation and OpenAPI reference.
### New
* **Dart and Flutter SDK** – Added first-class installation, environment, secure-session, readiness, and typed-error guidance for `crave_storefront_sdk`.
* **Flutter quickstart** – Added an end-to-end path for configuring a deployment, checking ordering readiness, loading a menu, and starting an order.
### Improved
* **TypeScript SDK** – Updated current installation guidance to the exact `@craveup/storefront-sdk@2.0.1` patch while preserving the durable 1.x migration history.
* **Environment safety** – Clarified that sandbox and production use separate deployments, variables, credentials, and session namespaces rather than an in-app production toggle.
### Fixed
* **OpenAPI reproducibility** – Pointed Mintlify at the reviewed generated Storefront OpenAPI artifact committed with the documentation.
* **Developer portal parity** – Replaced the retired browser-key example and stale documentation domain with the real SDK client and canonical docs links.
### New
* **Return URL support** – Added configurable return URLs across storefront API, admin API, and the SDK so partners can guide guests to custom destinations after checkout.
* **Cart metadata hooks** – Extended cart payloads so loyalty and analytics tools can append metadata without breaking totals.
### Improved
* **Mintlify parity** – Regenerated OpenAPI artifacts directly from Express routes and rewrote MDX guides so docs mirror production behaviour.
* **SDK alignment** – Synced storefront SDK method names and request signatures with the latest Swagger definitions.
### Fixed
* **Spec drift** – Resolved schema mismatches between Swagger and Mintlify by pointing both builds at the same generated artifact.
* **Checkout redirects** – Patched edge cases where return URLs were dropped for certain ordering sessions.
### New
* **Orders hub** – Introduced a token-protected orders portal with session creation, revocation, and analytics wiring for operations teams.
* **Embeddable pricing** – Shipped a drop-in pricing table, enterprise entity management, and sales-friendly landing pages.
* **Responsive UI primitives** – Published responsive dialog and sheet components in the shared UI package for both dashboard and storefront use.
### Improved
* **Tailwind v4 + shadcn** – Migrated every app to the Tailwind v4 pipeline, standardized tokens, and removed duplicated component code.
* **Onboarding guidance** – Added unsaved-change bars, fulfilment method milestones, and confetti banners so merchants know when setup is complete.
### Fixed
* **Checkout ergonomics** – Reworked the cart sidebar, order time picker, and thank-you flow to eliminate crashy states.
* **Error consistency** – Normalized API error responses and logging across admin, storefront, and order services.
### New
* **Location wizard** – Added account-setup steps with map pickers, contact info endpoints, and multi-location management inside the dashboard.
* **Order manager API** – Expanded admin endpoints for active, scheduled, and historical orders with dashboards and detail drawers.
* **Crave Admin shell** – Launched the standalone site for enterprise management alongside embeddable pricing and enterprise profiles.
### Improved
* **Discount workflow** – Polished validation, loading states, and edit/delete flows for promotions.
* **Ordering session flow** – Hardened session APIs, menu bundles, and redirect handling so storefronts stay in sync.
### Fixed
* **Environment + slug bugs** – Resolved slug mismatches, missing env vars, and redirect loops introduced during the restructure.
* **Checkout error states** – Tightened error messaging for bundle fetches, checkout validation, and order receipts.
### New
* **Store settings redesign** – Rolled out cohesive layouts for store info, fulfilment, holiday hours, tips, and notifications with mobile-friendly navigation.
* **Developer workspace** – Introduced a Resend-inspired developer page with code previews, dark mode, and API contact flows.
* **Onboarding wizard** – Delivered a multi-step welcome flow, sample dashboards, and progress trackers for new organizations.
### Improved
* **Multi-location support** – Upgraded navigation, location selectors, and analytics to handle complex brands.
* **Subscription management** – Updated pricing tiers, added cancellation banners, and clarified plan benefits in product copy.
### Fixed
* **Stripe + auth flows** – Hardened payment intent handling, sign-up flows, and logout paths across admin and storefront apps.
* **Dark mode polish** – Cleaned up contrast issues across headers, buttons, and schedule components.
### New
* **Menu templates & defaults** – Seeded menu, category, and modifier forms with templates, default schedules, and currency hints to shorten setup.
* **Stytch metadata sync** – Synced organization metadata, subscription state, and location identifiers directly with Stytch.
### Improved
* **Error boundary middleware** – Wrapped every admin route with a shared `withErrorHandler` helper to centralize logging and responses.
* **Store settings** – Upgraded tipping logic, address flows, and onboarding redirects to guide merchants to completion.
### Fixed
* **Validation gaps** – Smoothed subscription persistence, rehydrated location IDs, and tightened toast feedback during onboarding.
* **Access controls** – Restricted dashboard-only routes and enforced metadata checks before exposing sensitive endpoints.
### New
* **Comprehensive Swagger docs** – Authored storefront, admin, menu, analytics, discounts, and team endpoints with generated JSON sources.
* **Shared packages** – Promoted env, db, logger, and utils into reusable workspace modules with type-safe exports.
### Improved
* **Next.js 15 + modules** – Upgraded partner and storefront apps to Next.js 15, reorganized imports, and aligned modules with ESM standards.
* **Repository hygiene** – Removed legacy guest ordering code, normalized naming, and cleaned duplicate utilities.
### Fixed
* **Stripe + webhook logging** – Standardized logging hooks and resolved Stripe route regressions uncovered during doc generation.
* **Storybook coverage** – Patched component loaders and accessibility props flagged while documenting new flows.
### New
* **Modular apps** – Split the monorepo into dedicated Express admin/storefront APIs and Next.js ordering/partner surfaces using the latest Turbo scaffolding.
* **Admin console prototype** – Bootstrapped the Crave Admin app with data tables, navigation, and shared UI packages.
* **UI kit publishing** – Added shadcn-ready components plus shared ESLint and TypeScript configs for every project.
### Improved
* **Type safety** – Centralized types, resolved module resolution pitfalls, and aligned packages on strict TypeScript settings.
* **Build tooling** – Added pnpm/Turbo scripts to install dependencies, lint, and test reliably across workspaces.
### Fixed
* **Legacy imports** – Cleared unused hooks, duplicate components, and inconsistent paths carried over from earlier repos.
* **Order endpoints** – Stabilized active/history/scheduled order routes ahead of the new admin console.
### New
* **Customer card metadata** – Aligned stored card records with customer IDs so repeat guests see the right payment options.
* **Form primitives** – Introduced reusable checkbox and wrapper components ahead of the full shadcn migration.
### Improved
* **Checkout layout** – Tightened page wrappers, skeleton states, and CTA spacing to reduce friction on mobile.
* **Cart experience** – Smoothed quantity controls and tip defaults while preparing the cart sidebar revamp.
### Fixed
* **Payment glitches** – Resolved regressions around card selection, validation, and optimistic UI while orders are syncing.
* **Styling drift** – Squashed stray pointer-events, HTML entities, and prop casing issues that surfaced after the holiday builds.
### New
* **Transactional messaging** – Wired SendGrid email and Twilio SMS sends so guests and teams receive real-time order confirmations.
* **Store preview mockup** – Embedded an iPhone live preview within store settings so teams see branding updates instantly.
* **Menu manager shortcuts** – Enabled expanding categories by default and adding items/categories directly from the overview screen.
### Improved
* **Orders workspace** – Added filtered views, range calendars, and status chips to streamline fulfillment workflows.
* **Dashboard navigation** – Introduced dynamic store-settings navigation, refreshed onboarding copy, and persisted trial banners.
### Fixed
* **Checkout polish** – Tightened tip defaults, modifier validation, payment button behaviour, and cart totals after shadcn component migration.
* **Menu reliability** – Resolved nested button hydration errors, null checks, and pointer-event regressions spotted during QA.
### New
* **Order operations workspace** – Added filtered order lists, export tooling, and richer status insights so operators can reconcile sales without leaving the dashboard.
* **Team invitations & roles** – Introduced invite flows with pending/expired states, location scoping, and inline status badges for a clearer view of collaborator access.
* **API key lifecycle controls** – Surfaced key creation and deletion inside the developer settings so teams can rotate credentials without engineering help.
### Improved
* **Onboarding completion flow** – Refined step guidance, loaders, and success messaging to celebrate completion and nudge merchants toward key setup actions.
* **Location metadata** – Polished slugs, SEO metadata, and storefront links for cleaner sharing and cross-linking across partner surfaces.
### Fixed
* **Authentication cookies** – Hardened Stytch cookie handling to resolve cross-domain login edge cases and flaky session persistence.
* **Address capture** – Patched location onboarding to guard against partially saved address steps and duplicate business details.
### New
* **Developer center** – Published an in-app developers page with API key generation, waiter tip configuration, and quick links back to docs.
* **Discount builder** – Delivered a guided form for creating percentage and amount-off promos with usage limits, schedules, and summary previews.
* **Store settings depth** – Added waiter-tip policies, tax rate cards, and granular fulfillment scheduling to cover operational edge cases.
### Improved
* **Onboarding progression** – Synced the universal task checklist with confetti states, loader components, and error handling for a smoother start.
* **Performance polish** – Introduced shimmer loaders, refined layout spacing, and reorganized folders to keep the dashboard responsive at scale.
### Fixed
* **Endpoint reliability** – Tightened API contracts, types, and environment configuration so admin traffic stops tripping validation errors in production.
* **Discount validation** – Addressed edge cases around duplicate codes, date ranges, and negative values before launch.
### New
* **Guided onboarding** – Rolled out progress badges, celebratory confetti, and contextual upsells so merchants know exactly what unlocks go-live.
* **Menu studio** – Enabled create/edit/delete flows for menus, categories, products, and modifier groups directly inside the dashboard.
* **Team management** – Connected invite, delete, and list endpoints so admins can manage collaborators without support tickets.
### Improved
* **Fulfillment settings** – Added tabbed forms, schedule pickers, and defaults across delivery, pickup, and room service to cut configuration time.
* **Store hours tooling** – Synced location hours with validation, auto-fill defaults, and address autocomplete to reduce onboarding friction.
### Fixed
* **Authentication & routing** – Resolved stale token handling, ensured location fetches respect organization context, and removed unused codepaths.
### New
* **Stripe subscriptions** – Wired subscription plans, free trial entry points, and Connect onboarding so teams can self-serve upgrades.
* **Discount workflows** – Completed the promotion builder with date/time scheduling, code generation, and React Hook Form summaries.
### Improved
* **Start free trial UX** – Refined copy, country defaults, and toast feedback to boost conversion through the billing funnel.
* **Connect experiences** – Iterated on Stripe Connect screens and reusable dropdowns for a consistent payments setup journey.
### Fixed
* **Location data hydration** – Hardened location loaders and hook defaults so dashboards stop flashing empty states on refresh.
* **Package hygiene** – Raised dependency baselines to clear high-severity security advisories.
### New
* **Stytch SSO** – Replaced bespoke auth with Stytch-powered signup, login, and organization switching, unlocking OAuth and email-first flows.
* **Subscription plans hub** – Released subscription management with plan selection, add-ons, and plan enforcement inside the dashboard.
* **Analytics foundation** – Delivered merchant analytics endpoints, export tooling, and dashboard charts for real-time performance tracking.
### Improved
* **Scheduling UI** – Rebuilt fulfillment scheduling with reusable components, validation, and error prompts that mirror the live storefront logic.
* **Order operations** – Added order action toasts, filtered lists, and skeleton states to help managers triage tickets faster.
### Fixed
* **Security posture** – Cleared outstanding vulnerability alerts by bumping packages and removing unused dependencies.
* **Integration wiring** – Consolidated scattered fetchers and unused endpoints to keep telemetry dashboards accurate.
### New
* **OTP flows** – Added resend timers, auto-submit, and inline error toasts for a seamless verification experience on checkout.
* **Saved payment controls** – Enabled guests to remove saved cards and clear cart state once an order is complete.
### Improved
* **Loading states** – Rolled out skeletons, loaders, and disabled states across homepage, cart, and checkout to improve perceived performance.
* **Error messaging** – Surfaced precise validation feedback for modifiers, order time selectors, and fulfilment switching.
### Fixed
* **UI alignment** – Patched navbar spacing, modal heights, and image ratios so the storefront feels polished on every device.
* **Cart consistency** – Squashed duplicate items and race conditions introduced by rapid add/remove actions.
### New
* **Schedule delivery skeletons** – Added skeleton states, map previews, and order-time selectors that respect location availability.
* **Complementary cart** – Recommended add-on items with dedicated sliders and API-backed data.
* **Room service fulfillment** – Brought table-side fulfilment parity with delivery and pickup, including modal flows and validations.
### Improved
* **Cart logic** – Refactored prepare-cart flows, ensured location IDs propagate, and tightened revalidation.
* **Fulfillment designs** – Unified styling and responsiveness for fulfilment tabs, modals, and empty states.
### Fixed
* **Add to cart edge cases** – Addressed rounding, skeleton flickers, and missing imagery in popular-product sliders.
* **Type hygiene** – Cleaned up lingering TypeScript errors so builds stay green.
### New
* **Menu editor** – Added data tables, inline dialogs, and bulk actions for menus, categories, products, and modifiers.
* **Operational pages** – Launched dedicated views for discounts, statements, teams, integrations, and account settings.
* **Payments setup** – Integrated Stripe Setup Intents, OTP flows, and bank account onboarding for merchants.
### Improved
* **Component library** – Expanded Tremor-based components, wrappers, and responsive layouts used across onboarding.
* **Cart & checkout UX** – Finished checkout flows with skeletons, promo code modals, and cart formatting parity.
### Fixed
* **Product dialogs** – Resolved duplicate renders, scroll traps, and hardcoded imagery that crept into the early rebuild.
* **Type coverage** – Swept through new packages to align on shared types and eliminate implicit any usage.
### New
* **Redux Toolkit** – Centralized storefront state, unlocking persisted carts, fulfilment toggles, and shared selectors.
* **Fulfilment APIs** – Completed place-order, delivery timing, and gratuity endpoints with client integrations.
* **Location info surface** – Exposed location details, tips, and announcements directly in the partner UI.
### Improved
* **Navigation scaffolding** – Tightened sticky tab borders, section scrolling, and category selection experiences.
* **Design tokens** – Extended Tailwind palette, typography, and shared buttons to keep storefront branding consistent.
### Fixed
* **Cart formatting** – Normalized currency output and ensured duplicate add-to-cart mutations merge correctly.
* **Legacy packages** – Removed unused dependencies before shipping to partners.
### New
* **Data fetcher utilities** – Added SWR fetchers, shared constants, and API connectors for homepage and menu data.
* **Homepage integrations** – Wired the Next.js storefront to live product, timing, and location endpoints.
### Improved
* **Design system** – Continued Tailwind UI build-out with iconography, modals, and slider polish.
* **Notifications** – Adopted react-toast notifications for system feedback.
### Fixed
* **Code hygiene** – Deleted legacy scaffolding and unused exports left over from the first prototype.
* **API casing** – Corrected header names and auth keys to match production conventions.
### New
* **API key management** – Implemented key generation, rotation, and authorization middleware for partner integrations.
* **Order manager endpoints** – Delivered status, fulfilment, and export routes for merchants to drive operations from the API.
### Improved
* **Location access** – Linked API keys to location scoping and added guardrails for accessing multi-location menus.
* **Documentation tooling** – Updated error naming, response shapes, and OpenAPI alignment across new endpoints.
### Fixed
* **Endpoint inconsistencies** – Normalized casing (e.g., `X-API-Key`) and fixed authentication leaks discovered during QA.
### New
* **Onboarding wizard** – Launched multi-step onboarding with business, tax, schedule, and payments configuration.
* **Customer auth** – Introduced OTP verification, login/logout flows, and customer session middleware.
* **Merchant subscription** – Added plan subscription, plan cancellation guardrails, and Stripe setup intents.
### Improved
* **Storybook coverage** – Brought all major dashboard and storefront components into Storybook for team-wide reviews.
* **UX consistency** – Harmonized typography, spacing, and color tokens across shared packages.
### Fixed
* **Validation gaps** – Patched schema validation for forms, ensuring bad inputs are caught client and server side.
* **Session handling** – Closed logout edge cases and stale cookie states in the customer APIs.
### New
* **Atoms & molecules** – Shipped buttons, tabs, list tiles, checkout cards, toggles, modifiers, and more as reusable components.
* **Storybook integration** – Documented the new component library with stories for every state.
### Improved
* **Design tokens** – Expanded custom colors, font sizes, and line heights to match brand guidelines.
* **Input ergonomics** – Refined form components, radio buttons, and selectors based on early feedback.
### Fixed
* **Naming consistency** – Renamed components and files to align with PascalCase/Kebab-case conventions across the monorepo.
* **Lint noise** – Cleaned up duplicate exports and lint warnings caused by rapid scaffolding.
### New
* **Next.js partner app** – Bootstrapped the partner storefront with routing, layout primitives, and base styling.
* **UI primitives** – Added dialog, button, store cover, and item card components to accelerate vertical slice builds.
### Improved
* **Font & theming** – Applied the Crave brand typography stack and color palette across the new app.
### Fixed
* **Component duplication** – De-duped early experiments while consolidating on Tremor and internal UI kits.
### New
* **Cart lifecycle** – Implemented create, update, remove, and delete cart endpoints with quantity validation.
* **Fulfilment prep** – Added cart item routes, gratuity calculations, and groundwork for order placement.
### Improved
* **Rate limiting** – Hardened Express middleware to protect storefront APIs from abusive traffic.
* **Code style** – Adopted Prettier and repository-wide formatting for the service codebase.
### Fixed
* **Naming & structure** – Normalized file names and tightened up utilities to keep endpoints discoverable.
### New
* **Express storefront** – Shipped the initial Express service with database connectivity, store info, and menu/category endpoints.
* **Time utilities** – Added shared utilities for operating hours, laying the groundwork for scheduling logic.
### Improved
* **Database wiring** – Iterated on DB connections and data-reading layers to stabilize the new stack.
### Fixed
* **Deployment scaffolding** – Cleaned up early config so the v2 API could run locally and in staging.
### New
* **White-label iOS pipeline** – Enabled merchants to generate branded mobile apps straight from the dashboard with live previews.
* **Meta Pixel integration** – Brought first-party attribution to both the dashboard and guest storefront for unified campaign tracking.
* **Subscription safeguards** – Automated Stripe billing with past-due cancellations, trial countdowns, and customer-initiated plan management.
### Improved
* **Onboarding journey** – Smoothed bank setup, delivery toggles, and setup guides so teams can resume exactly where they left off.
* **Store settings** – Grouped settings by theme, refreshed analytics, and polished navigation for faster daily workflows.
### Fixed
* **Mobile responsiveness** – Tightened guest storefront spacing and padding so menus read cleanly on every device.
* **QR exports** – Resolved asset generation issues for print collateral and digital signage.
### New
* **Menu editor revamp** – Delivered click-to-edit menus, real-time hours, and search-driven category expansion for faster merchandising.
* **Zendesk + Google sign-in** – Embedded support workflows and SSO so operators stay productive without tab sprawl.
* **Statements & QR tooling** – Generated monthly statements and on-demand QR codes for marketing placements.
### Improved
* **Onboarding controls** – Added fulfilment-specific scheduling, tipping policies, and order-receiving preferences across SMS, email, and tablet.
* **Guest ordering 2.0** – Honoured location time zones, deep-linked table-side orders, and refined tip behaviour across channels.
### Fixed
* **DoorDash handoff** – Closed the loop on Drive delivery edge cases discovered during pilot launches.
* **Global domain routing** – Hardened custom domain flows while we wrapped up Apple Pay validation.
### New
* **Search-first storefront** – Launched item and modifier search, popular product carousels, and multi-select modifiers.
* **Merchant onboarding** – Introduced the first merchant onboarding, settings, and QR generation flows for digital menus.
* **PMS ordering** – Enabled property management system ordering to serve hotel and resort partners.
### Improved
* **Design system** – Swapped to the Lato typography stack, standardized imagery, and refined touch targets across mobile.
* **Brand storytelling** – Debuted the EMC brand page with refreshed photography and Google Maps distance calculations.
### Fixed
* **Tip accuracy** – Corrected gratuity calculations for multi-location partners.
* **Modal UX** – Eliminated scrolling bugs and duplicate popular items during fast cart edits.
***
Need the original Crave v1 timeline? Check the archived 2021–2023 release notes in the internal handbook or reach out at [hello@craveup.com](mailto:hello@craveup.com).
#
Source: https://docs.craveup.com/components/cart/cart-item-row
Individual cart line item with modifiers, quantity control, and price.
# CartItemRow
Renders a single line item in the cart, showing the product name, selected modifiers, special instructions, quantity stepper, and line total.
## Preview
## Import
```tsx theme={null}
import { CartItemRow } from '@/components/CartItemRow'
```
## Usage
```tsx theme={null}
updateItem(cartItem.id, qty)}
onRemove={() => removeItem(cartItem.id)}
onEdit={() => editItem(cartItem.id)}
/>
```
## Props
The cart item object with product details, modifiers, quantity, and price.
Callback fired when quantity is changed via the stepper.
Callback fired when the item is removed (quantity reaches 0 or delete is clicked).
Optional callback to re-open the product customization screen.
## Behavior
* Displays modifier selections as comma-separated tags
* Shows special instructions in italics below modifiers
* Swipe-to-delete gesture on mobile
* Tap on item opens edit mode via `onEdit`
#
Source: https://docs.craveup.com/components/cart/cart-nav-button
Floating or header-mounted button showing cart item count and total.
# CartNavButton
A button that displays the current cart item count as a badge and optionally shows the cart total. Typically placed in the header or as a floating action button on mobile.
## Preview
## Import
```tsx theme={null}
import { CartNavButton } from '@/components/CartNavButton'
```
## Usage
```tsx theme={null}
setIsCartOpen(true)}
/>
```
## Props
Number of items currently in the cart.
Cart total to display alongside the button text.
Callback fired when the button is clicked — typically opens the CartPanel.
## Behavior
* Badge animates with a bounce effect when item count changes
* Hides when cart is empty (itemCount === 0)
* On mobile, renders as a fixed-position floating button at the bottom
* On desktop, renders inline in the header navigation
#
Source: https://docs.craveup.com/components/cart/cart-panel
Slide-out panel displaying the current cart contents and order summary.
# CartPanel
A slide-out side panel that shows all items in the customer's cart, order summary with subtotal/tax/total, and a checkout button.
## Preview
## Import
```tsx theme={null}
import { CartPanel } from '@/components/CartPanel'
```
## Usage
```tsx theme={null}
setIsCartOpen(false)}
/>
```
## Props
The cart object containing items, totals, and applied discounts.
Callback to update an item's quantity.
Callback to remove an item from the cart.
Callback fired when the checkout button is clicked.
Controls the panel's open/closed state.
Callback to close the panel.
## Behavior
* Slides in from the right with backdrop overlay
* Each item row shows name, modifiers summary, quantity stepper, and line total
* Order summary section with subtotal, tax, discounts, and total
* Empty state with illustration and "Browse menu" CTA
* Traps focus when open for accessibility
#
Source: https://docs.craveup.com/components/cart/cart-related-items
Upsell carousel shown in the cart suggesting complementary items.
# CartRelatedItems
A horizontal carousel displayed within or below the cart panel that suggests related or complementary items to encourage upselling.
## Preview
## Import
```tsx theme={null}
import { CartRelatedItems } from '@/components/CartRelatedItems'
```
## Usage
```tsx theme={null}
```
## Props
Array of suggested products to display.
Callback fired when a suggested item's add button is clicked.
## Behavior
* Shows compact product cards with image, name, price, and quick-add button
* Horizontally scrollable with peek effect
* Items are determined by the backend based on cart contents
* Hidden when no suggestions are available
#
Source: https://docs.craveup.com/components/checkout/apply-promo-code
Input field for applying discount codes to an order.
# ApplyPromoCode
A compact input with an apply button that lets customers enter and validate promotional discount codes during checkout.
## Preview
## Import
```tsx theme={null}
import { ApplyPromoCode } from '@/components/ApplyPromoCode'
```
## Usage
```tsx theme={null}
```
## Props
Async callback to validate and apply the promo code. Should return the discount result or throw on invalid codes.
Currently applied promo code, if any.
Callback to remove the currently applied promo code.
## Behavior
* Inline validation with loading state during API call
* Shows success message with discount amount on valid code
* Shows error message for invalid or expired codes
* Displays applied code as a removable chip
* Collapses to "Have a promo code?" link when no code is applied
#
Source: https://docs.craveup.com/components/checkout/checkout-component
Top-level checkout orchestrator that manages the multi-step checkout flow.
# CheckoutComponent
The main checkout container that orchestrates the multi-step checkout flow — customer details, fulfillment selection, payment, and order confirmation.
## Preview
## Import
```tsx theme={null}
import { CheckoutComponent } from '@/components/CheckoutComponent'
```
## Usage
```tsx theme={null}
```
## Props
The finalized cart to check out.
The location ID for this order.
Callback fired when the order is successfully placed.
## Behavior
* Multi-step flow: Customer Details → Fulfillment → Payment → Confirmation
* Progress indicator at the top showing current step
* Validates each step before allowing progression
* Handles payment processing errors with retry
* Displays order summary sidebar on desktop
#
Source: https://docs.craveup.com/components/checkout/customer-details-form
Form for collecting customer name, email, and phone number during checkout.
# CustomerDetailsForm
Collects customer contact information (name, email, phone) as the first step of the checkout flow.
## Preview
## Import
```tsx theme={null}
import { CustomerDetailsForm } from '@/components/CustomerDetailsForm'
```
## Usage
```tsx theme={null}
```
## Props
Pre-filled values from a returning customer or saved session.
Callback fired when the form is submitted with valid data.
## Behavior
* Validates email format and phone number
* Remembers returning customer info via local storage
* Phone input with country code selector
* Auto-focuses the first empty field
#
Source: https://docs.craveup.com/components/checkout/order-time-picker
Date and time selector for scheduling pickup or delivery orders.
# OrderTimePicker
A picker that allows customers to choose between "ASAP" ordering or scheduling a future pickup/delivery time within the location's operating hours.
## Preview
## Import
```tsx theme={null}
import { OrderTimePicker } from '@/components/OrderTimePicker'
```
## Usage
```tsx theme={null}
```
## Props
Location ID used to fetch available time slots.
The fulfillment method — affects available time windows and lead times.
The currently selected order time.
Callback fired when the time selection changes.
## Behavior
* "ASAP" toggle selected by default with estimated ready time
* Date picker for future dates (within location's advance ordering window)
* Time slots in 15-minute increments within operating hours
* Grays out unavailable slots based on kitchen capacity
#
Source: https://docs.craveup.com/components/checkout/payment-form
Provider-selected secure payment input for Crave checkout.
# PaymentForm
Renders the server-selected payment experience. An active Square connection
uses Square Web Payments; a location without Square uses Stripe Elements.
## Preview
## Behavior
* Branches only on `paymentSession.provider`; browser state cannot choose or
switch the processor.
* Square supports card, Square gift-card partial authorization, authenticated
stored cards, optional card saving, and wallet tokens when an eligible Square
wallet method supplies the source token.
* Stripe Elements remains available only for non-Square locations.
* The parent checkout flow owns customer validation, confirmation, errors,
idempotent retries, and final redirect.
* Raw card and gift-card data stays in provider-hosted fields and is never
accepted or logged by Crave application code.
Do not render Stripe after a Square session has been created. A missing or unhealthy Square
dependency fails the cart closed so the customer cannot be charged by the wrong processor.
#
Source: https://docs.craveup.com/components/checkout/tip-selector
Preset and custom tip amount picker for the checkout flow.
# TipSelector
Allows customers to select a tip amount from preset percentages or enter a custom amount. Displays the calculated tip based on the order subtotal.
## Preview
## Import
```tsx theme={null}
import { TipSelector } from '@/components/TipSelector'
```
## Usage
```tsx theme={null}
```
## Props
Order subtotal used to calculate percentage-based tips.
The currently selected tip amount in cents.
Callback fired when the tip selection changes.
Array of percentage presets to display as quick-select buttons.
## Behavior
* Preset percentage buttons with calculated dollar amounts
* "Custom" option reveals a numeric input
* "No tip" option to explicitly set tip to \$0
* Active preset is highlighted with primary accent
#
Source: https://docs.craveup.com/components/fulfillment/delivery
Delivery address form with validation and delivery zone checking.
# Delivery
Collects and validates a delivery address, checks if the address falls within the merchant's delivery zone, and displays estimated delivery time and fees.
## Preview
## Import
```tsx theme={null}
import { Delivery } from '@/components/Delivery'
```
## Usage
```tsx theme={null}
```
## Props
The location ID to check delivery zones against.
Callback fired when a valid delivery address is confirmed.
## Behavior
* Google Places autocomplete for address input
* Validates address against the location's delivery zones
* Shows "Outside delivery area" error for out-of-range addresses
* Displays delivery fee and estimated time for valid addresses
* Optional delivery instructions text field
#
Source: https://docs.craveup.com/components/fulfillment/fulfilment-method-handler
Route-level handler that renders the appropriate fulfillment flow based on order type.
# FulfilmentMethodHandler
A routing component that determines and renders the correct fulfillment flow (delivery, table-side, room service) based on the ordering session's fulfillment method.
## Preview
## Import
```tsx theme={null}
import { FulfilmentMethodHandler } from '@/components/FulfilmentMethodHandler'
```
## Usage
```tsx theme={null}
```
## Props
The current ordering session containing the selected fulfillment method.
Callback fired when fulfillment details are confirmed by the customer.
## Behavior
* Reads the fulfillment method from the ordering session
* Renders `Delivery`, `TableSide`, or `RoomService` sub-component accordingly
* Falls back to pickup if no specific method is set
* Passes collected details (address, table number, room number) up via callback
#
Source: https://docs.craveup.com/components/fulfillment/room-service
Room number and guest name input for hotel room service ordering.
# RoomService
Collects room number and guest name for hotel room service fulfillment. Validates the room number against the property's room directory.
## Preview
## Import
```tsx theme={null}
import { RoomService } from '@/components/RoomService'
```
## Usage
```tsx theme={null}
```
## Props
The location (property) ID to validate room numbers against.
Callback fired when room details are confirmed.
## Behavior
* Room number input with validation
* Guest name field (required for room charge authorization)
* Optional delivery instructions
* Auto-populated when accessed via in-room QR code
#
Source: https://docs.craveup.com/components/fulfillment/table-side
Table number input for dine-in table-side ordering.
# TableSide
A simple input for customers to enter their table number for dine-in table-side ordering. Validates the table number against the location's configured tables.
## Preview
## Import
```tsx theme={null}
import { TableSide } from '@/components/TableSide'
```
## Usage
```tsx theme={null}
```
## Props
The location ID to validate table numbers against.
Callback fired when a valid table number is submitted.
## Behavior
* Numeric input with large, touch-friendly buttons
* Validates table number exists for the location
* Shows error for invalid table numbers
* Auto-populated when accessed via QR code scan with table parameter
# Installation
Source: https://docs.craveup.com/components/installation
Install dependencies and structure your app for Crave UI components.
Crave UI components are local React components that sit inside your storefront app. They pair with the [Storefront SDK](/getting-started/storefront-sdk), Tailwind CSS, and shadcn/ui conventions so you can own the markup, styling, and ordering flows.
Choose the setup that matches your starting point.
Configure the API origin, merchant slug, and location ID without a browser
credential.
Start from a Crave storefront template with components and SDK wiring
included.
Add the SDK, shared UI dependencies, and component source to an app you
already own.
See where components, API helpers, and shared utilities should live.
## Use the CLI
For new storefronts, start with the Crave CLI. The generated template includes the Storefront SDK, component files, Tailwind setup, and environment variables.
```bash theme={null}
npx @craveup/cli init
```
Then run the generated app:
```bash theme={null}
cd crave-storefront
pnpm install
pnpm dev
```
The template is the recommended source of truth for component file structure because the components are meant to be edited in your app.
## Existing app
For an existing React or Next.js app, install the SDK first:
```bash theme={null}
pnpm add --save-exact @craveup/storefront-sdk@2.0.1
```
If your app does not already use shadcn/ui, initialize it before copying component source:
```bash theme={null}
pnpm dlx shadcn@latest init
```
Install the shared UI utilities used by the component examples:
```bash theme={null}
pnpm add class-variance-authority clsx tailwind-merge lucide-react
```
Then copy the Crave component source from a generated storefront template into your app's `components` directory and adapt imports to match your aliases.
## Project structure
Use a structure that keeps UI, data access, and ordering state separate:
```txt theme={null}
src/
app/
menu/
cart/
checkout/
components/
menu/
cart/
checkout/
fulfillment/
modifiers/
post-order/
ui/
lib/
storefront-client.ts
utils.ts
types/
storefront.ts
```
## Configure the SDK
Create a shared Storefront SDK client and import it from your data loaders, hooks, or server actions.
Get the published location ID and merchant slug from Crave Business Manager. Configure the public API origin explicitly.
```ts filename="src/lib/storefront-client.ts" theme={null}
import { createStorefrontClient } from "@craveup/storefront-sdk";
export const storefrontClient = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
sessionStore,
getAuthToken,
});
```
Add the required environment variables:
```env filename=".env.local" theme={null}
NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com
NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_abc123
NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=demo-cafe
```
## Import components
Crave component docs use local imports so you can customize the implementation:
```tsx theme={null}
import { CartPanel } from "@/components/cart/cart-panel";
import { Item } from "@/components/menu/item";
import { MenuSwitcher } from "@/components/menu/menu-switcher";
```
Keep the component source in your app, then pass SDK data and callbacks from your own routes or state layer.
## Next steps
Browse the full component catalog.
Configure the typed API client used by the components.
Fetch menu data and render categories, products, and modifiers.
Wire cart creation, item updates, discounts, and checkout handoff.
#
Source: https://docs.craveup.com/components/menu/category-tabs
Sticky tab navigation for browsing menu categories.
# CategoryTabs
A horizontal tab bar that displays menu categories with an active indicator. Sticks to the top of the viewport as the user scrolls through the menu.
## Preview
## Import
```tsx theme={null}
import { CategoryTabs } from '@/components/CategoryTabs'
```
## Usage
```tsx theme={null}
scrollToCategory(id)}
/>
```
## Props
Array of menu categories to display as tabs.
The ID of the currently active/visible category.
Callback fired when a tab is clicked — typically scrolls to the category section.
## Behavior
* Sticks to the top of the viewport below the header on scroll
* Highlights the active category based on scroll position
* Horizontally scrollable on mobile for long category lists
* Smooth-scrolls the active tab into view automatically
#
Source: https://docs.craveup.com/components/menu/item
Unified product card that renders vertical or horizontal layouts.
# Item
A versatile product card component that displays a menu item with image, name, description, price, and a quick-add button. Supports both vertical (grid) and horizontal (list) layouts.
## Preview
## Import
```tsx theme={null}
import { Item } from '@/components/Item'
```
## Usage
```tsx theme={null}
{/* Vertical layout for grid view */}
{/* Horizontal layout for list view */}
```
## Props
The product object containing name, description, price, images, and modifier groups.
Controls the card layout direction.
Callback fired when the add button is clicked. If the product has modifiers, this should open the `ProductDescriptionScreen`.
## Behavior
* Truncates long descriptions with ellipsis
* Shows product image with fallback placeholder
* Quick-add button bypasses modifier screen when no modifiers exist
* Displays "From \$X.XX" when modifier groups affect pricing
#
Source: https://docs.craveup.com/components/menu/item-carousel
Horizontal carousel for featured or recommended menu items.
# ItemCarousel
A horizontally scrollable carousel that displays a row of product cards. Used for featured items, recommendations, or category highlights.
## Preview
## Import
```tsx theme={null}
import { ItemCarousel } from '@/components/ItemCarousel'
```
## Usage
```tsx theme={null}
```
## Props
Array of products to display in the carousel.
Optional heading displayed above the carousel.
Callback fired when a carousel item is clicked.
## Behavior
* Touch-friendly horizontal scrolling on mobile
* Peek effect showing partial next card to indicate scrollability
* Renders each item using the vertical `Item` component
#
Source: https://docs.craveup.com/components/menu/menu-switcher
Dropdown to switch between multiple menus with availability status.
# MenuSwitcher
A dropdown selector that allows customers to switch between multiple menus (e.g., Breakfast, Lunch, Dinner). Displays availability status based on operating hours.
## Preview
## Import
```tsx theme={null}
import { MenuSwitcher } from '@/components/MenuSwitcher'
```
## Usage
```tsx theme={null}
setActiveMenu(menuId)}
/>
```
## Props
Array of available menus for the location.
The ID of the currently selected menu.
Callback fired when a different menu is selected.
## Behavior
* Shows "Available now" or "Available at 11:00 AM" based on daypart logic
* Grays out menus outside their operating hours
* Auto-selects the first available menu on mount
#
Source: https://docs.craveup.com/components/modifiers/item-counter-button
Increment/decrement stepper for adjusting item or modifier quantities.
# ItemCounterButton
A compact stepper control with minus/plus buttons and a quantity display. Used for adjusting item quantities in the cart and modifier option counts.
## Preview
## Import
```tsx theme={null}
import { ItemCounterButton } from '@/components/ItemCounterButton'
```
## Usage
```tsx theme={null}
setQuantity(q => q + 1)}
onDecrement={() => setQuantity(q => q - 1)}
min={1}
max={99}
/>
```
## Props
The current quantity value.
Callback fired when the plus button is clicked.
Callback fired when the minus button is clicked.
Minimum allowed value. Decrement button is disabled at this value.
Maximum allowed value. Increment button is disabled at this value.
## Behavior
* Disables minus button when count reaches `min`
* Disables plus button when count reaches `max`
* Animates the count value on change
* Compact variant available for inline use in modifier options
#
Source: https://docs.craveup.com/components/modifiers/modifier-group
Renders a group of modifier options (radio, checkbox, or counter) for product customization.
# ModifierGroup
Displays a single modifier group (e.g., "Choose your size", "Add toppings") with its options. Supports radio (single-select), checkbox (multi-select), and counter (quantity) input types.
## Preview
## Import
```tsx theme={null}
import { ModifierGroup } from '@/components/ModifierGroup'
```
## Usage
```tsx theme={null}
```
## Props
The modifier group object containing name, options, min/max selections, and input type.
Currently selected modifiers for this group.
Callback fired when modifier selections change.
## Behavior
* Enforces min/max selection constraints with visual feedback
* Radio groups auto-deselect previous option
* Counter inputs respect per-option quantity limits
* Displays price adjustments next to each option (e.g., "+\$1.50")
#
Source: https://docs.craveup.com/components/modifiers/product-description-screen
Full-screen product detail view with modifiers, quantity, and add-to-cart action.
# ProductDescriptionScreen
A full-screen overlay that shows detailed product information including image, description, modifier groups, quantity selector, and an add-to-cart button with running total.
## Preview
## Import
```tsx theme={null}
import { ProductDescriptionScreen } from '@/components/ProductDescriptionScreen'
```
## Usage
```tsx theme={null}
```
## Props
The product to display, including all modifier groups.
Callback fired when the user confirms selections and adds the item to cart.
Callback to dismiss the screen.
## Behavior
* Displays product hero image with parallax scroll effect
* Renders all modifier groups using `ModifierGroup` components
* Shows running price total that updates as modifiers are selected
* Validates required modifier groups before enabling the add button
* Supports special instructions text input at the bottom
#
Source: https://docs.craveup.com/components/modifiers/special-instructions
Text input for customer notes and special requests on a menu item.
# SpecialInstructions
A text area input that allows customers to add special instructions or notes to a menu item (e.g., "no onions", "extra sauce on the side").
## Preview
## Import
```tsx theme={null}
import { SpecialInstructions } from '@/components/SpecialInstructions'
```
## Usage
```tsx theme={null}
```
## Props
The current instructions text.
Callback fired when the text changes.
Placeholder text shown when the input is empty.
Maximum character limit for the instructions.
## Behavior
* Character counter displayed when approaching the limit
* Expandable text area that grows with content
* Optional — collapses to a single-line prompt when empty
# Components
Source: https://docs.craveup.com/components/overview
Pre-built React components for menu browsing, cart management, checkout, and fulfillment.
Crave provides restaurant-specific React components that integrate with your [shadcn/ui](https://ui.shadcn.com) design system. Drop them into your storefront to handle menu display, cart logic, checkout, and fulfillment — then style them to match your brand.
Set up dependencies, project structure, and component source in your storefront.
Add the public API origin, merchant slug, and location ID without a browser credential.
## Menu & Products
Display menus, categories, and product cards.
Sticky tab navigation for menu categories with active indicator.
Dropdown to switch between menus (lunch, dinner) with availability.
Product card — renders vertical or horizontal with quick-add.
Carousel for featured or recommended menu items.
## Modifiers
Product customization — sizes, toppings, special instructions.
Renders modifier groups (e.g., size, toppings) with selection state.
Full-screen modal for product details, modifiers, and add-to-cart.
Textarea for customer special requests and dietary notes.
Quantity selector with increment/decrement buttons.
## Cart
Manage the shopping cart.
Side drawer showing cart contents with checkout CTA.
Header button showing item count — opens the cart panel.
Individual cart item with modifiers, quantity controls, and price.
Recommended upsell items shown inside the cart.
## Checkout
Customer details, payments, and order submission.
Main checkout layout orchestrating all checkout sections.
Form for name, email, and phone with validation.
Stripe PaymentElement wrapper for card and payment methods.
Interactive tip selection with presets and custom input.
ASAP or scheduled pickup/delivery time selector.
Promo code input with validation and discount display.
## Fulfillment
Order fulfillment method selection — delivery, pickup, table-side, room service.
Routes to the correct fulfillment UI based on method.
Address picker and delivery details.
Table number selection for dine-in service.
Room number and guest name input for hotel room service.
## Post-Order
Order confirmation and feedback.
Thank-you screen with order details and next steps.
Star rating component for post-order feedback.
#
Source: https://docs.craveup.com/components/post-order/experience-rating
Post-order feedback widget for rating the ordering experience.
# ExperienceRating
A feedback widget shown after order completion that lets customers rate their experience with star ratings and optional text feedback.
## Preview
## Import
```tsx theme={null}
import { ExperienceRating } from '@/components/ExperienceRating'
```
## Usage
```tsx theme={null}
```
## Props
The order ID to associate the feedback with.
Callback fired when the customer submits their rating.
Callback fired when the customer dismisses the rating prompt.
## Behavior
* 5-star rating with tap/click interaction
* Optional text feedback field appears after rating
* Category tags for specific feedback (food quality, speed, accuracy)
* Submits to the analytics events API
* Only shown once per order
#
Source: https://docs.craveup.com/components/post-order/order-confirmation
Post-checkout screen showing order details, status, and estimated ready time.
# OrderConfirmation
The confirmation screen displayed after a successful order placement. Shows the order number, item summary, estimated ready/delivery time, and real-time status updates.
## Preview
## Import
```tsx theme={null}
import { OrderConfirmation } from '@/components/OrderConfirmation'
```
## Usage
```tsx theme={null}
```
## Props
The placed order object with items, totals, status, and fulfillment details.
Optional callback to navigate to the order tracking view.
## Behavior
* Displays order number with a checkmark success animation
* Shows estimated ready time or delivery ETA
* Lists ordered items with quantities and prices
* Real-time status updates via polling or WebSocket
* "Track your order" CTA for delivery orders
* "Order again" button to reorder the same items
# CLI Quickstart
Source: https://docs.craveup.com/getting-started/cli-quickstart
Use the Crave CLI to scaffold, configure, and launch a storefront template.
The Crave CLI bootstraps a production-ready storefront in a few minutes. It installs dependencies, wires environment variables, and can even start the dev server for you.
The CLI publishes as **`@craveup/cli`**. The older unscoped `craveup` package
on npm is unmaintained and stops at 1.0.3 — install the scoped name. The
installed command is still `craveup`, so `craveup login` and the rest are
unchanged.
## Install & run
Run the CLI anywhere you want to create the project directory:
```bash theme={null}
npx @craveup/cli init
```
The wizard will guide you through:
* Choosing a template (`storefront-default`, Tomodachi Sushi, or Xichuan Noodles)
* Selecting the published organization and location
* Naming the project directory
* Installing dependencies with your detected package manager
* Optionally starting `pnpm dev` so you can preview immediately
## Sign in
Sign in before scaffolding so the CLI can discover the organizations and locations your account can manage:
```bash theme={null}
npx @craveup/cli login # browser OAuth; sign in or create a free account
npx @craveup/cli whoami # confirm the active account
npx @craveup/cli logout # remove local credentials
```
The session is stored in the macOS keychain, falling back to `~/.config/craveup/credentials.json`, and is **shared with the [CraveUp MCP server](/guides/ai/craveup-mcp-server)** — signing in through either one satisfies the other. On a headless or remote machine, use `craveup login --device` to pair with a code you open on any other device.
## Non-interactive usage
Prefer automation or CI pipelines? Use flags to skip prompts.
| Flag | Description |
| - | - |
| `--template ` | Force a template (`storefront-default`, `tomodachi-sushi`, or `xichuan-noodles`) |
| `--dir ` | Set the target directory name |
| `--api-key ` | Optional server-only CLI credential for non-interactive private API access |
| `--location-id ` | Provide the location ID for single-location templates |
| `--org-slug ` | Provide the organization slug for organization templates |
| `--skip-install` | Scaffold files without running the package manager |
| `--force` | Overwrite an empty directory without confirmation |
Example:
```bash theme={null}
npx @craveup/cli init \
--template storefront-default \
--dir crave-storefront \
--org-slug my-restaurant-group \
--skip-install
```
## Environment variables
The CLI creates `.env.local` with placeholders. Update them before running the app:
```env theme={null}
# Public Storefront API origin. No browser API key is used.
NEXT_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com
NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_abc123
NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=my-restaurant-group
```
> Tip: `CRAVEUP_DASHBOARD_URL`, `CRAVEUP_PARTNER_API_URL`, and a private `CRAVEUP_API_KEY` are CLI/server concerns. They are never written to a public storefront environment variable.
The merchant and location variables are standalone/local overrides. A hosted
`cravejs-storefront-template` resolves the merchant from its controlled
host/subdomain and the location from the `[locationId]` route instead of
duplicating tenant selection in production environment variables. The CLI does
not write `NEXT_PUBLIC_STOREFRONT_URL`; the template derives its canonical
metadata origin from the request host. Add that optional override manually only
when the deployed metadata origin must differ from the request host.
## Project anatomy
Every template ships with:
* **Next.js 16.3 + App Router** for modern server-first rendering
* **`@craveup/storefront-sdk@2.0.1`** pinned and preconfigured in `src/lib/storefront-client.ts`
* **Typed API helpers** co-located with app routes
* **Component library** for menu browsing, cart management, and checkout
Explore the generated repository after the CLI finishes:
```bash theme={null}
cd crave-storefront
pnpm install # if you skipped installation
pnpm dev --port 3000
```
You now have a fully functional storefront backed by live Crave data. Continue to the [Storefront SDK guide](./storefront-sdk) to integrate custom flows.
# Dart and Flutter Storefront SDK
Source: https://docs.craveup.com/getting-started/flutter-storefront-sdk
Build typed Flutter storefronts against the public Crave Storefront API.
`crave_storefront_sdk` is the official Dart client for Flutter mobile, web, and desktop storefronts.
It exposes the same reviewed `/api/v1/storefront` contract as
[`@craveup/storefront-sdk`](/getting-started/storefront-sdk), with Dart-native models, cancellation,
timeouts, and typed exceptions. It contains no UI, Flutter, or native runtime dependency.
## Install the exact release
```yaml pubspec.yaml theme={null}
dependencies:
crave_storefront_sdk: 0.2.0
```
Run `flutter pub get`. Dart-only consumers can run `dart pub get` instead.
## Configure one environment and merchant
```dart theme={null}
import 'package:crave_storefront_sdk/crave_storefront_sdk.dart';
final client = CraveStorefrontClient(
baseUri: Uri.parse('https://api.craveup.com'),
merchantSlug: 'downtown-pizza',
sessionStore: secureSessionStore,
customerTokenProvider: () async => currentCustomerJwt,
);
```
Use the exact sandbox API origin provided for your account in sandbox builds. Production and
sandbox are separate deployments with separate environment variables, credentials, and data; do
not add a runtime environment switch to a production app. The client accepts plain HTTP only for
exact loopback hosts during local development.
`StorefrontSessionStore` is application-owned. In production, implement it with encrypted platform
storage and isolate records by the supplied API-origin, merchant, and location scope. The included
`InMemoryStorefrontSessionStore` is only for tests and ephemeral demos. Keep customer JWTs in your
authentication layer, not in the cart session store.
## Check ordering readiness
Check availability before presenting an ordering action. This anonymous GET does not create a cart:
```dart theme={null}
final readiness = await client.locations.getOrderingReadiness(
locationId,
fulfillmentMethod: FulfillmentMethod.takeout,
);
switch (readiness) {
case OrderingReady():
showStartOrder();
case OrderingUnavailable(:final reason):
showOrderingUnavailable(reason);
}
```
## Handle typed failures
```dart theme={null}
try {
final menu = await client.menus.getForLocation(locationId, menuOnly: true);
renderMenu(menu);
} on StorefrontApiException catch (error) {
reportSafeFailure(error.code, error.status, error.requestId);
} on StorefrontTimeoutException {
showRetryMessage();
} on StorefrontRequestCancelledException {
// The owning screen was disposed or the shopper cancelled the action.
}
```
The exception contract excludes resolved URLs, headers, request bodies, customer data, and payment
values. Cart mutations are never retried implicitly. After `CART_CONFLICT`, re-read the cart,
reconcile the new revision, and let the shopper confirm the next mutation.
Close the client when its application-level owner is disposed:
```dart theme={null}
client.close();
```
Install the package, configure secure session storage, load a menu, and start an order.
# Build a Restaurant Ordering App in Minutes
Source: https://docs.craveup.com/getting-started/introduction
Scaffold a branded storefront, connect menus, carts, checkout, fulfillment, and payments, then ship against the Crave API.
Crave gives food and beverage developers the ordering layer they usually have to stitch together themselves: menu data, modifiers, cart state, checkout, fulfillment, payments, analytics, reusable UI components, and APIs that work across web and mobile apps.
Crave.js is the storefront developer surface inside **Crave Cloud**, a managed platform for franchise ordering, identity boundaries, loyalty integrations, payment routing, and multi-location operations.
## Start in 5 minutes
The fastest path is to scaffold a storefront, add its public API origin and restaurant identifiers, and run it locally.
```bash npm theme={null}
npx @craveup/cli init
cd crave-storefront
npm run dev
```
```bash pnpm theme={null}
pnpm dlx craveup init
cd crave-storefront
pnpm dev
```
```bash yarn theme={null}
yarn dlx craveup init
cd crave-storefront
yarn dev
```
Open [http://localhost:3000](http://localhost:3000), then use the generated `.env.local` to add the public API origin, merchant slug, and location ID. A storefront never receives a Crave API key.
Connect the generated app to published merchant, location, menu, and cart resources without a browser credential.
Already have an app? Skip the scaffold and install the exact `@craveup/storefront-sdk@2.0.1` release directly.
## Running? Read this next
Understand how Crave.js fits into the broader franchise platform.
Register exact browser origins and keep private credentials out of storefront bundles.
Connect the CraveUp MCP server and use a prebuilt storefront prompt before handing work to an AI agent.
Learn the CLI flags, templates, and generated project structure.
Connect your UI to menus, carts, checkout, and analytics with typed helpers.
Call Crave endpoints from any platform using standard HTTP requests.
## Where this fits
Learn what is available to developers and what stays managed by Crave Cloud.
Combine the CLI, Storefront SDK, REST APIs, and UI components for a branded ordering experience.
Give Cursor, Claude, ChatGPT, Codex, or another agent the right Crave context before it edits your app.
## Quickstarts & Tutorials
Explore our end-to-end tutorials and getting started guides for different application stacks.
Build a storefront with Next.js 16.3 App Router and the Crave SDK.
Get started with React + Vite and the Crave SDK.
Build a storefront with plain JavaScript and the REST API.
Create a mobile ordering app with Expo and the Crave SDK.
## UI Components
Crave's prebuilt UI components give you a fully-functional ordering experience in minutes — menus, carts, checkout, and fulfillment out of the box.
Browse all available components and learn how they work together.
The TypeScript SDK that powers the components and data fetching.
## SDK & API Reference
Dig into our SDK reference documentation and REST API endpoints. Everything you need to integrate Crave into any application.
API overview, authentication, endpoints, and response formats.
Merchants, locations, menus, carts, and the ordering lifecycle.
## Explore by feature
Crave supports the full ordering lifecycle. Pick a feature to dive deep.
Display categories, products, and modifiers. Support multiple menus with scheduled availability.
Manage the cart lifecycle — add items, apply modifiers, handle discounts, and submit orders.
Accept payments with Stripe Connect. Handle payment intents, test cards, and error recovery.
Configure takeout, delivery, table-side ordering, and room service for your locations.
Track funnel events across the ordering flow and measure conversion rates.
Review the current availability boundary before designing an event-driven integration.
## Explore by framework
Server-side rendering with the App Router. Fetch menus on the server, manage carts on the client.
Client-side React with Vite. Build a custom SPA storefront with the SDK.
No framework required. Call the REST API directly from plain JavaScript.
Build native mobile ordering apps with Expo and the Crave SDK.
## Beyond the basics
Customize colors, fonts, and component styles to match your restaurant's brand.
Use test mode, mock data, and example test suites to validate your integration.
Deploy to Vercel, Netlify, Docker, or custom hosting with production best practices.
Handle every error the API can return with status codes, retry strategies, and user messages.
## Community
Connect with the Crave community on Slack.
Reach out to our team directly for help with your integration.
# REST API Essentials
Source: https://docs.craveup.com/getting-started/rest-api
Call the public Crave Storefront API directly from web, mobile, or server applications.
The public Storefront API is rooted at:
```text theme={null}
https://api.craveup.com/api/v1/storefront
```
Published merchant, location, menu, and product reads are anonymous. A new ordering session returns a cart capability. Send that capability only in `X-Cart-Token` for the matching cart, use the current ETag in `If-Match` for mutations, and give every write an `Idempotency-Key`. Signed-in customer resources use `Authorization: Bearer `.
## Fetch a published menu
```bash theme={null}
curl "https://api.craveup.com/api/v1/storefront/locations/loc_123/menus?orderDate=2026-08-08&orderTime=18:30"
```
## Start an ordering session
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: session_01" \
-d '{"fulfillmentMethod":"takeout"}'
```
The optional `marketplaceId` is a source-attribution label such as `"web"` or
`"mobile"`, not a location ID. The route already supplies the location scope.
Store `cart.id`, `cartAccessToken`, and `cart.revision` together in tab-scoped browser storage or the mobile platform's secure storage.
## Add an item
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/items" \
-H "Content-Type: application/json" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-0"' \
-H "Idempotency-Key: add_item_01" \
-d '{"productId":"prod_margherita","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}'
```
The response includes a new ETag. Use that value for the next mutation. If the API returns `CART_CONFLICT`, fetch the cart with the capability, update local state and revision, and require an explicit user retry.
## Authentication boundaries
| Resource | Credential |
| - | - |
| Published catalog | None |
| Cart and checkout | Matching cart capability |
| Customer profile, orders, addresses, saved payments, loyalty | Customer JWT |
| Public receipt | Receipt capability from the URL fragment or customer JWT |
| Admin and partner APIs | Server-only credential; never a storefront client variable |
Do not put capabilities, JWTs, receipt tokens, or payment secrets in query strings or logs. Configure the API CORS allowlist with exact production and preview storefront origins.
Explore the [Storefront REST reference](/api-reference/overview/api-overview) for endpoint schemas.
# Storefront SDK
Source: https://docs.craveup.com/getting-started/storefront-sdk
Use the typed direct client for public catalog, cart, customer, checkout, receipt, and loyalty flows.
`@craveup/storefront-sdk@2.0.1` is the supported TypeScript client for the public API at `/api/v1/storefront`. Version 2 has no browser API-key option and is a deliberate major-version migration from the published 1.x contract.
## Install and configure
```bash theme={null}
pnpm add --save-exact @craveup/storefront-sdk@2.0.1
```
Create separate server and browser clients. A server client has no session or JWT provider; use it only for anonymous published calls:
```ts theme={null}
import { createStorefrontClient } from "@craveup/storefront-sdk";
export const storefrontServer = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
});
```
A browser client owns the caller-provided, tab-scoped stores:
```ts theme={null}
import { createStorefrontClient } from "@craveup/storefront-sdk";
import { customerTokenStore, sessionStore } from "./storefront-session";
export const storefrontBrowser = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
sessionStore,
getAuthToken: () => customerTokenStore.get(),
defaultTimeoutMs: 10_000,
});
```
| Option | Required | Purpose |
| - | - | - |
| `baseUrl` | Yes | Exact Crave API origin; remote origins must use HTTPS. |
| `sessionStore` | For carts | Caller-owned storage for the cart ID, capability, location, and revision. |
| `getAuthToken` | For customer flows | Supplies the merchant-bound customer JWT. |
| `fetch` | No | Custom fetch implementation for non-browser runtimes. |
| `defaultTimeoutMs` | No | Timeout for auth-token resolution and the network response. |
The client ignores caller-supplied authorization credentials and obtains customer auth only from `getAuthToken`. Private API keys are not a Storefront SDK option. The exact typed method surface is the complete public JSON transport API; raw request helpers are not exported, and the navigation-only hosted-storefront redirect is deliberately unwrapped. Do not pass the browser client, its store, or its token provider into a Server Component, server action, or route handler.
Every remote `baseUrl` must use HTTPS. Plain HTTP is accepted only for the exact
loopback hosts `localhost`, `127.0.0.1`, and `[::1]`; a phone-accessible LAN host
such as `192.168.x.x` is not loopback. Redirect responses are rejected rather
than followed. Async `getAuthToken` resolution uses the same timeout as the
network response. `defaultTimeoutMs` and a per-call `timeoutMs` start before the
token lookup, so a provider that does not settle produces a
`StorefrontTimeoutError` without sending a request.
Scope stored data as follows:
| Record | Required scope | Browser persistence | Mobile persistence |
| - | - | - | - |
| Guest cart session | API environment + merchant + location | Versioned `sessionStorage` | `expo-secure-store` |
| Customer JWT | API environment + merchant | Versioned `sessionStorage` | `expo-secure-store` |
| Receipt capability | API environment + merchant + receipt ID | Versioned `sessionStorage` | Only when resuming |
Reject malformed or unknown stored versions instead of casting unvalidated JSON. The [Next.js](/quickstarts/nextjs), [React](/quickstarts/react), and [Expo](/quickstarts/mobile) quickstarts provide complete adapters.
Within one client instance, the SDK serializes session-store mutations per
location, including async SecureStore adapters. It persists revisions
monotonically, coalesces identical concurrent ordering-session starts, and
serializes different starts for the same location. Separate client instances or
processes need coordination from the application or storage adapter.
## Authorization model
* Published merchant, location, menu, product, distance, order-time, and gratuity calls are anonymous and never receive the customer JWT.
* `orderingSessions.start` returns a purpose-limited cart capability and saves it through `sessionStore`.
* Cart calls attach the capability, current revision, and an idempotency key where required.
* Customer profile, order, address, saved-payment, and loyalty history calls use the customer JWT.
* Receipt access uses `receiptToken` and sends it only as `X-Receipt-Token`.
Use tab-scoped storage for browser capabilities. Clear the guest capability after claim, deletion, expiry, or terminal checkout handling; after claim, keep only the authoritative cart revision if customer-authenticated access continues. Clear the merchant-scoped JWT on logout and a receipt capability after terminal display or expiry. Never put capabilities or customer JWTs in URLs, public environment variables, logs, source maps, HTML, or analytics.
## Catalog and ordering
```ts theme={null}
const merchant = await storefrontServer.merchant.getBySlug("demo-cafe");
const location = await storefrontServer.locations.getById("loc_123");
const menus = await storefrontServer.menus.list(location.id, {
menuOnly: true,
});
const readiness = await storefrontServer.locations.getOrderingReadiness(
location.id,
"takeout",
);
if (!readiness.ready) throw new Error(readiness.reason);
const { cart } = await storefrontBrowser.orderingSessions.start(location.id, {
fulfillmentMethod: "takeout",
});
const updated = await storefrontBrowser.cart.addItem(location.id, cart.id, {
productId: "prod_123",
quantity: 1,
selections: [],
itemUnavailableAction: "remove_item",
});
```
`locations.getOrderingReadiness()` is an anonymous, side-effect-free GET. Use it
to check a fulfillment method without creating a cart; omit the second argument
to use the `takeout` default.
## Checkout and terminal result
```ts theme={null}
const paymentSession = await storefrontBrowser.checkout.createPaymentSession(
locationId,
cartId,
{ includeCustomerContext: true },
);
const result = await storefrontBrowser.checkout.getOrderResult(
locationId,
cartId,
);
switch (result.state) {
case "payment_pending":
case "order_pending":
break; // poll with bounded backoff
case "completed":
console.log(result.order.id);
break;
case "failed":
console.error(result.code);
break;
}
```
The PaymentIntent client secret is browser-usable but still sensitive: keep it in memory and do not log it or place it in a URL.
Use `includeCustomerContext: true` only when a signed-in shopper still has a
guest capability and payment creation needs Stripe customer association. Omit
it for guest checkout. A claimed cart already uses the customer JWT because its
guest capability has been cleared.
## Customer authentication
```ts theme={null}
const challenge = await storefrontBrowser.customer.login({
merchantSlug: "demo-cafe",
identifierString: "alex@example.com",
});
const { token } = await storefrontBrowser.customer.verifyOtp({
merchantSlug: "demo-cafe",
identifierString: "alex@example.com",
customerName: "Alex",
lastName: "Customer",
methodId: challenge.methodId,
otp: "123456",
});
```
After storing the JWT for the current tab, use `customer.getProfile`, `customer.orders`, `customer.addresses`, `customer.savedPayments`, `loyalty.ledger`, and `loyalty.claims`.
## Receipts
```ts theme={null}
const receipt = await storefrontBrowser.receipts.get(receiptId, {
receiptToken,
});
```
Receipt links place the capability in the URL fragment. Capture it, immediately remove the fragment with `history.replaceState`, and keep it only in tab-scoped storage for that receipt.
## Errors and concurrency
```ts theme={null}
import {
StorefrontApiError,
StorefrontClientStateError,
StorefrontProtocolError,
StorefrontTimeoutError,
} from "@craveup/storefront-sdk";
try {
await storefrontBrowser.cart.updateGratuity(locationId, cartId, {
percentage: "18",
});
} catch (error) {
if (error instanceof StorefrontApiError) {
console.error(error.status, error.code, error.requestId);
if (error.retryAfterMs !== undefined) scheduleRetry(error.retryAfterMs);
} else if (error instanceof StorefrontClientStateError) {
console.error(error.code);
} else if (error instanceof StorefrontProtocolError) {
console.error(error.reason, error.method, error.routeTemplate);
} else if (error instanceof StorefrontTimeoutError) {
console.error(error.method, error.routeTemplate);
}
}
```
`StorefrontClientStateError` is a typed local preflight failure. For example,
`CUSTOMER_AUTH_REQUIRED` means a customer-authenticated method was called
without an available JWT, so no network request was sent.
`StorefrontProtocolError.reason` is `EMPTY_RESPONSE` or `INVALID_JSON` for a
successful response that violates the JSON contract. `retryAfterMs` is a
bounded millisecond delay parsed from `Retry-After`; it is `undefined` when the
API did not provide a usable value. On `CART_CONFLICT`, fetch the cart again
before presenting a retry. Reuse an explicit idempotency key when your
application deliberately retries the same logical mutation.
## Navigation-only redirect
`GET /locations/{locationId}/redirect` is a navigation-only REST operation and
is intentionally not part of the typed SDK method surface. Navigate a browser or
native in-app browser to it directly when entering Crave's hosted storefront; do
not fetch it as JSON with the Storefront client. SDK requests reject redirect
responses and never follow them.
## Browser-origin onboarding
Register every exact browser origin with Crave before testing from a browser. The origin includes its scheme, hostname, and any non-default port; local, preview, and production origins are separate registrations. Wildcards are not accepted. Native mobile requests normally omit the browser `Origin` header, but an Expo web deployment still needs registration.
# Accept Payments
Source: https://docs.craveup.com/guides/accept-payments
Use the protected Crave payment session selected for the restaurant.
Crave selects the restaurant's connected payment provider server-side. Square is selected whenever it is connected; Stripe is used only where Square is disconnected.
## Create a payment session
```ts theme={null}
const payment = await storefront.checkout.createPaymentSession(
locationId,
cartId,
{ idempotencyKey: crypto.randomUUID() },
);
```
The SDK sends the cart capability, current revision, and idempotency key. A raw REST request must send the same headers:
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/payment-session" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-4"' \
-H "Idempotency-Key: payment_01"
```
If `payment.provider` is `SQUARE`, tokenize with Square Web Payments and send the token to `checkout.confirmPayment`. If it is `STRIPE`, confirm its client secret with Stripe Elements.
## Confirm a Stripe session with Stripe Elements
```tsx theme={null}
import {
PaymentElement,
useElements,
useStripe,
} from "@stripe/react-stripe-js";
export function PaymentForm() {
const stripe = useStripe();
const elements = useElements();
async function submit(event: React.FormEvent) {
event.preventDefault();
if (!stripe || !elements) return;
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/order/confirmation`,
},
});
if (error) showPaymentError(error.message);
}
return (
);
}
```
Use Stripe publishable configuration in the client. Stripe secret keys remain on Crave-managed services. Never collect or log raw card numbers.
After Stripe returns, poll `storefront.checkout.getOrderResult(locationId, cartId)`. Treat only `completed` as success; `payment_pending` and `order_pending` are retryable states, while `failed` is terminal.
See the complete checkout state machine.
Poll the order result and load protected receipts.
# Prebuilt Storefront Prompt
Source: https://docs.craveup.com/guides/ai/build-storefront-prompt
Give an AI coding agent the direct public Storefront API security and delivery contract.
Copy the prompt below into your coding agent's project instructions.
```text theme={null}
Build a production-ready Crave restaurant storefront.
Use the exact @craveup/storefront-sdk@2.0.1 package with:
const crave = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
sessionStore,
getAuthToken,
});
Requirements:
- Published catalog reads are anonymous; never add a browser API key.
- Use versioned sessionStorage scoped by canonical API environment, merchant, and location for cart capability and revision state.
- Keep cart capabilities, customer JWTs, receipt tokens, and PaymentIntent client secrets out of URLs, logs, analytics, and persistent storage.
- Start ordering sessions with idempotency, then use SDK cart methods so capability, ETag, If-Match, and idempotency headers remain consistent.
- On CART_CONFLICT, reload current cart state before presenting a retry.
- Include merchantSlug in customer login and OTP requests.
- Confirm payments with Stripe.js and poll all four order-result states: payment_pending, order_pending, completed, failed. Never assume success.
- Capture receipt capabilities from URL fragments, remove the fragment immediately, and keep each capability only in tab-scoped storage scoped by API environment, merchant, and receipt ID.
- Keep Stripe secret keys, POS credentials, loyalty credentials, webhook secrets, and private Crave integration keys on the API service.
- Use exact CORS origins for each deployed storefront.
Before completion, run tests, lint, type checking, a production build, a browser-bundle credential scan, and a sandbox end-to-end ordering check. Report exact commands and remaining deployment requirements.
```
Useful references:
* [Storefront SDK](/getting-started/storefront-sdk)
* [API overview](/api-reference/overview/api-overview)
* [Build a Custom Storefront](/guides/build-custom-storefront)
* [Deployment](/guides/deployment)
# Use the CraveUp MCP Server
Source: https://docs.craveup.com/guides/ai/craveup-mcp-server
Connect AI agents to CraveUp tools for storefront setup, menu import, readiness checks, and go-live onboarding.
The CraveUp MCP server is an agent-facing developer tool. It can create or
update restaurant setup data when authenticated, so connect it only in
workspaces where the agent is allowed to make those changes.
CraveUp provides a Model Context Protocol server through the `@craveup/mcp` package. It lets AI agents use Crave-specific tools instead of guessing API shapes from memory.
Use it when you want an agent to:
* inspect or import menus
* connect a storefront project to a Crave location
* check storefront readiness before launch
* configure location ordering settings
* work through go-live onboarding steps
* fetch Crave implementation guidance while building a storefront
## Setup
The MCP server runs over stdio. Use the public npm package for normal usage outside this monorepo, or point your client at the local repo build when testing unreleased tools.
You do not need a clone of `craveup-turborepo` to use the MCP server — `npx -y @craveup/mcp` fetches everything it needs.
Most configurations below set `CRAVEUP_API_KEY` because menu imports, edits, and private readiness checks need a trusted server credential. Public storefront guides, project wiring, and published menu reads do not use that key. If you do not have an organization yet, register the server with no credentials and sign in from chat instead — see [Sign in from chat](#sign-in-from-chat).
### Cursor
Add this to your Cursor MCP configuration:
```json theme={null}
{
"mcpServers": {
"craveup": {
"command": "npx",
"args": ["-y", "@craveup/mcp"],
"env": {
"CRAVEUP_ENVIRONMENT": "sandbox",
"CRAVEUP_API_KEY": "crv_test_...",
"CRAVEUP_LOCATION_ID": "loc_..."
}
}
}
}
```
Restart Cursor or reload the window after changing the MCP configuration.
Inside `craveup-turborepo`, pnpm workspace resolution can shadow the published
package. If you are working in this repo, use the local repo build
configuration below.
### Codex
Add separate sandbox and production servers from the terminal:
```bash theme={null}
codex mcp add craveup-sandbox \
--env CRAVEUP_ENVIRONMENT=sandbox \
--env CRAVEUP_API_KEY=crv_test_... \
--env CRAVEUP_LOCATION_ID=loc_... \
-- npx -y @craveup/mcp
codex mcp add craveup-production \
--env CRAVEUP_ENVIRONMENT=production \
-- npx -y @craveup/mcp
```
If your Codex configuration uses TOML, add:
```toml theme={null}
[mcp_servers.craveup]
command = "npx"
args = ["-y", "@craveup/mcp"]
[mcp_servers.craveup.env]
CRAVEUP_ENVIRONMENT = "sandbox"
CRAVEUP_API_KEY = "crv_test_..."
CRAVEUP_LOCATION_ID = "loc_..."
```
### Claude Code
CraveUp is a local stdio MCP server, not a hosted HTTP endpoint. Register
separate sandbox and production processes at user scope:
```bash theme={null}
claude mcp add -s user craveup-sandbox \
-e CRAVEUP_ENVIRONMENT=sandbox \
-- npx -y @craveup/mcp
claude mcp add -s user craveup-production \
-e CRAVEUP_ENVIRONMENT=production \
-- npx -y @craveup/mcp
```
Run `/mcp` in Claude Code to inspect or reconnect the server. Start a new Claude
Code session if the current session does not refresh its tool inventory after
registration.
Use `-s project` only when you intentionally want to commit a team-wide
`.mcp.json`. Never place API keys or access tokens in that file.
### Claude Desktop
Add this to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json theme={null}
{
"mcpServers": {
"craveup": {
"command": "npx",
"args": ["-y", "@craveup/mcp"],
"env": {
"CRAVEUP_ENVIRONMENT": "sandbox",
"CRAVEUP_API_KEY": "crv_test_...",
"CRAVEUP_LOCATION_ID": "loc_..."
}
}
}
}
```
Restart Claude Desktop after saving the file.
### Local repo build
Use this when you are working inside `craveup-turborepo` and need the newest MCP tools from source:
```bash theme={null}
pnpm --filter @craveup/mcp build
```
Then configure your MCP client to run the built CLI:
```json theme={null}
{
"mcpServers": {
"craveup": {
"command": "node",
"args": ["/absolute/path/to/craveup-turborepo/packages/mcp/dist/cli.js"],
"env": {
"CRAVEUP_ENVIRONMENT": "sandbox",
"CRAVEUP_API_KEY": "crv_test_...",
"CRAVEUP_LOCATION_ID": "loc_..."
}
}
}
}
```
## Environment and authentication
`CRAVEUP_ENVIRONMENT` is required and must be `sandbox` or `production`. The MCP server resolves the
dashboard, public/admin APIs, Clerk issuer/client, expected API-key prefix, and credential namespace
from that one value. It refuses a test/live key or endpoint mismatch before making a request.
The environment is immutable for the lifetime of an MCP process. Use two named
registrations rather than an in-session switch: `craveup-sandbox` for
development and `craveup-production` for go-live work. This keeps hosts,
credentials, API keys, and browser sessions isolated.
| Environment | Dashboard | API key prefix |
| - | - | - |
| `sandbox` | `sandbox.dashboard.craveup.com` | `crv_test_...` |
| `production` | `dashboard.craveup.com` | `crv_live_...` |
Sandbox remains isolated from live data and can be used for storefront setup,
catalog, and cart development. During the current direct-charge cutover, ordinary
Stripe payment admission is deliberately hard locked in both sandbox and production:
the API returns `STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE`. Present checkout as
temporarily unavailable and do not retry or bypass that response. Dashboard,
MCP, and setup tools remain available while the payment cutover is completed.
The MCP server supports three authentication modes.
| Mode | Use it for | Required setup |
| - | - | - |
| **Crave API key** | Menu import, product edits, integration status, and the private part of readiness checks | Set `CRAVEUP_API_KEY`, and usually `CRAVEUP_LOCATION_ID` |
| **Public Storefront API** | Published menu reads, direct-integration guides, and project environment wiring | No credential; provide a published `locationId` when the tool needs one |
| **CraveUp login session** | Organization setup, project wiring, go-live onboarding, and dashboard-backed operations | Ask the agent to sign you in (see below), run `craveup login`, or set `CRAVEUP_ACCESS_TOKEN` |
| **Expo robot token** | Dispatch and monitor approved internal mobile release workflows | Set `EXPO_TOKEN` in the MCP server environment; never put it in prompt text |
To create an API key, open Business Manager and go to [Dashboard > Developers > API Development > Step 1 - API Keys](/guides/generate-api-key). API keys are tied to an organization and may be restricted to specific locations; each MCP call still needs a target `locationId`.
### Sign in from chat
Requires `@craveup/mcp` 0.5.0 or later.
An API key is scoped to an organization that already exists, so it cannot unlock organization setup or the go-live tools. For those, the agent can start a browser sign-in for you — no `craveup login` in a terminal, and no API key in the configuration. `CRAVEUP_ENVIRONMENT` is still required, since it selects which dashboard and Clerk issuer the sign-in targets:
```json theme={null}
{
"mcpServers": {
"craveup": {
"command": "npx",
"args": ["-y", "@craveup/mcp"],
"env": {
"CRAVEUP_ENVIRONMENT": "sandbox"
}
}
}
}
```
Then ask the agent to sign you in. It runs:
1. `start_crave_auth` — returns a hosted Crave Up URL. Open it and sign in, or create a free account.
2. `complete_crave_auth` — finishes the exchange and stores the session.
The session is written to the macOS keychain, falling back to `~/.config/craveup/credentials.json`, and is shared with the `craveup` CLI — so signing in through either one satisfies the other. Run `whoami` at any time to confirm which account is active.
On versions before 0.5.0 these tools are absent. Upgrade, or run `craveup
login` from a terminal to produce the same shared session.
Normal MCP requests reuse the persisted CLI/MCP session; they do not open a
browser. Browser authentication happens only when `start_crave_auth` (or
`craveup login`) is explicitly started. Sandbox and production sessions are
stored separately.
Do not put production API keys in shared prompt text, screenshots, recordings,
or public repository files. Pass credentials through the MCP client
environment instead.
## Available tools
Exact tool availability depends on the installed package version.
The current repository source exposes these tool groups; installed npm versions may differ:
| Tool group | Examples | Purpose |
| - | - | - |
| **Onboarding** | `get_onboarding_flow`, `whoami`, `create_organization`, `connect_project` | Move a restaurant project from first setup to a connected Crave storefront |
| **Browser auth** | `start_crave_auth`, `complete_crave_auth`, `cancel_crave_auth` | Sign in or sign up from chat without leaving the conversation (0.5.0 and later) |
| **Menu and storefront** | `import_menu`, `list_menus`, `get_product`, `update_product`, `set_product_availability`, `delete_product`, `check_storefront_readiness` | Import, inspect, edit, and verify storefront data |
| **Location settings** | `get_location_settings`, `update_store_hours`, `set_takeout_enabled`, `set_scheduled_orders_enabled`, `open_location_for_orders` | Fix ordering gates such as closed-store or unavailable pickup states |
| **Discounts** | `list_discounts`, `create_order_discount` | Read location promo codes and create order-level discounts idempotently |
| **Go-live** | `get_onboarding_status`, `set_location_address`, `set_fulfillment_methods`, `list_subscription_plans`, `start_subscription_checkout` | Continue setup at any time; subscription and Connect are required only for production payments |
| **Guidance** | `get_integration_guide` | Ask for Crave-specific implementation guidance while coding |
| **Runtime status** | `get_mcp_capabilities` | Inspect versions, policy digest, and fail-closed compatibility blockers without credentials |
| **Mobile releases** | `check_mobile_release_readiness`, `start_mobile_release`, `get_mobile_release_status` | Preflight internal releases and dispatch TestFlight with explicit approval |
Multi-location and sandbox tools — `create_location`, `delete_location`, `list_locations`, `get_location_orders`, `list_customers`, `set_menu_active`, and `seed_sandbox_analytics` — are available from 0.4.0. Browser auth is available from 0.5.0. The repo source can carry tools ahead of the published package; use the local repo build if you need one before its release.
All current tools return one versioned operation envelope in both text and
`structuredContent`. Review-required writes preview without side effects and
require the exact returned intent digest for apply. Destructive, paid, and
release operations require a separate confirmation phrase. Use
`get_mcp_capabilities` to inspect the policy digest and current blockers. A
ready intent digest does not claim that target pre-state, project registry
evidence, bounded background jobs, or opaque cursor pagination exist; those
capabilities remain fail-closed until their server/release contracts are
published.
Policies whose inventory says `idempotencyStrategy: unverified` remain blocked
after preview. Today that includes `create_organization`, `create_location`,
`start_subscription_checkout`, and `start_mobile_release`. Complete those
actions in Business Manager or the provider workflow until the authoritative
service provides a durable operation identity and response-loss reconciliation.
`check_storefront_readiness` reads the published menu and the side-effect-free
`GET /ordering-readiness` endpoint without creating a cart, then reads private
integration status for payment configuration. The private step requires
`CRAVEUP_API_KEY`. A ready payment check describes configuration only; the tool
does not create a PaymentIntent, prove payment admission, or execute a test payment.
While the direct-charge lock exists, checkout still returns
`STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE`.
For mobile releases, first run `craveup apps configure` in the Expo/React Native project and commit
the generated `.eas/workflows` files. The MCP release trigger only supports internal TestFlight. It
requires the exact confirmation phrase and a scoped Expo robot token because dispatching a workflow
consumes EAS build minutes. Android internal publishing stays in the CLI so the local Play submit
profile can be verified immediately before submission. Apple password/2FA prompts remain in a
visible terminal, and public App Store review remains a separate App Store Connect action.
## Example prompts
Once connected, ask your agent for Crave-specific work:
```text theme={null}
Use the CraveUp MCP server to sign me in to Crave Up, then tell me which account and organization I am on.
```
```text theme={null}
Use the CraveUp MCP server to inspect my current location, list active menus, and tell me whether this storefront can accept orders.
```
```text theme={null}
Use the CraveUp MCP server and the Crave docs to build a Next.js menu page with category tabs, cart state, and the server-selected checkout payment-session flow. If payment admission returns STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE, show checkout as temporarily unavailable without retrying.
```
```text theme={null}
Import this menu into Crave. Preserve categories, products, prices, modifier groups, availability, and image URLs. After import, verify the active menu with list_menus.
```
## Faster setup with a prebuilt prompt
Use the prebuilt prompt when you want an AI agent to start with Crave Cloud, Crave.js, Storefront API, and MCP context already loaded.
Copy a ready-to-use agent prompt for building a Crave-powered storefront.
Create a server-only key only when private MCP tools need it. A storefront
never receives that key.
## Troubleshooting
| Problem | Fix |
| - | - |
| The agent cannot see CraveUp tools | Restart the MCP client after changing configuration. In Cursor, reload the window. |
| `CRAVEUP_ENVIRONMENT is required` | Set it to `sandbox` or `production` in the MCP server environment. |
| `No Crave session` appears | Call `start_crave_auth` then `complete_crave_auth`, run `craveup login`, or set `CRAVEUP_ACCESS_TOKEN`. |
| Storefront tools fail with an auth error | Confirm the key prefix matches the selected environment and can access the target location. |
| Stripe checkout reports `STRIPE_DIRECT_CHARGE_CUTOVER_INCOMPLETE` | The direct-charge activation lock is working as designed. Keep setup/catalog/cart work available, show checkout as temporarily unavailable, and do not retry or bypass it. |
| Sandbox asks for Stripe Connect setup | Do not bypass the returned setup state. During the cutover, continue non-payment work; after activation, follow the current onboarding status for that location. |
| Menu tools use the wrong location | Set `CRAVEUP_LOCATION_ID`, or pass `locationId` explicitly in the tool call. |
| New tools do not appear after a repo update | Rebuild `@craveup/mcp`, then restart the MCP client. |
# Analytics
Source: https://docs.craveup.com/guides/analytics
Track storefront events and measure ordering funnel performance.
The Crave SDK includes a built-in analytics module that tracks key events across the ordering funnel. Use it to measure conversion rates, identify drop-off points, and understand customer behavior.
## Event types
The public SDK accepts three cart-authorized interaction events. Crave records `ORDER_PLACED`
server-side only after payment succeeds and the order is created, so clients cannot forge a
conversion.
| Event | When to fire | Funnel stage |
| - | - | - |
| `SCAN` | Customer scans a QR code or visits the storefront URL | Entry |
| `CART_VIEW` | Customer opens the cart panel | Consideration |
| `CHECKOUT_VIEW` | Customer reaches the checkout page | Intent |
| `ORDER_PLACED` | Recorded automatically by Crave after order creation | Conversion |
## Track events with the SDK
```ts SDK theme={null}
import { storefront } from '@/lib/storefront';
// Track a storefront visit
await storefront.analyticsEvents.track(locationId, {
cartId,
eventType: 'SCAN',
});
// Track cart view with metadata
await storefront.analyticsEvents.track(locationId, {
cartId,
eventType: 'CART_VIEW',
metadata: {
itemCount: cart.totalQuantity,
total: cart.orderTotalWithServiceFee,
},
});
// Track checkout view
await storefront.analyticsEvents.track(locationId, {
cartId,
eventType: 'CHECKOUT_VIEW',
metadata: {
fulfillmentMethod: cart.fulfilmentMethod,
},
});
```
```bash REST theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/analytics-events" \
-H "Content-Type: application/json" \
-H "X-Cart-Token: CART_CAPABILITY" \
-H "Idempotency-Key: analytics_01" \
-d '{
"cartId": "cart_456",
"eventType": "CHECKOUT_VIEW"
}'
```
The API returns `{ "status": "accepted" }` after it authorizes the cart and records or deduplicates
the event.
## Where to fire events
Place analytics calls at these points in your storefront:
```tsx filename="src/app/page.tsx" theme={null}
// SCAN — fire after the ordering session creates or restores the cart
useEffect(() => {
if (cartId) {
storefront.analyticsEvents.track(locationId, { cartId, eventType: "SCAN" });
}
}, [cartId]);
```
```tsx filename="src/components/CartPanel.tsx" theme={null}
// CART_VIEW — fire when cart panel opens
function CartPanel({ isOpen }: { isOpen: boolean }) {
useEffect(() => {
if (isOpen && cartId) {
storefront.analyticsEvents.track(locationId, {
cartId,
eventType: "CART_VIEW",
});
}
}, [isOpen]);
// ...
}
```
```tsx filename="src/app/checkout/page.tsx" theme={null}
// CHECKOUT_VIEW — fire on checkout page mount
useEffect(() => {
if (cartId) {
storefront.analyticsEvents.track(locationId, {
cartId,
eventType: "CHECKOUT_VIEW",
});
}
}, [cartId]);
```
Do not send `ORDER_PLACED` from a browser or mobile client. Poll the authoritative order result for
the UI; the API writes the conversion event during server-side order creation.
## Custom metadata
The `metadata` field accepts any key-value pairs. Use it to attach context to events:
```ts theme={null}
await storefront.analyticsEvents.track(locationId, {
cartId,
eventType: "CART_VIEW",
metadata: {
source: "qr_code",
campaignId: "summer_promo_2025",
deviceType: "mobile",
itemCount: cart.totalQuantity,
total: cart.orderTotalWithServiceFee,
},
});
```
## Google Analytics integration
Forward Crave events to Google Analytics for unified reporting:
```ts filename="src/lib/analytics.ts" theme={null}
import { storefront } from "@/lib/storefront";
type PublicFunnelEvent = "SCAN" | "CART_VIEW" | "CHECKOUT_VIEW";
const GA_EVENT_MAP: Record = {
SCAN: "storefront_visit",
CART_VIEW: "view_cart",
CHECKOUT_VIEW: "begin_checkout",
};
export async function trackEvent(
locationId: string,
eventType: PublicFunnelEvent,
cartId: string,
metadata?: Record,
) {
// Send to Crave
await storefront.analyticsEvents.track(locationId, {
cartId,
eventType,
metadata,
});
// Forward to Google Analytics
if (typeof window !== "undefined" && window.gtag) {
window.gtag("event", GA_EVENT_MAP[eventType], {
currency: metadata?.currency ?? "USD",
value: metadata?.total ?? 0,
items: metadata?.items ?? [],
});
}
}
```
If you also send a Google Analytics purchase event, do so only after
`checkout.getOrderResult()` returns `completed`; do not forward that event to the Crave public
analytics endpoint.
## View analytics in the Dashboard
Analytics data is available in the Crave Dashboard under **Analytics > Storefront**. The dashboard shows:
* **Funnel visualization** — conversion rates between each stage
* **Event timeline** — raw event stream with metadata
* **Location comparison** — performance across multiple locations
* **Time-based trends** — daily, weekly, and monthly patterns
## Best practices
* **Fire events once** — deduplicate using a ref or flag to prevent double-counting on re-renders
* **Don't block the UI** — fire analytics calls without `await` to keep the interface responsive
* **Include metadata** — the more context you attach, the more useful your Dashboard reports
* **Test outside production first** — confirm events appear in the intended Dashboard environment
```ts theme={null}
// Fire-and-forget pattern (don't await)
storefront.analyticsEvents
.track(locationId, {
cartId,
eventType: "CART_VIEW",
})
.catch(console.error);
```
## Next steps
Track order status after checkout.
Deploy your storefront and go live.
# Build a Custom Storefront
Source: https://docs.craveup.com/guides/build-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,
});
```
## 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
```ts theme={null}
const payment = await storefront.checkout.createPaymentSession(
locationId,
cartId,
{ 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 Square Web Payments or Stripe Elements based on `payment.provider`, then poll `checkout.getOrderResult`. Treat
`payment_pending` and `order_pending` as nonterminal; render success only for
`completed`; render an actionable failure for `failed`.
## 6. Customer and receipt access
Login and OTP requests must include `merchantSlug`. Customer resources use the returned JWT, stored by API environment and merchant. 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](/guides/deployment) and the [Storefront SDK](/getting-started/storefront-sdk) for the full contract.
# Checkout Flow
Source: https://docs.craveup.com/guides/checkout-flow
Finalize a capability-protected cart, confirm payment, and poll the authoritative order result.
Checkout uses the same cart capability, revision, and idempotency protections as other cart mutations. Creating a payment intent is a write and is never an anonymous lookup by cart ID.
## 1. Finalize the cart
```ts theme={null}
await storefront.cart.update("loc_123", cartId, {
fulfillmentMethod: "takeout",
});
await storefront.cart.updateOrderTime("loc_123", cartId, {
pickupType: "ASAP",
});
await storefront.cart.validateAndUpdateCustomer("loc_123", cartId, {
customerName: "Alex Johnson",
emailAddress: "alex@example.com",
phoneNumber: "+1234567890",
});
```
Each call uses the latest cart revision. If a call returns `CART_CONFLICT`, refresh the cart and require an explicit retry.
## 2. Create the payment session
```ts theme={null}
const payment = await storefront.checkout.createPaymentSession(
"loc_123",
cartId,
{ idempotencyKey: crypto.randomUUID() },
);
```
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/payment-session" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-4"' \
-H "Idempotency-Key: payment_01"
```
Branch on `payment.provider`. Pass a Stripe `clientSecret` to Stripe Elements, or initialize Square Web Payments with its application and location IDs. Raw card data and provider secrets never pass through storefront code.
## 3. Confirm payment
```tsx theme={null}
const { error } = await stripe.confirmPayment({
elements,
confirmParams: {
return_url: `${window.location.origin}/order/confirmation`,
},
});
```
Do not add the cart ID or capability to `return_url`. Keep the cart session in tab-scoped storage so the returning page can continue securely.
## 4. Poll the order result
```ts theme={null}
const result = await storefront.checkout.getOrderResult("loc_123", cartId);
switch (result.state) {
case "payment_pending":
case "order_pending":
// Poll again with bounded backoff.
break;
case "completed":
// Render result.order, then clear persisted cart capability state.
break;
case "failed":
// Show a retry-safe failure using result.code, then clear terminal state.
break;
}
```
Only `completed` is success. Never infer success from a redirect or from the absence of an error.
Integrate Stripe Elements without exposing server secrets.
Implement bounded result polling and receipts.
# Crave Cloud Overview
Source: https://docs.craveup.com/guides/crave-cloud-overview
How Crave Cloud connects franchise ordering, identity, loyalty, payments, operations, APIs, and open developer surfaces.
Crave Cloud is the franchise platform behind Crave.js. It gives restaurant brands one integration surface for digital ordering while letting each module be adopted independently, connected to systems the brand already trusts, or kept on the roadmap until the brand is ready.
For developers, that means Crave.js is not a standalone demo app. It is the storefront layer inside a broader cloud platform for restaurants with multiple locations, operators, payment accounts, loyalty providers, and guest touchpoints.
This page separates shipped surfaces from target product direction. Named bring-your-own providers are integration boundaries, not guaranteed prebuilt connectors.
## The platform model
Guests, teams, apps, kiosks, franchisees, operators, developers, and restaurant locations.
Business Manager foundations, Crave.js, API contracts, provider integration health, readiness, and module configuration.
One customer experience across ordering, loyalty, payments, locations, and brand operations as each module is enabled.
## Ecosystem map
Read Crave Cloud left to right: your guest and operator channels connect once to Crave Cloud, Crave Cloud coordinates the modules, and the brand gets one connected franchise experience. Status labels show what is shipped today versus target direction.
```mermaid theme={null}
flowchart LR
subgraph World["Your world"]
Guests["Guests Order, pay, earn, redeem when loyalty is connected"]
Teams["Your teams HQ, franchisees, operators, developers"]
Channels["Digital channels Mobile app, web ordering, kiosk, custom storefronts"]
Network["Restaurant network One brand, 1 to 500+ locations"]
Guests --> Channels
Teams --> Network
end
subgraph Cloud["Crave Cloud"]
Connection["One connection Choose modules independently"]
Manager["Business Manager Orgs, locations, roles, readiness, integrations Shipped foundations"]
Surface["Crave.js surface SDK, REST, CLI, UI components Shipped"]
subgraph Modules["Choose modules independently"]
Identity["Customer identity Target managed identity or BYO"]
Ordering["Ordering Crave.js shipped"]
Loyalty["Loyalty Engine exists; Cloud integration work"]
Payments["Payments Contract implemented; admission locked"]
end
Connection --> Surface
Manager --> Surface
Surface --> Identity
Identity --> Ordering
Identity --> Loyalty
Ordering --> Payments
Ordering --> Loyalty
end
subgraph Result["Connected franchise experience"]
Customer["One customer across the brand Target"]
DigitalOrdering["Flexible digital ordering Shipped for Crave.js path"]
Rewards["Brand-wide loyalty Target"]
Routing["Flexible payment routing Stripe contract"]
Ops["Centralized operations Foundations"]
Keep["Keep what works Design"]
end
Channels --> Surface
Network --> Surface
Surface --> Customer
Surface --> DigitalOrdering
Loyalty --> Rewards
Payments --> Routing
Manager --> Ops
Modules --> Keep
```
This map is intentionally simplified from the internal architecture diagram. Bring-your-own vendors are integration boundaries, not prebuilt connector promises. Confirm enablement per brand.
## Core surfaces
| Surface | What it does |
| - | - |
| **Business Manager** | Provides shipped foundations for organizations, locations, roles, readiness, integrations, analytics, and operational configuration. |
| **Crave.js** | Provides the shipped storefront developer surface: SDK, REST API patterns, CLI, and React components for branded ordering experiences. |
| **APIs** | Give digital channels one integration surface for menus, carts, checkout, fulfillment, the Stripe payment contract, and analytics. Direct-charge admission remains locked. |
| **Modules** | Let brands choose ordering, identity, loyalty, and payments independently. Some modules are shipped, some are target direction, and some require brand-specific integration work. |
## Choose modules independently
Crave Cloud is designed for gradual adoption. A brand can use Crave.js ordering while keeping an existing loyalty provider, or prepare the Stripe payment contract while connecting an existing ordering platform. The goal is to replace only what needs replacing; payment admission remains unavailable until the direct-charge cutover is activated.
For loyalty, **Loyalty Interchange Protocol** means Crave's standard contract for balances, rewards, tiers, earning, redemption, refunds, and reversals across channels. The engine and contract exist; the Cloud-to-ordering integration layer is the product glue that makes it usable inside Crave Cloud.
| Module | Current or managed path | Bring-your-own path | Status |
| - | - | - | - |
| **Customer identity** | Target Clerk-backed Crave Customer Identity. The storefront today still uses Stytch OTP. | Existing identity provider using Auth0, Okta, Entra, Clerk, or OIDC patterns. | Target + BYO |
| **Ordering** | Crave.js menus, carts, checkout, fulfillment, analytics, Storefront SDK, REST API, and components. | Existing ordering platform, OMS, or custom storefront integration. | Crave.js shipped |
| **Loyalty** | Loyalty Interchange Protocol contract and engine exist; Cloud-to-ordering integration work connects it to ordering flows. | Existing loyalty provider such as Punchh, Paytronix, Thanx, or Spendgo through integration work. | Engine + integrate |
| **Payments** | Server-selected checkout sessions; Stripe direct-charge payment admission is cutover locked until activation. | Existing gateway or processor integration such as Adyen, Braintree, Worldpay, or a custom gateway. | Direct-charge cutover locked |
Bring-your-own providers are integration boundaries, not blanket availability promises. Use the current implementation guides and your Crave integration plan to confirm what is enabled for a specific brand.
## Identity boundary
Crave Cloud keeps **guest identity** and **staff identity** separate.
* Guest identity powers storefront ordering, loyalty, rewards, and customer-facing channels.
* Staff identity powers Business Manager access, roles, franchise operations, and administrative workflows.
* Guests never become Business Manager organization members.
* Crave does not store guest passwords or issue guest auth tokens.
* Merchant keys and guest JWTs are not sent to Loyalty Interchange Protocol endpoints.
This separation matters for franchise brands because a customer can order across locations without becoming a staff user, and a franchise operator can manage locations without gaining access to guest accounts.
The target managed identity path is Clerk-backed Crave Customer Identity. The current storefront implementation still uses Stytch OTP, and bring-your-own identity providers are handled through brand-specific integration planning.
## Where Crave.js fits
Crave.js is the developer-facing ordering layer inside Crave Cloud:
* install the exact `@craveup/storefront-sdk@2.0.1` release
* scaffold storefronts with `npx @craveup/cli init`
* connect menus, modifiers, carts, checkout, fulfillment, the activation-locked Stripe payment contract, and analytics
* use typed SDK helpers or direct REST calls
* customize UI components while keeping the restaurant ordering workflow intact
Crave.js is not the staff dashboard, the loyalty runtime, or a POS replacement. Those belong to other Crave Cloud surfaces or brand-specific integration work.
Start with [Build a Restaurant Ordering App in Minutes](/getting-started/introduction), then use this overview to understand how that storefront fits into Crave Cloud.
# Deployment
Source: https://docs.craveup.com/guides/deployment
Deploy a direct Crave storefront with exact origins and no browser credentials.
## Storefront environment
| Variable | Required | Description |
| - | - | - |
| `NEXT_PUBLIC_CRAVEUP_API_URL` | Yes | Public Crave API origin. |
| `NEXT_PUBLIC_CRAVEUP_LOCATION_ID` | Standalone/local quickstarts | Published location override. |
| `NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG` | Standalone/local quickstarts | Published merchant override. |
| `NEXT_PUBLIC_STOREFRONT_URL` | Optional | Canonical metadata override; defaults to the request host. |
| `NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY` | For checkout | Browser-safe Stripe publishable key for the same environment. |
| `NEXT_PUBLIC_GOOGLE_MAPS_API_KEY` | For delivery maps | Browser key restricted by domain and API. |
In the hosted `cravejs-storefront-template`, the controlled host/subdomain
selects the merchant and the `[locationId]` route selects the location. Do not
duplicate those tenant selectors as production environment variables. The
prefixed merchant and location variables above are only for standalone or local
quickstart wiring. The canonical storefront origin is host-derived from request
headers by default; set `NEXT_PUBLIC_STOREFRONT_URL` only when deployment
metadata needs an explicit override.
Do not configure private Crave API keys, provider credentials or inbound webhook secrets, cart capabilities, customer JWTs, or receipt capabilities as public variables.
## API service environment
Provider credentials remain on the API service. Every browser deployment needs its exact browser origin registered before it sends credentials or capabilities. An origin is the scheme, hostname, and non-default port; paths are not part of an origin.
For the Crave-managed API, provide the local-development, stable-preview, and production origins to the Crave integration owner. There is currently no wildcard or arbitrary self-service bypass. Register stable preview origins individually; do not attempt to allow every generated preview hostname.
Operators of the API service configure the approved origins as a comma-separated list:
```env theme={null}
CORS_ALLOWED_ORIGINS=https://order.example.com,https://preview.example.com
```
First-party `https://*.craveup.com` origins are allowed by default. Localhost is allowed only outside production. The API accepts `Authorization`, `X-Cart-Token`, `X-Checkout-Handoff`, `X-Receipt-Token`, `Idempotency-Key`, `If-Match`, `X-Request-Id`, and `Content-Type`, and exposes `ETag` plus `X-Request-Id`. Restart or redeploy the API after changing its static allowlist, then verify the preflight from the exact deployed origin.
Native iOS and Android requests normally omit the browser `Origin` header. Expo web, embedded web views that send an origin, and browser storefronts still require exact registration.
## Vercel or Netlify
1. Import the storefront repository.
2. Add the public variables for the correct environment.
3. Add the deployed origin to the API allowlist before testing cross-origin requests.
4. Build and deploy a preview.
5. Verify anonymous catalog reads, ordering-session creation, cart mutation, PaymentIntent creation, and terminal order polling.
6. Promote the exact verified commit to production.
## Required release gates
```bash theme={null}
pnpm test
pnpm lint
pnpm check-types
pnpm build
```
Also scan generated browser assets for credentials and validate that cart/customer/receipt capabilities never appear in URLs or analytics. A healthy page load alone is not an ordering-flow verification.
## Rotation and rollback
Rotating private provider credentials does not require rebuilding the storefront. Rotating the cart capability key ring requires the API migration/runbook for active carts. Roll back API and storefront releases together only when their public contract differs; otherwise deploy the failing surface independently.
# Display Menu
Source: https://docs.craveup.com/guides/display-menu
Fetch menus, categories, and products for a location.
This guide covers how to load a restaurant's menu data and display it in your storefront — including multiple menus, category filtering, and product details.
## How menus work
Every Crave location has one or more **menus**. Each menu contains **categories**, and each category links to **products**. The active menu depends on the current time and the location's schedule.
```
Merchant → Location → Menu(s) → Categories → Products → Modifiers
```
## Fetch location data
The location response includes basic metadata. Menu and product data comes from the ordering session and API endpoints.
```ts SDK theme={null}
import { storefront } from '@/lib/storefront';
const location = await storefront.locations.getById('loc_123');
console.log(location.restaurantDisplayName);
```
```bash REST theme={null}
curl "https://api.craveup.com/api/v1/storefront/locations/loc_123"
```
## Start a session to get the menu
Menus are anonymous published resources. Start an ordering session only when the customer begins a cart; the response includes the cart capability used for later cart operations.
```ts SDK theme={null}
const session = await storefront.orderingSessions.start('loc_123', {
marketplaceId: 'web',
fulfillmentMethod: 'takeout',
});
const menu = await storefront.menus.list('loc_123', {});
```
```bash REST theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: session_01" \
-d '{"marketplaceId":"web","fulfillmentMethod":"takeout"}'
```
`marketplaceId` is optional source attribution, such as `"web"`; it is not the
location identifier. The route already provides the location scope.
## Display categories
Categories are used to group products (e.g., "Appetizers", "Mains", "Drinks"). Render them as tabs or a sidebar.
```tsx filename="components/CategoryTabs.tsx" theme={null}
interface Category {
id: string;
name: string;
productIds: string[];
}
function CategoryTabs({
categories,
activeId,
onSelect,
}: {
categories: Category[];
activeId: string;
onSelect: (id: string) => void;
}) {
return (
);
}
```
## Filter and search
Crave menus are returned as structured data. You can implement client-side filtering:
```ts theme={null}
// Filter products by category
const categoryProducts = products.filter((p) =>
activeCategory.productIds.includes(p.id)
);
// Search by name
const searchResults = products.filter((p) =>
p.name.toLowerCase().includes(query.toLowerCase())
```
## Get order times
Fetch the available time slots for ASAP or scheduled orders.
```ts SDK theme={null}
const orderTimes = await storefront.locations.getOrderTimes('loc_123');
if (orderTimes.requireScheduledOrders) {
// Show day/time picker from orderTimes.orderDays
} else {
// ASAP is available; optionally show schedule option
console.log('Schedule allowed:', orderTimes.scheduleAllowed);
}
```
```bash REST theme={null}
curl "https://api.craveup.com/api/v1/storefront/locations/loc_123/time-intervals"
```
## Product types
The SDK exports these types for working with menu data:
| Type | Description |
| - | - |
| `Menu` | Top-level menu with `id`, `name`, `isActive`, and `categories` |
| `Category` | Grouping with `id`, `name`, and `productIds` |
| `Product` | Full product with price, images, modifiers, and nutrition |
| `Modifier` | Modifier group with selection rules and items |
| `ModifierItem` | Individual modifier option with price and max quantity |
## Next steps
Add products to the cart with modifier selections.
Collect customer details and process payments.
# Error Codes
Source: https://docs.craveup.com/guides/error-codes
Handle public Storefront API errors without leaking credentials or replaying unsafe writes.
Every Storefront API error has a stable machine-readable code and a request ID:
```json theme={null}
{
"code": "CART_CONFLICT",
"message": "The cart changed. Refresh it and try again.",
"requestId": "req_123",
"details": {}
}
```
## SDK errors
```ts theme={null}
import {
StorefrontApiError,
StorefrontClientStateError,
StorefrontProtocolError,
StorefrontTimeoutError,
} from "@craveup/storefront-sdk";
try {
await storefront.cart.addItem(locationId, cartId, payload);
} catch (error) {
if (error instanceof StorefrontApiError) {
console.error(error.code, error.requestId);
if (error.retryAfterMs !== undefined) scheduleRetry(error.retryAfterMs);
} else if (error instanceof StorefrontClientStateError) {
console.error(error.code);
} else if (error instanceof StorefrontProtocolError) {
console.error(error.reason, error.method, error.routeTemplate);
} else if (error instanceof StorefrontTimeoutError) {
showRetryMessage();
}
}
```
`StorefrontApiError.retryAfterMs` is a bounded millisecond delay parsed from a
valid `Retry-After` response header, or `undefined` when no usable delay was
provided. `StorefrontClientStateError` is a local preflight failure; for
example, `CUSTOMER_AUTH_REQUIRED` means no customer JWT was available and no
request was sent. `StorefrontProtocolError.reason` is `EMPTY_RESPONSE` or
`INVALID_JSON` when a successful response violates the SDK's JSON contract.
Neither error retains response content, credentials, or secrets.
Do not log capability, JWT, receipt-token, authorization, or payment-secret values with an error.
## Public error codes
| Code | Typical status | Client behavior |
| - | - | - |
| `VALIDATION_ERROR` | 400 or 422 | Correct the fields described in `details` |
| `UNAUTHORIZED` | 401 | Restore the matching cart capability or reauthenticate the customer |
| `FORBIDDEN` | 403 | Stop; the authenticated principal does not own the resource |
| `NOT_FOUND` | 404 | Treat the resource as unavailable; protected endpoints may conceal existence |
| `RATE_LIMITED` | 429 | Honor `Retry-After` and back off |
| `CART_CONFLICT` | 409 | Fetch the latest cart and require an explicit retry |
| `RESOURCE_CONFLICT` | 409 | Refresh the affected customer resource before retrying |
| `CART_IMMUTABLE` | 409 | Stop mutating a locked, completed, or expired cart |
| `IDEMPOTENCY_KEY_REQUIRED` | 400 | Retry once with a stable unique key for that logical write |
| `IDEMPOTENCY_KEY_REUSED` | 409 | Generate a new key because the old key belongs to different input |
| `IDEMPOTENCY_IN_PROGRESS` | 409 | Poll or retry with bounded backoff using the same logical operation |
| `DEPENDENCY_UNAVAILABLE` | 503 | Show a temporary failure and retry with backoff |
| `INTERNAL_ERROR` | 500 | Show a generic message and retain the request ID for support |
## Retry rules
Retry reads and explicitly retryable failures such as `RATE_LIMITED` or
`DEPENDENCY_UNAVAILABLE` with exponential backoff. Prefer `retryAfterMs` when
the API supplies it. Treat `StorefrontProtocolError` as an upstream contract or
edge-transformation failure, retain its method and route template for support,
and retry only when the underlying operation is safe. Never automatically
replay a cart mutation after `CART_CONFLICT`, and never invent a new idempotency
key while the outcome of an earlier write is unknown.
```ts theme={null}
if (error instanceof StorefrontApiError && error.code === "CART_CONFLICT") {
const cart = await storefront.cart.get(locationId, cartId);
replaceCartState(cart);
askCustomerToRetry();
}
```
See the revision and idempotency lifecycle.
Review authentication boundaries and required headers.
# Fulfillment Methods
Source: https://docs.craveup.com/guides/fulfillment-methods
Configure delivery, table-side, and room service for your storefront orders.
Crave supports four fulfillment methods. This guide covers how to set each one on a cart and what data each method requires.
## Available methods
| Method | Value | Use case |
| - | - | - |
| **Takeout / Pickup** | `takeout` | Customer picks up at the counter |
| **Delivery** | `delivery` | Order delivered to customer's address |
| **Table-side** | `table_side` | Dine-in, delivered to a table number |
| **Room service** | `room_service` | Hotel/hospitality, delivered to a room |
## Check which methods are enabled
Each location has a `methodsStatus` field indicating what's available.
```ts theme={null}
const merchant = await storefront.merchant.getBySlug("downtown-pizza");
const location = merchant.locations[0];
console.log(location.methodsStatus);
// { pickup: true, table: true, delivery: true, roomService: false }
```
Only show fulfillment options that are enabled for the location.
## Takeout (default)
Takeout is the simplest method — just set the fulfillment method on the cart.
```ts SDK theme={null}
await storefront.cart.update('loc_123', cartId, {
fulfillmentMethod: 'takeout',
});
```
```bash REST theme={null}
curl -X PATCH "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-2"' \
-H "Idempotency-Key: fulfillment_01" \
-H "Content-Type: application/json" \
-d '{"fulfillmentMethod": "takeout"}'
```
## Delivery
Delivery requires the customer's address with geographic coordinates for distance calculation and fee computation.
Calculate the distance between the customer and the location to validate the
delivery zone.
Save the address to the cart.
### Check delivery distance
```ts SDK theme={null}
const distance = await storefront.locations.distance('loc_123', {
lat: 37.7749,
lng: -122.4194,
unit: 'miles',
});
console.log(`${distance.distance.miles} miles away`);
```
```bash REST theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/distance" \
-H "Content-Type: application/json" \
-d '{"lat": 37.7749, "lng": -122.4194, "unit": "miles"}'
```
### Set delivery address
```ts SDK theme={null}
await storefront.cart.setDelivery('loc_123', cartId, {
street: '123 Main Street',
city: 'San Francisco',
state: 'CA',
zipCode: '94105',
country: 'United States',
lat: 37.7749,
lng: -122.4194,
});
```
```bash REST theme={null}
curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-delivery" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-3"' \
-H "Idempotency-Key: delivery_01" \
-H "Content-Type: application/json" \
-d '{
"street": "123 Main Street",
"city": "San Francisco",
"state": "CA",
"zipCode": "94105",
"country": "United States",
"lat": 37.7749,
"lng": -122.4194
}'
```
The `Address` type requires these fields:
| Field | Type | Required | Description |
| - | - | - | - |
| `street` | `string` | Yes | Street address |
| `streetOptional` | `string` | No | Apt/suite/floor |
| `city` | `string` | Yes | City |
| `state` | `string` | Yes | State/province code |
| `zipCode` | `string` | Yes | Postal code |
| `country` | `string` | Yes | Full supported country name (for example, `United States`) |
| `lat` | `number` | Yes | Latitude |
| `lng` | `number` | Yes | Longitude |
Use a geocoding service (Google Places, Mapbox) to get `lat`/`lng` from the
customer's address input.
## Table-side
Table-side service sends the order to a specific table number within the restaurant.
```ts SDK theme={null}
await storefront.cart.setTable('loc_123', cartId, '12');
```
```bash REST theme={null}
curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-table" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-3"' \
-H "Idempotency-Key: table_01" \
-H "Content-Type: application/json" \
-d '{"tableNumber": "12"}'
```
Table numbers are numeric strings, such as `"12"` or `"12.5"`. Do not send
labels or prefixes such as `"T-12"` or `"Patio 3"`.
## Room service
Room service is designed for hotels and hospitality venues. It requires a room number and the guest's last name for verification.
```ts SDK theme={null}
await storefront.cart.setRoom('loc_123', cartId, {
roomNumber: '405',
lastName: 'Johnson',
});
```
```bash REST theme={null}
curl -X PUT "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/{cartId}/set-room" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-3"' \
-H "Idempotency-Key: room_01" \
-H "Content-Type: application/json" \
-d '{"roomNumber": "405", "lastName": "Johnson"}'
```
## Fulfillment fees
Each fulfillment method may have associated fees configured by the restaurant. These are automatically calculated and included in the cart totals:
| Cart field | Description |
| - | - |
| `fulfillmentMethodFeeTotal` | Fulfillment fee amount in cart currency |
Always render totals returned by the refreshed cart rather than recalculating fees in the client.
## Next steps
Complete the order with customer details and payment.
Set up Stripe for secure payment processing.
# Generate a Private Integration API Key
Source: https://docs.craveup.com/guides/generate-api-key
Create and protect a server-only Crave credential for admin or partner integrations.
Public storefronts do not use API keys. Browser and mobile apps call `/api/v1/storefront` directly with anonymous published reads, cart capabilities, and customer JWTs.
Generate an API key only for a private admin, partner, or CLI integration that runs in a trusted server environment.
## Create the key
1. Open the [Dashboard Developers page](https://dashboard.craveup.com/dashboard/developers).
2. Sign in as an organization admin for the merchant account.
3. Create a key with a clear environment and service name.
4. Select only the permissions the server integration needs. Read and write permissions are
independent; selecting write does not grant read.
5. Copy the token when it is shown, verify the displayed granted permissions, and put the token in
the server platform's secret manager.
```env theme={null}
# Server process only. Never expose this through a public frontend prefix.
CRAVEUP_API_KEY=replace_with_secret
```
Send the token only in the private `X-API-Key` request header from your trusted server process.
| Permission | Allows |
| - | - |
| `merchant:read` / `merchant:write` | Merchant and location settings reads or changes |
| `catalog:read` / `catalog:write` | Menu, product, modifier, and discount reads or changes |
| `orders:read` / `orders:write` | Order reads or lifecycle changes |
| `customers:read` | Customer reads and exports |
| `integrations:read` / `integrations:write` | Provider/integration status reads or configuration changes |
Use separate credentials for development, staging, and production. Test deployments accept only
`crv_test_` keys and production accepts only `crv_live_` keys. A wrong-tier, expired, revoked, or
under-scoped key fails closed.
Keys created before explicit permissions were introduced may show no scopes and can no longer call
private scoped routes. Create a least-privilege replacement, update the consumer through its secret
manager, verify the replacement, and then revoke the old key. Rotate or revoke any key that is
exposed or no longer used. Expired and revoked keys do not count toward the active-key limit. If
three active keys already exist, use the fourth slot only for replacement overlap and return to no
more than three after cutover.
Never embed a private integration key in browser JavaScript, an Expo bundle, a mobile app, a
public repository, a URL, or client-visible logs.
For storefront implementation, continue with the [Storefront SDK guide](/getting-started/storefront-sdk) or [REST API essentials](/getting-started/rest-api).
# Glossary
Source: https://docs.craveup.com/guides/glossary
Key terms and concepts used across the Crave platform.
## A
A server-only credential for private admin or partner integrations. Public Storefront API clients
do not use an API key.
A tracked action in the ordering funnel. Clients may fire cart-authorized `SCAN`, `CART_VIEW`, or
`CHECKOUT_VIEW`; Crave records `ORDER_PLACED` server-side after authoritative order creation. See
[Analytics guide](/guides/analytics).
## C
A temporary container holding a customer's selected items, modifiers, fulfillment method, and
order timing. Created when an ordering session starts. Identified by `cartId`. Carts expire after
inactivity.
The secret `cartAccessToken` returned when starting an ordering session. Persist it with the cart
ID and revision in tab-scoped browser storage or secure mobile storage, and send it only in
`X-Cart-Token`.
A grouping of products within a menu (e.g., "Appetizers", "Mains", "Drinks"). Contains an ordered
list of `productIds`. Displayed as tabs or sections in the storefront.
Authentication provider used for the Admin API and Dashboard. Clerk manages user sessions,
organization memberships, and JWT tokens. Not used for storefront customer-facing flows.
## D
The geographic area a location delivers to. Checked via `storefront.locations.distance()` before
setting a delivery address on a cart.
A promo code (e.g., `SAVE10`) applied to a cart for a percentage or fixed discount. Applied via
`storefront.cart.applyDiscount()`, removed via `storefront.cart.removeDiscount()`.
## F
How a customer receives their order. Four options: `takeout` (counter pickup), `delivery`
(address-based), `table_side` (dine-in to a table number), `room_service` (hotel room delivery).
Set on the cart before checkout. See [Fulfillment Methods guide](/guides/fulfillment-methods).
## G
An optional tip amount added during checkout. Locations configure whether tipping is enabled,
suggested percentages, a default percentage, and whether custom amounts are allowed. Set via
`storefront.cart.updateGratuity()`.
## L
A physical or virtual restaurant location within a merchant organization. Each location has its
own menus, hours, fulfillment methods, and payment settings. Identified by UUID or slug.
## M
A collection of categories and products for a location. Locations can have multiple menus (e.g.,
Lunch, Dinner, Weekend Brunch). The active menu depends on the current time and the location's
schedule.
The top-level business entity. A merchant has one or more locations. Looked up by slug via
`storefront.merchant.getBySlug()`.
A set of customization options for a product (e.g., Size, Toppings, Extras). Each group has
`min`/`max` selection rules. Contains modifier items.
A single option within a modifier group (e.g., "Large", "Extra Cheese", "Bacon"). Has an optional
price and max quantity.
## O
The checkout result state returned to a customer: `payment_pending`, `order_pending`, `completed`,
or `failed`.
The entry point for a customer interaction. Starting a session returns the cart, its capability,
and current revision. Created via `storefront.orderingSessions.start()`.
## P
A Stripe PaymentIntent created for an authorized cart via
`storefront.checkout.createPaymentIntent()`. Its `clientSecret` is passed to Stripe.js. See
[Accept Payments guide](/guides/accept-payments).
A menu item with name, description, price, images, and optional modifier groups. Added to carts
with quantity and modifier selections.
## R
A post-order experience rating (1-5 stars) with optional comment. Submitted via
`storefront.ratings.submit()` while the completed cart capability is available.
## S
A developer mode toggle that routes all operations through Stripe test mode. Orders are logged but
not sent to the restaurant. Toggled via the Admin API's `/developer-mode` endpoint.
A URL-safe identifier for merchants and locations (e.g., `downtown-pizza`). Used in storefront
location paths as a human-readable alternative to UUIDs.
The typed TypeScript client (`@craveup/storefront-sdk`) for the Storefront API. It manages public
reads, cart capabilities/revisions, customer JWTs, checkout, receipts, ratings, and loyalty. See
[SDK Reference](/getting-started/storefront-sdk).
Stripe's platform for marketplace payments. Each restaurant connects their Stripe account to
receive payouts. Crave handles the Connect setup; storefronts only need the publishable key for
Stripe.js.
## W
An event delivered through an HTTP POST. Crave currently receives private inbound provider
webhooks, but does not yet publish an outbound merchant subscription contract. See the [outbound
webhook availability guide](/guides/webhooks).
# Clover POS
Source: https://docs.craveup.com/guides/integrations/clover
Understand Clover connection, merchant mapping, catalog discovery, and activation gates.
Clover POS remains in development preview. An authorization connection is not
proof that catalog, order routing, Ecommerce payment, or refunds are ready for
production.
## Current connection flow
An organization administrator completes Clover OAuth v2. Crave stores access
and refresh credentials encrypted on the server and never returns them to
Business Manager.
The authorized Clover merchant must be explicitly mapped to one Crave
location. Connecting does not automatically select Clover as the active
payment processor.
A manual provider sync retrieves minimized items, categories, modifiers,
taxes, discounts, service charges, order types, stock, and opening hours.
Crave stores immutable provider snapshots, maps them to canonical catalog
identities, and reports conflicts or missing evidence without publishing a
menu.
An administrator may atomically publish a valid, current staged catalog.
Order routing, provider checkout, refunds, stored cards, and gift cards
remain separate capability gates. Existing carts and orders retain the
authority captured when they were created.
## Safety boundaries
* Crave never silently moves a Clover-owned checkout or refund to Stripe during an outage.
* Browser card data must be tokenized by Clover; raw card numbers and security codes must not reach
Crave servers.
* Tips, stored cards, native gift cards, split tender, discounts, and partial returns require their
own sandbox and entitlement evidence.
* Verified Clover webhooks are durable reconciliation signals. Financial state is re-read from
Clover before Crave treats it as authoritative.
## Availability
Production activation is not generally available. Contact
[hello@craveup.com](mailto:hello@craveup.com) for sandbox pilot requirements.
# Square commerce setup
Source: https://docs.craveup.com/guides/integrations/square
Connect Square as the operational commerce authority for menu, checkout, orders, refunds, loyalty, and gift-card tenders.
When an eligible Square connection is active for a location, Square is the
source of truth for operational catalog data, order calculation, customer
payments, refunds, loyalty, gift-card tenders, and the Square fulfillment
lifecycle. Checkout remains embedded in Crave through Square Web Payments.
Stripe is selected only for locations without an active Square connection; an
in-progress cart never switches processors silently.
## Activation flow
Authorize the requested merchant, customer, catalog, order, payment, refund, card, gift-card,
inventory, location, and loyalty scopes.
Bind the Crave location to the exact Square location after currency, timezone, and capability
checks.
Resolve catalog exceptions and activate a fresh, reviewed snapshot. Square owns price, tax,
discounts, availability, and schedule after activation.
Confirm checkout, webhook, and reconciliation health in the Dashboard before accepting traffic.
Delivery additionally requires explicit Square Delivery certification and DoorDash Drive
credentials.
## Authority rules
* A connected and mapped Square location uses Square Payments. Stripe is not a
fallback after Square has started a checkout.
* Crave never receives raw card or gift-card data. Square Web Payments returns
a token that the server uses with the mapped seller and location.
* Square calculates the order using mapped catalog variations, automatic taxes,
automatic discounts, service charges, and an optional order-linked Loyalty
reward. A customer-entered Crave coupon is rejected for Square commerce.
* Staff tip is recorded on the final Square Payment. DoorDash courier tip and
the authoritative Drive fee plus tax are Square Order service charges.
* Gift cards can partially authorize first; one final card, wallet, or stored
card pays the remaining balance. Saved cards require authenticated customer
ownership, buyer verification, and explicit consent. When a customer selects
a saved card, the storefront passes its opaque Card ID back to Square Web
Payments `Card.tokenize` with the current amount, currency, buyer contact,
`customerInitiated=true`, and `sellerKeyedIn=false`. Only the resulting
authenticated one-time payment token is used as the Square payment source;
the deprecated separate `verificationToken` field is not part of the Crave
payment contract. The selected Card ID remains server-side ownership
evidence. The storefront sends the current versioned
`cardSaveConsent` object only after the customer selects “Save this card
securely with Square for future orders.” Before calling Square `CreateCard`,
Crave writes immutable tenant-, customer-, connection-, and checkout-scoped
consent evidence. Removing a saved card disables it in Square and records a
separate revocation event. Crave stores a separate Square customer identity
for each connected Square seller account, so cards and Loyalty never cross
merchant boundaries when one Crave organization operates multiple Square
accounts.
* Authenticated customers can buy a new digital Square gift card in Crave. A
non-gift-card Square payment must complete before activation; the card is
linked to the same Square customer and its full number is returned once over
a private, no-store response. Crave projections, logs, and webhook storage
retain only safe IDs or redacted values.
* Full and partial refunds return to gift-card tenders first, then the remaining
card or wallet tenders. Mixed outcomes are surfaced as action required.
* A refund of the payment that purchased a gift card triggers an idempotent
Square `ADJUST_DECREMENT` activity with reason `PURCHASE_WAS_REFUNDED`, because
refunding that funding payment does not reverse the loaded gift-card balance.
* Square Loyalty replaces LIP for Square-authoritative locations. Enrollment,
reward preview, issue, redemption, point accrual, and webhook projection use
the Square customer and Loyalty account.
* Checkout reads the member quote from `GET .../loyalty/quote`. A first-time
member joins through the separate, explicitly consented
`POST .../loyalty/enrollment` mutation before earning can begin; applying a
reward never enrolls a customer as a side effect. Enrollment, reward apply,
and reward removal require the current cart revision and an idempotency key,
and each refreshes the Square payment session before payment can continue.
* Enrollment requires a phone that the active Crave customer has verified by
OTP. Checkout can add that factor to the current customer through
`POST /customer/phone-verification` and confirm it through
`POST /customer/phone-verification/confirmation`; an E.164-shaped profile
value alone is not accepted as proof. Crave's one-time verification consent
does not accept Square marketing text terms.
* Customer loyalty history is location-scoped at
`GET /customer/locations/{locationId}/loyalty/ledger`. A Square-authoritative
location reads the current balance with `RetrieveLoyaltyAccount` and the
reverse-chronological activity ledger with `SearchLoyaltyEvents` (up to 30
entries per page). It never falls back to LIP when Square is connected. The
retained location binding keeps that exclusion in force if Square credentials
or runtime health degrade, so Crave cannot mutate a second loyalty ledger
through quote, cart, completion, cancellation, refund, or durable retry
hooks. A queued LIP mutation rechecks the task location before delivery and
terminates without a provider call after Square takes authority. Legacy tasks
without location authority fail closed as well.
* When an administrator approves a customer loyalty claim, Crave resolves the
claim order's location before choosing the provider. A Square-authoritative
order writes the approved correction through Square `AdjustLoyaltyPoints`
with a stable provider idempotency key and retains the returned Square event
ID. Provider errors leave the claim retryable; Crave never posts that
adjustment to LIP. Orders at locations without Square authority continue to
use the configured LIP program.
* Completed Square orders accrue Loyalty points with an order-scoped provider
idempotency key. A failed or ambiguous accrual is retained as a durable
`LOYALTY_EFFECT` operation; the worker re-proves the completed Square tenders,
current tenant and location binding, and returned Loyalty events before it
marks the cart projection complete.
* Vendor confirmation, ready-for-pickup, and completion actions update the
exact Square fulfillment before Crave commits the matching local state. Each
update uses the current Square order version, fulfillment UID, and a stable
idempotency key. Square-originated fulfillment webhooks apply locally without
echoing the same transition back to Square.
## Delivery with DoorDash Drive
Delivery is fail-closed until the location has fresh capability evidence whose
source is `square_live_pos_delivery_certification`, a selected
`DOORDASH_DRIVE` provider, and valid Drive credentials.
Crave requests a five-minute Drive quote, fingerprints its address and cart
inputs, adds `fee + tax` and courier tip to the Square Order, pays that exact
order, then accepts the same Drive quote. Definite dispatch rejection triggers
idempotent Square refund compensation. An ambiguous timeout is reconciled using
the same external delivery ID and is never auto-refunded while a courier job
might exist. DoorDash webhooks update Crave tracking and the Square delivery
fulfillment.
Active deliveries are also retrieved every 60 seconds when their last
reconciliation is more than two minutes old, so a missing webhook does not
strand tracking state. Order cancellation reaches a conclusive DoorDash cancel
state before Square refund processing begins.
## Apple Pay domains
Apple Pay requires the storefront's exact HTTPS domain to be registered for
this Square application. Crave proxies Square's current
`apple-developer-merchantid-domain-association` file at
`/.well-known/apple-developer-merchantid-domain-association` without an
extension or long-lived cache. Then add the host under **Apple Pay** in Square
Developer Console, using Sandbox mode for a sandbox storefront and Production
mode for production. Apple Pay cannot be verified on localhost.
## Webhooks
Create the Square subscription against:
```text theme={null}
https:///api/v1/webhooks/square
```
Subscribe to the exact events handled by this release:
```text theme={null}
catalog.version.updated
inventory.count.updated
location.created
location.updated
order.created
order.updated
order.fulfillment.updated
payment.created
payment.updated
refund.created
refund.updated
customer.created
customer.updated
customer.deleted
card.created
card.updated
card.disabled
card.forgotten
card.automatically_updated
loyalty.account.created
loyalty.account.updated
loyalty.account.deleted
loyalty.event.created
loyalty.program.created
loyalty.program.updated
loyalty.promotion.created
loyalty.promotion.updated
gift_card.created
gift_card.updated
gift_card.customer_linked
gift_card.customer_unlinked
gift_card.activity.created
gift_card.activity.updated
oauth.authorization.revoked
```
Set `SQUARE_WEBHOOK_SIGNATURE_KEY` only in the sandbox secret manager. Never
paste it into tickets, chat, source control, or browser configuration. Crave
verifies Square's signature over the untouched body and exact notification URL,
then acknowledges only after durable inbox storage.
For DoorDash Drive, register:
```text theme={null}
https:///api/v1/webhooks/doordash
```
Configure the same Basic or OAuth `Authorization` value in DoorDash and the
`DOORDASH_WEBHOOK_AUTHORIZATION` sandbox secret. Crave compares the exact value
in constant time, durably stores the event, serializes delivery projections,
and retries failures.
## Health and recovery
The Square integration page shows authorization and mapping status, capability
readiness, last reconciliation, last webhook receipt, and pending or failed
events. Use **Reconcile** to refresh locations, catalog, inventory, orders,
payments, refunds, customers, cards, Loyalty programs, promotions, accounts,
events and rewards, plus gift cards and their location activity. Gift-card
account numbers are redacted before reconciliation evidence is stored. Use
**Retry events** after repairing a dependency. A webhook HTTP success means the
event was stored; projection occurs asynchronously and remains observable.
The runtime also enqueues one tenant-scoped full completeness sweep for every
mapped Square location in each UTC day. The durable operation pins the exact
organization, connection, and binding, runs the same canonical path as the
operator action, and records the successful system reconciliation in the
health timeline. Provider or network failures use bounded exponential retry;
an expired read-only lease is safe to replay, while changed binding evidence
fails closed for operator review. Faster catalog, webhook, payment/refund,
checkout-abandonment, Loyalty, and DoorDash workers continue to repair their
own nonterminal domains between full sweeps.
## Admin endpoints
| Method | Path | Purpose |
| - | - | - |
| `POST` | `/locations/:id/integrations/square/oauth` | Start OAuth |
| `GET` | `/locations/:id/integrations/square` | Status, health, capabilities |
| `GET` | `/locations/:id/integrations/square/locations` | List Square locations |
| `POST` | `/locations/:id/integrations/square/locations` | Bind the Square location |
| `POST` | `/locations/:id/integrations/square/catalog/import` | Import the catalog |
| `POST` | `/locations/:id/integrations/square/reconcile` | Run full reconciliation |
| `POST` | `/locations/:id/integrations/square/events/retry` | Retry failed webhook events |
| `DELETE` | `/locations/:id/integrations/square` | Disconnect after deactivation |
Disconnecting preserves canonical catalog and historical evidence. Active or
degraded commerce modules must be deactivated or rolled back first.
# Stripe Payments
Source: https://docs.craveup.com/guides/integrations/stripe
Set up Stripe Connect for payment processing in your storefront.
Crave uses [Stripe Connect](https://stripe.com/connect) to process payments. Each restaurant connects their own Stripe account to receive payouts directly. This guide covers the full Stripe setup.
## How it works
When you sign up, Crave provisions a Stripe Connect Express account for your
organization.
Stripe collects your bank details, identity verification, and tax
information.
Your storefront uses Stripe.js with the connected account to securely
collect card details.
Funds are routed to your connected Stripe account after Crave's platform
fee.
## Prerequisites
* A Crave account with at least one location
* A bank account for payouts
* Government-issued ID for verification (Stripe requirement)
## Complete Stripe onboarding
1. In the Dashboard, go to **Settings > Payments**
2. Click **Complete Stripe Setup**
3. Stripe's embedded onboarding form collects:
* Business type (individual, company, non-profit)
* Business address and phone
* Bank account or debit card for payouts
* Identity verification (SSN or ID upload)
* Tax information (EIN or SSN)
4. Once submitted, Stripe verifies your information (usually instant, sometimes 1-2 business days)
You can start building your storefront immediately. Stripe onboarding only
needs to be complete before accepting real payments.
## Storefront integration
Your storefront only needs the **Stripe publishable key** on the client. The secret key and connected account routing are handled by Crave's backend.
### Environment setup
```env filename=".env.local" theme={null}
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_live_your_key
```
For test mode, use the test publishable key (`pk_test_...`).
### Payment flow
```tsx filename="app/checkout/page.tsx" theme={null}
import { loadStripe } from "@stripe/stripe-js";
import { Elements } from "@stripe/react-stripe-js";
// 1. Create a capability-protected payment session via Crave
const payment = await storefront.checkout.createPaymentSession(
locationId,
cartId,
);
if (payment.provider !== "STRIPE") throw new Error("Square owns this checkout");
// 2. Initialize Stripe only for a Stripe session
const stripePromise = loadStripe(
process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY!,
);
// 3. Wrap checkout UI with Elements provider
;
```
For the full implementation, see the [Accept Payments guide](/guides/accept-payments).
## Testing
In test mode, Crave automatically uses Stripe's test environment:
| Card | Scenario |
| - | - |
| `4242 4242 4242 4242` | Successful payment |
| `4000 0000 0000 3220` | 3D Secure required |
| `4000 0000 0000 0002` | Card declined |
Toggle test/live mode from the Dashboard under **Settings > Developer > Mode**.
View the current payout schedule and fee details in the connected Stripe Dashboard. Do not hard-code payout timing or commercial rates in storefront code.
## Troubleshooting
### "Stripe onboarding incomplete"
* Return to **Settings > Payments** and click **Complete Stripe Setup**
* Stripe may need additional documents for verification
* Check your email for messages from Stripe
### Payments failing in production
* Verify you're using the **live** publishable key (`pk_live_...`), not the test key
* Ensure the restaurant's Stripe Connect account is fully verified
* Check the Stripe Dashboard for declined payment details
# Toast POS
Source: https://docs.craveup.com/guides/integrations/toast
Connect Toast POS to sync menus and orders with your Crave storefront.
Toast POS integration is currently in development. This page will be updated with setup instructions, API details, and catalog sync documentation when the integration is available.
## Planned features
| Feature | Description |
| - | - |
| **Menu sync** | Import menus, categories, and items from Toast |
| **Order forwarding** | Push storefront orders to Toast for kitchen display and fulfillment |
| **Price sync** | Keep Crave prices in sync with Toast catalog changes |
| **Modifier mapping** | Map Toast modifier groups to Crave modifiers |
## How it will work
Enter your Toast API key and restaurant GUID in the Crave Dashboard.
Select which Toast restaurant maps to your Crave location.
Pull menus and items from Toast into Crave's menu editor.
Orders placed on your storefront are automatically sent to Toast.
## Get notified
To be notified when the Toast integration launches, contact [hello@craveup.com](mailto:hello@craveup.com) or join our [Slack community](https://join.slack.com/t/cravejscommunity/shared_invite/zt-3hfr081io-aYuLC3n6gBFKQuho0HrTng).
# Manage Cart
Source: https://docs.craveup.com/guides/manage-cart
Create and mutate a cart with capability, revision, and idempotency protection.
Every cart has three values that belong together: `cart.id`, the secret `cartAccessToken`, and the current `cart.revision`. The SDK persists them through the `sessionStore` you provide.
## Start a session
```ts theme={null}
const session = await storefront.orderingSessions.start("loc_123", {
marketplaceId: "web",
fulfillmentMethod: "takeout",
});
const cartId = session.cart.id;
```
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/ordering-sessions" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: start_01" \
-d '{"marketplaceId":"web","fulfillmentMethod":"takeout"}'
```
`marketplaceId` is optional source attribution, such as `"web"` or
`"mobile"`; it is not a location ID. The route already supplies the location
scope.
Store the response capability in `sessionStorage` for browsers or secure device storage for mobile. Do not use a URL, cookie shared across origins, analytics property, or application log.
## Add an item
```ts theme={null}
const cart = await storefront.cart.addItem("loc_123", cartId, {
productId: "prod_burger",
quantity: 1,
itemUnavailableAction: "remove_item",
selections: [
{
groupId: "mod_size",
selectedOptions: [{ optionId: "opt_large", quantity: 1 }],
},
],
});
```
```bash theme={null}
curl -X POST "https://api.craveup.com/api/v1/storefront/locations/loc_123/carts/cart_456/items" \
-H "Content-Type: application/json" \
-H "X-Cart-Token: $CART_ACCESS_TOKEN" \
-H 'If-Match: "cart-0"' \
-H "Idempotency-Key: add_item_01" \
-d '{"productId":"prod_burger","quantity":1,"selections":[],"itemUnavailableAction":"remove_item"}'
```
## Read and mutate the cart
```ts theme={null}
const current = await storefront.cart.get("loc_123", cartId);
await storefront.cart.updateItemQuantity("loc_123", cartId, "item_abc", 2);
await storefront.cart.removeItem("loc_123", cartId, "item_abc");
await storefront.cart.applyDiscount("loc_123", cartId, "SAVE10", {
includeCustomerContext: true,
});
await storefront.cart.removeDiscount("loc_123", cartId);
```
The SDK sends the capability automatically. For raw REST calls, send `X-Cart-Token` on every cart read and write. Send `If-Match` and a stable `Idempotency-Key` on every mutation.
Pass `includeCustomerContext: true` only when a signed-in shopper still has a
guest-capability cart and discount validation needs customer context. Omit it
for a guest; a claimed cart uses the customer JWT automatically.
## Handle conflicts safely
The API returns `CART_CONFLICT` when `If-Match` is stale. The SDK refreshes the stored revision but deliberately does not repeat the failed mutation. Replace the UI cart with the fresh server state, explain the change, and require the user to retry.
## End the capability lifecycle
`cart.delete()` clears the SDK session. `cart.claim()` removes the guest capability while retaining the authoritative revision for customer-authenticated access. After checkout reaches `completed` or `failed`, clear the persisted capability once terminal handling such as a same-page rating is finished.
Protect payment creation and order-result polling.
Handle authorization and concurrency failures.
# Developer Package and Crave Cloud
Source: https://docs.craveup.com/guides/open-source-and-cloud
How Crave's developer-facing package fits into the managed Crave Cloud platform.
Crave pairs a documented developer surface with a managed cloud product: the
integration contract is explicit, while production franchise operations remain
in a managed control plane.
The Storefront SDK is publicly downloadable from npm but is marked
`UNLICENSED`. Its canonical source repository is private, and npm availability
or public documentation does not grant permission to copy, modify, or
redistribute the package. Contact [hello@craveup.com](mailto:hello@craveup.com)
for licensing questions. Crave Cloud is where brands coordinate real locations,
roles, integrations, payment routing, customer identity, loyalty, and
operational readiness.
## How to think about it
| Layer | Use it for | Operational owner |
| - | - | - |
| **Crave.js packages** | Storefront SDK, CLI, examples, components, and local developer workflows | Your engineering team |
| **Storefront API** | Customer-facing ordering flows across web, mobile, kiosk, and custom channels | Shared between your app and Crave |
| **Crave Cloud** | Business Manager, locations, roles, identity boundaries, integrations, payment routing, observability, and readiness | Crave-managed cloud |
| **Bring-your-own systems** | Existing loyalty, ordering, identity, or payment systems that a brand wants to keep | Joint integration ownership |
## Managed cloud is the production default
Production restaurant franchise infrastructure is not just a package install. It needs secure roles, location readiness, integration monitoring, audit trails, payment routing, and support for operational edge cases across many stores.
Use Crave Cloud when you need:
* multiple locations, franchisees, or business entities
* staff roles and Business Manager access
* payment routing between corporate, franchisee, or location accounts
* monitored integrations with ordering, loyalty, payments, or identity providers
* supportable production operations
## Public developer surface
Use the Crave.js developer surface when you need:
* a local storefront you can run and customize
* typed SDK calls for menus, carts, checkout, payments, and analytics
* React components that can live inside your application
* REST examples for non-TypeScript clients
* clear contracts for webhooks and API behavior
Start with the CLI, SDK, API, and UI components.
See how storefronts connect to identity, ordering, loyalty, payments, and operations.
## What is not self-hosted
The documented Crave.js developer surface does not make the SDK package or
every part of Crave Cloud open source or self-hostable. Business Manager,
production identity boundaries, managed loyalty, payment routing, integration
health, and franchise operations are managed platform capabilities unless your
Crave agreement says otherwise.
This keeps the developer experience flexible without pushing the operational burden of a franchise platform onto every storefront team.
# Order Tracking
Source: https://docs.craveup.com/guides/order-tracking
Poll the authoritative checkout result and display a protected receipt.
After Stripe redirects back to the storefront, recover the cart session from tab-scoped storage and poll the protected order-result endpoint.
## Result states
| State | Meaning |
| - | - |
| `payment_pending` | Stripe has not reached a terminal payment state |
| `order_pending` | Payment succeeded and Crave is finalizing the order |
| `completed` | The response contains the public order detail |
| `failed` | Checkout reached a terminal failure with a machine-readable code |
```ts theme={null}
async function waitForOrder(locationId: string, cartId: string) {
for (let attempt = 0; attempt < 40; attempt += 1) {
const result = await storefront.checkout.getOrderResult(locationId, cartId);
if (result.state === "completed" || result.state === "failed")
return result;
await new Promise((resolve) =>
setTimeout(resolve, Math.min(1000 + attempt * 250, 3000)),
);
}
throw new Error("Order confirmation is taking longer than expected.");
}
```
Do not treat a redirect, cart lock, timeout, or missing response as success. Keep the capability until terminal handling finishes, then clear it from persisted storage.
## Receipt links
Guest receipt links place a receipt capability in the URL fragment so browsers do not send it to servers as part of the request URL. Capture it in the browser, immediately remove it with `history.replaceState`, store it in versioned, tab-scoped `sessionStorage` keyed by API environment, merchant, and receipt ID, and send it as `X-Receipt-Token`:
```ts theme={null}
const order = await storefront.receipts.get(receiptId, { receiptToken });
```
A signed-in customer can access their own order through the customer JWT instead. Receipt identifiers alone never authorize access.
## Ratings
Submit a rating while the completed cart capability is still available in memory:
```ts theme={null}
await storefront.ratings.submit(locationId, cartId, {
rating: 5,
comment: "Great food and fast service.",
});
```
Handle terminal and retryable failures.
Review all checkout and receipt methods.
# Testing
Source: https://docs.craveup.com/guides/testing
Test storefront capability, concurrency, checkout, and secret-handling behavior.
Use sandbox API and Stripe publishable values for local and CI runs:
```env theme={null}
NEXT_PUBLIC_CRAVEUP_API_URL=https://dev-api-43233223.craveup.com
NEXT_PUBLIC_CRAVEUP_LOCATION_ID=loc_test
NEXT_PUBLIC_CRAVEUP_MERCHANT_SLUG=test-cafe
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
```
## Unit tests
Test the caller-owned session adapter independently:
* versioned `sessionStorage` keys include the canonical API environment, merchant, and location;
* the cart capability and customer JWT never enter URLs, analytics, or `localStorage`;
* ETag revisions are retained;
* claim, delete, expiry, and terminal checkout handling clear persisted capability state;
* malformed or old-version records fail closed.
Mock `fetch` when testing the SDK:
```ts theme={null}
const fetchMock = vi.fn(
async () =>
new Response(JSON.stringify(cart), {
status: 200,
headers: { "Content-Type": "application/json", ETag: '"cart-4"' },
}),
);
const storefront = createStorefrontClient({
baseUrl: "https://api.example.test",
fetch: fetchMock,
sessionStore,
});
```
Assert cart calls send the capability, current `If-Match`, and an idempotency key. For `CART_CONFLICT`, assert the mutation is not replayed automatically and the current cart revision is refreshed.
## Checkout tests
Cover every authoritative result:
* `payment_pending` continues bounded polling;
* `order_pending` continues bounded polling;
* `completed` renders the returned order and clears persisted cart state after terminal handling;
* `failed` renders failure, never falls back to success, and clears persisted cart state after terminal handling;
* authorization, timeout, and stale-revision errors remain visible.
## Receipt tests
Start with a URL containing `#receiptToken=...`. Assert the app calls `history.replaceState` immediately, stores the token only for that API environment, merchant, and receipt ID in `sessionStorage`, and sends it in the receipt header—not in the request URL.
## End-to-end sandbox flow
1. Fetch published merchant, location, menu, product, distance, order-time, and gratuity data anonymously and verify no customer JWT is attached.
2. Create a cart and verify the capability is returned only once for a new cart.
3. Mutate the cart and verify revision changes.
4. Create a PaymentIntent and confirm with a sandbox payment method.
5. Observe nonterminal polling followed by exactly one terminal result.
6. Verify rating and receipt access.
7. Confirm no secret or capability appears in browser URLs, logs, analytics, or static assets.
Run the same build artifact you intend to promote.
# Theming
Source: https://docs.craveup.com/guides/theming
Customize your storefront's colors, fonts, and component styles.
Crave storefront templates are built with [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com). You customize the look by editing CSS variables and Tailwind config — no proprietary theming API to learn.
## How theming works
Every template ships with a `globals.css` file that defines CSS custom properties. Tailwind maps these variables to utility classes, and all Crave components use those classes. Change a variable, and every component updates.
```css filename="src/app/globals.css" theme={null}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 160 50% 22%; /* your brand color */
--primary-foreground: 0 0% 98%;
--accent: 160 30% 96%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
--primary: 160 50% 40%;
--primary-foreground: 0 0% 98%;
}
}
```
## Change brand colors
Update the `--primary` variable to match your brand. Use HSL values (hue, saturation, lightness):
```css filename="src/app/globals.css" theme={null}
:root {
--primary: 210 100% 50%; /* blue brand */
--primary-foreground: 0 0% 100%; /* white text on primary */
}
```
All buttons, links, active states, and accent elements update automatically.
Use the [shadcn/ui theme generator](https://ui.shadcn.com/themes) to preview color combinations and copy the CSS variables.
## Customize fonts
Templates use `next/font` for self-hosted fonts with zero layout shift. Update the font in `layout.tsx`:
```tsx filename="src/app/layout.tsx" theme={null}
import { Inter } from 'next/font/google';
// or use a local font:
// import localFont from 'next/font/local';
const font = Inter({ subsets: ['latin'] });
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
For local font files (`.woff`, `.woff2`):
```tsx filename="src/app/layout.tsx" theme={null}
import localFont from 'next/font/local';
const brandFont = localFont({
src: './fonts/BrandFont-Regular.woff2',
variable: '--font-brand',
});
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
Then reference the variable in your Tailwind config:
```ts filename="tailwind.config.ts" theme={null}
export default {
theme: {
extend: {
fontFamily: {
sans: ['var(--font-brand)', 'system-ui', 'sans-serif'],
},
},
},
};
```
## Customize component styles
Crave components use Tailwind utility classes with shadcn/ui's `cn()` helper. Override styles by passing `className`:
```tsx theme={null}
```
For deeper customization, edit the component source directly. All components live in your project's `src/components/` directory — they are not locked in a package.
## Border radius
Control the roundness of all components with the `--radius` variable:
```css filename="src/app/globals.css" theme={null}
:root {
--radius: 0.75rem; /* more rounded */
/* --radius: 0rem; /* sharp corners */
}
```
## Dark mode
Templates support dark mode out of the box using Tailwind's `dark:` variant. Toggle dark mode by adding the `dark` class to the `` element:
```tsx theme={null}
// Toggle dark mode
document.documentElement.classList.toggle('dark');
```
For Next.js apps, use [next-themes](https://github.com/pacocoursey/next-themes) for automatic system preference detection:
```tsx filename="src/app/providers.tsx" theme={null}
'use client';
import { ThemeProvider } from 'next-themes';
export function Providers({ children }: { children: React.ReactNode }) {
return (
{children}
);
}
```
## Restaurant branding
Load the restaurant's branding dynamically from the API:
```tsx theme={null}
const merchant = await storefront.merchant.getBySlug('downtown-pizza');
// Use merchant data for dynamic theming
```
Each merchant location also exposes `restaurantLogo` and `coverPhoto` for location-specific branding.
## Next steps
See all available storefront components and their props.
End-to-end guide for building a custom storefront.
# Troubleshooting
Source: https://docs.craveup.com/guides/troubleshooting
Diagnose direct Storefront API configuration, capability, concurrency, and checkout failures.
## The SDK says an explicit base URL is required
Set `NEXT_PUBLIC_CRAVEUP_API_URL` to the API origin and rebuild. Do not append `/api/v1/storefront`; the SDK owns that path.
## Browser requests fail CORS
Add the exact storefront origin, including scheme and host, to the API service's `CORS_ALLOWED_ORIGINS`. Avoid wildcard origins. Confirm the preflight allows the headers used by the failed request.
## Published data returns 404
Public catalog routes deliberately hide unpublished, inactive, or cross-tenant resources as not found. Verify the merchant/location is published and that the location belongs to the requested merchant.
## Cart request returns 401
The cart capability is absent, invalid, or belongs to another cart/location. Start a new ordering session if the saved capability is stale. Never substitute a private API key.
## Cart request returns `CART_CONFLICT`
Another mutation changed the cart revision. Fetch the cart, update the saved revision from `ETag`, present the current state, and let the shopper retry. Do not silently replay the mutation.
## Cart request returns `CART_IMMUTABLE`
The cart is locked, completed, or expired. Preserve terminal checkout handling when applicable; otherwise clear the stale session and create a new cart.
## Customer request returns 401
Confirm login and OTP requests used the same `merchantSlug`, and that `getAuthToken` returns the current customer JWT. Clear the JWT and restart login when the session is expired or revoked.
## Checkout appears stuck
Poll `checkout.getOrderResult` with bounded backoff. `payment_pending` and `order_pending` are not failures or success. Render success only for `completed`; render the supplied code for `failed`. Surface timeouts rather than manufacturing an order.
## Receipt is unavailable
Receipt access requires either the authorized customer JWT or the receipt capability. For email/SMS links, capture `receiptToken` from the fragment, remove the fragment immediately, and send the token through the SDK. A missing, wrong, or expired token intentionally looks like not found.
## PaymentIntent errors
Check cart revision first, then confirm the API service has matching provider credentials for the selected environment. The browser receives only the PaymentIntent client secret and Stripe publishable key.
## Useful error fields
`StorefrontApiError` provides `status`, `code`, `message`, `requestId`, `details`, `method`, and `routeTemplate` without retaining URLs, bodies, or credentials. Include `requestId` in support reports.
# Upgrade Guide
Source: https://docs.craveup.com/guides/upgrade-guide
Migrate a Storefront SDK 1.x integration to the direct, capability-safe 2.0.0 contract.
Storefront SDK `2.0.0` is a major version. It replaces the published 1.x browser API-key client with the direct public `/api/v1/storefront` contract. Treat the upgrade as an application migration: use a branch, update all callers together, and verify a sandbox order before production.
Published 1.x artifacts remain a durable npm history. Do not unpublish them or silently replace their contents. They may be deprecated after `2.0.0` is verified, but 2.x does not include an API-key shim, a dual transport, or duplicate names for the same operation. Use the canonical `cart.applyDiscount()`, `cart.removeDiscount()`, and location-scoped `loyalty.ledger(locationId)` methods.
## Install the exact major
```bash theme={null}
pnpm remove @craveup/storefront-sdk
pnpm add --save-exact @craveup/storefront-sdk@2.0.0
```
Commit the updated lockfile. Do not use `latest`, a semver range, a Git URL, or a workspace build in a released storefront.
## Replace configuration
Remove the 1.x browser configuration:
```ts theme={null}
// 1.x — remove this configuration.
createStorefrontClient({
apiKey: process.env.NEXT_PUBLIC_CRAVEUP_API_KEY!,
});
```
Create a 2.x browser client with public configuration and caller-owned stores:
```ts theme={null}
const storefront = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
sessionStore,
getAuthToken: () => customerTokenStore.get(),
});
```
Create a separate server client with only `baseUrl`; use it for anonymous published reads. Remove browser/mobile API keys, mint endpoints, BFF proxies added only for the old SDK, and hand-built authorization headers.
## Update methods, arguments, and types
| 1.x surface | 2.0.0 surface |
| - | - |
| `locations.getDistance(locationId, input)` | `locations.distance(locationId, input)` |
| `locations.submitRating(locationId, { cartId, rating, comment })` | `ratings.submit(locationId, cartId, { rating, comment })` |
| `payments.createIntent(locationId, cartId)` | `checkout.createPaymentSession(locationId, cartId)` |
| `cart.getRecommendations(locationId, cartId)` | `cart.recommendations(locationId, cartId)` |
| `discounts.apply(locationId, { cartId, code })` | `cart.applyDiscount(locationId, cartId, code)` |
| `discounts.remove(locationId, cartId)` | `cart.removeDiscount(locationId, cartId)` |
| `client.http.*` and `RequestOptions.skipAuth` | Removed; use the typed namespaces and SDK-owned authorization boundaries |
| `StorefrontAnalyticsEventType` enum | `StorefrontAnalyticsEvents` constant plus `StorefrontAnalyticsEvent` union |
| Client-supplied `ORDER_PLACED` | Removed; Crave records it after authoritative order creation |
| `StorefrontRecommendedProduct` / `RecommendedProductsResponse` | `RecommendationProduct` / `RecommendationProduct[]` |
| `SubmitRatingPayload` / `SubmitRatingResponse` | `RatingRequest` / `RatingResponse` |
| `GetLocationViaSlugType` | `StorefrontLocation` |
| `LocationAddressDTO` | `DeliveryAddress` |
| `ApiError` | `StorefrontApiError`, `StorefrontProtocolError`, `StorefrontTimeoutError`, or `StorefrontClientStateError` |
Only `SCAN`, `CART_VIEW`, and `CHECKOUT_VIEW` remain valid client analytics
events. `StorefrontClientStateError` provides a typed local preflight failure,
including `CUSTOMER_AUTH_REQUIRED`, before a request is sent without required
customer authentication.
## Update response handling
| Operation | 1.x response | 2.0.0 response |
| - | - | - |
| `orderingSessions.start` | `{ cartId }` or `{ errorMessage }` | `{ cart, cartAccessToken? }`; the SDK persists the capability and revision through `sessionStore` |
| `cart.addItem` | `{ cartId, cart }` | `StorefrontCart` |
| `cart.update` | Partial cart object | Authoritative `StorefrontCart` |
| `cart.updateOrderTime`, `cart.validateAndUpdateCustomer`, `cart.setDelivery`, `cart.setTable`, `cart.setRoom`, `cart.delete` | `{ success: true }` | Authoritative `StorefrontCart` |
| Apply or remove discount | `{ success: true }` | Authoritative `StorefrontCart` |
| Create PaymentIntent | `{ clientSecret, stripeAccountId }` | `{ clientSecret }`; Stripe account routing is server-owned |
Do not rely on structural casts to bridge these response changes. Remove
wrapper access such as `result.cart`, replace success-only branches with the
returned authoritative cart, and recompile every caller against 2.0.0. Product
fixtures and adapters must now provide the required `Product.locationId`.
`OrderTimesResponse` exposes labeled `orderDays`; replace the legacy regular-day
`{ from, to }` interval branch and render only the days and intervals returned by
the API.
## Update request validation
* Use only `takeout`, `table_side`, `room_service`, or `delivery` for
fulfillment. Set table and room identifiers through `cart.setTable()` and
`cart.setRoom()` rather than a generic cart patch.
* Use `cart.updateOrderTime()` for scheduling. `cart.update()` accepts
fulfillment and/or a note, not order-date or order-time fields.
* Send exactly one of `amount` or `percentage` to `cart.updateGratuity()`.
* Send a full supported country name such as `United States`, not `US`.
* Send table numbers as numeric strings such as `"12"`, not `"T-12"` or
`"Patio 3"`.
* Treat `marketplaceId` as optional source attribution such as `"web"` or
`"mobile"`; it is not a location ID.
* For a signed-in shopper who still has a guest-capability cart, use the named
`{ includeCustomerContext: true }` request option only when customer-aware
discount validation or Stripe customer association is needed. Omit it for a
guest; claimed carts use the customer JWT automatically.
## Required migration
1. Configure the explicit public API origin for the correct environment.
2. Provide a `StorefrontSessionStore` for `{ locationId, cartId, accessToken, revision }`, scoped by canonical API environment, merchant, and location.
3. Create carts through `orderingSessions.start()`. Let the SDK persist the returned capability and authoritative revision; do not copy the token into application state or UI props.
4. Let cart methods attach `X-Cart-Token`, `If-Match`, and `Idempotency-Key`. On `CART_CONFLICT`, refresh and ask the customer to retry; never silently replay intent.
5. Store the merchant-bound customer JWT in versioned, tab-scoped browser storage or `expo-secure-store`, and clear it on logout.
6. Replace legacy payment calls with `checkout.createPaymentSession()`, branch on the selected provider, and use bounded `checkout.getOrderResult()` polling; stop reading the removed `stripeAccountId` response field.
7. Capture a receipt capability from the URL fragment, remove the fragment immediately, and scope storage by environment, merchant, and receipt ID.
8. Register each exact deployed browser origin with Crave. Native apps normally need no CORS registration; Expo web does.
9. Delete the obsolete 1.x client, API-key configuration, wrapper types, adapters, tests, and documentation in the same application change.
## Verification checklist
* Public bundles contain no private Crave or payment-provider secrets.
* Published merchant, location, menu, product, distance, order-time, and gratuity requests carry no customer JWT.
* Wrong, expired, cross-location, and cross-merchant capabilities are denied.
* Stale revisions return `CART_CONFLICT` and are not automatically replayed.
* Only `completed` is treated as checkout success.
* CORS is restricted to the exact deployed storefront origins.
* A clean install, typecheck, production web build or Expo export, and sandbox vertical slice use the exact registry artifact.
If a production 1.x storefront cannot move atomically, keep its pinned 1.x artifact and deployed API path unchanged while a separate 2.0.0 release candidate is verified. Do not make one bundle switch between the two contracts at runtime.
# Outbound Webhooks (Planned)
Source: https://docs.craveup.com/guides/webhooks
Current availability and integration guidance for outbound merchant events.
Crave does not currently offer a public outbound webhook subscription service. There is no
merchant endpoint configuration screen, external event catalog, signing-secret flow, delivery log,
retry contract, or production support commitment to integrate against yet.
## Current boundary
Crave receives inbound provider webhooks from services such as Stripe, Square, and DoorDash. Those
provider callbacks are private Crave infrastructure and are not an outbound developer event API.
The repository contains persistence foundations for future outbound delivery, but the endpoint
registry, event producers, delivery worker, signing contract, operator UI, and external acceptance
evidence are not complete. Older examples that referenced events such as `order.created` or
`cart.abandoned`, the `X-Crave-Signature` header, fixed retry delays, or a **Settings > Webhooks**
screen are not implemented contracts and must not be used for production integrations.
## What to use today
* Use the Storefront API and SDK order-result resources for guest checkout outcomes.
* Use private, server-side management API keys only for the documented scoped routes.
* Use provider-specific integration guides only for the inbound provider configuration they
explicitly describe.
* Coordinate polling, reconciliation, or brand-specific event requirements with your Crave
integration owner before depending on them.
## Planned contract
The outbound webhook contract will be published only after its endpoint lifecycle, versioned event
schemas, signature and replay policy, retry and deduplication behavior, tenant isolation, delivery
observability, and sandbox conformance tests are implemented. Event names and headers remain subject
to change until that release.
Review the currently supported public and private request boundaries.
Validate the API behavior that is available today.
# Flutter quickstart
Source: https://docs.craveup.com/quickstarts/flutter
Connect a Flutter application to the Crave Storefront API with typed, capability-safe ordering.
This quickstart configures the official Dart SDK, checks whether takeout ordering is available, and
loads a published menu without a browser API key or BFF.
## 1. Install
```yaml pubspec.yaml theme={null}
dependencies:
flutter:
sdk: flutter
crave_storefront_sdk: 0.2.0
```
Run `flutter pub get`.
## 2. Configure deployment values
Pass deployment-specific values with `--dart-define`:
```bash theme={null}
flutter run \
--dart-define=CRAVEUP_API_URL=https://api.craveup.com \
--dart-define=CRAVEUP_MERCHANT_SLUG=downtown-pizza \
--dart-define=CRAVEUP_LOCATION_ID=loc_123
```
For sandbox testing, replace the origin and identifiers with the exact sandbox deployment values
provided for your account. Sandbox and production have separate environment variables and data;
never reuse a `StorefrontSessionStore` record or customer token across those origins.
## 3. Create the client
```dart theme={null}
import 'package:crave_storefront_sdk/crave_storefront_sdk.dart';
const apiUrl = String.fromEnvironment('CRAVEUP_API_URL');
const merchantSlug = String.fromEnvironment('CRAVEUP_MERCHANT_SLUG');
final sessionStore = InMemoryStorefrontSessionStore();
final client = CraveStorefrontClient(
baseUri: Uri.parse(apiUrl),
merchantSlug: merchantSlug,
sessionStore: sessionStore,
);
```
This anonymous guest quickstart needs no customer-token provider. The in-memory store makes the
sample runnable, but it loses the cart when the process exits. Before shipping, replace it with a
`StorefrontSessionStore` backed by encrypted platform storage.
## 4. Check availability and load the menu
```dart theme={null}
const locationId = String.fromEnvironment('CRAVEUP_LOCATION_ID');
Future loadTakeoutMenu() async {
final readiness = await client.locations.getOrderingReadiness(
locationId,
fulfillmentMethod: FulfillmentMethod.takeout,
);
if (readiness case OrderingUnavailable(:final reason)) {
throw StateError(reason);
}
return client.menus.getForLocation(locationId, menuOnly: true);
}
```
`getOrderingReadiness` is anonymous and side-effect free. Starting an ordering session is the point
where the SDK receives and stores the guest cart capability and revision.
## 5. Start an order
```dart theme={null}
final result = await client.orderingSessions.start(
locationId,
StartOrderingSessionRequest.fresh(
fulfillmentMethod: FulfillmentMethod.takeout.wireValue,
channel: OrderChannel.app,
),
);
final cart = result.cart;
```
Keep one client per application session so its serialized cart lifecycle protects monotonic
revisions. Dispose it with `client.close()` when the application-level owner is torn down.
## 6. Surface safe failures
```dart theme={null}
try {
await loadTakeoutMenu();
} on StorefrontApiException catch (error) {
showApiMessage(error.code, error.requestId);
} on StorefrontTimeoutException {
showRetryMessage();
}
```
See the [Dart and Flutter Storefront SDK guide](/getting-started/flutter-storefront-sdk) for session
storage, cancellation, conflict recovery, and security guidance.
# React Native / Expo Quickstart
Source: https://docs.craveup.com/quickstarts/mobile
Build a mobile storefront that calls the public Crave API directly.
The Storefront API is safe to call directly from an Expo app. Published catalog reads are anonymous, cart access is scoped by a short-lived capability, and signed-in customer resources use a customer JWT. No API key or mobile BFF is required.
## 1. Create the app
```bash theme={null}
npx create-expo-app my-storefront --template blank-typescript
cd my-storefront
npx expo install expo-secure-store
npm install --save-exact @craveup/storefront-sdk@2.0.1
```
## 2. Configure public values
```env filename=".env" theme={null}
EXPO_PUBLIC_CRAVEUP_API_URL=https://api.craveup.com
EXPO_PUBLIC_CRAVEUP_LOCATION_ID=loc_your_location_id
EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG=your-restaurant-slug
```
Never put Crave admin, partner, or payment-provider secrets in an `EXPO_PUBLIC_` variable.
## 3. Create the client
```ts filename="src/lib/storefront.ts" theme={null}
import * as SecureStore from "expo-secure-store";
import {
createStorefrontClient,
type StorefrontCartSession,
type StorefrontSessionStore,
} from "@craveup/storefront-sdk";
const apiUrl = process.env.EXPO_PUBLIC_CRAVEUP_API_URL;
const merchantSlug = process.env.EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG;
if (!apiUrl || !merchantSlug) throw new Error("Missing Storefront configuration");
const apiOrigin = new URL(apiUrl).origin;
const secureKeyPart = (value: string) =>
Array.from(value, (character) =>
character.codePointAt(0)!.toString(16).padStart(6, "0"),
).join("");
const environmentNamespace = secureKeyPart(apiOrigin);
const merchantNamespace = secureKeyPart(merchantSlug);
const key = (
kind: "cart" | "customer" | "receipt",
resourceId: string,
) =>
[
"storefront",
kind,
"v1",
environmentNamespace,
merchantNamespace,
secureKeyPart(resourceId),
].join(".");
const cartKey = (locationId: string) => key("cart", locationId);
const customerKey = key("customer", "jwt");
const receiptKey = (receiptId: string) => key("receipt", receiptId);
async function readCartSession(
locationId: string,
): Promise {
const storageKey = cartKey(locationId);
const raw = await SecureStore.getItemAsync(storageKey);
if (!raw) return null;
try {
const value = JSON.parse(raw) as Partial;
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 {
await SecureStore.deleteItemAsync(storageKey);
return null;
}
}
export const sessionStore: StorefrontSessionStore = {
async get(locationId) {
return readCartSession(locationId);
},
async set(session) {
await SecureStore.setItemAsync(
cartKey(session.locationId),
JSON.stringify(session),
);
},
async clear(locationId) {
await SecureStore.deleteItemAsync(cartKey(locationId));
},
};
export const customerTokenStore = {
get: () => SecureStore.getItemAsync(customerKey),
set: (token: string) => SecureStore.setItemAsync(customerKey, token),
clear: () => SecureStore.deleteItemAsync(customerKey),
};
export const receiptTokenStore = {
get: (receiptId: string) =>
SecureStore.getItemAsync(receiptKey(receiptId)),
set: (receiptId: string, token: string) =>
SecureStore.setItemAsync(receiptKey(receiptId), token),
clear: (receiptId: string) =>
SecureStore.deleteItemAsync(receiptKey(receiptId)),
};
export const storefront = createStorefrontClient({
baseUrl: apiOrigin,
sessionStore,
getAuthToken: customerTokenStore.get,
});
```
## 4. Load the merchant and create a cart
```ts theme={null}
const merchant = await storefront.merchant.getBySlug(
process.env.EXPO_PUBLIC_CRAVEUP_MERCHANT_SLUG!,
);
const locationId = process.env.EXPO_PUBLIC_CRAVEUP_LOCATION_ID!;
const session = await storefront.orderingSessions.start(locationId, {
marketplaceId: "mobile",
fulfillmentMethod: "takeout",
});
await storefront.cart.addItem(locationId, session.cart.id, {
productId: "prod_margherita",
quantity: 1,
selections: [],
itemUnavailableAction: "remove_item",
});
```
SecureStore keys accept only alphanumeric characters, `.`, `-`, and `_`; the example encodes every dynamic component before composing dot-delimited keys. The environment, merchant, and location scopes prevent credentials from crossing applications or deployment tiers. Store only the small cart session, merchant-scoped customer JWT, and receipt capability there—not cart contents or API caches.
Clear the guest cart capability after claim, deletion, expiry, or terminal checkout handling; retain only the returned revision when a claimed cart remains customer-authenticated. Call `customerTokenStore.clear()` on logout. Store a deep-link receipt capability with `receiptTokenStore.set(receiptId, token)` only when resume support requires it, and remove it after terminal display or expiry. If the API returns `CART_CONFLICT`, fetch the latest cart before asking the customer to retry.
Native requests normally send no browser `Origin` header, so they do not require CORS registration. If you also ship an Expo web build, register that exact browser origin with Crave; never use a wildcard workaround.
`marketplaceId` is optional attribution for the app source, such as `"mobile"`;
it is not a location ID. The route already scopes the cart to `locationId`.
For a signed-in shopper who still has a guest-capability cart, set the named
`includeCustomerContext` option to `true` on discount or payment-session calls
that need customer-aware validation or provider customer association. Omit it for
guest checkout; a claimed cart uses the customer JWT automatically.
## Next steps
Learn the capability and revision lifecycle.
Integrate payment and authoritative order-result polling.
# Next.js Quickstart
Source: https://docs.craveup.com/quickstarts/nextjs
Build a direct, capability-safe storefront with Next.js and the Crave Storefront SDK.
This quickstart connects a Next.js App Router project directly to the public Storefront API. No BFF or browser API key is required.
## Prerequisites
* Node.js 20+
* A published merchant slug and location ID
* The exact browser origin for local, preview, and production deployments registered with Crave
* A Stripe publishable key when you add checkout
## 1. Create the app and install the SDK
```bash theme={null}
npx create-next-app@latest my-storefront --typescript --app --tailwind
cd my-storefront
pnpm add --save-exact @craveup/storefront-sdk@2.0.1
```
## 2. Configure the public origin
The SDK requires an explicit `baseUrl`; it never derives an origin from a credential.
## 3. Create the browser client and scoped stores
```ts filename="src/lib/storefront-browser.ts" theme={null}
"use client";
import type {
StorefrontCartSession,
StorefrontSessionStore,
} from "@craveup/storefront-sdk";
import { createStorefrontClient } from "@craveup/storefront-sdk";
const apiUrl = process.env.NEXT_PUBLIC_CRAVEUP_API_URL;
const merchantSlug = process.env.NEXT_PUBLIC_CRAVEUP_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 key = (kind: "cart" | "customer" | "receipt", resourceId: string) =>
`storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`;
const cartKey = (locationId: string) => key("cart", locationId);
const customerKey = key("customer", "jwt");
const receiptKey = (receiptId: string) => key("receipt", receiptId);
function readCartSession(locationId: string): StorefrontCartSession | null {
const storageKey = cartKey(locationId);
const raw = sessionStorage.getItem(storageKey);
if (!raw) return null;
try {
const value = JSON.parse(raw) as Partial;
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(storageKey);
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 customerTokenStore = {
get: () => sessionStorage.getItem(customerKey),
set: (token: string) => sessionStorage.setItem(customerKey, token),
clear: () => sessionStorage.removeItem(customerKey),
};
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,
getAuthToken: customerTokenStore.get,
});
```
The environment, merchant, and location scopes prevent a staging token or one merchant's token from being reused by another storefront. Persist the JWT returned by `customer.verifyOtp` with `customerTokenStore.set(token)`. Capture a receipt capability from the URL fragment, remove the fragment immediately, and keep it with `receiptTokenStore.set(receiptId, token)` only while the receipt is needed.
## 4. Create a separate server client
```ts filename="src/lib/storefront-server.ts" theme={null}
import { createStorefrontClient } from "@craveup/storefront-sdk";
export const storefrontServer = createStorefrontClient({
baseUrl: process.env.NEXT_PUBLIC_CRAVEUP_API_URL!,
});
```
Use the server client only for anonymous published merchant, location, menu, product, distance, order-time, and gratuity calls. It deliberately has no `sessionStore` or `getAuthToken`. Never import the browser client into a Server Component, Route Handler, or Server Action.
## 5. Start a cart and mutate it
```tsx filename="src/app/order/page.tsx" theme={null}
"use client";
import { useEffect, useState } from "react";
import type { StorefrontCart } from "@craveup/storefront-sdk";
import { storefront } from "@/lib/storefront-browser";
const locationId = process.env.NEXT_PUBLIC_CRAVEUP_LOCATION_ID!;
export default function OrderPage() {
const [cart, setCart] = useState(null);
useEffect(() => {
void storefront.orderingSessions
.start(locationId, { fulfillmentMethod: "takeout" })
.then(({ cart }) => setCart(cart));
}, []);
async function addItem(productId: string) {
if (!cart) return;
setCart(
await storefront.cart.addItem(locationId, cart.id, {
productId,
quantity: 1,
selections: [],
itemUnavailableAction: "remove_item",
}),
);
}
return (
);
}
```
The SDK stores the returned cart capability, sends it only in a header, updates the saved revision from `ETag`, and generates idempotency keys. A `CART_CONFLICT` is surfaced to your UI; reload the cart before asking the shopper to retry. Clear the guest capability after claim, deletion, expiry, or terminal checkout handling; clear the customer JWT on logout and receipt capabilities after terminal display.
## 6. Complete checkout safely
For a signed-in shopper who still has a guest-capability cart, opt into the
customer context when discount validation or provider customer association needs
it:
```ts theme={null}
const paymentSession = await storefront.checkout.createPaymentSession(
locationId,
cart.id,
{ includeCustomerContext: true },
);
```
Omit `includeCustomerContext` for a guest checkout. A claimed cart already uses
the customer JWT because its guest capability has been cleared. Confirm the
Render the payment provider named by the returned session, then poll
`storefront.checkout.getOrderResult(locationId, cart.id)`. Handle all four
states: `payment_pending`, `order_pending`, `completed`, and `failed`. Clear the
cart session only after terminal handling.
## 7. Register every browser origin
Register the exact origin for local development, each stable preview, and production before testing browser calls. An origin includes the scheme, hostname, and any non-default port. Do not request a wildcard and do not send a cart capability or customer JWT while working around a CORS failure. See [Deployment](/guides/deployment).
## Next steps
Add fulfillment, discounts, gratuity, and conflict handling.
Confirm payment and poll the authoritative order result.
Review the complete typed client surface.
Configure exact origins and production environment variables.
# React Quickstart
Source: https://docs.craveup.com/quickstarts/react
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" | "customer" | "receipt",
resourceId: string,
) => `storefront.${kind}.v1.${scope}.${storageKeyPart(resourceId)}`;
const cartKey = (locationId: string) => storageKey("cart", locationId);
const customerKey = storageKey("customer", "jwt");
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;
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 customerTokenStore = {
get: () => sessionStorage.getItem(customerKey),
set: (token: string) => sessionStorage.setItem(customerKey, token),
clear: () => sessionStorage.removeItem(customerKey),
};
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,
getAuthToken: customerTokenStore.get,
});
```
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 credentials from crossing storefronts. Save the verified customer JWT with `customerTokenStore.set(token)`; 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(null);
useEffect(() => {
storefront.merchant
.getBySlug(import.meta.env.VITE_MERCHANT_SLUG)
.then(setMerchant);
}, []);
if (!merchant) return
Loading...
;
return
{merchant.name}
;
}
```
## 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, and clear the merchant-scoped JWT on logout.
`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
Fetch the published menu and product data.
Handle capabilities, revisions, and idempotent mutations.
Create a payment intent and poll the authoritative order result.
See the complete direct API client.
# Vanilla JavaScript Quickstart
Source: https://docs.craveup.com/quickstarts/vanilla-js
Use the public Crave Storefront API directly from a browser without a framework.
This example uses `fetch` against the public Storefront API. Catalog reads are anonymous; cart operations are protected by the capability returned with the ordering session.
## 1. Create a small client
```js filename="storefront.js" theme={null}
const API_ORIGIN = "https://api.craveup.com";
const API = `${API_ORIGIN}/api/v1/storefront`;
const merchantSlug = "your-restaurant-slug";
const locationId = "loc_your_location_id";
const storageKeyPart = (value) =>
encodeURIComponent(value).replaceAll(".", "%2E");
const environmentNamespace = storageKeyPart(new URL(API_ORIGIN).origin);
const cartStorageKey = [
"crave",
"storefront",
"cart",
"v1",
environmentNamespace,
storageKeyPart(merchantSlug),
storageKeyPart(locationId),
].join(".");
function readCartSession() {
const raw = sessionStorage.getItem(cartStorageKey);
if (!raw) return null;
try {
const value = JSON.parse(raw);
if (
!value ||
typeof value !== "object" ||
typeof value.cartId !== "string" ||
typeof value.accessToken !== "string" ||
!Number.isInteger(value.revision) ||
value.revision < 0
) {
throw new Error("Invalid cart session");
}
return value;
} catch {
sessionStorage.removeItem(cartStorageKey);
return null;
}
}
let cartSession = readCartSession();
function mutationHeaders() {
if (!cartSession) throw new Error("Start an ordering session first.");
return {
"Content-Type": "application/json",
"X-Cart-Token": cartSession.accessToken,
"If-Match": `"cart-${cartSession.revision}"`,
"Idempotency-Key": crypto.randomUUID(),
};
}
async function parse(response) {
const body = await response.json();
if (!response.ok) throw body;
const match = response.headers.get("ETag")?.match(/^(?:W\/)?"cart-(\d+)"$/);
if (match && cartSession) {
const nextRevision = Number(match[1]);
if (nextRevision > cartSession.revision) {
cartSession.revision = nextRevision;
sessionStorage.setItem(cartStorageKey, JSON.stringify(cartSession));
}
}
return body;
}
```
Before running this in a browser, register its exact origin with Crave. Scheme, hostname, and non-default port are part of the origin, so local, preview, and production deployments need separate registrations; wildcards are not accepted.
## 2. Read the published catalog
```js theme={null}
const merchant = await fetch(`${API}/merchant/${merchantSlug}`).then(parse);
const menu = await fetch(`${API}/locations/${locationId}/menus`).then(parse);
```
## 3. Start an ordering session
```js theme={null}
const response = await fetch(
`${API}/locations/${locationId}/ordering-sessions`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
"Idempotency-Key": crypto.randomUUID(),
},
body: JSON.stringify({
marketplaceId: "web",
fulfillmentMethod: "takeout",
}),
},
);
const session = await parse(response);
cartSession = {
cartId: session.cart.id,
accessToken: session.cartAccessToken,
revision: session.cart.revision,
};
sessionStorage.setItem(cartStorageKey, JSON.stringify(cartSession));
```
`marketplaceId` is optional source attribution, such as `"web"`; it is not a
location ID. The route already scopes the cart to `locationId`.
## 4. Add an item
```js theme={null}
const cart = await fetch(
`${API}/locations/${locationId}/carts/${cartSession.cartId}/items`,
{
method: "POST",
headers: mutationHeaders(),
body: JSON.stringify({
productId: "prod_margherita",
quantity: 1,
selections: [],
itemUnavailableAction: "remove_item",
}),
},
).then(parse);
```
Do not place the cart ID or capability in a query string. Clear the guest record after claim, cart deletion, expiry, or terminal checkout handling. If customer-authenticated access continues after claim, retain only the authoritative revision in a separate authenticated-cart record. On `CART_CONFLICT`, fetch the cart using `X-Cart-Token`, accept the latest ETag, and ask the user to retry instead of replaying the write.
## Next steps
Understand every authentication boundary.
Complete payment and poll the authoritative order result.