# Building B2B checkout extensions on Shopify Plus: a field guide

- URL: https://sevsolutions.com/blog/shopify-b2b-checkout-extensions-2026
- Published: 2025-12-06T02:19:21.000Z
- Updated: 2026-10-27T12:00:00.000Z
- Category: Shopify Plus builds
- Tags: B2B, Shopify Functions, Shopify Plus

Net-30 terms, PO numbers, company pricing and custom shipping: what Shopify's B2B does on every plan, what Shopify Plus adds at checkout, and the 14-week build it typically takes, from discovery to launch.

## The short version

- Shopify's built-in wholesale (B2B) tools work on the Basic, Grow, Advanced and Plus plans. All of them include company accounts, B2B catalogs with their own prices, and purchase order (PO) numbers. They also include net payment terms: invoices paid later, such as Net-30.
- This guide covers what Shopify Plus adds on top. Only Plus runs apps that customize the information, shipping and payment steps of checkout. Plus also gets unlimited B2B catalogs (other plans get 3), catalogs for a single company, and deposits and partial payments. Not sure your store needs Plus? See [what changes when you upgrade](https://sevsolutions.com/blog/shopify-vs-shopify-plus).
- The work is in the gaps. Think approval steps, credit limits, and getting the PO number into every system that needs it. Watch for overlapping catalogs: when more than one applies, Shopify shows the lowest price, which can undercut the price you set for a buyer. And keep public prices on product pages, so Google can still show them.
- A typical mid-sized build takes about 14 weeks, starting with two weeks of discovery. [Talk to us](https://sevsolutions.com/contact), or see our [app development services](https://sevsolutions.com/services/app-development).

Wholesale buyers expect what they get from their other suppliers. That means their own prices, invoices they can pay in 30 days (Net-30), and a purchase order (PO) number on every order. Some want an approval step before big orders go through. Shopify's native B2B launched in 2022. It's built into Shopify rather than bolted on as an app, and today it covers much of that list on every plan from Basic up, including Net-30 terms and PO numbers. The old `checkout.liquid` file was retired in August 2024. Since then, the custom parts run on checkout UI extensions (app add-ons that show inside checkout) and Shopify Functions (small programs that run inside checkout). On Shopify Plus, you can also give each company its own prices, and that stack can add custom shipping logic and approval workflows. The pieces are there.

The pieces don't assemble themselves. Every B2B build we've shipped on Plus over the last 18 months ran into the same set of decisions and the same set of traps. This is the field guide for that work.

## Why checkout extensibility replaced checkout.liquid

Until August 2024, B2B customization on Plus was done through `checkout.liquid` — the original Liquid-templated checkout file (Liquid is Shopify's theme template language) that Plus merchants could edit directly. It was flexible, dangerous, and increasingly incompatible with Shop Pay, Apple Pay, and the modern payment-method auto-negotiation that Shopify shipped in 2023.

The replacement is a stack of three things, and you need all three to do real B2B.

[checkout extensibility reference](https://shopify.dev/docs/api/checkout-ui-extensions) — the UI primitive. Shopify's ready-made interface pieces (Polaris web components) that render in the Shopify-hosted checkout. Limited surface area (you can add fields, banners, and buttons in pre-defined slots) but consistent behavior across payment methods.

Shopify Functions — for any business logic that needs to compute a result. Discount calculation, payment routing, which delivery options a buyer sees, validation. Functions run in a Wasm sandbox (compiled code walled off on Shopify's servers) with an instruction budget: up to 11 million instructions for carts with up to 200 line items, more for larger carts. Same primitives we cover in the [discount API deep dive](https://sevsolutions.com/blog/shopify-functions-discount-api-deep-dive) and [Rust Functions tutorial](https://sevsolutions.com/blog/shopify-functions-rust-tutorial), applied to B2B-specific problems.

[customer accounts](https://help.shopify.com/en/manual/customers/customer-accounts) — the account-layer that exposes company and location to checkout extensions and Functions. The old Customer model was DTC-shaped, built for direct-to-consumer shoppers (one user, one address book); customer accounts model the B2B reality (companies have multiple locations, locations have multiple buyers, each buyer has one of two permissions for each location they buy for, "Ordering only" or "Location admin"). An "Ordering only" buyer places orders and sees their own order history; a "Location admin" also sees every order placed for that location and edits its billing and shipping addresses.

If you're starting a new B2B build, you use all three. If you're migrating from a `checkout.liquid` build, you're rewriting it.

![Five layers of a B2B checkout and who owns each: B2B catalogs, the products and prices assigned to company locations (Shopify); checkout UI extensions for the PO field and terms acceptance (your code); Discount and Payment Functions for Net-30 routing and approval (your code); order metadata and email triggers for the PO number and approval status (your code); and customer accounts, with each buyer's company, location and permission, Location admin or Ordering only (Shopify).](https://sevsolutions.com/posts/shopify-b2b-checkout-extensions-2026-architecture.svg)

Figure: Five layers, three of them yours. The two Shopify ones do the heavy lifting on auth and pricing

## B2B catalog vs storefront catalog

A Plus store running both DTC and B2B has two catalog perspectives that need to coexist. Same products, different pricing, different visibility, different shipping, sometimes different payment methods.

Shopify's primitive for this is the B2B catalog: the products a business buyer can order and the prices they pay. A catalog can raise or lower every price by a percentage, set fixed prices for specific products or variants, or both, and a fixed price overrides the percentage. Catalogs apply to company locations, the branches or sites a business customer buys for. On the Basic, Grow and Advanced plans you assign catalogs to B2B markets (markets for business buyers), up to 3 active catalogs across all of them, and the store must use the new Shopify Markets (the current version of Shopify's settings for selling to different regions and buyers). On Shopify Plus you can create unlimited catalogs and also assign them directly to a company or company location. Either way, the buyer's market, not the catalog, sets the currency they see.

The trap is overlapping catalogs. Catalogs don't add up: when more than one catalog applies to a company location and they price the same product differently, Shopify shows the lowest price. We have seen merchants accidentally undercut their own B2B pricing this way. Say Acme Co's location has its negotiated catalog at -10%, and a -15% catalog meant for other buyers gets assigned to it too: Acme Co's buyers now see the -15% price on every product the two catalogs share. One exception, on Plus: a catalog assigned directly to a location wins over a B2B market's catalog for that location, even when the market's price is lower.

The fix is to keep each location's catalogs deliberate. Give a location only the catalogs meant for it, and on Plus, put a buyer's negotiated prices in a catalog assigned directly to their location. Then check the price each buyer sees before launch: sign in as a test buyer at each company location and look at the products that matter. Document which catalogs go to which locations in a runbook (the team's written how-to) so the next person editing pricing doesn't undercut a negotiated price by assigning one more catalog in the admin.

Inventory is the other coexistence question. B2B customers often want to order from a specific warehouse — "ship from our preferred coast for fast delivery" — while DTC inventory pulls from any-available. Shopify picks the location that fulfills each order through order routing, and an Order Routing Location Rule Function can rank the locations for each order, with the buyer's company in its input (a beta that Shopify Plus merchants request from Shopify). We typically build a small admin tool that lets the merchant assign companies to preferred locations, write the location ID to a company metafield, and have the location rule rank that location first.

## Net-30 terms

The most-requested feature in every B2B engagement: "we want to bill our biggest customers Net-30 instead of capturing payment up front."

Shopify B2B supports this natively as payment terms. In the Shopify admin, go to Customers > Companies and set Net-30 on a company or on one of its locations; its buyers then see those terms on every order they place. The order is created with `paymentTerms` of net-30 type, due 30 days after it's placed, and its financial status stays "pending" until it's paid: the buyer pays from their account, or the merchant charges a card the buyer saved or marks the order paid (or you wire it up to your accounting system via webhooks, the messages Shopify sends to other systems when something changes).

The trap is the approval workflow. Most B2B merchants don't want to give every buyer in every company unlimited Net-30 — they want a credit-limit check, an approver review for large orders, and a paper trail. Shopify covers the basic review on every plan from Basic up: set a company location to "Submit all orders as drafts for review", and you review each of its orders before it's final. To hold only some orders, such as those over a set value, Shopify points to a Payment Customization Function. A credit-limit check, an approver at the buyer's own company and the paper trail are still yours to build.

![A Net-30 order's five states in order: submitted, pending review, approved, fulfilled and captured.](https://sevsolutions.com/posts/shopify-b2b-checkout-extensions-2026-net30-fsm.svg)

Figure: The 'pending review' step is where most teams forget to send the approver an email

The pattern we use: order submitted → automatically tagged based on order value + company credit limit. Orders under threshold and within credit go straight to approved (marker tag added; nothing is charged yet, so payment stays pending). Orders over threshold go to pending-review (custom tag, email sent to the company's approver), and the order is held until manual approval through a custom admin page (we ship a small Polaris React app, built with Shopify's admin design system, for this — typically 3-4 days of work). Approved orders get the tag flipped, the email sent to fulfillment, and Shopify's standard order pipeline kicks in.

The pending-review email is the part teams forget. The approver doesn't have a Shopify admin login — they're an internal procurement person at the customer's company. The email needs a magic link (a one-click sign-in link) that opens a hosted approval page (you build this; sits at /approve-order/{token}). The token is signed and time-bounded. Approval click writes the approval to the order metafield and emails the buyer to confirm.

Capture, meaning actually charging the buyer, doesn't happen on its own. Net-30 counts from the day the order is placed, not from approval. When the 30 days are up, Shopify doesn't charge anything, even a card the buyer picked at checkout; the order shows as overdue in their account. You charge the card from the Shopify admin, or automate it with Shopify Flow (Shopify's built-in automation tool) or your own scheduled job (we usually use a Cloudflare Worker on a schedule). Failed captures (card declined, account closed) need a re-prompt to the approver. This is also a footgun — every B2B build we've shipped has needed at least one revision to handle the "card on file expired between order and capture" case.

## PO numbers and custom checkout fields

Purchase Order numbers are the single most universal B2B requirement and the most boring to implement, which is why teams skip steps and end up with bad data.

Start with what's built in: Shopify's B2B checkout takes a PO number on every plan from Basic up. You need an extension only to check the format per company: a checkout UI extension that renders its own PO input above the payment section, validates that the PO number matches the company's expected format (we support a custom regex, a text pattern, per company, written in a metafield), and writes it to a custom field on the order.

The trap is downstream. The PO number needs to flow into the merchant's invoicing system, the order confirmation email, the shipping label (some procurement systems require it on the receiving paperwork), and the company's accounting export. If you write it to a single field, you have to surface that field in five places. If you write it to a metafield with a known shape, every consumer can read the metafield without needing to know about your specific setup.

We standardize on `app--{your-app-id}.po-number` as a metafield namespace. Email templates read from it via Liquid; shipping label includes it via the same; the accounting export script reads it via Admin GraphQL, Shopify's admin API. One source of truth.

The validation logic varies by industry. Government procurement often uses strict PO formats; corporate procurement is usually freeform. Don't hardcode a single regex — make it per-company configurable. We expose the regex in the company-management page of our custom admin app; the procurement person at the merchant configures it once per customer at onboarding.

The other custom-field requests we've shipped: cost center, project code, end-user name (for office supplies billed centrally but used by individuals), delivery instructions specific to receiving docks. All follow the same pattern: extension renders the field, validates per company config, writes to a known metafield.

## Per-company pricing without breaking SEO

The pricing question that always comes up: "we don't want our public storefront to show our B2B pricing." Reasonable. The implementation has a subtle SEO trap.

The naive approach: hide the price block on the storefront when the customer isn't logged into a B2B account. Show "Login for pricing" instead. The problem: Google's crawler isn't logged in, so the indexed price for every product is "Login for pricing", which kills Product schema's `price` field (Product schema is the structured data Google reads from a product page), which kills the rich-result eligibility (search listings that show price and ratings) for the entire storefront.

The right approach: show the public DTC price on the storefront (this is the price Google indexes and the Product schema reports), and let the B2B catalog set the price for B2B customers once they log in. Logged-out visitors, Google's crawler included, see DTC pricing; a logged-in B2B buyer sees their catalog's prices on the online store itself, not only at checkout. SEO stays intact.

If the merchant genuinely doesn't want public pricing visible (some B2B-only stores), you turn on private mode (Online Store > Preferences) with "Restrict access to B2B customers only", and accept that you're not getting indexed: in private mode, Shopify hides every page of the online store from visitors and search engines. That's a different business model.

Hreflang, the tag that tells Google which language version of a page to show, is the other SEO consideration. If the B2B catalog and DTC catalog are on the same Plus store but the merchant operates in multiple markets, hreflang tags on the product page need to declare the relationships between locale variants. Shopify generates these automatically when you use Markets and the localized URL structure (/en, /de, /fr). Don't override them in the theme.

## Custom shipping logic

B2B shipping is rarely "calculate the carrier rate and pass it through." Three patterns dominate.

Per-company flat rate. The merchant has negotiated $50 freight to Acme Co regardless of order weight. No Function sets a shipping price, so add the $50 rate in Shopify's shipping settings and a Delivery Customization Function that reads the company metafield: it hides that rate from every other buyer and hides the other rates from Acme Co's buyers. That Function can rename, sort and hide the delivery options checkout offers.

Free shipping above a company-specific threshold. The merchant offers free shipping to top-tier customers above $5,000 cart total. Implement as a Discount Function with a shipping discount: it reads the company tier metafield, checks the cart subtotal and, when both qualify, takes 100% off the delivery cost. One Discount Function can discount products, orders and shipping.

Carrier rates with a per-company multiplier. The merchant has a freight carrier integration (FedEx Freight, R+L Carriers) and wants the rate computed by the carrier, adjusted for each company. The carrier's rate comes from carrier-calculated shipping: at checkout, Shopify asks the carrier account or a carrier-service app for real-time rates (included on the Advanced and Plus plans). A Discount Function on shipping then takes the company's percentage off that rate, reading it from a company metafield.

The carrier call is where teams get stuck. A Function can't call the carrier itself: with network access, Shopify makes an HTTP request on a Function's behalf, but only for a few Function APIs, mostly for custom apps on Shopify for enterprises, and only once Shopify enables it. And no Function returns a shipping rate, so a Function that fetches or caches the carrier's quote and hands back its own price has nowhere to plug in. Let the carrier's real-time rate come through carrier-calculated shipping, keep each company's percentage in a company metafield the Discount Function can read, and remember that a discount can only lower the delivery cost, never raise it.

## When NOT to use checkout extensions

Three categories of B2B requirement that look like they want checkout extensions but actually need something different.

Multi-step approval workflows with complex state. Anything beyond "one approver per company over threshold X" — for example, three-tier approval where an order needs supervisor sign-off then VP sign-off then procurement validation — should be a separate workflow tool, not a checkout extension. The order gets created in a "pending external approval" state via the API, lives outside Shopify until approved, then gets pushed back to Shopify with the approval signature. Build this in your existing ERP (the system that runs orders, stock and accounting) or a custom tool, not in Shopify.

Quote-to-order flows. B2B customers often want a formal quote (PDF, signed by both sides) before placing the order. Quote-tooling is its own category — Shopify's native draft orders can do simple quotes, but anything with line-by-line customer comments, conditional pricing, or multi-revision negotiation is better handled in a tool like DealHub, Salesforce CPQ, or PandaDoc, with the final accepted quote pushed to Shopify as a completed order.

Heavy Inventory commitment. Some B2B customers want to "reserve" inventory for a period before formally placing the order. Shopify's checkout doesn't have a hold-without-purchase primitive; the inventory commits at order placement. If your merchant needs reservations (build-to-order, configured products with material allocation), build it in a separate inventory tool and have Shopify just record the final order.

The pattern across all three: don't bend Shopify into doing things it isn't designed for. Use it for the parts it's good at (catalog, checkout, payment, fulfillment), and integrate from outside for the rest.

## What an actual engagement looks like

Across our B2B Plus builds, the shape of the work is consistent enough to scope:

Discovery — 2 weeks. Document the merchant's current B2B operations, the price list structure, the company onboarding flow, the approval policies, the custom field requirements. The output is a written spec the merchant signs off on. This is where most surprises surface.

Phase 1: catalogs — 2 weeks. Configure catalogs, set up companies + locations + buyers in customer accounts, migrate existing B2B customers if any. No code yet — this is admin configuration.

Phase 2: checkout UI extensions — 3 weeks. PO field, approval banner, terms acceptance, custom shipping notes. The extension work and the metafield write paths.

Phase 3: Functions — 3 weeks. Discount logic (shipping discounts included), Net-30 routing, delivery customizations. The most code-heavy phase. We typically write these in JavaScript unless the logic is complex enough to justify Rust (see [Rust Functions tutorial](https://sevsolutions.com/blog/shopify-functions-rust-tutorial)).

Phase 4: admin tools — 2 weeks. The custom React app that the merchant's account managers use to onboard new B2B customers, configure price tiers, approve large orders. This is often skipped on initial scoping and added back when the merchant realizes the Shopify admin alone doesn't cover their workflow.

Phase 5: integration + go-live — 2 weeks. Hooking the order webhook to the merchant's ERP, testing the end-to-end flow with a small set of pilot customers, monitoring the first few weeks of orders for unexpected edge cases.

Total: 14 weeks for a typical mid-sized B2B engagement. Larger engagements (multi-region, multi-currency, complex approval hierarchies) push to 18-22 weeks. The discovery phase is non-negotiable; cutting it always costs more in re-scoping during phase 3 or 4.

For the consumer side — what Shopify's checkout already does, and how to test a change — see our [checkout guide](https://sevsolutions.com/blog/shopify-checkout-cro-what-moves-the-needle).

For the underlying Function patterns that this entire stack depends on, the [discount API deep dive](https://sevsolutions.com/blog/shopify-functions-discount-api-deep-dive) and [Rust Functions tutorial](https://sevsolutions.com/blog/shopify-functions-rust-tutorial) cover the language and testing strategies. Most B2B builds end up writing 3-6 Functions; understanding the testing and deployment workflow before you start saves weeks.

If you're scoping a B2B Plus build and want help on the discovery phase, [talk to us](https://sevsolutions.com/contact). We've run this discovery on our past B2B builds, and the pattern is repeatable enough that we usually spot the structural issues in the first call. The full engagement scope sits under [app development services](https://sevsolutions.com/services/app-development). The output of the discovery alone — the written spec — is something most merchants find valuable even if they take it to a different agency for the build.
