Shopify B2B — registration & approval

Wholesale applications become real Shopify companies.

A buyer applies. A human reads it. On approval, B2B Triage writes native Shopify objects — Company, location, contact, catalog, payment terms, tax exemption. Not a tag. Not a note. The objects Shopify's own B2B engine runs on.

$20/month. Native Shopify B2B is on every paid plan — it stopped being Plus-only in April 2026.

01 — The object graph

One approval. Six native objects.

The common approach ends at a customer record with the answers pasted into a note. B2B Triage creates what Shopify's B2B engine actually reads.

01

Company

A stable external ID, derived when the buyer applies rather than when you approve. Shopify does not enforce unique company names — without it, a double-clicked Approve silently creates two.

02

Company location

Including the second one Shopify auto-creates behind your back, carrying no terms, no exemption and no catalog. It is the one buyers pick, and it sells at retail. We detect and reconcile it.

03

Company contact

The buyer, attached to the location, using the assignment mutation that works when the customer already exists — which is always.

04

Catalog assignment

Their price list, asserted ACTIVE on read-back rather than assumed. Shopify can silently force it to ARCHIVED.

05

Payment terms

Net 30 resolved from the template to a typed integer — never parsed out of the display string, which breaks on the first French store.

06

Tax exemption

The jurisdiction-correct value from Shopify's 75. Canada alone has six kinds. A blanket boolean is not the same thing.

Nine steps, each recorded on the application's own timeline. When Shopify refuses one, you get Shopify's own words — not an error code we invented.

02 — Ownership

Cancel us. Keep everything.

Every app wants your data. Almost none will tell you what happens to it the day you leave.

Shopify records no app-ownership link on a Company, a location, a contact, a catalog assignment, payment terms or a tax exemption. Metafields do have a reserved $app: namespace, and an uninstall severs those. Companies have no such field. There is nothing for an uninstall to take away.

Stated precisely, because this is the kind of claim that deserves it: what is proven is the mechanism, and it holds whoever created the objects. Our live uninstall run wrote them with an independent Admin API credential rather than the app's own token — we would rather show you the seam than sand it down.

03 — Your data

Structured at input, so the export is CRM-ready.

We build apps that structure your data and track ROI. It shows up in an unglamorous place: where the answers go.

The common approach stores registration answers as a delimited plaintext blob inside the customer's Notes field. Any staff member can edit it or wipe it, and no report, segment or query can reach inside it. The data is on the record without being data.

B2B Triage writes typed, namespaced metafields. A number is a number.

Reportable
Metafields reach ShopifyQL as a GROUP BY dimension. Tags have no analytics dimension at all — you cannot report on a tag. (Shopify does not offer admin-list filtering on customer metafields; that answer comes from analytics and the export, not the customer list.)
Typed
net_terms_days = 30 supports "greater than 30". The tag net30 string-collides with net300 and supports nothing.
Namespaced
Everything lands under b2b_triage. Tags are one flat global namespace shared with every app on your store — two apps writing "wholesale" collide in silence.
Exportable
CSV with Shopify GIDs as join keys, so the row matches your CRM instead of approximating it. Values are normalised at input.

