Skip to Content
GuidesFrontend Rendering

Frontend Rendering

You can render documents directly from the browser — no proxy endpoint on your backend — using your public key (pk_live_…). The public key is safe to ship to clients because every request must clear three checks:

  1. Domain allowlist — the request’s Origin hostname must be in your organization’s Allowed domains (exact match or a subdomain of an allowed entry).
  2. Signed user identity — the request must carry x-user-id and x-user-hash, where the hash is an HMAC-SHA256 of the user ID computed with your signing secret. Only your backend knows the signing secret, so only your backend can mint valid hashes.
  3. Scoping — public-key requests act as that end user, not as your whole organization: they see only that user’s templates, documents, and usage.

The handshake

Step 1 — Allow your domain

In the dashboard, Settings → Allowed domains: add your site’s hostname (e.g. example.com — this also allows app.example.com and other subdomains). Requests from anywhere else are rejected with 401 Domain <hostname> is not in allowedDomains.

Step 2 — Sign the user ID on your backend

Use signUserId from the server entry — it’s a one-liner over Node’s crypto:

app/api/session/route.ts
import { signUserId } from '@reforgio/sdk-core/server' // however you resolve the current user… const userId = 'user-123' const userHash = signUserId(userId, process.env.REFORGIO_SIGNING_SECRET!) // return { userId, userHash } to the client with the session response

Sign on the server, ship the hash. Never send the signing secret itself to the browser — anyone holding it can impersonate any of your users.

Step 3 — Render from the browser

With the plain TypeScript client:

import { Reforgio, ReforgioError } from '@reforgio/sdk-core' const client = new Reforgio('pk_live_…', { userId: 'user-123', userHash: '<hash from your backend>', }) try { const doc = await client.render('YOUR_TEMPLATE_ID', { month: 'June', total: 8200 }) window.open(doc.downloadUrl) } catch (err) { if (err instanceof ReforgioError) { console.error(`Render failed (${err.statusCode}): ${err.message}`) } }

The browser client also exposes getTemplates() and getTemplate(id) — scoped to the authenticated user.

Step 3, React edition

@reforgio/sdk-react wraps the client in a provider and hooks:

app.tsx
import { ReforgioProvider, useRender } from '@reforgio/sdk-react' export function App({ userId, userHash, children }: { userId: string userHash: string // from your backend — see step 2 children: React.ReactNode }) { return ( <ReforgioProvider publicKey={process.env.NEXT_PUBLIC_REFORGIO_PUBLIC_KEY!} userId={userId} userHash={userHash} > {children} </ReforgioProvider> ) } export function InvoicePreview({ items }: { items: Array<{ name: string; quantity: number; price: number }> }) { const { result, loading, error, refetch } = useRender('YOUR_TEMPLATE_ID', { company_name: 'ACME Corp', items, }) if (loading) return <p>Generating PDF…</p> if (error) return ( <div> <p>Failed to generate: {error.message}</p> <button onClick={refetch}>Retry</button> </div> ) if (!result) return null return ( <div> <iframe src={result.downloadUrl} width="100%" height="800px" /> <a href={result.downloadUrl} download>Download PDF</a> </div> ) }

useRender(templateId, data, format?) renders on mount and re-renders when its inputs change (data is compared by value, so inline object literals are fine). It returns { result, loading, error, refetch }. The package also ships useTemplates(), useTemplate(id), and useReforgio() for direct client access.

What the requests look like

Under the hood every public-key request sends:

HeaderValue
x-api-keypk_live_…
x-user-idyour user’s ID
x-user-hashHMAC-SHA256(userId, signingSecret) as hex
Originset automatically by the browser

Common 401s

MessageCause
Public key requires Origin headerRequest didn’t come from a browser context (e.g. server-side fetch with a public key — use the secret key there)
Domain <hostname> is not in allowedDomainsOrigin hostname missing from Settings → Allowed domains
Public key requires x-user-id and x-user-hash headersClient constructed without userId/userHash
Invalid x-user-hashHash computed with the wrong secret, for a different user ID, or the keys were rotated

Full catalog: Errors.