- Client SDK — typed RPC client with per-entity CRUD methods and auth operations
- Entity types — TypeScript types derived from your entity schemas
access.d.ts— TypeScript declarations that makectx.can('typo')a compile errorrls-policies.sql(opt-in) — PostgreSQL Row-Level Security policies fromrules.where()conditions
Client SDK generation
The primary codegen output is a typed client SDK that gives you end-to-end type safety from entity schemas to query results.Configuration
Add avertz.config.ts at your project root:
Running codegen
There are three ways to run the code generator: 1. Viavertz dev (recommended) — codegen runs automatically on startup and re-runs when schema files change:
vertz codegen — run codegen manually:
If your app uses a custom dev server (calling
createBunDevServer directly instead of vertz dev), you must run codegen explicitly before starting the server. The vertz dev unified command
handles this automatically.Generated output
Codegen writes files to.vertz/generated/:
Using the generated client
Import the generated client via the#generated import alias (configured in package.json):
#generated alias maps to .vertz/generated/client.ts. Scaffolded apps include this in package.json:
Including generated types in tsconfig
Make sure.vertz/generated is included in your TypeScript config:
Type-safe entitlements
The problem
Without codegen, entitlement strings are untyped:How it works
The compiler statically extracts all entitlement keys from yourdefineAccess() call. The code generator emits a access.d.ts file that augments the EntitlementRegistry interface in both @vertz/server and @vertz/ui/auth:
Entitlement type narrows from string to the union of registered keys. This gives you compile-time errors on typos and full autocomplete in your editor.
Setup
Run codegen
The access type generator runs as part of the standard Vertz codegen pipeline:This produces
.vertz/generated/access.d.ts.What you get
After codegen, entitlement strings are fully typed across both server and client:Backward compatibility
When no codegen output exists (e.g., before runningbunx vertz codegen for the first time), the Entitlement type falls back to string. This means:
- Existing code works without codegen — no breakage
- Type narrowing activates only when generated types are present
- You can adopt codegen incrementally
Generated types
In addition to theEntitlementRegistry, the generator also produces:
.vertz/generated/access.d.ts for use in your application code.
RLS policy generation (opt-in)
If your entitlements userules.where() conditions, the code generator can produce PostgreSQL Row-Level Security (RLS) policy SQL from them. This is opt-in — you must enable it in your codegen config.
RLS generation produces a SQL file as a starting point. Vertz does not automatically apply or
enforce these policies — access control is enforced at the application layer via
enforceAccess(). The generated SQL is for teams that want to add database-level row filtering as
an additional security layer on PostgreSQL.Enabling RLS generation
Addtypescript.rls: true to your codegen config:
How it works
The compiler statically analyzesrules.where() calls inside your defineAccess() entitlements and extracts the conditions. The code generator translates these into CREATE POLICY statements:
bunx vertz codegen produces:
Supported condition types
The static analyzer extracts conditions fromrules.where() calls. Each condition maps to a SQL expression:
| Condition | Example | Generated SQL |
|---|---|---|
| User ID marker | r.user.id | column = current_setting('app.user_id')::UUID |
| Tenant ID marker | r.user.tenantId | column = current_setting('app.tenant_id')::UUID |
| String literal | 'active' | column = 'active' |
| Boolean literal | false | column = false |
| Numeric literal | 1 | column = 1 |
where() call are combined with AND.
Naming conventions
The generator applies these transformations:- Table names: Entity name converted to snake_case and pluralized (e.g.,
task→tasks,projectMember→project_members) - Column names: Property names converted from camelCase to snake_case (e.g.,
createdBy→created_by,tenantId→tenant_id) - Policy names: Derived from entitlement name (e.g.,
task:edit→task_edit)
Applying the generated policies
Recommended: migration integration. Pass the codegen RLS output tomigrateDev() and let the migration system handle it:
Setting session variables per request
RLS policies referenceapp.user_id and app.tenant_id via current_setting(). These must be set per-request within a transaction using SET LOCAL:
SET LOCAL scopes the variables to the current transaction — they’re automatically cleared on commit or rollback. The framework’s entity CRUD pipeline handles this automatically when RLS policies are present.
The generated policies use
FOR ALL, applying to SELECT, INSERT, UPDATE, and DELETE. If you need
operation-specific policies (e.g., FOR SELECT only), customize the generated SQL before
applying.Non-translatable conditions
If arules.where() condition can’t be statically analyzed (e.g., it references a variable or calls a function), the compiler emits a warning and skips that condition:
Next steps
Authentication & Access Control
Full RBAC setup with defineAccess(), plans, and billing.
Client-Side Access Control
Use typed can() and AccessGate in your UI components.
Database Migrations
Manage schema changes and apply RLS policies.
Entities
Define entities with access rules.