Developer guide
Storefront tutorial: your own shop on btab
Build a shop on your own domain that sells btab products. You start with a test key and a ready-made starter shop. In about 30 minutes you place a test order. Nothing is charged and nothing is sent.
Written with the code it describes, in the same pull request. Plain Markdown: /developers/storefront-tutorial.md
Build a shop on your own domain that sells btab products. You start with a test key and a ready-made starter shop. In about 30 minutes you place a test order. Nothing is charged and nothing is sent.
flowchart TD
K[Test key<br/>btab dashboard] --> S[Starter shop<br/>localhost:3000]
S -->|products, cart, freight| A[Storefront API]
S -->|Check out| C[Checkout page<br/>Place test order]
C -->|Back to your shop| T[Your thank-you page]
What you build:
- A product list and product pages, with prices, stock and options from btab.
- A cart that btab prices, and a freight price by postcode.
- A "Check out" button that sends the buyer to the store's checkout, and a thank-you page they come back to.
The full API reference is the Storefront API guide.
1. Get a test key
- Sign in to your btab dashboard. No account yet? Sign up at https://btab.app: a store is ready at once.
- Go to Settings → Developers → Publishable keys → Make a key.
- Tick Test key. (If the Storefront API is not open to your store yet, the form makes a test key anyway.)
- Under Allowed sites, enter
http://localhost:3000. - Press Make key. Copy the key now: it starts with
btab_pk_test_and is shown once.
You should see the key in the list with a test badge.
A test key reads btab's demo store, a shop of made-up goods. Its checkout ends in Place test order. Nothing is charged, delivered or invoiced.
2. Run the starter shop
You need Node.js 20 and git.
git clone https://github.com/SteveDeals/btab-storefront-starter.git
cd btab-storefront-starter
npm install
cp .env.example .env.local
Open .env.local and paste your key:
NEXT_PUBLIC_BTAB_PUBLISHABLE_KEY=btab_pk_test_…
NEXT_PUBLIC_SITE_URL=http://localhost:3000
Start it:
npm run dev
Open http://localhost:3000. You should see five made-up products from the demo store:
| Product | What it shows |
|---|---|
| Demo Mug | a plain product |
| Demo Cushion | three colours: Red, Blue, Green |
| Demo Lamp | Sold out: the button is off |
| Demo Boardroom Table | Enquire: sold by quote, no Add to cart |
| Demo Oversized Crate | a large item with no size data (see Known issues) |
Other ways to run it:
- Docker:
docker compose --env-file .env.local up --build, then open http://localhost:3000. - GitHub Codespaces: open the repo in a codespace. Add the codespace's address (for example
https://<name>-3000.app.github.dev) to your key's Allowed sites, and setNEXT_PUBLIC_SITE_URLto it.
Seeing "The shop is not available right now"? The page shows the reason under it while you develop:
INVALID_KEY: the key in.env.localis wrong or was revoked.ORIGIN_NOT_ALLOWED: the address in your browser is not on the key's allowed sites.NETWORK_ERRORin the browser: a site that is not allowed looks like this too, because the browser hides the refusal.
3. Cart, freight and a test order
- Open Demo Cushion, choose Blue, press Add to cart.
- Open Demo Mug, press Add to cart. The cart shows both lines and a subtotal that btab worked out.
- Under Delivery, enter postcode
2000and press Get freight price. You should see a delivery price, for exampleDelivery $16.95. - Press Check out. The starter asks btab for a checkout session and sends you to its
continue_url: the demo store's checkout page, with your cart filled in. - Press Place test order.
- Press the Back to … link. You land on http://localhost:3000/thanks, which says Thank you.
How the starter does it (lib/btab.ts wraps each call):
| Step | Call |
|---|---|
| Product list | GET /products |
| Product page | GET /products/{id} |
| Add to cart | POST /carts, then PATCH /carts/{id} |
| Freight | POST /carts/{id}/shipping-estimate with postal_code |
| Quote request for the table | POST /carts/{id}/enquiry |
| Check out | POST /checkout_sessions with cart_id and return_url |
| Thank-you page | GET /checkout_sessions/{id}: complete |
Where is the test order? A test order belongs to btab's demo store, not to your store. So it is not in your dashboard's orders. You see it in two places:
- The thank-you page reads the checkout session: its status is
complete. - If you subscribe to the
checkout_session.completedwebhook (Settings → Developers → Webhooks), your server gets it with"livemode": false. See Webhooks.
4. WooCommerce
Already run a WooCommerce shop? The plugin copies btab products into it as real WooCommerce products: title, price, photos, stock, options and categories. The photos go into your own Media Library. Code and issues: https://github.com/SteveDeals/btab-for-woocommerce.
You need WordPress 6.5 or later, WooCommerce 9.0 or later and PHP 7.4 or later.
- Add your shop's address (for example
https://www.yourshop.com.au) to your test key's Allowed sites (step 1). - Open https://github.com/SteveDeals/btab-for-woocommerce, press Code → Download ZIP. (Until release zips are attached to the repo, this source zip is the install file.)
- In WordPress: Plugins → Add New → Upload Plugin, choose the zip, Install, Activate.
- Go to WooCommerce → btab. Paste your key, press Save, then Test connection. You should see the store's name.
- Press Sync now. Under Products you should see the five demo products. The lamp says out of stock, the table offers an enquiry instead of Add to cart, and the cushion has three colours.
What it does today, and what is still being built:
| Product import, photos, options, sold out, sold by quote | works |
| Freight priced by btab at the cart | being built |
| Place order hands the cart to the store's checkout | being built |
| Paid orders copied back into WooCommerce | being built |
Until the hand-off ships, a WooCommerce shop cannot place a test order. Use the starter shop (steps 2 and 3) to see the whole flow.
5. Keep btab out of your buyer's view
The buyer pays on the store's checkout page. Give it your own address:
- In the btab dashboard, go to My store → Your web address → Already have a domain? and enter a subdomain such as
checkout.yourshop.com.au. - At your DNS provider, add the record the dashboard shows you, then wait until the dashboard says the domain is live.
From then on, continue_url opens on checkout.yourshop.com.au. The page carries your store's name and design.
In your own code:
- Name things after your shop, not after btab.
- Load photos through your own server if you want no btab address in the page. The starter serves every photo from
/photo/<token>on the shop's own address (app/photo/[token]/route.ts).
6. Going live
- Make a live key (
btab_pk_live_…) in Settings → Developers. Live keys open when the Storefront API is open to your store. If Make a key offers only test keys, ask btab to open it. - List every address the key is used on under Allowed sites:
https://www.yourshop.com.au, andhttp://localhost:3000if you still develop against it. A key takes up to 5. - Set
return_urlto a page on one of those sites. Areturn_urlanywhere else is refused withRETURN_URL_NOT_ALLOWED. - Point your checkout subdomain at btab (step 5).
- Swap the key in your settings and deploy.
A live key's checkout takes real payment. The order arrives in your btab dashboard like any other order from your store.
7. Limits, errors and what the API never returns
Allowed sites. A browser call is accepted only from the key's allowed sites, compared as the whole address: https://evil-yourshop.com.au is not https://yourshop.com.au. Answers carry CORS for those sites only. A call from a server (no Origin header) is accepted. The key's powers are narrow on purpose, so a publishable key is safe in page code.
Never put a store API key (btab_live_…) in page code. The Storefront API refuses one (STORE_API_KEY_REFUSED). Revoke it if it was ever exposed.
Rate limits. Over a limit, the answer is 429 with Retry-After in seconds.
| What | Limit |
|---|---|
| Reads | 120 a minute per buyer address per key; 3000 a minute per key |
| Cart changes | 20 a minute per buyer address per key |
| Enquiries | 8 per 10 minutes per buyer address per store |
Calls from your own server all share your server's address. Call from the browser, or cache reads on the server, as the starter does for product pages (60 seconds).
Errors you will meet. Every refusal has a code. The starter turns each one into a plain sentence (BtabError.friendly in lib/btab.ts).
| Code | Meaning | What to do |
|---|---|---|
ORIGIN_NOT_ALLOWED | the page's site is not on the key | add it under Allowed sites |
RETURN_URL_NOT_ALLOWED | return_url is not on the key's sites | use a page on an allowed site |
CART_NOT_FOUND | the cart expired (72 hours after its last change) or the id is wrong | start a new cart |
CART_NOT_BUYABLE | a line is sold out, enquiry only, or needs an option | show unavailable_reason per line |
TOO_MANY_BAD_KEYS | too many calls with a wrong key from one address | fix the key, wait a minute |
On a cart line, unavailable_reason says why it cannot be bought: sold_out, low_stock, enquiry_only, needs_option or not_available. An enquiry_only product is sold by quote: send its lines with POST /carts/{id}/enquiry.
What the API never returns. It returns what the store's own shop window shows: names, descriptions, photos, prices, stock, options, reviews. It never returns cost prices, supplier details, other stores' data, buyers or orders.
Known issues
Written as they stand on 2026-10-21.
- The large crate quotes $0 freight. The demo store's Oversized Crate has no weight or size. It should answer "needs a quote" (
refusal_code: SHIPPING_VOLUME_UNKNOWN). Instead it quotesshipping_cents: 0withfree_over_met: true, although the demo store has no free-shipping threshold. The store's own checkout gives the same answer, so it is not a Storefront API fault. - Test orders are not in your dashboard. They belong to the demo store. Use the checkout session's status or the webhook (step 3).
- The WooCommerce plugin imports products but cannot check out yet. Freight at the cart and the hand-off at Place order are being built. Install it from the repo's source zip until release zips are attached.
- The starter reads products on its own server. The product list and product pages are read there and cached for 60 seconds, so those reads share your server's address (see Rate limits). Cart, freight and checkout calls go from the buyer's browser, so each buyer counts against their own limit.
- Codespaces addresses must be added one by one. A key's allowed sites are exact addresses, with no wildcards.
Help
- API reference: Storefront API
- Webhooks: Webhooks
- What changed: Changelog
Checked against btab-api origin/main @ 13e2ef972 on 2026-10-21.