We write tags too, as a compatibility bridge for tools that cannot read metafields (Klaviyo's native Shopify sync reads tags, not metafields). The metafields are the truth. The tags are a courtesy.

04 — Tax IDs

The 21 minutes Google Ireland was a fraud.

VIES is the EU's VAT registry. It answers HTTP 200 when it is broken, and reports its own outages as isValid: false. The actual reason hides in a different field.

On 16 July 2026 we asked it about IE6388047V — Google Ireland's real, live VAT number. For 21 minutes it came back invalid, because Ireland's member-state service was down. Then, without anything changing at Google, it came back valid.

Time (UTC) isValid userError The naive reading
05:14 false MS_UNAVAILABLE "Invalid" — fraud-flagged
05:35 true VALID GOOGLE IRELAND LIMITED

We never read isValid. We read the error code as an allowlist: exactly one value means "not registered". Everything else — including codes the EC ships next year — means "we could not check right now", which is a different sentence and gets a different word on screen.

And we never reject anyone because a government registry blinked. There is no branch in our code that turns a registry's bad day into a rejection — not a policy we promise, a branch that does not exist. "Never checked", "checked and found nothing" and "the check failed" are three different facts; a boolean would collapse them into a lie.

What we check, precisely

"Validated" and "checked against the registry" are not the same word. So we use both, and we tell you which one you got.

Validated offline — format and check digit
EU VAT, UK VAT, Australian ABN, India GST, US EIN. Real arithmetic, not a regex. A value we can already prove is a typo never reaches a government registry.
Checked against the live registry
EU VAT, via VIES. That is the whole list, and we would rather say so than imply five.
Built, waiting on a credential
Australia's ABR. Written and tested; we do not hold the GUID yet, so no call has returned real ABN data.
Not built
UK HMRC needs an OAuth credential. We return "unsupported" rather than shipping a client that 401s on every call and reports it as the buyer's problem.
Impossible, not missing
India and the US have no public registry to check. Anyone charging you for it is checking the format.
05 — Forms

(A and B) or C.

Conditional logic is usually the thing you upgrade for. Ours does nested AND/OR groups — not a single global "match all / match any" switch, which by construction cannot express that line above. At $20.

Show the resale certificate field when the country is Canada and the province is Québec — or whenever they picked "distributor", wherever they are. That is one rule.

Hidden means absent, and it is enforced on our side: the server re-evaluates every condition before it accepts anything, so a crafted POST cannot smuggle a value your logic never showed. The same module runs the editor preview and the submission.

Normalise. Don't reject.

  • h9b2g9 becomes H9B 2G9. Per country — a US ZIP is not a Canadian postal code.
  • Phone numbers store as E.164. Formatting happens on blur, never on keystroke.
  • 244 countries, with province and state dropdowns for every country that has them — built from Shopify's own zone data, which is not ISO 3166-2. Mexico's Aguascalientes is AGS to Shopify and AGU to ISO, and Shopify accepts the wrong one without an error.
  • Errors diagnose instead of grade. The word "invalid" never appears — a test enforces it.
  • 16px inputs so iOS Safari does not zoom on focus. Real labels, errors announced to screen readers, 44px touch targets.
06 — Your storefront

Zero third-party requests. Works with JavaScript off.

Your storefront's speed is your SEO and your conversion rate. An app that taxes every page to serve one form is not free — you just do not get the invoice.

  • No third-party requests at all: no tracker, no CDN font, no analytics pixel. Nothing on your storefront phones anywhere but your storefront.
  • It loads only where you place it. It is a theme app block, not an app embed — an embed runs on every page of your store, including the ones with no form on them.
  • Under 13 KB gzipped, measured from the built artifact by a build step that fails when it grows.
  • The form works with JavaScript disabled. Any tax-registry lookup runs on our server, never in your buyer's browser.
07 — Pricing

Two plans. That's the whole page.

Billed by Shopify, on your existing invoice.

Capability B2B Triage $20/month Everything above. + RetailNot built yet $35/month Everything in $20, plus three things that do not exist yet.
Registration forms — multi-step, custom fields, nested AND/OR logic Included Included
Approval queue — manual, automatic, or auto-approve on an email domain Included Included
Native Company, location, contact, catalog, terms, tax exemption on approval Included Included
Typed metafields + tags for compatibility Included Included
Tax ID validation, and EU VAT checked live against VIES Included Included
CSV export with Shopify GIDs as join keys Included Included
Shopify Flow trigger carrying typed application data Included Included
Point of sale — B2B prices at the counter Not included Not built yet
Public API Not included Not built yet
Klaviyo integration Not included Not built yet

The three retail rows are in development, and listed here so the roadmap is not a secret. Do not buy the $35 plan for them today — buy the $20 plan, and we will tell you when this one is real.

Native Shopify B2B is available on every paid Shopify plan — it has not been Plus-only since April 2026. A few pieces of Shopify's own B2B are still Plus-gated, and we are specific about which in the FAQ.

08 — Questions

The questions worth answering.

What does B2B Triage create when I approve a buyer?

Native Shopify B2B objects: a Company, a CompanyLocation, a CompanyContact, a catalog assignment, payment terms and a jurisdiction-correct tax exemption — plus typed metafields on the customer, and tags for tools that only read tags. They are ordinary Shopify records: your staff can edit them, your other apps can read them, and Shopify's B2B engine uses them directly because they are its own.

Do I need Shopify Plus?

No. Shopify's native B2B has been on every paid plan since April 2026 — the "B2B is Plus-only" rule is real history and out of date. Three things are still Plus-gated by Shopify, not by us: assigning a catalog directly to a company, having more than three catalogs (non-Plus caps at three), and partial payments or deposits. Our own store is Plus, and we have not yet watched a non-Plus install pass or fail — if you are on one and something is wrong, we want to be the ones who find out.

What happens to my companies if I uninstall?

They stay. Shopify records no app-ownership link on a Company, a location, a contact, a catalog assignment, payment terms or a tax exemption — unlike metafields, which live under a reserved $app: namespace that an uninstall does sever. There is nothing for our removal to take away. Precisely: what is proven is that mechanism, and it holds whoever created the objects — our live uninstall run wrote them with an independent Admin API credential, not the app's own token.

How do you handle spam?

A honeypot field, an IP rate limit, and the approval queue — the real backstop, because no application becomes a company until a human reads it. We do not ship a CAPTCHA: we had one, it caused more problems than it solved, and we removed it rather than leave a control that lies. Spam can cost you attention and a database row. It cannot cost you money, and it cannot create anything on your store. If a CAPTCHA is a hard requirement for you, that is a real gap.

Is my buyers' data safe? Where does it go?

It stays in your Shopify store and in our database, nowhere else. Tax IDs and VAT numbers are private to the storefront — never readable from your theme. No PII in logs, none in URLs. Applicant personal data is anonymised automatically 730 days after the application is decided. Approved companies are never touched by that — they are your records, not ours.

Who builds this?

Human After All, a Shopify agency in Montréal. We build B2B on Shopify for a living, and made this because the tools we needed either did not exist or built a parallel system beside the store instead of using the store. Argue with an engineering decision on this page and you will get an engineer.

09 — Contact

Ask us something specific.

A real person reads these. If you have a question about a claim on this page, ask it — we would rather be corrected than believed.

Optional — your myshopify.com domain or your storefront

We use what you send to reply to you. Nothing else, and nobody else.