Skip to content

[BUG]: entities config option is silently stripped by the config loader, breaking role management (entities.roles) #5896

@unishigure

Description

@unishigure

Report hasn't been filed before.

  • I have verified that the bug I'm about to report hasn't been filed before.

What version of drizzle-orm are you using?

1.0.0-beta.22

What version of drizzle-kit are you using?

1.0.0-beta.22

Other packages

No response

Describe the Bug

The entities option in drizzle.config.ts (e.g. entities: { roles: true }) is silently dropped while the config file is loaded, so it never reaches the push/pull diff engine.
As a result, entities.roles has no effect from the CLI:

  • Roles that already exist in the database are not introspected, so every role defined in the schema is emitted as CREATE ROLE on every run. The first push succeeds (the role is created), but any subsequent push regenerates the same CREATE ROLE and fails with role "<name>" already exists — i.e. push is not idempotent.
  • exclude / include / provider under entities.roles are equally ignored.

Note

This only affects the CLI (drizzle-kit push/pull).
The programmatic API (pushSchema from drizzle-kit/api) works fine because entities is passed as a function argument and never goes through the config-file loader.

Expected behavior

entities set in drizzle.config.ts should reach the push/pull engine,
so that entities.roles enables role management (and include/exclude/provider work) exactly as documented.

Reproduction

Starting from a database that does not yet contain app_role:

  1. Schema:

    import { pgRole } from "drizzle-orm/pg-core";
    export const appRole = pgRole("app_role");
  2. Config:

    export default defineConfig({
      dialect: "postgresql",
      schema: "./src/db/schema.ts",
      dbCredentials: { url: process.env.DATABASE_URL! },
      entities: { roles: true },
    });
  3. Run drizzle-kit pushsucceeds, the role is created.

  4. Run drizzle-kit push again → fails with role "app_role" already exists.

The first run works, but push is not idempotent: the now-existing role is never introspected, so the second run still generates CREATE ROLE "app_role"; instead of a no-op. (drizzle-kit push --explain re-emits the same CREATE ROLE statement on every run.)

Actual: the second push generates CREATE ROLE "app_role"; again and fails with role "app_role" already exists.
Expected: the second push generates no role statement and reports No changes detected — the existing role is recognized and matched.

Root cause

drizzleConfigFromFile() validates the loaded config with the Zod schema configCommonSchema.
That schema does not declare an entities field:

configCommonSchema = objectType({
  dialect, schema, out, breakpoints, verbose, driver,
  tablesFilter, schemaFilter, migrations, dbCredentials, casing, sql
  // <-- no `entities`
}).passthrough();

.passthrough() is supposed to keep unknown keys, but it relies on enumerable own keys.
loadModule() returns an object whose only enumerable own key is default (the actual config properties are read via non-enumerable interop getters).
So:

  • declared fields (dialect, schema, …) survive because Zod reads them by direct property access, but
  • the unknown entities field is not enumerable, so .passthrough() never copies it → it is dropped.

By the time pushParams / pullParams (which do declare entities) run, entities is already gone.
So the field can never reach the engine via a config file.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions