Skip to content
Back to all articles
Shopify B2B
Shopify Plus
Wholesale
Liquid
Price Lists

Shopify B2B: Company Accounts, Catalogs and Price Lists Next to a B2C Storefront

How native Shopify B2B models wholesale — companies, locations, catalogs, price lists — and the Liquid that makes one theme serve both audiences.

August 15, 2026 12 min readBy Matheus Abrahão

One store, two audiences, one theme

Native Shopify B2B lets you run wholesale inside the same store as your consumer storefront. I have also run the alternative — two entirely separate stores on one catalogue — and I have written about why we made that trade and what it costs.

This post is the other side: what native B2B actually is, how it models a wholesale relationship, and what you have to write in the theme to make one storefront serve a contractor and a consumer without either of them noticing the other exists.

If you are choosing today and you do not have a hard reason for separation — a different tax engine, a genuinely different app stack, a different catalogue — native B2B in one store is the right answer. The duplication tax on two stores is real and it is paid every single week.

The object model, in the order it matters

Shopify B2B is four objects and one configuration, and once you hold them in your head the rest of the platform makes sense.

Company. The business you sell to. A company organises multiple locations and multiple contacts who can place orders on behalf of the organisation. Note that a company is not a customer — customers are attached to it.

Company location. A location or branch of a company. This is the object that carries almost everything operational: its own billing and shipping addresses, its own tax settings, its own checkout configuration. Locations are why a national contractor with fourteen branches works on Shopify at all.

Catalog. Attached to a company location, a catalog determines which products are published to that location and what they cost. Different locations of the same company can have different catalogs.

Price list. The pricing inside a catalog. This is where wholesale pricing actually lives — not as a discount code, not as a customer tag conditional in the theme, but as a first-class price for a specific set of buyers.

Buyer experience configuration. Attached to the location, this determines checkout behaviour — payment terms, and whether orders require merchant review before they are accepted. Plus tax exemption values on the location.

The crucial mechanic: a B2B customer selects which location they are purchasing for, and that selection determines the applicable catalogs, pricing, tax exemption and checkout settings for the order. Everything downstream of that choice is derived. Which is why the location picker is not a nice-to-have — it is the control that makes the entire model work.

What you need turned on

Before any of this exists in your store:

  • A plan that supports B2B capabilities, with B2B enabled.
  • New customer accounts activated. B2B does not work on the legacy account system.
  • At least one company with customer access configured.

That second one strands people. If your store is still on classic accounts, enabling B2B is an account-system migration first and a wholesale project second, and the account-system change touches every login link in every email flow you have ever sent.

The theme work: six Liquid properties

Shopify gives you a small, clean set of B2B objects in Liquid. This is the whole surface you need:

  • customer.b2b?
  • customer.current_company
  • customer.current_location
  • customer.company_available_locations
  • company and company_location

The location picker is the first thing to build, and it is genuinely simple:

{% if customer.b2b? %}
  <div>
    <h2>Welcome, {{ customer.name }} from {{ customer.current_company.name }}!</h2>
    <p>You are purchasing for the {{ customer.current_location.name }} location.</p>

