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

# Create a storefront app

> Build a React or Next.js storefront on top of Firmhouse using the GraphQL API.

Use this guide when you are building a React, Next.js, or other JavaScript storefront that needs to create subscriptions in Firmhouse.

The flow is:

1. Fetch products and plans from Firmhouse.
2. Create a cart.
3. Add products to the cart.
4. Let customers update product quantities or remove products.
5. Update address and customer details.
6. Create a payment link and redirect the customer to payment.

## Requirements

Before you start:

* Create the products you want to sell in Firmhouse.
* For Product-as-a-Service projects, create at least one plan.
* Set up a payment provider.
* Generate a project access token in **Settings > Integrations**. For client-side storefront calls, use a token with **Storefront** access.

## Fetch products

Use the `products` query to show products configured in the Firmhouse Portal.

```javascript theme={null}
const projectAccessToken = "<Your-project-access-token>";

const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
  },
  body: JSON.stringify({
    query: `
      query Products($after: String, $first: Int) {
        products(after: $after, first: $first) {
          totalCount
          pageInfo {
            endCursor
            hasNextPage
          }
          nodes {
            id
            title
            slug
            available
            imageUrl
            priceCents
            priceWithSymbol
            productType
            sku
          }
        }
      }
    `,
    variables: {
      after: null,
      first: 10,
    },
  }),
});

const body = await response.json();
const { nodes: products, pageInfo } = body.data.products;
```

Use `after` and `first` for pagination. Pass `pageInfo.endCursor` as `after` to fetch the next page.

## Fetch plans

For Product-as-a-Service projects, use the `plans` query to show the plans configured in Firmhouse.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
  },
  body: JSON.stringify({
    query: `
      query Plans($after: String, $first: Int) {
        plans(after: $after, first: $first) {
          totalCount
          pageInfo {
            endCursor
            hasNextPage
          }
          nodes {
            id
            name
            slug
            available
            currency
            monthlyAmountCents
            initialAmountIncludingTaxCents
            planProducts {
              quantity
              product {
                id
              }
            }
          }
        }
      }
    `,
    variables: {
      after: null,
      first: 10,
    },
  }),
});

const body = await response.json();
const { nodes: plans } = body.data.plans;
```

## Create a cart

Create a cart with `createCart`. Firmhouse returns a subscription token for the draft subscription.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
  },
  body: JSON.stringify({
    query: `
      mutation CreateCart {
        createCart(input: {}) {
          subscription {
            token
          }
        }
      }
    `,
  }),
});

const body = await response.json();
const subscriptionToken = body.data.createCart.subscription.token;
```

Pass this token in the `X-Subscription-Token` header for later cart calls. In a web app, store it in a cookie or local storage so returning customers can continue the same checkout.

If the default plan includes products, those products are automatically added when you create the cart.

## Add products to the cart

Use `createOrderedProduct` to add a product.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation CreateOrderedProduct($input: CreateOrderedProductInput!) {
        createOrderedProduct(input: $input) {
          orderedProduct {
            id
            title
            quantity
            priceIncludingTaxCents
            totalAmountIncludingTaxCents
          }
          subscription {
            monthlyAmountCents
            currency
            orderedProducts {
              id
              title
              quantity
              totalAmountIncludingTaxCents
            }
          }
          errors {
            attribute
            message
            path
          }
        }
      }
    `,
    variables: {
      input: {
        productId: products[0].id,
        quantity: 1,
      },
    },
  }),
});

const body = await response.json();
const { orderedProduct, subscription, errors } = body.data.createOrderedProduct;
```

Use `orderedProduct` to show an added-to-cart confirmation. Use `subscription` to update the cart UI with totals and cart contents.

If you need to add metadata to ordered products, use a project access token with **Write** access and pass `metadata`. If multiple rows of the same product need different metadata, pass `ensureNewRecord: true`.

Use `createOrderedProductV2` when you need the same cart behavior with extra options such as `orderedProductType`. Set `orderedProductType` to `ONE_OFF_ADDON` when the product should only be included in the next order.

## Remove products from the cart

Use `destroyOrderedProduct` to remove a product from the cart.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation DestroyOrderedProduct($input: DestroyOrderedProductInput!) {
        destroyOrderedProduct(input: $input) {
          orderedProduct {
            id
          }
          subscription {
            monthlyAmountCents
            orderedProducts {
              id
              title
              quantity
            }
          }
        }
      }
    `,
    variables: {
      input: {
        id: orderedProduct.id,
      },
    },
  }),
});

const { data } = await response.json();
const { subscription } = data.destroyOrderedProduct;
```

