Crystallize logo

Building a Modern Checkout Flow with Crystallize APIs

A typical ecommerce experience needs a reliable checkout flow that moves a customer from browsing products to a complete order. With Crystallize, you can build such flows using a combination of the Discovery API and the Shop API. In this post, we walk through the core steps involved in a modern checkout flow: fetching products, hydrating a cart, placing a cart, and creating an order. The examples are language-agnostic GraphQL operations you can adapt to your frontend or backend stack.

clockPublished December 22, 2025
clock3 minutes
Bård Farstad
Bård Farstad
Checkout Flow: Leveraging Crystallize APIs

Fetching a Product

The first step in any checkout flow is to retrieve product information. With Crystallize, product data is fetched from the Discovery API, which is optimized for read performance and flexible querying. You can access it without authentication unless you have tightened security for your storefront.

API endpoint:

https://api.crystallize.com/your-tenant-identifier/discovery

Use a GraphQL query like this to fetch the minimal product data needed for checkout:

query GetProduct($language: String!, $path: String!) {
  browse {
    product(language: $language, path: $path) {
      hits {
        name
        path
        variants {
          name
          sku
          retailPrice: defaultPrice
          salesPrice
        }
      }
    }
  }
}

You specify the product by path and language. The response includes each variant’s SKU and price data, which you will use when adding items to the cart.

If you need additional product fields such as images, stock, or custom attributes, you can expand the query accordingly.

Adding to Cart

Once you have product data, the next step is to add items to a cart. Crystallize’s Shop API handles cart management, pricing, totals, taxes, and promotions for you. It requires authentication.

API endpoint:

https://shop-api.crystallize.com/your-tenant-identifier/cart

Use a mutation like this to create or update a cart with items and context:

mutation HydrateCart($input: HydrateCartInput!) {
  hydrate(input: $input) {
    id
    state
    isStale
    isExpired
    appliedPromotions {
      identifier
      name
      mechanism {
        type
        value
      }
    }
    items {
      name
      variant {
        sku
        yourPrice: price {
          gross
          net
          taxAmount
          taxPercent
        }
        compareAtPrice {
          gross
          net
        }
        product {
          name
        }
      }
      price {
        net
        gross
        taxAmount
        discounts {
          percent
          amount
        }
      }
    }
    total {
      net
      gross
      discounts {
        percent
        amount
      }
    }
  }
}

The hydrate operation takes:

  • customer details, for pricing rules
  • context, such as language, currency, price variant, and tax settings
  • items, each defined by SKU and quantity

The Shop API calculates pricing, taxes, and promotions automatically. You simply send the item SKUs and quantity.

Cart context

Make sure you define cart context correctly:

  • language must match a published language in your catalogue
  • currency and selected price variant determine pricing
  • tax settings drive B2B versus B2C behaviour
  • Optional fallback prices help when specific variants are missing

This context is essential for consistency across locales and pricing scenarios.

💡Note.

I recommend going to the endpoint (https://shop-api.crystallize.com/your-tenant/cart) https://shop-api.crystallize.com/your-tenant/cart and playing around with the queries and mutations. The docs within the playground are easy to comprehend, and if you have any questions, you can always contact us on Slack.

Place the cart

Before finalizing checkout, place the cart. This locks the cart so its content, quantities, and pricing cannot change during payment processing.

mutation PlaceCart($cartId: ID!) {
  place(id: $cartId) {
    id
    items {
      name
      quantity
    }
  }
}

Once placed, the cart becomes immutable, and you can safely use it as the basis for order creation. If a user navigates back and edits items after placement, create a new cart by running the hydrate mutation without a cart ID.

Create an order

With a placed cart, you can create an order via the Shop API. This step finalizes the transaction in Crystallize and allows you to specify order type and payment status.

API endpoint:

https://shop-api.crystallize.com/your-tenant-identifier/order

The mutation to create order from a placed cart.

mutation CreateOrderFromCart($id: ID!, $input: CreateOrderFromCartInput!) {
  createFromCart(id: $id, input: $input) {
    id
    type
  }
}

In this mutation:

  • id is the cart ID
  • input can include type, such as standard, draft, quote, or subscription
  • you can also set paymentStatus to reflect payment outcomes

Crystallize supports multiple order types, helping you model various business cases from normal orders to quotes and recurring orders.

💡Note.

You, as a user, have full control over the information provided to the Shop API. The data from your cart will be automatically used, but you can add additional and override data when creating the order.

Summary

A basic checkout flow in Crystallize consists of:

  1. Querying the product with the Discovery API
  2. Hydrating a cart with items and context using the Shop API
  3. Placing the cart to lock it
  4. Creating an order from the placed cart

Most pricing logic, totals, taxes, and promotions are handled by the Shop API. You only need to send the relevant SKUs, context, and identifiers. But as usual with Crystallize you can still override and add to make it fit to your use case.

If you use the official Crystallize JS API Client, it simplifies authentication and request handling, letting you focus on your business logic.