Contract and custom pricing beyond price lists.
What Shopify's pricing APIs and Functions actually allow past static price lists, and why a true real-time ERP price lookup needs a different architecture.
Shopify custom pricing is pre-computed, not live: contract prices are stored in price lists attached to catalogs, and every surface reads those stored values. No native hook calls an external system for a price at page render or checkout time. ERP-driven pricing on Shopify is therefore a synchronization problem, solved by pushing prices into price lists ahead of time or rewriting cart lines with a Shopify Function fed by pre-written metafields.
Static price lists answer most contract-pricing questions on Shopify. This chapter covers everything past them: what Shopify Functions and the pricing APIs allow, how ERP-driven contract pricing gets onto the platform, and why true real-time price lookup is architecturally constrained.
What is Shopify custom pricing and where does it live?
Shopify's native engine for custom pricing is the catalog: a set of products and prices published to a context such as a B2B company location. A catalog carries a publication (which products the buyer sees) and a price list (what they pay). Price lists support two modes: a percentage adjustment relative to base prices, capped at a 100% decrease or a 1000% increase, and per-variant fixed prices. A fixed price always takes precedence over the percentage adjustment.
When a logged-in buyer loads a product, Shopify resolves the price by precedence:
- Catalogs connected directly to the buyer's company location
- Company-location market catalogs
- Region market catalogs
- App catalogs, such as the online store
Ties resolve to the lowest price. Direct catalog assignment to companies and locations is Plus-only; Basic, Grow, and Advanced plans cap at 3 active catalogs across all B2B markets, while Plus is unlimited. The full model, including volume pricing and quantity rules, is covered in the price lists chapter.
The architectural fact that matters here sits underneath all of it: these are stored values. Every surface, theme, Storefront API, and checkout reads prices that were written into a price list before the buyer arrived.
Why is true real-time price lookup constrained on Shopify?
Shopify prices are data, not code. There is no native mechanism that calls your ERP for a price when a product page renders or when checkout loads. A price that changes in the ERP is wrong on Shopify until a sync writes it back.
The only checkout-time compute surface is Shopify Functions, and Functions are deliberately pure: no network access, no filesystem, no clock, no randomness. All data must arrive through the function's input query. Shopify's documented alternative to a network call is retrieving pre-written data from a metafield, because fetching from metafields during checkout does not involve an external network call.
Network access for Functions does exist, but it is gated: it is available primarily for merchants on Shopify's enterprise tier, must be enabled by Shopify, and even when granted, successful responses are cached for up to 300 seconds with request timeouts configurable between 100ms and 2000ms. The escape hatch is still not per-request live.
| What you might want | Native behavior | Status |
|---|---|---|
| External price call at render or checkout | No mechanism exists; prices come from stored price lists | NOT NATIVE |
| Network calls inside Functions | Enterprise tier only, enabled by Shopify, responses cached up to 300s | NOT NATIVE |
| Cart line price override | Cart Transform update operation, Plus and development stores only | NATIVE |
| Bulk price rewrite across a cart | Function output capped at 20 kB; docs direct you to catalogs or discounts instead | NOT NATIVE |
| Fixed-price writes via API | 250 prices per call, add-and-replace per variant | NATIVE |
| Concurrent writers on one price list | Rejected with PRICE_LIST_LOCKED; one writer at a time | NATIVE |
What can Shopify Functions do to a B2B price?
The Cart Transform Function API is the only way to change the pricing and presentation of items in a cart. It supports three operations: expand (split a line into components), merge (combine lines into a bundle), and update (override a line's price, title, or image). The update operation is the price-override primitive: it sets a fixed price per unit on a cart line. Two gates apply. Update is only available on Shopify Plus stores and development stores, and each app can register at most one cart transform per store. cartTransformCreate also accepts a blockOnFailure flag that blocks checkout if the function fails, the right setting when the transform is what resolves the correct price.
Discount Functions apply to B2B sessions by default since December 6, 2023, and the function input exposes the purchasing company, so logic can target one company, all B2B buyers, or D2C only. Since June 10, 2024 they also apply when B2B buyers submit orders as drafts for review. Discounts only subtract: raising a price requires a cart transform update or a higher base price.
The resource limits are hard: 128 kB of input, 11 million instructions, 20 kB of output, and metafield values in the function input capped at 10,000 bytes. Shopify's documentation states directly that the output limit does not support bulk price transformations across all line items, and points to discount functions and B2B catalogs instead. A Function can adjust specific lines using pre-staged metafield data; it cannot be a general pricing engine. There is also a display gap: cart transforms act in cart and checkout, so product and collection pages still render the catalog price the buyer saw first. Custom apps with Function APIs require Plus; any plan can use public App Store apps that contain them. The full picture is in Shopify Functions for B2B.
How does ERP-driven contract pricing sync work?
Since checkout cannot ask the ERP, the standard architecture pushes ERP prices into price lists ahead of time:
- Model each pricing tier as a price list on a pricing-only catalog. Shopify documents this layering pattern: 50 pricing tiers and 10 assortments become 60 catalogs instead of 500 full ones, and buyers with multiple pricing catalogs receive the lowest price.
- Write prices with
priceListFixedPricesAdd, which accepts up to 250 prices per call and acts as add-and-replace per variant. - For large volumes, run the same mutation asynchronously through
bulkOperationRunMutationwith a JSONL file; API 2026-01 and later allows 5 concurrent bulk mutation operations per shop. - Send deltas, not full refreshes, and serialize writes per list: a price list that is being modified rejects other writers with
PRICE_LIST_LOCKED.
mutation priceListFixedPricesAdd($priceListId: ID!, $prices: [PriceListPriceInput!]!) {
priceListFixedPricesAdd(priceListId: $priceListId, prices: $prices) {
prices { variant { id } price { amount currencyCode } }
userErrors { field code message }
}
}Throughput is the ceiling. The GraphQL Admin API allows 100 points per second on standard plans, 200 on Advanced, 1,000 on Plus, and 2,000 on enterprise, with mutations costing 10 points by default and input arrays capped at 250 items. A contract-price matrix in the hundreds of thousands of rows syncs in hours, not seconds. That makes freshness, ownership, and failure handling the real design problem, which is why the sync usually lives inside a broader integration; see the ERP and CRM sync fix.
How do you read a buyer's contract price programmatically?
Two query surfaces exist. On the Admin API, contextualPricing on Product and ProductVariant takes a companyLocationId and returns the resolved price, compare-at price, quantity rule, and quantity price breaks; it has been available since API 2022-10. On the Storefront API, any query can be contextualized with @inContext(buyer: { customerAccessToken, companyLocationId }) from version 2024-04 onward. Online Store themes get buyer context automatically through Liquid objects such as customer.b2b? and the per-variant quantity_rule.
query VariantPriceForLocation($id: ID!, $companyLocationId: ID!) {
productVariant(id: $id) {
contextualPricing(context: { companyLocationId: $companyLocationId }) {
price { amount currencyCode }
quantityRule { minimum maximum increment }
quantityPriceBreaks(first: 10) {
nodes { minimumQuantity price { amount currencyCode } }
}
}
}
}Read these for what they are: a query surface, not a pricing engine. Both resolve against the same stored price lists. One warning for headless builds: never cache B2B-contextualized responses, or other users could see another customer's contract pricing.
Where do draft orders fit?
Draft orders are the one native surface where any price can be set at order time. Custom line items accept an arbitrary title and price with no variant required, and an applied discount adjusts variant-based lines at the order or line level. B2B draft orders are created against a company location, invoiced with draftOrderInvoiceSend, and converted with draftOrderComplete. Shopify pitches admin UI extensions on draft order pages precisely for this: calculating complex wholesale pricing or generating quotes with pricing from external systems before the draft converts. The trade-off is that draft orders bypass the self-serve storefront; a rep, an app, or a quote flow has to construct the order. See quoting on Shopify B2B and the quoting fix.
When do you need a live pricing API?
Everything above assumes the buyer is on a Shopify surface: a theme, a headless storefront, or checkout. Large accounts often are not. Punchout catalogs and procurement platforms need to query a buyer's contract price from outside Shopify, and the Admin API lookups above are app-credentialed, rate-limited resources, not a buyer-facing price endpoint. That query layer has to be built on top of the contract data.
Buying Tools: live pricing API. B2B Supercharge's Buying Tools module includes a live pricing API: punchout and procurement systems query each buyer's real contract price, including quantity breaks, in real time instead of working from stale exports. Book a demo to see it against your price model.
The pattern to take away: on Shopify, contract pricing is a data pipeline. Price lists hold the truth the storefront reads, Functions make bounded checkout-time adjustments, draft orders absorb the exceptions, and anything that must answer in real time gets its own API layer above the platform.
Frequently asked questions
Can Shopify pull prices from my ERP in real time at checkout?
Not natively. Checkout reads stored price-list prices, and Shopify Functions run without network access except on the enterprise tier, where responses are still cached for up to 300 seconds. The standard pattern is syncing ERP prices into price lists ahead of time.
Does Shopify support contract pricing per customer?
Per company location, yes: contract prices live in price lists attached to catalogs, and catalogs attach to company locations. True per-individual pricing requires one location per buyer or an app layer. See the price lists chapter for the full model.
Can a Shopify Function change a product's price?
In the cart, yes: the Cart Transform update operation overrides a line's price with a fixed price per unit, but only on Shopify Plus and development stores. Discount functions can only reduce prices, and neither changes what shows on product pages.
What is the API for reading a B2B customer's price?
The Admin API exposes contextualPricing on products and variants with a companyLocationId context, available since API 2022-10. The Storefront API contextualizes any query with @inContext buyer arguments from version 2024-04. Both return the resolved catalog price, quantity rules, and price breaks.
How many prices can an ERP sync push per API call?
250 fixed prices per priceListFixedPricesAdd request, acting as add-and-replace per variant. Deletes are also capped at 250 variant IDs per call, and larger volumes run asynchronously through bulk operations with a JSONL file.
How fast can a full contract-price sync run?
It is bounded by Admin API rate limits: 100 GraphQL points per second on standard plans, 200 on Advanced, 1,000 on Plus, and 2,000 on enterprise, with mutations costing 10 points by default and input arrays capped at 250 items. Large price matrices sync in hours, not real time.
Can a sales rep set any price on an order?
Yes, through draft orders: custom line items take arbitrary prices, and order-level or line-level custom discounts adjust variant-based lines. B2B draft orders then flow through invoice send and completion like consumer drafts.
Sources · verified 2026-07-12
Contract pricing, wherever your buyers order.
See how B2B Supercharge keeps contract prices consistent across storefront, reps, and procurement systems on a live store walkthrough.