## Update product quantities

Use `updateOrderedProductQuantity` to update quantities in the cart.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation UpdateOrderedProductQuantity($input: UpdateOrderedProductQuantityInput!) {
        updateOrderedProductQuantity(input: $input) {
          orderedProduct {
            id
            quantity
          }
          subscription {
            monthlyAmountCents
            orderedProducts {
              id
              title
              quantity
            }
          }
          errors {
            attribute
            message
            path
          }
        }
      }
    `,
    variables: {
      input: {
        orderedProduct: {
          id: orderedProduct.id,
          quantity: 2,
        },
      },
    },
  }),
});

const body = await response.json();
const { subscription, errors } = body.data.updateOrderedProductQuantity;
```

## Update the subscription plan

Use `updatePlan` to change the active plan for the draft subscription.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation UpdatePlan($planSlug: String!) {
        updatePlan(input: { planSlug: $planSlug }) {
          subscription {
            activePlan {
              id
              name
              slug
            }
            subscribedPlan {
              id
              name
              nextBillingDate
            }
          }
        }
      }
    `,
    variables: {
      planSlug: plans[0].slug,
    },
  }),
});

const body = await response.json();
const { subscription } = body.data.updatePlan;
```

## Fetch current cart information

Use `getSubscription` to fetch the current cart state. This is useful when customers return to the storefront or when they navigate to checkout.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      query GetSubscription($token: ID!) {
        getSubscription(token: $token) {
          amountForStartingSubscriptionCents
          monthlyAmountCents
          checkoutUrl
          currency
          name
          lastName
          email
          orderedProducts {
            id
            title
            quantity
            totalAmountIncludingTaxCents
          }
        }
      }
    `,
    variables: {
      token: subscriptionToken,
    },
  }),
});

const body = await response.json();
const subscription = body.data.getSubscription;
```

If you want to use the default Firmhouse checkout page, redirect customers to `subscription.checkoutUrl`.

## Update address details

Use `updateAddressDetails` to collect billing, invoice, and shipping details.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation UpdateAddressDetails($input: UpdateAddressDetailsInput!) {
        updateAddressDetails(input: $input) {
          subscription {
            id
            name
            lastName
            email
            address
            city
            country
          }
          errors {
            attribute
            message
            path
          }
        }
      }
    `,
    variables: {
      input: {
        name: "John",
        lastName: "Doe",
        email: "johndoe@example.com",
        address: "Example street 1",
        city: "Rotterdam",
        country: "NL",
        termsAccepted: true,
      },
    },
  }),
});

const { data = {}, errors: requestErrors } = await response.json();
const { subscription, errors } = data.updateAddressDetails ?? {};
```

By default, Firmhouse requires `name`, `email`, `address`, `city`, and `country` before payment. You can change the required fields in **Checkout > Preferences > Customer fields** in your Firmhouse project.

## Generate a payment link

Use `createSubscriptionFromCart` to create a `paymentUrl`. Redirect the customer to that URL to complete payment.

```javascript theme={null}
const response = await fetch("https://portal.firmhouse.com/graphql", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
    "X-Project-Access-Token": projectAccessToken,
    "X-Subscription-Token": subscriptionToken,
  },
  body: JSON.stringify({
    query: `
      mutation CreateSubscriptionFromCart($input: CreateSubscriptionFromCartInput!) {
        createSubscriptionFromCart(input: $input) {
          paymentUrl
          returnUrl
          errors {
            attribute
            message
            path
          }
        }
      }
    `,
    variables: {
      input: {
        returnUrl: "https://yourdomain.com/return",
        paymentPageUrl: "https://yourdomain.com/checkout",
      },
    },
  }),
});

const body = await response.json();
const { paymentUrl, returnUrl, errors } = body.data.createSubscriptionFromCart;
```

If required fields are missing, `paymentUrl` is `null` and `errors` explains what still needs to be collected.

<Warning>
  If you have not added a payment provider to your Firmhouse project, creating a payment link will fail.
</Warning>

## Related

* [GraphQL API introduction](https://developer.firmhouse.com/graphql-api/introduction)
* [API Reference](https://developer.firmhouse.com/graphql-api/api-reference)
* [Generate API access tokens](/configure/integrations/api-access-tokens)
