Holaboss:Docs

Bring your own portal

Build and host your own customer portal on Holaboss using the publishable key and the @holaboss/client SDK.

Guides

Your customer-facing portal doesn't have to be the iMerch template. The publishable key and the @holaboss/client SDK are the portal surface — the template is just one app that uses them. Point your own app at the same two things and it works.

The short answer — yes. A portal you build and host yourself works, as long as it (1) signs users in with your org's publishable key and (2) talks to Holaboss through the SDK with the resulting session token. Those two pieces are decoupled from the template on purpose. What you give up by self-hosting is the console's deploy / analytics / domain conveniences — not the runtime.

The model

There are two layers, and it helps to keep them apart:

  • The runtime surface — the publishable key + @holaboss/client. This is what a customer's browser uses to sign in, read the org's data, upload files, and chat. It's a public, bring-your-own surface: run it on any stack.
  • The hosting pipeline — the console's create → preview → publish → analytics → custom-domain flow. That part is specific to the iMerch template: it clones the starter repo, bakes your brand, and deploys a Cloudflare Worker for you. A self-hosted app lives outside it.

The publishable key is safe to ship in the browser. It only says which org's user pool a request belongs to — every authorization decision comes from the session token, never the key.

1 · Sign in

Sign-in is a passwordless email code, scoped to your org by the publishable key. Two calls: request a code, then redeem it for a session.

auth.ts
// 1 — Ask for a code. The publishable key scopes it to your org's user pool.
await requestCode(PUBLISHABLE_KEY, email);
//   → POST /auth/otp   { publishable_key, email }
//   Always "succeeds" — it never reveals whether the address is a known user.

// 2 — Redeem the code for a session.
const session = await redeemCode(PUBLISHABLE_KEY, email, code);
//   → POST /auth/verify  { publishable_key, email, code }
//   → { access_token, refresh_token, expires_in, end_user: { id, email } }

Store the session (the reference template keeps it in localStorage — fine, since auth is a bearer token, not a cookie). Access tokens are short-lived, so refresh ahead of expiry and rotate the refresh token on each use.

2 · Read & write data

After sign-in the key steps aside — everything runs on the session bearer. Hand the SDK a token getter that returns a fresh access token, and it does the rest.

hola-client.ts
import { createApiTransport, createPortalData } from "@holaboss/client";

const config = {
  baseUrl: "",                              // same-origin — your proxy answers /api/*
  token: async () => (await currentAccessToken()) ?? "",
};

export const chat = createApiTransport(config);   // the customer's one thread
export const data = createPortalData(config);     // tables + files

data.listRows("orders"), data.fileLink(id), chat.getInfo() — the same surface the template uses. Build whatever UI you like on top.

Reaching the gateway

The SDK builds paths like /api/v1/end-user/… and expects them to reach the Holaboss gateway's end-user surface (/gateway/endUser/*). You have two ways to connect it.

Option A — same-origin proxy (what the template does)

A tiny edge worker forwards /api/* to the gateway. No CORS, no cross-site cookie questions, and the day you move to a custom domain, only DNS changes.

worker.ts — the whole job
if (url.pathname.startsWith("/api/")) {
  const upstream = `${env.HOLABOSS_GATEWAY}/gateway/endUser/${rest}`;
  // forward ONLY: authorization, content-type, accept.
  // never pass through cookies or a client-set identity header.
  return fetch(upstream, { method, headers, body });
}
return env.ASSETS.fetch(request); // otherwise serve your app

Option B — call the gateway cross-origin

Set baseUrl to the gateway directly. It works because auth is an Authorization header, not a cookie — but the end-user gateway has to allow your origin (CORS). The proxy avoids that entirely, so prefer Option A unless you have a reason not to.

Header hygiene: forward an allowlist (authorization, content-type, accept) — never copy arbitrary client headers through, or a caller could smuggle an identity header to the gateway.

What your app can see

Through the SDK your app reaches the org's portal-visible tables — orders, services, files, client tasks, and each running service's own data — the same slices the template shows. Access is gated by the table permission model (a table is readable/writable to customers only if it's marked so), and every request is still authorized by the session token. You can't reach anything a customer isn't allowed to.

Hosted vs. bring-your-own

The runtime is fully portable; the console's management features are wired to the template's Worker. Here's the split:

CapabilityiMerch template (hosted)Your app (self-hosted)
Email-code sign-in (key)✓ yes✓ yes
Tables · files · chat (SDK)✓ yes✓ yes
Console create / preview / deploy✓ yes— you deploy it
Analytics in the console✓ yes— not your worker
Custom domain via console✓ yes— you manage DNS
Where it runsour Cloudflare Workeryour infrastructure

Two things to set right.

Analytics won't cover a self-hosted app — the console reads Cloudflare metrics off our portal-<slug> worker, so a self-hosted app registers as an external provider ("BYO — we don't deploy it"). Bring your own analytics.

Point the portal URL at your app — set the org's portal URL to your own address, so OTP and invitation emails link customers to your app rather than the hosted template.

Ship checklist

  1. Grab the publishable key — Console → your App → Access & sign-in. It's public, safe to bake into the browser bundle.
  2. Wire email-code sign-inrequestCoderedeemCode with the key. Store the session; refresh the access token ahead of expiry.
  3. Point the SDK at the gateway — proxy /api/* same-origin (recommended), or set baseUrl to the gateway with CORS.
  4. Read & write the org's datacreatePortalData for tables and files, createApiTransport for chat — within the table permissions.
  5. Set the org's portal URL — so customer emails (OTP, invitations) link to your app, not the hosted template.

That's the whole contract. The key and the SDK give you a working portal on your own stack; the only thing reserved for the template is the console's hands-off deploy, analytics, and domain management.

On this page