Skip to content
Ownfold
Esc
navigateopen⌘Jpreview
On this page

TanStack Start integration

Run Ownfold through a real React, TanStack Start, Better Auth session resolver, Drizzle, and PostgreSQL-compatible full-stack boundary.

The TanStack Start adapter does not select a database or authentication provider. Choose both in the framework recipe builder; browser and React packages remain optional.

The production tracer under examples/tanstack-start exercises Ownfold’s browser, React, fetch, TanStack Start, Better Auth, server, and Drizzle packages together. Vault metadata and application records cross the same boundaries they use in a production deployment.

Boundary map

application-owned React controls


@ownfold/browser ── encrypts and decrypts locally


@ownfold/fetch ── sends only vault metadata and encrypted envelopes


TanStack Start server route

      ├── Better Auth resolves the authenticated user
      └── Drizzle stores vault metadata in PostgreSQL

Application journal route ── stores validated encrypted records separately

The Ownfold coordination transport never receives journal plaintext or application-record storage methods. The journal route is deliberately application-owned.

Vault server

Create the server with the application database and Better Auth instance:

import { betterAuthUserResolver } from "@ownfold/better-auth"
import { drizzleVaultAdapter } from "@ownfold/drizzle"
import { createVaultServer } from "@ownfold/server"

export const vaultServer = createVaultServer({
  adapter: drizzleVaultAdapter(db),
  getUserId: betterAuthUserResolver(auth),
})

drizzleVaultAdapter accepts normal schema-registered PostgreSQL Drizzle instances. The adapter uses only ownfold_vaults; host-application tables remain independent.

Server route

TanStack Start server routes receive a Web Request and return a Web Response, so the adapter can delegate directly to Ownfold’s canonical fetch handler:

import { createTanStackStartVaultHandlers } from "@ownfold/tanstack-start"
import { createFileRoute } from "@tanstack/react-router"

const handlers = createTanStackStartVaultHandlers({ server: vaultServer })

export const Route = createFileRoute("/api/ownfold/$")({
  server: {
    handlers,
  },
})

Mount Better Auth separately at /api/auth/$. Configure Better Auth’s tanstackStartCookies() plugin last so its session cookies are applied through TanStack Start. Ownfold never receives login passwords or treats an encryption recovery secret as an account secret.

Browser client

The browser uses the framework-independent fetch transport:

import { createVaultClient } from "@ownfold/browser"
import { createFetchVaultTransport } from "@ownfold/fetch"

export const vault = createVaultClient({
  transport: createFetchVaultTransport(),
})

Wrap the application with <VaultProvider client={vault}> and compose lifecycle controls from the headless hooks. The application supplies all markup and styles.

Encrypted application records

The example’s journal route validates EncryptedEnvelopeV1 before persistence. It also checks the authenticated namespace, record ID, and owner ID against the request route and session. Invalid, unauthenticated, or context-mismatched writes are rejected before reaching Drizzle.

The Playwright test inspects the actual journal HTTP request body and fails if either plaintext field appears. It then loads ciphertext from Drizzle and decrypts it locally.

Run the tracer

pnpm --filter @ownfold/example-tanstack-start dev

The example uses in-memory PostgreSQL-compatible PGlite so it runs without external infrastructure. Its fixed identity header is explicitly demo-only. Replace demoAuth with a real Better Auth instance and use secure session cookies before adapting the example for production.

Last updated on August 4, 2026

Was this page helpful?