Skip to content
Docs

Command palette

Jump to a page or view, or find a todo by name.

Changelog

Changelog

New features, architectural changes and improvements to the template.

MarkdownRSS

Release notes are curated from repository history. Dates reflect integration into the main branch; grouped entries are not semantic-version tags. Each entry links to its implementation and current developer guide.

Auth and tenant security fixes

Isolation gaps found before row-level security are closed, client IPs are trusted only where the deployment vouches for them, and failed sign-ins are counted.

Client IP addresses, used by the Better Auth rate limiter, audit records and MCP JWKS limits, now come from one rule. On Workers the template reads cf-connecting-ip, set by Cloudflare’s edge. On a Node deployment behind a proxy, set TRUSTED_PROXY_HEADER (for example x-forwarded-for) and TRUSTED_PROXIES (hops from the right, default 1). Without them the socket address is used and client headers are ignored, so forged headers no longer pick a fresh rate-limit bucket.

Failed sign-ins and failed second factors are now counted: the rate limiter commits on its own connection, so the counter survives the rolled-back auth request. On Node the limiter has a small pool of its own, so concurrent sign-ins no longer hang waiting for a connection. A User removed from their personal Organization is no longer made its owner again on the next sign-in. A removed MCP signing key stops validating within 60 seconds. The per-request session memo is keyed by the presented cookie or bearer token, so an inner request with other credentials reads its own session.

Uploads recheck the uploader’s membership before committing a file, document submission checks ownership before locking the queue row, and integrations hide deliveries from Organizations the User has left. A saved billing customer link now wins over provider user hints, and the seed refuses non-local hosts. When another tab switches Organization or account, each open tab resets its query cache and reloads its routes instead of showing the previous identity’s rows.

Sources: isolation gaps, removed owners and failed credentials, trusted client IP, limiter pool, JWKS revocation, session memo, cross-tab sessions.

Database, Worker and web performance

Tenant statements cost one round trip, the Worker runs next to the database, oRPC calls are batched and pages load their data on the server.

On the database side, row-level security costs no extra round trip: tenant settings and the statement are pipelined, row membership is checked once per statement rather than per row, and the identity functions are plpgsql so their plans are cached. The membership check is one system statement and the session is read once per request. A migration adds indexes for real filters and unindexed foreign keys, stores the document search vector as a generated column and drops redundant indexes. Todo list, board and lane renumbering, audit outbox flushes and queue scans use fewer statements or their indexes. An hourly cron job deletes rate-limit rows, sessions and verifications a day past expiry.

In the web app, oRPC calls made in the same tick go as one /__batch__ request, sharing one Context and session read on the server. Queries no longer retry 4xx errors and retry network failures and 5xx at most twice. Settings, admin flags, files and integrations load their data in route loaders, and the dashboard renders its prefetched sections on the server through the new todo.stats aggregate. todo.calendar now returns at most 500 todos, with truncated set when there are more. React Compiler covers more web and mobile components, date-fns and files-sdk stay out of the client entry, thumbnail URLs are cached per Organization and the documents poll backs off.

Fixes from the same round: each board lane is counted once, large libraries show a document status for every file, and file preview URLs are always revoked.

Sources: pipelining, per-statement membership, indexes, auth cleanup job, oRPC batching, dashboard stats, server-rendered dashboard, placement.

Row-level security per Organization

PostgreSQL now enforces tenant isolation on every table. The template requires PostgreSQL 16 or newer, on any host, and verifies RLS on every migration.

Every table in public has row-level security. Procedures, assistant and MCP tools and the files gateway run as the app_tenant role for the session’s User and Active Organization, in one short transaction per statement. app.org_id() re-checks membership on every statement, so a removed Member sees nothing on the next query. Better Auth, cron, billing webhooks and admin keep the connection role through asSystem(db, reason, fn), whose call sites are inventoried and checked by pnpm lint. The application’s organizationId filters stay as defence in depth.

The design uses plain PostgreSQL, so it works the same on Neon, Supabase, RDS, a self-hosted server or a local Docker database. The connection role may be a superuser, an owner with BYPASSRLS or a plain owner; tables never use FORCE ROW LEVEL SECURITY.

What changes for a project built on the template:

  • db:apply-pending now ends with db:verify-rls, which checks the role, the identity functions, every policy and the exact grants. After a pg_dump/pg_restore into another host, grants and policies are lost: run db:apply-pending against the target, or db:verify-rls [ENV_VAR] --repair.
  • Tenant identity is honoured only for roles listed in app.session_roles, seeded with the migration role. If the Worker connects with a different role, add it there, or tenant requests see no rows.
  • Hyperdrive query caching must stay disabled. pnpm setup --hyperdrive-id <id> disables it, pnpm doctor --connect reports it, and the production deploy refuses to publish while it is on.
  • A new table needs pgTable.withRLS, its tenantPolicy(...), its grant in packages/db/scripts/rls-baseline.mjs and a custom migration. A new cross-tenant path needs a SystemReason and node scripts/check-system-call-sites.mjs --write.

Each tenant statement is pipelined: the identity settings and the statement travel in one round trip with a single Sync. Setting DB_TENANT_PIPELINE=0 as a Worker var or secret restores explicit BEGIN/COMMIT without a code change. pnpm --filter @template-cloudflare/db test:rls-matrix (Docker) runs both modes on PostgreSQL 16 and 18 for every connection-role profile, including a real restore, and CI runs it as its own job.

Pull request previews now take the production connection path. When production uses Hyperdrive, each PR gets its own Hyperdrive with caching disabled, pointing at the PR’s database branch. Closing the PR deletes it, and the CI token needs Hyperdrive edit permission. Previews also deploy with their secrets in the same version (wrangler deploy --secrets-file), so a new preview Worker no longer fails env validation on its first upload. Only previews set TENANCY_PROBE=1, which mounts the tenancy.probe oracle; scripts/check-production-config.mjs refuses it in the production config. Before a production deploy that touches tenancy, run node scripts/rls-preview-proof.mjs <previewUrl>, which exits non-zero on any cross-tenant or pooled-connection leak.

Sources: foundation, tenant requests, verify on apply, Hyperdrive caching, pipelining, preview-only probe, preview Hyperdrive and secrets.

Earlier releases

Edit on GitHubDocumentation is in English.