@biab-dev/sdk
v0.9.35
Published
Business In A Box developer SDK — the data layer that lets you build customer-facing surfaces on your own domain while BIAB handles the operations stack behind the scenes. 0.9.x ships a comprehensive programmatic surface: storefront (products, categories,
Downloads
2,499
Maintainers
Readme
@biab-dev/sdk
Alpha: This package is in an alpha release. APIs and behavior may change without notice. It is not intended for production use yet — evaluate only in development, staging, or experiments.
Install
npm install @biab-dev/sdk
# or
pnpm add @biab-dev/sdkReleases
Versioning convention: the patch field's tens digit identifies the feature group (e.g.
0.7.5x= chatbot work). Hot-fixes within a group stay in the same band —0.7.51,0.7.52, … — so consumers on^0.7.5pick them up automatically. New features bump the next band (0.7.6, then0.7.61,0.7.62, …).0.8.x = schema-driven marketing flow. Major surface bump (still alpha). New entrypoints:
defineSiteMarketingSchema(),@biab-dev/sdk/seofor typed JSON-LD builders, and abiab-devCLI.0.9.x = full programmatic consumer surface. The largest expansion yet: native storefront / cart / checkout / coupons / subscriptions, customer portal + tenant auth (
createAuthHandler,getTenantSession,<SignIn/><SignUp/><SignOut/>useUser), blog, a paginated reviews wall, address autocomplete + shipping, programmatic local SEO (defineParallelPage()), the revalidation webhook channel (@biab-dev/sdk/next/revalidate+@biab-dev/sdk/adapters/revalidate), privacy-conscious analytics (@biab-dev/sdk/react-analytics+@biab-dev/sdk/analytics-core), and a three-state billing-lifecycle degradation contract. Detailed per-version notes below.
The 3 most recent releases are below. For the complete release history, see the changelog.
0.9.34
Read your visitor counts back. Analytics used to be write-only — the
<BIABAnalytics/>tracker recorded pageviews and you could only see the totals in the dashboard. Newclient.site(siteId).analytics.pageViews()returns them:// Site-wide, all time const all = await biab.site(siteId).analytics.pageViews() if (all.available) console.log(`${all.total.views} views`) // A batch of pages in one round-trip (e.g. a view count per portfolio item) const each = await biab.site(siteId).analytics.pageViews({ paths: ['/work/atrium', '/work/harbor'], days: 30, })Pass
pathsfor a per-path breakdown (every requested path present,0if unvisited) plus a combinedtotal; omit them for a site-wide total.dayswindows it.visitorsapproximates unique visitors (distinct daily-rotating anon id);viewsis the exact pageview number. Branch on.available— visitor analytics is a plan feature.“Powered by BusinessDash” footer, now in the SDK. New export
@biab-dev/sdk/react-attribution:import { BusinessDashFooter } from '@biab-dev/sdk/react-attribution' // In a Server Component — reads your plan's policy and renders accordingly <BusinessDashFooter client={biab.site(SITE_ID)} companyName="Acme Co" />BusinessDashFooterBanneris the presentational badge (passsiteId);BusinessDashFooteris a policy-aware Server Component. The badge carries abiab.app/?ref=<siteId>backlink — server-render it so it's visible.client.site(id).attributionexposes the policy read + an (optional) heartbeat. On Launch the badge is required; Growth/Scale may remove it — or keep it for a discount, toggled from Settings → Billing.sync-recordsshows live progress. A large seed used to sit silent between “Discovered N rows” and the final tally. It now prints a progress line (rows done, running created/updated) — a single updating line in a terminal, a bounded trail in CI.
0.9.33
Read your custom database back. The data model used to be write-only from the SDK — you could push a schema and seed rows, then never read either. New
client.site(siteId).dataModel.listRecords({ object })returns a page of a custom object's rows, newest first, keyset-paginated (nextCursor);listAllRecords()walks every page for you. Relations come back expanded as links —record.relations.<field>is[{ recordId, object }], since a relation value lives in a link table, not on the row. Every declared relation field is present,[]when it has none, so an empty relation is never mistaken for a missing one.Not the same as
collections/rows, which read Site Data. Rows seeded withsync-recordslive in the custom database and are reachable only throughdataModel.visibilityper object — decide who can read a table.defineObject()takes an optionalvisibility:private(default) — your server only, with a secret key.authenticated— any signed-in customer (tenant auth).public— anyone, from a publishable (pk_…) key in page JS.
So a table you mark
publicis readable straight from the browser; a private one never is. Because it's part of your model, opening a table is a reviewed change — a push shows it on the dashboard's Review tab as an exposure, confirmed separately from destructive changes, and a human promotes it. You can also flip it from Site Builder → Site Data → Database → Schema.⚠️
authenticatedmeans every signed-in customer can read every row of that table. It is not per-customer ownership — never use it to keep one customer's data away from another's.sync-data-modelplans now flag exposure. Opening a table to a wider audience shows up as its own EXPOSURE group in the printed plan, distinct from destructive changes — a change that loses no data but can't be undone once the data's been read shouldn't read as "you might lose rows".New scope
metadata:read_records. Reads from the custom database need it. Scopes freeze when a key is minted, so if you get "Missing scope metadata:read_records", issue a fresh key from Site Builder → Developer — it grants every org-safe scope.
0.9.32
- Corrected the dashboard locations the CLI prints.
sync-data-modelpointed at "CRM → Settings → Data Model", which was wrong twice over: the review + promote surface moved to Site Builder → Site Data → Database (beside Schema, since both are artifacts pushed from a repo for a human to review), and "Static Data" is now Site Data.sync-schema/sync-contentupdated to match.
Entrypoints
| Import path | Contents |
| --- | --- |
| @biab-dev/sdk | Main client, auth handler, all contract types |
| @biab-dev/sdk/react | Embed iframes, chatbot inline, auth links, useUser, marketing page hooks |
| @biab-dev/sdk/seo | JSON-LD builders + renderJsonLdNodes / renderJsonLdToHtml |
| @biab-dev/sdk/marketing-schema | defineSiteMarketingSchema, section builder |
| @biab-dev/sdk/contracts | Bare zod schemas + TS types (tree-shakeable) |
| @biab-dev/sdk/proxy | Customer-portal proxy helpers |
Reserved page paths
When you attach a custom hostname to your org (pay.acme.com, acme.com, etc.), BIAB serves a small set of transactional public routes on that hostname so quote / contract / invoice links emailed to your customers stay on your own domain. Those routes are reserved — any marketing page (or contentSync.pages[].pageKey, or BIAB site-builder page slug) declared at one of them will be rejected by both sync-schema and sync-content, and by the site-builder createPage / updatePage mutations, with a clear error.
Reserved paths (and everything under them):
quotes,quotes/acceptcontracts,contracts/signinvoices,invoices/payportal
If the CLI exits with "<path>" is reserved by the BIAB platform., rename the offending page (e.g. quotes → our-quotes, contracts → legal/contracts) and re-run the sync.
Exact source of truth: src/reserved-paths.ts (mirrored server-side at src/server/sites/reserved-page-paths.ts).
0.8 — Schema-driven marketing content
1. Declare your schema
Create biab.config.ts at your project root:
import { defineSiteMarketingSchema, section } from "@biab-dev/sdk";
import { z } from "zod";
export default defineSiteMarketingSchema({
brandTokens: ["company.name", "company.phone", "company.email"],
sections: {
hero: section({
label: "Hero",
schema: z.object({
title: z.string(),
subtitle: z.string().optional(),
primaryCta: z.object({ label: z.string(), href: z.string() }).optional(),
}),
defaults: { title: "Welcome" },
ui: { subtitle: { widget: "textarea" } },
}),
faq: section({
label: "FAQ",
schema: z.object({
items: z.array(z.object({ q: z.string(), a: z.string() })),
}),
}),
// Managed-data section — items come from BIAB project media
gallery: section({
label: "Gallery",
schema: z.object({
layout: z.enum(["grid", "masonry"]).default("grid"),
maxItems: z.number().int().positive().default(12),
}),
dataSource: {
kind: "project-media",
configureHref: "/dashboard/projects",
},
}),
},
});Valid dataSource.kind values: "project-media" · "blog-posts" · "services-products"
2. Set environment variables
BIAB_API_KEY=... # needs marketing:write_schema scope
BIAB_SITE_ID=... # UUID from Site Builder → Developer
BIAB_PACKAGE_API_BASE_URL=https://your-host.tld # auto-normalised to /api/package/v1Site Builder vs visitor HTML — when changes actually show up
Operators often confuse “saved in BIAB” with “the public homepage changed.” They are connected only through whatever your frontend wires up.
There are three separate ideas:
| Layer | What happens | Latency mental model |
| --- | --- | --- |
| BIAB datastore + package API | Dashboard writes go through BIAB servers. client.site(siteId).marketing.getPageBundle({ pageKey, locale }) always reads the platform’s notion of published marketing data — not your CSS bundle. | After a successful server write + successful read, milliseconds to seconds depending on infra. |
| Your SSR / ISR / CDN | The HTML you serve is assembled by Astro, Next, etc. Static HTML is whatever was produced at build/export. | For static artifacts, edits don’t ship until HTML is rebuilt and redeployed (or ISR revalidated, etc.). |
| Consumer-side caching | Your SDK wrapper may memoize bundles (see BIAB_BUNDLE_CACHE). CDNs attach Cache-Control. | Minutes to “never” depending on TTLs unless you deliberately bypass cache. |
Why Site Builder preview can diverge from production: previews talk to drafted or preview backends. Production Astro output: 'static' bakes bundles at build time unless you hydrate from the API in the browser (not recommended — exposes keys) or migrate to SSR.
Operational patterns:
- Prefer SSR (or SSR + CDN with short TTL) for BIAB-heavy sites. Example: Astro
output: 'server'with a runtime adapter (@astrojs/vercel, Node, etc.). Every request executes your page code →getPageBundle()→ fresh markup. Cold starts trade off against API chatter. - Stay static-only for marketing sites that rarely change. Cheaper infra: accept that
astro build && deploy(or webhook-triggered pipelines) gates updates — BIAB edits still land in the DB immediately but visitors read yesterday’s artifact until the redeploy completes. - Hybrid: Astro
output: 'hybrid', flipexport const prerender = falseon routes that rely on BIAB while keeping deterministic pages static. - Never ship
BIAB_API_KEYto the browser. Public env vars prefixed withPUBLIC_leak to client bundles — keep privileged keys server-only (import.meta.env.BIAB_API_KEY).
Schema drafts vs promoted schema: Updating section shapes still flows through draft → Promote gates (needs website.manage). Copy edits on already-published schemas hit the bundle read path independently — but browsers still won’t magically update if your HTML remains static until rebuild.
The Astro reference site bundled with BIAB tooling (UrbanAirNYC) sets output: 'server' + @astrojs/vercel, optional bundle memoization via BIAB_BUNDLE_CACHE, and composes BIAB-managed SEO/json-ld helpers (resolveHeadFromBundle(), marketingJsonLdFromBundle()) on top of local JSON defaults.
3. Publish to draft
The CLI needs tsx to execute your TypeScript config file. Add it as a dev dependency:
pnpm add -D tsxThen run via tsx so it can load biab.config.ts:
pnpm exec tsx node_modules/@biab-dev/sdk/dist/cli.js sync-schemaOr add a script to package.json for convenience:
{
"scripts": {
"sync-schema": "tsx node_modules/@biab-dev/sdk/dist/cli.js sync-schema --config biab.config.ts",
"print-schema": "tsx node_modules/@biab-dev/sdk/dist/cli.js print-schema --config biab.config.ts"
}
}Then run:
pnpm sync-schema
# [biab-dev] Loaded schema with 3 section(s).
# [biab-dev] Checksum: 7f2a9e1d…
# [biab-dev] Uploaded draft v4.
# [biab-dev] Promote in the dashboard at Site Builder → Static Data → Schema.CLI flags:
| Flag | Description |
| --- | --- |
| --config path | Override config file discovery (default: biab.config.ts → .mjs → .js) |
| --dry-run | Validate locally without uploading |
print-schema dumps the resolved JSON-Schema artifact + checksum to stdout (useful for CI debugging).
4. Promote in the dashboard
Site Builder → Static Data → Schema shows a diff of added / removed / changed sections. Click Promote to make schema-shape changes live. That gate requires website.manage, not only an SDK key. Text/column edits for already-published sections still persist through the bundle read path independently (but static HTML won’t react until rebuilt unless you SSR).
5. Seed initial content from local JSON
A freshly-promoted schema gives operators an empty editor — every section starts blank. If you've already authored content as JSON files in your repo (e.g. while building the site against local fallbacks), the sync-content command bulk-imports the whole tree in one shot.
Convention: directory layout
The CLI walks a tree shaped like one of:
# Default layout (zero-config — just set `rootDir`)
src/content/
en/
home/
hero.json
stats.json
about/
page.json
# Or: page dirs nested under an intermediate segment, with shared
# cross-page sections in a sibling dir (declare `pages` explicitly)
src/content/
en/
shared/
brand.json
footer.json
pages/
home/hero.json
about/page.jsonEach leaf JSON file becomes one (pageKey, locale, sectionKey) row in BIAB.
Declare the mapping in biab.config.ts
Add a contentSync field next to sections:
// biab.config.ts
import { defineSiteMarketingSchema } from "@biab-dev/sdk";
export default defineSiteMarketingSchema({
sections: { /* ... your schema ... */ },
brandTokens: [ /* ... */ ],
contentSync: {
rootDir: "src/content",
locales: ["en"],
// Explicit page dirs — needed when your layout has `pages/` and
// `shared/` segments. Omit for the default `<locale>/<page>/` layout.
pages: [
{ dir: "{locale}/shared", pageKey: "shared" },
{ dir: "{locale}/pages/home", pageKey: "home" },
{ dir: "{locale}/pages/about", pageKey: "about" },
{ dir: "{locale}/pages/services", pageKey: "services" },
],
// Filename → schema sectionKey aliases. Supports both:
// "<basename>" — global alias
// "<pageKey>/<basename>" — page-scoped alias (wins over global)
sectionAliases: {
"service-areas": "serviceAreas", // global: kebab → camel
"home/marquee": "trustMarquee", // scoped: only inside home/
"about/page": "aboutPage", // scoped: page.json varies
"services/page": "servicesPage",
},
// Sections whose values come from other dashboards (gallery from
// media uploads, blogFeed from the blog editor, pricing from
// services/products). The CLI warns on matches but doesn't upload.
skipSections: ["gallery", "blogFeed"],
},
});If you don't supply pages, the CLI auto-discovers: every direct child of <rootDir>/<locale>/ becomes a page, with the dir name as the pageKey.
Add the script and required env
The API key needs marketing:write_content (distinct from marketing:write_schema — a content-sync key can't reshape the schema, and vice versa). Reuse the same BIAB_API_KEY / BIAB_SITE_ID / BIAB_PACKAGE_API_BASE_URL from sync-schema.
{
"scripts": {
"sync-content": "tsx node_modules/@biab-dev/sdk/dist/cli.js sync-content --config biab.config.ts"
}
}Run it
pnpm sync-content
# [biab-dev] Discovered 24 section file(s) under src/content.
# ⚠ Skipping pages/home/marquee.json — alias resolves to "trustMarquee" ✓
# [biab-dev] POST .../sites/.../marketing/sections in 1 batch(es)…
# [biab-dev] Batch 1/1: ✓ 22 ✕ 2
#
# [biab-dev] ✓ 22 succeeded ✕ 2 failed
# ✕ en/contact/page — Required field "email" is missing.
# ✕ en/quote/page — Field "tiers[0].price" expected number, got string.
#
# Tip: pass --lax to skip validation for this run.Per-row failures don't abort the rest of the batch — the CLI keeps going and prints a summary so you can see every validation gap in one pass.
CLI flags
| Flag | Effect |
| --- | --- |
| --config path | Override config discovery (default: biab.config.ts → .mjs → .js) |
| --dry-run | Walk the tree and report discovery, but don't POST |
| --lax | Skip server-side schema validation for this run — useful for first imports of legacy content that doesn't perfectly match the schema yet. Server still records revisions, so you can clean up incrementally in the dashboard. |
| --note "first import" | Tag every revision row created in this run with a human note. Shows up in the V7 Settings tab's version timeline. |
Re-runs are safe
Every file POST becomes a versioned revision on the server, append-only. Running sync-content twice in a row with no file changes just writes a new revision tagged identical (the dashboard's restore picker still shows the lineage). It will NOT clobber dashboard edits with stale file content unless the file actually differs.
If you want to wipe a section's history and restart from the JSON, do it from Site Builder → Static Data → Settings → History → Restore in the dashboard — that's gated on website.manage, not the API key.
6. Read content in your pages
SSR / RSC (Next.js, Astro, etc.)
import { createBiabDevClient } from "@biab-dev/sdk";
const client = createBiabDevClient({
apiKey: process.env.BIAB_API_KEY!,
baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
});
const { sections, seo } = await client
.site(process.env.BIAB_SITE_ID!)
.marketing.getPageBundle({ pageKey: "home", locale: "en" });
// Each section is tagged ok|error — one bad row never crashes the page
const hero = sections.hero?.ok ? sections.hero.data : null;With typed schema validation
import marketing from "./biab.config";
const bundle = await client
.site(siteId)
.marketing.getPageBundle({ pageKey: "home", locale: "en" });
// marketing.parseSection validates through your zod schema
const hero = marketing.parseSection("hero", bundle.sections.hero);
// → typed as z.output<typeof heroSchema> | nullReact hook
import { useMarketingPageBundle } from "@biab-dev/sdk/react";
import marketing from "./biab.config";
function HomePage() {
const bundle = useMarketingPageBundle({
apiKey: process.env.NEXT_PUBLIC_BIAB_API_KEY!,
baseUrl: process.env.NEXT_PUBLIC_BIAB_PACKAGE_API_BASE_URL!,
siteId: process.env.NEXT_PUBLIC_BIAB_SITE_ID!,
pageKey: "home",
locale: "en",
});
if (bundle.status !== "ready") return null;
const hero = marketing.parseSection("hero", bundle.bundle.sections.hero);
const { seo } = bundle.bundle;
return (
<>
<title>{seo.seoTitle}</title>
{hero ? <h1>{hero.title}</h1> : null}
</>
);
}Astro (two-tier: BIAB with local JSON fallback)
---
import { createBiabDevClient } from "@biab-dev/sdk";
import marketing from "../../biab.config";
import { getContent } from "../lib/i18n"; // existing local fallback
const apiKey = import.meta.env.BIAB_API_KEY;
const siteId = import.meta.env.BIAB_SITE_ID;
const baseUrl = import.meta.env.BIAB_PACKAGE_API_BASE_URL;
let heroContent = getContent(locale, "pages/home/hero"); // always the safe fallback
if (apiKey && siteId && baseUrl) {
try {
const client = createBiabDevClient({ apiKey, baseUrl });
const bundle = await client.site(siteId).marketing.getPageBundle({
pageKey: "home",
locale,
});
const parsed = marketing.parseSection("hero", bundle.sections.hero);
if (parsed) heroContent = parsed; // BIAB wins when configured
} catch {
// silently fall back to local JSON
}
}
---SEO & AIEO — @biab-dev/sdk/seo
Typed JSON-LD builders for every common schema.org type. One <script type="application/ld+json"> per node — Google indexes them independently.
import {
biabSchemas,
renderJsonLdNodes,
renderJsonLdToHtml,
} from "@biab-dev/sdk/seo";
const nodes = [
biabSchemas.localBusiness({
siteUrl: "https://example.com",
businessType: "HVACBusiness", // any schema.org subtype
name: "Urban Air NYC",
telephone: "+1-646-535-4001",
priceRange: "$$",
areaServed: [{ name: "Manhattan, NY", type: "AdministrativeArea" }],
aggregateRating: { ratingValue: "5.0", reviewCount: "120" },
}),
biabSchemas.faq([
{ question: "Do you offer 24/7 service?", answer: "Yes." },
]),
biabSchemas.breadcrumb({
siteUrl: "https://example.com",
items: [{ name: "Home", url: "/" }, { name: "Services", url: "/services" }],
}),
];
// One <script> string per node (for manual injection)
renderJsonLdNodes(nodes); // → [{ type, html }, ...]
// Concatenated string (for dangerouslySetInnerHTML or Astro set:html)
renderJsonLdToHtml(nodes); // → "<script type=\"application/ld+json\">...</script>\n..."Available builders: localBusiness · organization · website · service · faq · breadcrumb · article · review · productOffer
React hooks (0.8 additions)
All new hooks are exported from @biab-dev/sdk/react alongside the existing chatbot / auth / marketing-pages hooks.
import {
// Bundle (sections + SEO in one call)
useMarketingPageBundle,
useMarketingSection,
useMarketingPageSeo,
// Site config
useMarketingLocales,
// Schema
usePublishedMarketingSchema,
// Prefetch on hover
useMarketingBundlePreloader,
} from "@biab-dev/sdk/react";useMarketingPageBundle
const bundle = useMarketingPageBundle({
apiKey, baseUrl, siteId,
pageKey: "home",
locale: "en",
enabled: true, // gate on consent, auth, etc.
});
// bundle.status: "idle" | "loading" | "ready" | "error"
// bundle.bundle.sections → Record<key, {ok:true,data,source} | {ok:false,error}>
// bundle.bundle.seo → SiteMarketingSeoData
// bundle.bundle.availableLocales → string[]
// bundle.refresh() → re-fetch bypassing cacheuseMarketingSection<T>
const hero = useMarketingSection<HeroContent>({
apiKey, baseUrl, siteId,
pageKey: "home",
sectionKey: "hero",
parse: (entry) => marketing.parseSection("hero", entry),
});
// hero.data: HeroContent | nulluseMarketingBundlePreloader
Wire to <Link onPointerEnter> so the next page is warm before the visitor clicks:
const preload = useMarketingBundlePreloader({ apiKey, baseUrl, siteId });
<Link onPointerEnter={() => preload({ pageKey: "about" })} href="/about">
About
</Link>Previous surfaces (0.7.x and earlier)
Marketing pages (legacy — still works)
const site = client.site(siteId);
const { page } = await site.marketingPages.get("home");
// page.payload → the full JSON document from Site Builder → Static DataThe 0.7 marketingPages.* API is fully supported. The 0.8 server-side legacy adapter re-aggregates per-section rows back into the payload shape when no legacy row exists — existing consumers render unchanged.
Collections
await site.collections.list();
await site.rows.query("page-content", {
filters: [{ fieldName: "pageKey", operator: "equals", value: "home" }],
limit: 10,
});Quick-start example
import { createBiabDevClient } from "@biab-dev/sdk";
const client = createBiabDevClient({
baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!, // https://…/api/package/v1
apiKey: process.env.BIAB_API_KEY!,
});
// Identity probe — always the first call in a fresh integration
const auth = await client.introspect();
const site = client.site(process.env.BIAB_SITE_ID!);
// ── 0.8 schema-driven bundle ──────────────────────────────────────────
const bundle = await site.marketing.getPageBundle({ pageKey: "home", locale: "en" });
// ── 0.7 legacy marketing page (still works) ───────────────────────────
const { page } = await site.marketingPages.get("home");
// ── Collections / data ────────────────────────────────────────────────
await site.collections.list();
await site.rows.query("page-content", {
filters: [{ fieldName: "pageKey", operator: "equals", value: "home" }],
limit: 10,
});Tenant auth (sign-in / sign-up / sign-out)
The SDK includes per-tenant authentication built on WorkOS Organizations. Visitors who click "Sign in" are signed in to the org bound to your API key — not to BIAB itself.
Install the auth handler
createAuthHandler returns Fetch-standard handlers. Mount in any framework:
Next.js (App Router)
// app/api/biab-auth/[...biab]/route.ts
import { createAuthHandler } from "@biab-dev/sdk";
const handler = createAuthHandler({
baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
apiKey: process.env.BIAB_API_KEY!,
callbackUrl: `${process.env.NEXT_PUBLIC_SITE_URL}/api/biab-auth/callback`,
});
export const GET = handler.GET;
export const POST = handler.POST;Astro
// src/pages/api/biab-auth/[...biab].ts
import { createAuthHandler } from "@biab-dev/sdk";
import type { APIRoute } from "astro";
const handler = createAuthHandler({
baseUrl: import.meta.env.BIAB_PACKAGE_API_BASE_URL,
apiKey: import.meta.env.BIAB_API_KEY,
callbackUrl: `${import.meta.env.PUBLIC_SITE_URL}/api/biab-auth/callback`,
});
export const GET: APIRoute = ({ request }) => handler.GET(request);
export const POST: APIRoute = ({ request }) => handler.POST(request);Astro requires
output: "server"(or"hybrid") for the auth handler to work.
Remix / SvelteKit / SolidStart / Hono / Cloudflare Workers
Pass the Fetch-standard Request directly — handler.GET(request) and handler.POST(request). For Node frameworks (Express, Fastify), bridge with @whatwg-node/server.
React components
import { SignIn, SignOut, useUser } from "@biab-dev/sdk/react";
export default function Nav() {
const me = useUser();
if (me.status === "loading") return null;
if (me.status === "signed-in") return <SignOut>Sign out</SignOut>;
return <SignIn>Sign in</SignIn>;
}SSR session
import { getTenantSession, DEFAULT_AUTH_COOKIE_NAME } from "@biab-dev/sdk";
import { cookies } from "next/headers";
const cookieValue = (await cookies()).get(DEFAULT_AUTH_COOKIE_NAME)?.value;
const session = await getTenantSession({
cookieValue,
baseUrl: process.env.BIAB_PACKAGE_API_BASE_URL!,
apiKey: process.env.BIAB_API_KEY!,
});Configuration
| Env var | Where it comes from |
| --- | --- |
| BIAB_API_KEY | One-time reveal in Site Builder → Developer → Package API keys |
| BIAB_SITE_ID | UUID shown above the key list in the same panel |
| BIAB_PACKAGE_API_BASE_URL | "Package API base URL" — must be https:// in production; the SDK auto-normalises any path to /api/package/v1 |
Two production traps to avoid:
http://against a production host —fetchstripsAuthorizationon cross-scheme redirects.- Base URL missing
/api/package/v1— SDK versions ≤ 0.2.0 silently drop the pathname; 0.2.1+ auto-corrects it.
Publishing to npm
Build and dry-run
cd biab-dev
pnpm build
npm pack --dry-runConfirm only dist/, README.md, LICENSE, and package.json are packed.
Publish
npm publish --access publicprepublishOnly runs pnpm build automatically.
Troubleshooting
| Error | Meaning |
| --- | --- |
| 404 Scope not found | The @biab-dev org doesn't exist on npm, or your account isn't a member. |
| 403 Forbidden | Your npm user doesn't have publish rights for this scope. |
| Web login during publish | Normal with 2FA — complete the browser prompt, then retry. |
