[GH-ISSUE #3472] Undefined organization role statements #9617

Closed
opened 2026-04-13 05:10:43 -05:00 by GiteaMirror · 2 comments
Owner

Originally created by @MoneyDropLobby on GitHub (Jul 19, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3472

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

This issue is for the organization plugin using drizzle:

  1. Create permission.ts
  2. Define`
    const statement = {
    ...defaultStatements,
    erfassung: ['create', 'read', 'update', 'delete'],
    disposition: ['create', 'read', 'update', 'delete'],
    } as const;

export const ac = createAccessControl(statement);

export const owner = ac.newRole({
erfassung: ['create', 'read', 'update', 'delete'],
disposition: ['create', 'read', 'update', 'delete'],

ownerAc.statements,
});

export const admin = ac.newRole({
erfassung: ['create', 'read', 'update', 'delete'],
disposition: ['create', 'read', 'update', 'delete'],

adminAc.statements,
});3. Execute the better auth cli to generate the db schema 4. Getting this error messagenpx @better-auth/cli generate --config ./auth
2025-07-19T17:05:29.619Z ERROR [Better Auth]: Couldn't read your auth config. TypeError: Cannot read properties of undefined (reading 'statements')
at C:/Users/Desktop/Shadcn//auth/organization-permissions.ts:28:28
at async import (C:\Users*\AppData\Local\npm-cache_npx\167ca1f116d365e6\node_modules\jiti\dist\jiti.cjs:1:199772)
at async C:/Users//Desktop/Shadcn//auth/auth.ts:6:32
at async Function.import (C:\Users*\AppData\Local\npm-cache_npx\167ca1f116d365e6\node_modules\jiti\dist\jiti.cjs:1:199772)
at async resolveConfig (file:///C:/Users//AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/c12/dist/shared/c12.PQMoYrit.mjs:346:18)
at async loadConfig (file:///C:/Users/
/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/c12/dist/shared/c12.PQMoYrit.mjs:146:23)
at async getConfig (file:///C:/Users//AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/@better-auth/cli/dist/index.mjs:218:26)
at async Command.generateAction (file:///C:/Users/
/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/@better-auth/cli/dist/index.mjs:750:18)`

My drizzle instance file:
`import { neon } from '@neondatabase/serverless';
import { drizzle } from 'drizzle-orm/neon-http';
import * as schema from './schema/auth';

const sql = neon(process.env.DATABASE_URL || '');
export const db = drizzle({
client: sql,
schema,
logger: {
logQuery: (query, params) => {
console.log('🔍 [DRIZZLE QUERY]:', query);
if (params && params.length > 0) {
console.log('📋 [QUERY PARAMS]:', params);
}
},
},
});
`

Current vs. Expected behavior

If I define the role statements by myself like this:
organization: ['update', 'delete'], member: ['create', 'update', 'delete'], invitation: ['create', 'cancel'], team: ['create', 'update', 'delete'],

It works and it can create the db schema.

What version of Better Auth are you using?

1.2.12

Provide environment information

BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_API_URL=http://localhost:3000

Which area(s) are affected? (Select all that apply)

Backend

Auth config (if applicable)

import { betterAuth } from 'better-auth';
import { drizzleAdapter } from 'better-auth/adapters/drizzle';
import { nextCookies } from 'better-auth/next-js';
import { organization } from 'better-auth/plugins/organization';
import { db } from '../db/index';
import {
  ac,
  admin,
  disposition,
  erfassung,
  owner,
} from './organization-permissions';

export const auth = betterAuth({
  database: drizzleAdapter(db, {
    provider: 'pg',
  }),
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 8,
    maxPasswordLength: 100,
    revokeSessionsOnPasswordReset: true,
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 60 * 5, // 5 minutes
    },
  },
  plugins: [
    organization({
      allowUserToCreateOrganization: true,
      ac,
      roles: {
        owner,
        admin,
        erfassung,
        disposition,
      },
      // async sendInvitationEmail(data) {
      //   const inviteLink = `${process.env.NEXT_PUBLIC_BASE_URL}/organisation/einladung-akzeptieren/${data.id}`;

      //   console.log(inviteLink);
      // },
    }),
    nextCookies(),
  ],
  // databaseHooks: {}
  user: {
    additionalFields: {
      vorname: {
        type: 'string',
        required: true,
      },
      nachname: {
        type: 'string',
        required: true,
      },
    },
  },
});

Additional context

No response

