---
title: B2B Commerce in Hydrogen
description: >-
  Add Business-to-Business (B2B) functionality with company locations, quantity
  rules, and volume pricing
source_url:
  html: 'https://shopify.dev/docs/storefronts/headless/legacy/hydrogen/cookbook/b2b'
  md: >-
    https://shopify.dev/docs/storefronts/headless/legacy/hydrogen/cookbook/b2b.md
api_name: hydrogen
---

Legacy Hydrogen

These pages refer to Hydrogen versions 2026-04 and earlier. For versions 2026-10 and later, refer to the latest [Hydrogen docs](https://shopify.dev/docs/storefronts/hydrogen). If you're not using the latest version of Hydrogen, then we recommend that you migrate your storefront. To learn more, refer to [Migrate to the latest version of Hydrogen](https://shopify.dev/docs/storefronts/hydrogen/migrate).

# B2B Commerce in Hydrogen

This recipe adds comprehensive B2B functionality to your Hydrogen storefront, enabling business customers to:

* Select their company location for contextualized pricing
* View and respect quantity rules (minimum, maximum, increment)
* See volume-based price breaks for bulk purchases
* Access B2B-specific pricing through contextualized GraphQL queries

The implementation includes a location selector modal, quantity rule displays on product pages, and cart functionality that respects B2B quantity rules. All product queries are contextualized with buyer information (company location + customer token) to ensure accurate B2B pricing.

**Note:**

For production, all product queries should be contextualized with buyer information

***

## Requirements

* A plan that supports B2B capabilities, with B2B enabled
* New customer accounts activated
* At least one B2B company with customer access configured
* Products with quantity rules or volume pricing configured (optional but recommended for testing)

***

## Ingredients

*New files added to the template by this recipe.*

| File | Description |
| - | - |
| [app/components/B2BLocationProvider.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/B2BLocationProvider.tsx) | |
| [app/components/B2BLocationSelector.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/B2BLocationSelector.tsx) | |
| [app/components/PriceBreaks.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/PriceBreaks.tsx) | |
| [app/components/QuantityRules.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/QuantityRules.tsx) | |
| [app/graphql/customer-account/CustomerLocationsQuery.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/graphql/customer-account/CustomerLocationsQuery.ts) | |
| [app/routes/b2blocations.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/routes/b2blocations.tsx) | |

***

## Step 1: Document B2B features, requirements, and implementation details in the project README

#### File: [README.md](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/README.md) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/README.md.db10ed.patch))

```diff
@@ -18,6 +18,45 @@ Hydrogen is Shopify’s stack for headless commerce. Hydrogen is designed to dov
 - TypeScript and JavaScript flavors
 - Minimal setup of components and routes
 
+## B2B Features
+
+This template includes B2B (Business-to-Business) functionality for stores on plans that support B2B capabilities:
+
+### What's included:
+
+1. **Company Location Selection**: Allows B2B customers to select their company location for contextualized pricing and rules
+2. **Quantity Rules**: Display and enforce minimum, maximum, and increment quantity rules for B2B products
+3. **Volume Pricing**: Show quantity-based price breaks for bulk purchases
+4. **Contextualized Queries**: Product queries use buyer context (company location + customer token) for accurate B2B pricing
+
+### B2B Requirements:
+
+- Your store must be on a plan that supports B2B capabilities
+- Your store must use [new customer accounts](https://help.shopify.com/en/manual/customers/customer-accounts/new-customer-accounts)
+- You need a customer with permission to order for a [B2B company](https://help.shopify.com/en/manual/b2b)
+
+### Key B2B Files:
+
+| File | Description |
+| --- | --- |
+| `app/routes/b2blocations.tsx` | Handles B2B location selection and session management |
+| `app/components/B2BLocationProvider.tsx` | React context provider for B2B location state |
+| `app/components/B2BLocationSelector.tsx` | Modal component for choosing company location |
+| `app/components/PriceBreaks.tsx` | Displays volume-based pricing tiers |
+| `app/components/QuantityRules.tsx` | Shows quantity rules (min/max/increment) |
+| `app/graphql/customer-account/CustomerLocationsQuery.ts` | GraphQL query for fetching company locations |
+
+### How it works:
+
+1. When a B2B customer logs in, the system retrieves their company location data using the Customer Account API
+2. If the customer has access to multiple locations, they can select one via the location selector
+3. The selected location ID is stored in the session and used to contextualize all product queries
+4. Products display B2B-specific information like quantity rules and volume pricing
+5. Cart operations respect B2B quantity rules and pricing
+
+> [!NOTE]
+> In this implementation, only the product display page (`app/routes/products.$handle.tsx`) uses contextualized queries. For production, all product queries should be contextualized with buyer information.
+
 ## Getting started
 
 **Requirements:**
```

***

## Step 2: Create a React context provider to manage company location state across the app

#### File: [B2BLocationProvider.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/B2BLocationProvider.tsx)

## File

```tsx
import {createContext, useContext, useEffect, useState, useMemo} from 'react';
import {useFetcher} from 'react-router';
import {type CustomerCompany} from '~/root';


export type B2BLocationContextValue = {
  company?: CustomerCompany;
  companyLocationId?: string;
  modalOpen?: boolean;
  setModalOpen: (b: boolean) => void;
};


const defaultB2BLocationContextValue = {
  company: undefined,
  companyLocationId: undefined,
  modalOpen: undefined,
  setModalOpen: () => {},
};


const B2BLocationContext = createContext<B2BLocationContextValue>(
  defaultB2BLocationContextValue,
);


export function B2BLocationProvider({children}: {children: React.ReactNode}) {
  const fetcher = useFetcher<B2BLocationContextValue>();
  const [modalOpen, setModalOpen] = useState(fetcher?.data?.modalOpen);


  useEffect(() => {
    if (fetcher.data || fetcher.state === 'loading') return;


    void fetcher.load('/b2blocations');
  }, [fetcher]);


  const value = useMemo<B2BLocationContextValue>(() => {
    return {
      ...defaultB2BLocationContextValue,
      ...fetcher.data,
      modalOpen: modalOpen ?? fetcher?.data?.modalOpen,
      setModalOpen,
    };
  }, [fetcher, modalOpen]);


  return (
    <B2BLocationContext.Provider value={value}>
      {children}
    </B2BLocationContext.Provider>
  );
}


export function useB2BLocation(): B2BLocationContextValue {
  return useContext(B2BLocationContext);
}
```

***

## Step 3: Update quantity controls to respect B2B minimum, maximum, and increment rules

#### File: [CartLineItem.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/CartLineItem.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/CartLineItem.tsx.2c9a50.patch))

```diff
@@ -76,8 +76,13 @@ export function CartLineItem({
 function CartLineQuantity({line}: {line: CartLine}) {
   if (!line || typeof line?.quantity === 'undefined') return null;
   const {id: lineId, quantity, isOptimistic} = line;
-  const prevQuantity = Number(Math.max(0, quantity - 1).toFixed(0));
-  const nextQuantity = Number((quantity + 1).toFixed(0));
+  // @description Calculate quantity changes based on B2B quantity rules
+  const {increment, minimum, maximum} = line.merchandise.quantityRule || {increment: 1, minimum: 1, maximum: null};
+  const nextIncrement = increment - (quantity % increment);
+  const prevIncrement =
+    quantity % increment === 0 ? increment : quantity % increment;
+  const prevQuantity = Number(Math.max(0, quantity - prevIncrement).toFixed(0));
+  const nextQuantity = Number((quantity + nextIncrement).toFixed(0));
 
   return (
     <div className="cart-line-quantity">
@@ -85,7 +90,7 @@ function CartLineQuantity({line}: {line: CartLine}) {
       <CartLineUpdateButton lines={[{id: lineId, quantity: prevQuantity}]}>
         <button
           aria-label="Decrease quantity"
-          disabled={quantity <= 1 || !!isOptimistic}
+          disabled={prevQuantity < minimum || !!isOptimistic}
           name="decrease-quantity"
           value={prevQuantity}
         >
@@ -98,7 +103,9 @@ function CartLineQuantity({line}: {line: CartLine}) {
           aria-label="Increase quantity"
           name="increase-quantity"
           value={nextQuantity}
-          disabled={!!isOptimistic}
+          disabled={Boolean(
+            (maximum && nextQuantity > maximum) || !!isOptimistic,
+          )}
         >
           <span>&#43;</span>
         </button>
```

***

## Step 4: Build a modal that lets customers choose which company location to use for pricing

#### File: [B2BLocationSelector.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/B2BLocationSelector.tsx)

## File

```tsx
import {CartForm} from '@shopify/hydrogen';
import type {
  CustomerCompanyLocation,
  CustomerCompanyLocationConnection,
} from '~/root';
import {useB2BLocation} from '~/components/B2BLocationProvider';


export function B2BLocationSelector() {
  const {company, modalOpen, setModalOpen} = useB2BLocation();


  const locations = company?.locations?.edges
    ? company.locations.edges.map(
        (location: CustomerCompanyLocationConnection) => {
          return {...location.node};
        },
      )
    : [];


  if (!company || !modalOpen)
    return <p>No company found for logged in user.</p>;


  return (
    <div className="modal">
      <div className="modal-content">
        <h2>Logged in for {company.name}</h2>
        <legend>Choose a location:</legend>
        <div className="location-list">
          {locations.map((location: CustomerCompanyLocation) => {
            const addressLines =
              location?.shippingAddress?.formattedAddress ?? [];
            return (
              <CartForm
                key={location.id}
                route="/cart"
                action={CartForm.ACTIONS.BuyerIdentityUpdate}
                inputs={{
                  buyerIdentity: {companyLocationId: location.id},
                }}
              >
                {(fetcher) => (
                  <div>
                    <button
                      aria-label={`Select B2B location: ${location.name}`}
                      onClick={(event) => {
                        setModalOpen(false);
                        fetcher.submit(event.currentTarget.form, {
                          method: 'POST',
                        });
                      }}
                      className="location-item"
                    >
                      <div>
                        <p>
                          <strong>{location.name}</strong>
                        </p>
                        {addressLines.map((line: string) => (
                          <p key={line}>{line}</p>
                        ))}
                      </div>
                    </button>
                  </div>
                )}
              </CartForm>
            );
          })}
        </div>
      </div>
    </div>
  );
}
```

***

## Step 5: Add a location selector button to the header for switching between company locations

#### File: [Header.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/Header.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/Header.tsx.e25645.patch))

```diff
@@ -7,6 +7,9 @@ import {
 } from '@shopify/hydrogen';
 import type {HeaderQuery, CartApiQueryFragment} from 'storefrontapi.generated';
 import {useAside} from '~/components/Aside';
+// @description Import B2B types and hooks for company location management
+import {type CustomerCompanyLocationConnection} from '~/root';
+import {useB2BLocation} from './B2BLocationProvider';
 
 interface HeaderProps {
   header: HeaderQuery;
@@ -91,6 +94,8 @@ export function HeaderMenu({
           </NavLink>
         );
       })}
+      {/* @description Add B2B location selector to header navigation */}
+      <ChangeLocation />
     </nav>
   );
 }
@@ -175,6 +180,29 @@ function CartBanner() {
   return <CartBadge count={cart?.totalQuantity ?? 0} />;
 }
 
+// @description Add B2B location change button for company location selection
+function ChangeLocation() {
+  const {company, companyLocationId, setModalOpen} = useB2BLocation();
+
+  const locations = company?.locations?.edges
+    ? company.locations.edges.map(
+        (location: CustomerCompanyLocationConnection) => {
+          return {...location.node};
+        },
+      )
+    : [];
+
+  if (locations.length <= 1 || !company) return null;
+
+  return (
+    <button onClick={() => setModalOpen(true)}>
+      {locations.find(
+        (companyLocation) => companyLocation.id == companyLocationId,
+      )?.name || 'Select Location'}
+    </button>
+  );
+}
+
 const FALLBACK_HEADER_MENU = {
   id: 'gid://shopify/Menu/199655587896',
   items: [
```

***

## Step 6: Display volume pricing tiers that show bulk purchase discounts

#### File: [PriceBreaks.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/PriceBreaks.tsx)

## File

```tsx
import {Money} from '@shopify/hydrogen';
import type {MoneyV2} from '@shopify/hydrogen/storefront-api-types';


type PriceBreak = {
  minimumQuantity: number;
  price: MoneyV2;
};


export type PriceBreaksProps = {
  priceBreaks: PriceBreak[];
};


export function PriceBreaks({priceBreaks}: PriceBreaksProps) {
  return (
    <>
      <h4>Volume Pricing</h4>
      <table className="rule-table">
        <thead>
          <tr>
            <th className="table-haeading">Minimum Quantity</th>
            <th className="table-haeading">Unit Price</th>
          </tr>
        </thead>
        <tbody>
          {priceBreaks.map((priceBreak, index) => {
            return (
              <tr key={`price-break-${priceBreak.minimumQuantity}`}>
                <th className="table-item">{priceBreak.minimumQuantity}</th>
                <th className="table-item">
                  <Money data={priceBreak.price} />
                </th>
              </tr>
            );
          })}
        </tbody>
      </table>
    </>
  );
}
```

***

## Step 7: Accept quantity parameter to support B2B minimum quantity and increment rules

#### File: [ProductForm.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/components/ProductForm.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/ProductForm.tsx.649d3b.patch))

```diff
@@ -8,12 +8,15 @@ import {AddToCartButton} from './AddToCartButton';
 import {useAside} from './Aside';
 import type {ProductFragment} from 'storefrontapi.generated';
 
+// @description Add quantity parameter for B2B quantity support
 export function ProductForm({
   productOptions,
   selectedVariant,
+  quantity,
 }: {
   productOptions: MappedProductOptions[];
   selectedVariant: ProductFragment['selectedOrFirstAvailableVariant'];
+  quantity?: number;
 }) {
   const navigate = useNavigate();
   const {open} = useAside();
@@ -111,7 +114,8 @@ export function ProductForm({
             ? [
                 {
                   merchandiseId: selectedVariant.id,
-                  quantity: 1,
+                  // @description Use B2B quantity or default to 1
+                  quantity: quantity || 1,
                   selectedVariant,
                 },
               ]
```

***

## Step 8: Show minimum, maximum, and increment quantity requirements for B2B products

#### File: [QuantityRules.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/components/QuantityRules.tsx)

## File

```tsx
import type {Maybe} from '@shopify/hydrogen/customer-account-api-types';


export type QuantityRulesProps = {
  maximum?: Maybe<number> | undefined;
  minimum?: Maybe<number> | undefined;
  increment?: Maybe<number> | undefined;
};


export const hasQuantityRules = (quantityRule?: QuantityRulesProps) => {
  return (
    quantityRule &&
    (quantityRule?.increment != 1 ||
      quantityRule?.minimum != 1 ||
      quantityRule?.maximum)
  );
};


export function QuantityRules({
  maximum,
  minimum,
  increment,
}: QuantityRulesProps) {
  return (
    <>
      <h4>Quantity Rules</h4>
      <table className="rule-table">
        <thead>
          <tr>
            <th className="table-haeading">Increment</th>
            <th className="table-haeading">Minimum</th>
            <th className="table-haeading">Maximum</th>
          </tr>
        </thead>
        <tbody>
          <tr>
            <th className="table-item">{increment}</th>
            <th className="table-item">{minimum}</th>
            <th className="table-item">{maximum}</th>
          </tr>
        </tbody>
      </table>
    </>
  );
}
```

***

## Step 9: Add quantity rules and price breaks to cart Graph​QL fragments

#### File: [fragments.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/lib/fragments.ts) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/fragments.ts.026109.patch))

```diff
@@ -52,6 +52,21 @@ export const CART_QUERY_FRAGMENT = `#graphql
           name
           value
         }
