Build your application
Database and backend choices
Your account model belongs to your application. A subject can be a row in your existing customer table or an identity managed by another service. Yielded Auth needs ways to look up that identity and durably store credentials, proofs, and sessions; core does not require an ORM or a particular database.
Choose how to provide those services:
| Start with | When it fits |
|---|---|
| Managed tables | You want auth storage supplied alongside your account table. |
| Your schema | You already own SQL tables and migrations. |
| Your services | Your backend needs a custom store or transaction model. |
Replacing storage Layers keeps your contract, AppAuth, and client unchanged. Moving existing data still
requires an application-owned migration.
Managed tablesPersistence. | Your schemaPersistence. | Your servicesLayer. | |
|---|---|---|---|
| Account table | YouYour table and IDs, mapped by column. | YouYour table and IDs, mapped by column. | YouYour records and IDs, in any store. |
| Auth tables and migrations | Yielded AuthDefined for you. Drizzle Kit generates migrations you review. | YouYour table names, columns, and migrations. | YouYour durable store; SQL is optional. |
| Queries and transactions | Yielded AuthRun for you. | Yielded AuthRun against your tables, checked at startup. | YouYour implementation of each storage service. |
| Sign-in workflows | Yielded AuthRun for you. | Yielded AuthRun for you. | Yielded AuthRun for you, on your storage. |
| Contract and client | UnchangedThe same AuthApi, AppAuth, and client at every level. Only Layers change. | ||
| Runnable app | Managed Drizzle | Custom Drizzle, Effect SQL | Custom services |
Managed tables
Persistence.managed(…)- Account table
- You
- Your table and IDs, mapped by column.
- Auth tables and migrations
- Yielded Auth
- Defined for you. Drizzle Kit generates migrations you review.
- Queries and transactions
- Yielded Auth
- Run for you.
- Sign-in workflows
- Yielded Auth
- Run for you.
Your schema
Persistence.map(…)- Account table
- You
- Your table and IDs, mapped by column.
- Auth tables and migrations
- You
- Your table names, columns, and migrations.
- Queries and transactions
- Yielded Auth
- Run against your tables, checked at startup.
- Sign-in workflows
- Yielded Auth
- Run for you.
Your services
Layer.mergeAll(…)- Account table
- You
- Your records and IDs, in any store.
- Auth tables and migrations
- You
- Your durable store; SQL is optional.
- Queries and transactions
- You
- Your implementation of each storage service.
- Sign-in workflows
- Yielded Auth
- Run for you, on your storage.
Contract and client
UnchangedThe same
AuthApi,AppAuth, and client at every level. Only Layers change.
Managed tables
Section titled “Managed tables”Map your account table and let Yielded Auth define the rest:
import { Schema as AuthSchema } from "@yielded/auth";import { AuthPersistence } from "@yielded/auth-persistence-drizzle/SqliteBun";import { Effect } from "effect";
import { AppAuth, requirement } from "./auth";import { customers } from "./customers";
export const Persistence = AuthPersistence.make(AppAuth);
export const storage = Persistence.managed({ subjects: { table: customers, id: "id", status: "enabled", activeValue: true, securityRevision: "securityRevision", idCodec: AuthSchema.SubjectId, requirements: () => Effect.succeed(requirement), }, prefix: "customer_auth",});
export const authSchema = storage.schema;Your table
customerscustomer_keysubject IDenabledactive flagauth_revisionsecurity revisiondisplay_name- Your other columns
Managed by Yielded Auth
Each references customers.customer_key as subject_id.
customer_auth_identifierscustomer_auth_passwordscustomer_auth_sessionscustomer_auth_passkeyCredentials- 24 more, depending on the methods you install
Only the methods you install add tables. Export them from authSchema and
Drizzle Kit generates their migrations; review, commit, and apply them like any
other migration. In the example, a migration Layer applies committed files at
startup; it never pushes schema changes. You decide when migrations run.
To give a single table your own layout, pass it in tables. The rest stay managed.
Your schema
Section titled “Your schema”Declare every table yourself, with your own names and columns:
export const identifiers = sqliteTable( "app_identifiers", { namespace: text("c_namespace").notNull(), value: text("c_value").notNull(), subjectId: text("c_subject_id").notNull(), revision: text("c_revision").notNull(), verifiedAt: integer("c_verified_at"), active: integer("c_active", { mode: "boolean" }).notNull(), }, (table) => [uniqueIndex("app_identifiers_key_0").on(table.namespace, table.value)],);Then map them with Persistence.map. TypeScript requires a table for every
storage role your installed methods use:
export const storage = Persistence.map({ subjects: { table: customers, id: "id", status: "enabled" /* … */ }, tables: { identifiers: tables.identifiers, credentials: tables.credentials, passwords: tables.passwords, sessions: tables.sessions, // …one entry per role },});The library still runs every query and transaction. Before serving auth, the persistence Layer checks each mapped column and unique key against the database.
The same Persistence.map works without Drizzle. Import AuthPersistence from
@yielded/auth-persistence, describe the tables for Effect SQL, and write the DDL
yourself. The Effect SQL app
runs on SQLite and PostgreSQL.
Your services
Section titled “Your services”Each storage role is a public service, such as Password.PasswordPersistence.
Implement them in your own Layers. The rest of auth calls those services through
Effect, without knowing whether they use SQL:
export const PersistenceLive = Layer.mergeAll( PasswordsLive, ProofsLive, EmailLive, PasskeysLive, SessionsLive,);The Layers above are application implementations, not library exports. The custom services app implements these boundaries with a single-writer file store and no persistence package. It runs the account workflows with the same shared UI; its extra username sign-in uses an extended contract.
What a custom backend must guarantee
Section titled “What a custom backend must guarantee”Storage services describe workflow commits, not just CRUD. Your implementation must recheck the relevant account and credential state, consume proofs, and commit changes with their retry receipts atomically where the operation requires it. A receipt records the original decision so a retry can recover it safely.
Choose a transaction or batch owner that can uphold those contracts. Splitting account and credential writes across independent backends does not make them atomic. An uncertain commit requires reconciliation, not another credential issuance. The adapter reference covers the supported transaction boundaries, including D1 and Durable Objects.
The file-store example supports one process on a local filesystem. It illustrates the service boundary; replication and production storage operations remain yours.
Run each level
Section titled “Run each level”The four example apps implement the
same account features with managed Drizzle, custom Drizzle, Effect SQL, and
custom services. Compare their schema.ts and live.ts files to see what changes.
The driver reference lists the maintained
SQL adapters; workflow-specific support is described beside each adapter.