{% if customer.company_available_locations.size > 1 %} <h3>Select a different location:</h3> <ul> {% for location in customer.company_available_locations %} {% unless location.current? %} <li> <a href="{{ location.url_to_set_as_current }}">{{ location.name }}</a> </li> {% endunless %} {% endfor %} </ul> {% endif %} </div> {% endif %} ```

location.url_to_set_as_current is the piece to notice. You do not write a form, a POST handler or a session variable — Shopify gives you a URL that switches the buying context, and the catalog and price list follow.

The current? check on each location is what keeps the currently-selected branch out of the "switch to" list. Small thing, and it is the difference between a picker that reads clearly and one that makes a buyer second-guess which branch they are on.

Serving two audiences from one theme

customer.b2b? is the conditional that does the heavy lifting. On a store serving both audiences, or on a dedicated B2B store, the elements you typically gate are:

  • Product prices
  • The cart icon in the header
  • Add to cart buttons
  • Other calls to action on the product page
{%- if customer.b2b? -%}
  {% comment %} trade price and add-to-cart {% endcomment %}
{%- else -%}
  {% comment %} log in to see trade pricing {% endcomment %}
{%- endif -%}

You can also branch on the company or the location itself, which is how you do account-specific merchandising without an app:

{% if customer.current_company.name == 'Northline Supply' %}
  <h3>Delivery and installation included.</h3>
{% endif %}

Two design notes from having shipped this:

Do not build the B2B experience as a hidden layer under the B2C one. A logged-out contractor researching your product is a lead. If your product pages show nothing useful until login, you have suppressed the exact top-of-funnel that generates trade signups. Show the product, show the specs, show a "get trade pricing" call to action — hide the number, not the page.

Cross-link the audiences deliberately. A visible trade-pricing prompt on the consumer storefront is, in my experience, one of the highest-converting acquisition paths in a mixed setup, because contractors land on the consumer site constantly while researching. It costs one link.

Quantity rules and volume pricing

Two B2B pricing mechanics that people try to build in theme JavaScript and should not.

Quantity rules are per-product and carry a minimum, a maximum and an increment. Case packs, pallet quantities, and "you can only buy these in fours" are all this feature. The increment is the one themes get wrong — the plus and minus buttons on a quantity selector have to step by the increment, and the decrement has to stop at the minimum rather than at 1.

Volume price breaks are quantity-based tiers within a price list — buy 10, get the tier-2 price. Surfacing these on the product page is a real conversion lever on a wholesale storefront, because a buyer who cannot see the break does not buy up to it.

The rule I hold: quantity rules must be enforced server-side, not in the theme. Theme-level quantity validation is trivially bypassed by anyone hitting the cart API directly, and on a wholesale store somebody eventually will — usually not maliciously, usually through a punchout system or a bulk-order script. Shopify's own recommendation for checkout validation is Cart and Checkout Validation Functions, and that is the right place for anything that must be true about an order.

If you are on headless or Hydrogen, the equivalent discipline is that every product query must be contextualised with buyer information — company location plus customer token. An uncontextualised query returns consumer pricing, and it will do so silently and correctly-looking. That is the single most common B2B headless bug.

Checkout, and what B2B needs that B2C does not

The consumer checkout is optimised for speed. The wholesale checkout has different jobs: purchase order numbers, net payment terms, quantity minimums, tax exemption certificates, freight quoting for palletised goods, and a sales rep attached to the order.

Payment terms and merchant order review come from the buyer experience configuration on the location, so those are configuration rather than code. Tax exemption is on the location too.

The custom fields — a PO number input, a "ship to my yard" toggle, a certificate upload — are Checkout UI extensions, and here is the constraint that decides your project: checkout UI extensions for the Information, Shipping and Payment steps are Shopify Plus only. Off Plus, you push what you can into cart attributes and accept the ceiling. The full set of deadlines and targets is in the checkout extensibility piece.

The thing to test before you launch

One finding I would want every merchant to read before turning wholesale on.

Test your storefront logged out, with a normal browser user agent, including /products.json and /collections/<handle>/products.json.

The theme can be perfectly correct — hiding prices, showing "contact us for pricing" — and the number can still be sitting in the JSON embedded in the product page and in those public endpoints. Those are native platform endpoints that do not pass through Liquid. No theme code opens them and no theme code can close them.

The levers are configuration, not code: Shopify's catalog restriction settings, the store-wide password or login-required setting, or a CDN edge rule blocking the JSON endpoints for unauthenticated requests.

Choose your position on that trade-off consciously. The alternative is discovering it when a retail partner emails you a screenshot of your trade pricing.

The short version

  • Company → company location → catalog → price list. The location is where the operational configuration lives.
  • The customer picks a location, and that choice determines pricing, catalogue, tax and checkout behaviour.
  • New customer accounts are required. If you are on classic accounts, that migration comes first.
  • The theme work is six Liquid properties and a location picker built on url_to_set_as_current.
  • Hide the price, not the page. A logged-out contractor is a lead.
  • Quantity rules go server-side. Theme validation is a suggestion.
  • On headless, contextualise every product query with buyer identity or you will silently serve consumer prices.
  • Test logged out, including the JSON endpoints, before launch.

Related services

B2B setup, theme work and checkout extensions sit under Shopify expert development; the catalogue and price-list data work is Matrixify catalog operations. Related reading: running B2B wholesale alongside B2C on separate storefronts and Shopify checkout extensibility.


I run B2B and B2C Shopify storefronts in production on shared catalogues, including the pricing, tagging and checkout work underneath them. See [Shopify expert development](/shopify-expert) or [hire a Shopify developer](/hire-shopify-developer).

Direct: [WhatsApp +55 11 98851-2788](https://wa.me/5511988512788) · [contato.matheusabrahao@gmail.com](mailto:contato.matheusabrahao@gmail.com)

Need a senior engineer who thinks like an operator?

I take on a small number of Shopify operations and senior engineering engagements each quarter. If your store needs catalog hygiene, technical SEO, performance, or marketing automation done right — let's talk.

Continue reading

Vamos conversar