+        # @description Add B2B quantity rules and price breaks
+        quantityRule {
+          maximum
+          minimum
+          increment
+        }
+        quantityPriceBreaks(first: 5) {
+          nodes {
+            minimumQuantity
+            price {
+              amount
+              currencyCode
+            }
+          }
+        }
       }
     }
   }
@@ -102,6 +117,21 @@ export const CART_QUERY_FRAGMENT = `#graphql
           name
           value
         }
+        # @description Add B2B quantity rules and price breaks for cart line component
+        quantityRule {
+          maximum
+          minimum
+          increment
+        }
+        quantityPriceBreaks(first: 5) {
+          nodes {
+            minimumQuantity
+            price {
+              amount
+              currencyCode
+            }
+          }
+        }
       }
     }
   }
```

***

## Step 10: Query company locations from the Customer Account API for B2B customers

#### File: [CustomerLocationsQuery.ts](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/graphql/customer-account/CustomerLocationsQuery.ts)

## File

```ts
// NOTE: https://shopify.dev/docs/api/customer/latest/objects/Customer
export const CUSTOMER_LOCATIONS_QUERY = `#graphql
  query CustomerLocations {
    customer {
      id
      emailAddress {
        emailAddress
      }
      companyContacts(first: 1){
        edges{
          node{
            company{
              id
              name
              locations(first: 10){
                edges{
                  node{
                    id
                    name
                    shippingAddress {
                      countryCode
                      formattedAddress
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
` as const;
```

***

## Step 11: Wrap the app with B2B location provider and add company location types

#### File: [root.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/root.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/root.tsx.5e9998.patch))

## File

```diff
@@ -16,9 +16,39 @@ import {FOOTER_QUERY, HEADER_QUERY} from '~/lib/fragments';
 import resetStyles from '~/styles/reset.css?url';
 import appStyles from '~/styles/app.css?url';
 import {PageLayout} from './components/PageLayout';
+// @description Import B2B components and types for company location management
+import {B2BLocationProvider} from '~/components/B2BLocationProvider';
+import {B2BLocationSelector} from '~/components/B2BLocationSelector';
+import type {
+  Company,
+  CompanyAddress,
+  CompanyLocation,
+  Maybe,
+} from '@shopify/hydrogen/customer-account-api-types';
 
 export type RootLoader = typeof loader;
 
+// @description Define B2B customer company types
+export type CustomerCompanyLocation = Pick<CompanyLocation, 'name' | 'id'> & {
+  shippingAddress?:
+    | Maybe<Pick<CompanyAddress, 'countryCode' | 'formattedAddress'>>
+    | undefined;
+};
+
+export type CustomerCompanyLocationConnection = {
+  node: CustomerCompanyLocation;
+};
+
+export type CustomerCompany =
+  | Maybe<
+      Pick<Company, 'name' | 'id'> & {
+        locations: {
+          edges: CustomerCompanyLocationConnection[];
+        };
+      }
+    >
+  | undefined;
+
 /**
  * This is important to avoid re-fetching root queries on sub-navigations
  */
@@ -176,9 +206,13 @@ export default function App() {
       shop={data.shop}
       consent={data.consent}
     >
-      <PageLayout {...data}>
-        <Outlet />
-      </PageLayout>
+      {/* @description Wrap PageLayout with B2B location provider for company location management */}
+      <B2BLocationProvider>
+        <PageLayout {...data}>
+          <Outlet />
+        </PageLayout>
+        <B2BLocationSelector />
+      </B2BLocationProvider>
     </Analytics.Provider>
   );
 }
```

***

## Step 12: Handle location selection and automatically set location if customer has only one

#### File: [b2blocations.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/ingredients/templates/skeleton/app/routes/b2blocations.tsx)

## File

```tsx
import {useLoaderData} from 'react-router';
import type {Route} from './+types/b2blocations';
import {B2BLocationSelector} from '../components/B2BLocationSelector';
import {CUSTOMER_LOCATIONS_QUERY} from '~/graphql/customer-account/CustomerLocationsQuery';


export async function loader({context}: Route.LoaderArgs) {
  const {customerAccount} = context;


  const buyer = await customerAccount.getBuyer();


  let companyLocationId = buyer?.companyLocationId || null;
  let company = null;


  // Check if logged in customer is a b2b customer
  if (buyer) {
    const customer = await customerAccount.query(CUSTOMER_LOCATIONS_QUERY);
    company =
      customer?.data?.customer?.companyContacts?.edges?.[0]?.node?.company ||
      null;
  }


  // If there is only 1 company location, set it in session
  if (!companyLocationId && company?.locations?.edges?.length === 1) {
    companyLocationId = company.locations.edges[0].node.id;


    customerAccount.setBuyer({
      companyLocationId,
    });
  }


  const modalOpen = Boolean(company) && !companyLocationId;


  return {company, companyLocationId, modalOpen};
}


export default function CartRoute() {
  return <B2BLocationSelector />;
}
```

***

## Step 13: Clear company location and customer data from cart when logging out

#### File: [account\_.logout.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/routes/account_.logout.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/account_.logout.tsx.a1f12e.patch))

```diff
@@ -7,5 +7,10 @@ export async function loader() {
 }
 
 export async function action({context}: Route.ActionArgs) {
+  // @description Clear B2B company location on logout
+  await context.cart.updateBuyerIdentity({
+    companyLocationId: null,
+    customerAccessToken: null,
+  });
   return context.customerAccount.logout();
 }
```

***

## Step 14: Contextualize product queries with buyer information and display B2B pricing details

#### File: [products.$handle.tsx](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/templates/skeleton/app/routes/products.$handle.tsx) ([patch](https://github.com/Shopify/hydrogen/blob/12374c8f03f82c6800000cf08e327c4db4c287bb/cookbook/recipes/b2b/patches/products.$handle.tsx.6f2e82.patch))

## File

```diff
@@ -15,6 +15,19 @@ import {ProductPrice} from '~/components/ProductPrice';
 import {ProductImage} from '~/components/ProductImage';
 import {ProductForm} from '~/components/ProductForm';
 import {redirectIfHandleIsLocalized} from '~/lib/redirect';
+// @description Import B2B components for quantity rules and price breaks
+import {QuantityRules, hasQuantityRules} from '~/components/QuantityRules';
+import {PriceBreaks} from '~/components/PriceBreaks';
+
+// @description Define B2B buyer variables type for contextualized queries
+type BuyerVariables =
+  | {
+      buyer: {
+        companyLocationId: string;
+        customerAccessToken: string;
+      };
+    }
+  | {};
 
 export const meta: Route.MetaFunction = ({data}) => {
   return [
@@ -27,11 +40,24 @@ export const meta: Route.MetaFunction = ({data}) => {
 };
 
 export async function loader(args: Route.LoaderArgs) {
+  // @description Get B2B buyer context for contextualized product queries
+  const buyer = await args.context.customerAccount.getBuyer();
+
+  const buyerVariables: BuyerVariables =
+    buyer?.companyLocationId && buyer?.customerAccessToken
+      ? {
+          buyer: {
+            companyLocationId: buyer.companyLocationId,
+            customerAccessToken: buyer.customerAccessToken,
+          },
+        }
+      : {};
+
   // Start fetching non-critical data without blocking time to first byte
-  const deferredData = loadDeferredData(args);
+  const deferredData = loadDeferredData({...args, buyerVariables});
 
   // Await the critical data required to render initial state of the page
-  const criticalData = await loadCriticalData(args);
+  const criticalData = await loadCriticalData({...args, buyerVariables});
 
   return {...deferredData, ...criticalData};
 }
@@ -44,7 +70,8 @@ async function loadCriticalData({
   context,
   params,
   request,
-}: Route.LoaderArgs) {
+  buyerVariables,
+}: Route.LoaderArgs & {buyerVariables: BuyerVariables}) {
   const {handle} = params;
   const {storefront} = context;
 
@@ -54,7 +81,7 @@ async function loadCriticalData({
 
   const [{product}] = await Promise.all([
     storefront.query(PRODUCT_QUERY, {
-      variables: {handle, selectedOptions: getSelectedProductOptions(request)},
+      variables: {handle, selectedOptions: getSelectedProductOptions(request), ...buyerVariables},
     }),
     // Add other queries here, so that they are loaded in parallel
   ]);
@@ -76,7 +103,7 @@ async function loadCriticalData({
  * fetched after the initial page load. If it's unavailable, the page should still 200.
  * Make sure to not throw any errors here, as it will cause the page to 500.
  */
-function loadDeferredData({context, params}: Route.LoaderArgs) {
+function loadDeferredData({context, params, buyerVariables}: Route.LoaderArgs & {buyerVariables: BuyerVariables}) {
   // Put any API calls that is not critical to be available on first page render
   // For example: product reviews, product recommendations, social feeds.
 
@@ -117,8 +144,26 @@ export default function Product() {
         <ProductForm
           productOptions={productOptions}
           selectedVariant={selectedVariant}
+          // @description Pass B2B quantity increment or default to 1
+          quantity={selectedVariant?.quantityRule?.increment || 1}
         />
         <br />
+        {/* @description Display B2B quantity rules if they exist */}
+        {hasQuantityRules(selectedVariant?.quantityRule) ? (
+          <QuantityRules
+            maximum={selectedVariant?.quantityRule.maximum}
+            minimum={selectedVariant?.quantityRule.minimum}
+            increment={selectedVariant?.quantityRule.increment}
+          />
+        ) : null}
+        <br />
+        {/* @description Display B2B price breaks if they exist */}
+        {selectedVariant?.quantityPriceBreaks?.nodes &&
+        selectedVariant?.quantityPriceBreaks?.nodes?.length > 0 ? (
+          <PriceBreaks
+            priceBreaks={selectedVariant?.quantityPriceBreaks?.nodes}
+          />
+        ) : null}
         <br />
         <p>
           <strong>Description</strong>
@@ -174,6 +219,21 @@ const PRODUCT_VARIANT_FRAGMENT = `#graphql
       name
       value
     }
+    # @description Add B2B quantity rules and price breaks to variant fragment
+    quantityRule {
+      maximum
+      minimum
+      increment
+    }
+    quantityPriceBreaks(first: 5) {
+      nodes {
+        minimumQuantity
+        price {
+          amount
+          currencyCode
+        }
+      }
+    }
     sku
     title
     unitPrice {
@@ -224,13 +284,15 @@ const PRODUCT_FRAGMENT = `#graphql
   ${PRODUCT_VARIANT_FRAGMENT}
 ` as const;
 
+// @description Add buyer parameter for B2B contextualized queries
 const PRODUCT_QUERY = `#graphql
   query Product(
     $country: CountryCode
+    $buyer: BuyerInput
     $handle: String!
     $language: LanguageCode
     $selectedOptions: [SelectedOptionInput!]!
-  ) @inContext(country: $country, language: $language) {
+  ) @inContext(country: $country, language: $language, buyer: $buyer) {
     product(handle: $handle) {
       ...Product
     }
```

***
