drizzle-team/drizzle-orm · error · Error
crudPolicy requires a read policy
Error message
crudPolicy requires a read policy
What it means
An Error 'crudPolicy requires a read policy' thrown by crudPolicy() in drizzle-orm/src/neon/rls.ts:21 when options.read === undefined. crudPolicy generates the four RLS policies (select/insert/update/delete) for a role; it requires an explicit read decision (true, false, null, or a SQL expression) because omitting it is almost certainly a mistake. Note null is valid (suppresses the select policy) — only undefined throws.
Source
Thrown at drizzle-orm/src/neon/rls.ts:21
import { PgRole, pgRole } from '~/pg-core/roles.ts';
import { type SQL, sql } from '~/sql/sql.ts';
/**
* Generates a set of PostgreSQL row-level security (RLS) policies for CRUD operations based on the provided options.
*
* @param options - An object containing the policy configuration.
* @param options.role - The PostgreSQL role(s) to apply the policy to. Can be a single `PgRole` instance or an array of `PgRole` instances or role names.
* @param options.read - The SQL expression or boolean value that defines the read policy. Set to `true` to allow all reads, `false` to deny all reads, or provide a custom SQL expression. Set to `null` to prevent the policy from being generated.
* @param options.modify - The SQL expression or boolean value that defines the modify (insert, update, delete) policies. Set to `true` to allow all modifications, `false` to deny all modifications, or provide a custom SQL expression. Set to `null` to prevent policies from being generated.
* @returns An array of PostgreSQL policy definitions, one for each CRUD operation.
*/
export const crudPolicy = (options: {
role: PgPolicyToOption;
read: SQL | boolean | null;
modify: SQL | boolean | null;
}) => {
if (options.read === undefined) {
throw new Error('crudPolicy requires a read policy');
}
if (options.modify === undefined) {
throw new Error('crudPolicy requires a modify policy');
}
let read: SQL | undefined;
if (options.read === true) {
read = sql`true`;
} else if (options.read === false) {
read = sql`false`;
} else if (options.read !== null) {
read = options.read;
}
let modify: SQL | undefined;
if (options.modify === true) {
modify = sql`true`;View on GitHub (pinned to b7862528fd)
Solutions
- Provide an explicit read value: true (allow all), false (deny all), null (no select policy), or a SQL expression.
- If constructing options dynamically, default read explicitly instead of leaving it unset.
- Tighten the config object's type so TS flags the missing field at compile time.
Example fix
// before
crudPolicy({ role: authenticatedRole, modify: sql`(select auth.user_id() = ${posts.userId})` });
// throws: crudPolicy requires a read policy
// after
crudPolicy({
role: authenticatedRole,
read: sql`(select auth.user_id() = ${posts.userId})`,
modify: sql`(select auth.user_id() = ${posts.userId})`,
}); Defensive patterns
Strategy: validation
Validate before calling
import type { crudPolicy } from 'drizzle-orm/neon';
type CrudOpts = Parameters<typeof crudPolicy>[0];
function assertCrudPolicyOptions(o: CrudOpts): void {
if (o.read === undefined) {
throw new Error('crudPolicy: set `read` (true | false | null | SQL). null suppresses the select policy.');
}
}
assertCrudPolicyOptions(opts); Type guard
function hasReadPolicy(o: { read?: unknown }): boolean {
return o.read !== undefined;
} Prevention
- Always pass read explicitly — use null when you want no select policy.
- Build crudPolicy options from a fully-typed helper, not from partial/any objects.
- Enable strict TS so undefined is not silently allowed.
When it happens
Trigger: Calling crudPolicy({ role, modify: ... }) and forgetting the read field. TypeScript's declared type (read: SQL | boolean | null) does not include undefined, but undefined slips through when the object is built dynamically or cast through any.
Common situations: Building policy config from partial config objects, spreading defaults, or passing an any-typed payload where the read key is absent. The runtime guard catches what the type system should have prevented.
Related errors
- crudPolicy requires a modify policy
- No transactions support in neon-http driver
- You have an empty array for "${name}" enum values
- Cannot pass undefined values to any set operator
- values() must be called with at least one value
AI-assisted analysis of drizzle-team/drizzle-orm@b7862528fd (2026-08-03).
Data as JSON: /data/errors/040118a4e6896f0a.json.
Report an issue: GitHub.