Shopify B2B Wholesale Alongside B2C: Running Two Storefronts on One Catalog
How to run a Shopify B2B wholesale storefront next to a consumer store — MAP vs MSRP pricing, customer tagging, trade tiers, catalog sync, checkout differences, and the price-leakage problem nobody mentions.
Two audiences, one catalog, zero tolerance for drift
I operate two Shopify storefronts for a US building-products manufacturer. A consumer store and a wholesale store, running the same theme, sharing the same physical products, and sharing almost nothing else.
The shape of the problem, so you know what the constraints are:
- Consumer store: millions of sessions a year against an enterprise-scale catalog — 2,800+ products and 13,000+ multi-level variants.
- Wholesale store: the same physical products behind customer accounts, net terms, separate price lists and a completely different checkout.
Both storefronts, one source of truth. This post is about what it actually takes to keep both correct at the same time.
Decision one: two stores or one store with B2B enabled?
Shopify Plus gives you native B2B — Companies, company locations, catalogs, price lists, payment terms — inside a single store. It is genuinely good, and for most brands it is the right answer.
We run two separate stores. That is a deliberate trade, and the honest version is:
What two stores buy you: completely separate app stacks (the wholesale store runs request-a-quote, checkout blocks, direct-mail and landing-page tools the consumer store has no business loading); separate tax providers, which turns out to matter more than anyone expects; separate email accounts and separate analytics, so wholesale behaviour never pollutes consumer reporting; a wholesale domain you can put behind a login without touching consumer SEO; and no risk of a merchandising change on one side leaking to the other.
What two stores cost you: every catalog action happens twice, and the two stores do not share product IDs. A product ID from the consumer store used against the wholesale store returns "product not found." Every bulk operation is generated twice, from two exports, keyed on SKU.
The golden rule, written down and enforced: any SKU change in one store must be replicated in the other and in every connected app. Out-of-sync inventory between the two is the number one operational risk, above everything else on the list.
If you are choosing today and you do not have a hard reason for separation — a different tax engine, a different app stack, a genuinely different catalog — use native B2B in one store. The duplication tax is real.
Pricing: MSRP, MAP, and trade tiers
This is where wholesale gets structurally different from consumer, and where mistakes are expensive.
The model in Shopify terms:
- MSRP → `compare_at_price`. The struck-through number. What the product "is worth."
- MAP → `variant_price`. Minimum Advertised Price. What is actually charged and displayed.
- Trade tier 1 → 30% off, applied to the trade audience.
- Trade tier 2 → certified installer. And here is the interesting part: tier 2 is not a deeper discount. It is a lead-routing tier — certified installers get referred work. Promoted on the consumer site, not on the wholesale one.
Two rules that carry real money:
MAP must be byte-identical across every channel and the webstore. Not "close." Not "we'll test a lower price on one channel." A price that lands on one retail channel and nowhere else is treated as a defect, and it is one, because your retail partners are watching and their agreements say so.
Cross-link the tiers. The consumer store carries a "get trade pricing" CTA pointing at wholesale signup. That single link is one of the highest-converting acquisition paths in the whole system, because contractors land on the consumer site constantly while researching.
Customer tagging: the mess you will inherit
Every B2B Shopify store I have touched has this problem, and it is worth describing precisely because the fix is counterintuitive.
A single logical concept — this customer is a trade account — was encoded across at least four different tag variants, including one with a typo. On top of that: legacy migration artifacts from the previous platform (<platform> Group:1, migrated), test and debug tags left in production, and one tag repeated four times inside a single tag string.
The instinct is to clean it up first. That is the wrong order, because segmentation is blocking revenue today and tag cleanup is a project.
What I did instead: build segments that OR across every known tag variant, ship the revenue work, and file full tag cleanup as its own tracked piece of technical debt. Trusting one canonical tag before you have made it canonical is how you silently exclude a third of your trade customers from a campaign.
When you do clean up: inventory every tag first, with a status per tag (KEEP / MERGE / REMOVE), and confirm what each one is actually wired to — smart collections, Klaviyo segments, theme conditionals, discount rules — before deleting anything.
Product tagging as feature-gating, and the multi-theme trap
A pattern worth stealing: use one master product tag to drive a smart collection (Tag Equals <master>), and separate child tags to gate different PDP and grid render paths.
The bug that pattern produces, and I have shipped it: the theme's grid badge gated on tag X while the smart collection filtered on tag Y. Silent mismatch — the collection populated correctly and the badge never rendered anywhere. Nothing errors. You find it by eye.
And the genuinely non-obvious trap: tags are product-level data shared by every theme in the store. Importing tags to test a badge on an unpublished preview theme immediately changes smart-collection membership on the live theme. There is no theme scoping on catalog data. Your options are: accept the brief exposure, reverse it with a second import, or defer the tag import to go-live day. There is no fourth option.
For the import itself: key on Variant SKU, use Tags Command: MERGE (append, never wipe), Command: UPDATE (touch existing only, create nothing), and run the Analyze pass first to confirm every row matches.
Checkout is where B2B and B2C diverge most
The consumer checkout is optimised for speed: guest checkout, express payment buttons, upsells, a shipping-threshold progress bar.
The wholesale checkout has different jobs entirely — purchase order numbers, net payment terms, quantity minimums and multiples, tax exemption certificates, freight quoting for palletised goods, and a sales rep attached to the order.
On Shopify Plus, Checkout UI Extensions are the supported way to add these: a PO number field, a "ship to my yard" toggle, a certificate upload. They render inside Shopify's checkout without the maintenance liability of checkout.liquid. Off Plus, you push what you can into cart attributes and post-purchase, and you accept the ceiling.
Order-quantity rules — minimums, multiples, case packs — are worth solving with a dedicated app rather than theme JavaScript, because theme-level quantity validation is trivially bypassed by anyone hitting the cart API directly. And on a wholesale store, someone will.
The price-leakage problem nobody mentions
This is the finding I would most want a merchant to read before launching wholesale on Shopify.
I tested the wholesale storefront with unauthenticated requests carrying a normal browser user agent. The home page, collection pages, product pages, a resources page and `/products.json` all returned 200 with no redirect to login.
The theme behaves correctly: for a logged-out visitor it hides the price and renders a "contact us for pricing" message. That part works exactly as designed.
But the number is still in the JSON embedded in the product page, and in the public /products.json and /collections/<handle>/products.json endpoints. A logged-out request returned the discounted price against the compare-at, consistent with the trade tier.
The critical detail: these are native platform endpoints that do not pass through Liquid. No theme code opens them and no theme code can close them. This is default behaviour on every Shopify storefront, not a defect anyone introduced.
Your actual levers are configuration, not code:
- Shopify's catalog restriction settings on the B2B catalog
- The store-wide password / login-required setting
- A CDN or edge rule blocking the JSON endpoints for unauthenticated requests
And the honest conclusion I wrote for the client: running true trade pricing on a publicly reachable Shopify storefront has an inherent leakage trade-off. You choose your position on it consciously, or you discover it when a retail partner sends you a screenshot.
Architecture: when the right answer is not a catalog
A related question came up: how do you serve a specifier audience — architects and engineers who research products and never buy online — alongside a transactional trade audience, when their product lines conflict?
Four options were on the table:
- A non-ecommerce specification hub on a subdomain or path, with "Download Specs" and "Contact Rep" instead of Add to Cart.
- A tabbed PDP that swaps SKU, specs and CTA per install method — consolidates page authority for SEO, but needs real PDP template work.
- Role-based catalog gating via customer tagging — rejected, because putting research content behind a login suppresses exactly the lead generation the content exists for.
- A guided product finder that forces self-identification before showing conflicting information.
The recommendation was the first one, on the reasoning that the research audience are not ecommerce users at all. Building them a catalog is solving the wrong problem well.
That is the general lesson of B2B on Shopify: most of the hard decisions are not about Shopify. They are about correctly identifying who is on the page and what job they came to do.
The short version
- Choose two stores only for a concrete reason. Otherwise use native B2B.
- MSRP in compare-at, MAP in price, and MAP is identical everywhere.
- Segment across every tag variant you have, then clean the tags as a separate project.
- Tags are store-wide, not theme-scoped. Plan imports around go-live.
- Test your wholesale storefront logged out, including
/products.json, before you launch. - Solve wholesale checkout with Checkout UI Extensions and server-side quantity rules, not theme JavaScript.
I run paired B2C and B2B Shopify storefronts in production, on one enterprise-scale catalog. If you are launching or fixing wholesale on Shopify, see [Shopify development services](/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
Shopify Canonical URLs: The Duplicate Content Trap in Collections and Filters
How Shopify canonical URLs actually work, why collection-scoped product URLs and filter parameters multiply your index, and the theme-level fixes that hold.
Shopify Schema Markup: What Actually Earns Rich Results Now
FAQ rich results are gone. What still earns them: the structured_data Liquid filter, Product vs ProductGroup, and the required merchant listing fields.