Originally created by @MoneyDropLobby on GitHub (Jul 19, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3472 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce This issue is for the organization plugin using drizzle: 1. Create permission.ts 2. Define` const statement = { ...defaultStatements, erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], } as const; export const ac = createAccessControl(statement); export const owner = ac.newRole({ erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], ownerAc.statements, }); export const admin = ac.newRole({ erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], adminAc.statements, });` 3. Execute the better auth cli to generate the db schema 4. Getting this error message `npx @better-auth/cli generate --config ./auth 2025-07-19T17:05:29.619Z ERROR [Better Auth]: Couldn't read your auth config. TypeError: Cannot read properties of undefined (reading 'statements') at C:/Users/*Desktop/Shadcn/*/auth/organization-permissions.ts:28:28 at async import (C:\Users\*\AppData\Local\npm-cache\_npx\167ca1f116d365e6\node_modules\jiti\dist\jiti.cjs:1:199772) at async C:/Users/*/Desktop/Shadcn/*/auth/auth.ts:6:32 at async Function.import (C:\Users\*\AppData\Local\npm-cache\_npx\167ca1f116d365e6\node_modules\jiti\dist\jiti.cjs:1:199772) at async resolveConfig (file:///C:/Users/*/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/c12/dist/shared/c12.PQMoYrit.mjs:346:18) at async loadConfig (file:///C:/Users/*/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/c12/dist/shared/c12.PQMoYrit.mjs:146:23) at async getConfig (file:///C:/Users/*/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/@better-auth/cli/dist/index.mjs:218:26) at async Command.generateAction (file:///C:/Users/*/AppData/Local/npm-cache/_npx/167ca1f116d365e6/node_modules/@better-auth/cli/dist/index.mjs:750:18)` My drizzle instance file: `import { neon } from '@neondatabase/serverless'; import { drizzle } from 'drizzle-orm/neon-http'; import * as schema from './schema/auth'; const sql = neon(process.env.DATABASE_URL || ''); export const db = drizzle({ client: sql, schema, logger: { logQuery: (query, params) => { console.log('🔍 [DRIZZLE QUERY]:', query); if (params && params.length > 0) { console.log('📋 [QUERY PARAMS]:', params); } }, }, }); ` ### Current vs. Expected behavior If I define the role statements by myself like this: ` organization: ['update', 'delete'], member: ['create', 'update', 'delete'], invitation: ['create', 'cancel'], team: ['create', 'update', 'delete'],` It works and it can create the db schema. ### What version of Better Auth are you using? 1.2.12 ### Provide environment information ```bash BETTER_AUTH_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:3000 ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from 'better-auth'; import { drizzleAdapter } from 'better-auth/adapters/drizzle'; import { nextCookies } from 'better-auth/next-js'; import { organization } from 'better-auth/plugins/organization'; import { db } from '../db/index'; import { ac, admin, disposition, erfassung, owner, } from './organization-permissions'; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: 'pg', }), emailAndPassword: { enabled: true, minPasswordLength: 8, maxPasswordLength: 100, revokeSessionsOnPasswordReset: true, }, session: { cookieCache: { enabled: true, maxAge: 60 * 5, // 5 minutes }, }, plugins: [ organization({ allowUserToCreateOrganization: true, ac, roles: { owner, admin, erfassung, disposition, }, // async sendInvitationEmail(data) { // const inviteLink = `${process.env.NEXT_PUBLIC_BASE_URL}/organisation/einladung-akzeptieren/${data.id}`; // console.log(inviteLink); // }, }), nextCookies(), ], // databaseHooks: {} user: { additionalFields: { vorname: { type: 'string', required: true, }, nachname: { type: 'string', required: true, }, }, }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 05:10:43 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Jul 19, 2025):

Hi @MoneyDropLobby! I'm Dosu and I’m helping the better-auth team.

The error happens because ownerAc.statements and adminAc.statements are undefined—there are no such properties on your role objects, and referencing them causes the TypeError. In Better Auth, when defining roles with ac.newRole, you should directly spread the permissions or statements you want, not reference a .statements property from another role object.

Here's the correct pattern:

const statement = {
  ...defaultStatements,
  erfassung: ['create', 'read', 'update', 'delete'],
  disposition: ['create', 'read', 'update', 'delete'],
} as const;

export const ac = createAccessControl(statement);

export const owner = ac.newRole({
  ...statement,
  // or just the permissions you want to override/add
  erfassung: ['create', 'read', 'update', 'delete'],
  disposition: ['create', 'read', 'update', 'delete'],
});

export const admin = ac.newRole({
  ...statement,
  erfassung: ['create', 'read', 'update', 'delete'],
  disposition: ['create', 'read', 'update', 'delete'],
});

If you want to base a role on another, spread the relevant permission object directly, not a .statements property. This matches the expected static format for roles in the organization plugin and will resolve the undefined error you’re seeing. See this example from a related issue for more context.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other  Join Discord Share on X

<!-- gh-comment-id:3092467656 --> @dosubot[bot] commented on GitHub (Jul 19, 2025): <!-- Greeting --> Hi @MoneyDropLobby! I'm [Dosu](https://go.dosu.dev/dosubot) and I’m helping the better-auth team. <!-- Answer --> The error happens because ownerAc.statements and adminAc.statements are undefined—there are no such properties on your role objects, and referencing them causes the TypeError. In Better Auth, when defining roles with ac.newRole, you should directly spread the permissions or statements you want, not reference a .statements property from another role object. Here's the correct pattern: ```ts const statement = { ...defaultStatements, erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], } as const; export const ac = createAccessControl(statement); export const owner = ac.newRole({ ...statement, // or just the permissions you want to override/add erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], }); export const admin = ac.newRole({ ...statement, erfassung: ['create', 'read', 'update', 'delete'], disposition: ['create', 'read', 'update', 'delete'], }); ``` If you want to base a role on another, spread the relevant permission object directly, not a .statements property. This matches the expected static format for roles in the organization plugin and will resolve the undefined error you’re seeing. See [this example from a related issue](https://github.com/better-auth/better-auth/issues/2762) for more context. <!-- Dosu Comment Footer --> *To reply, just mention [@dosu](https://go.dosu.dev/dosubot).* --- <sup>How did I do? [Good](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/71668ab4-c25e-47fe-897d-541bbdfb215c?feedback_type=other)</sup>&nbsp;&nbsp;[![Join Discord](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&label=)](https://go.dosu.dev/discord-bot)&nbsp;[![Share on X](https://img.shields.io/badge/X-share-black)](https://twitter.com/intent/tweet?text=%40dosu_ai%20helped%20me%20solve%20this%20issue!&url=https%3A//github.com/better-auth/better-auth/issues/3472)
Author
Owner

@MoneyDropLobby commented on GitHub (Jul 20, 2025):

I had to import the ownerAc and adminAc from better-auth/plugins/organization/access

<!-- gh-comment-id:3094726342 --> @MoneyDropLobby commented on GitHub (Jul 20, 2025): I had to import the ownerAc and adminAc from `better-auth/plugins/organization/access`
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9617