[GH-ISSUE #6791] Cannot use auth.api.createOrganization in database hooks when allowUserToCreateOrganization is false #10630

Closed
opened 2026-04-13 06:52:28 -05:00 by GiteaMirror · 3 comments
Owner

Originally created by @DevLubab on GitHub (Dec 16, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/6791

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

I am trying to implement a flow where a generic user cannot create organizations manually, but the system creates a "Personal Organization" for them automatically upon signup via a database hook.

When I set allowUserToCreateOrganization: false in the Organization plugin configuration, it prevents auth.api.createOrganization from being used within the databaseHooks.user.create.after hook.

Current vs. Expected behavior

Current Behavior:
If allowUserToCreateOrganization is false, and we call auth.api.createOrganization in databaseHooks.user.create.after, it fails with You are not allowed to create a new organization.

Expected Behavior:
auth.api.createOrganization called from the server-side (without user headers) should bypass the allowUserToCreateOrganization: false restriction.

What version of Better Auth are you using?

1.4.7

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132",
    "release": "25.1.0",
    "cpuCount": 10,
    "cpuModel": "Apple M4",
    "totalMemory": "16.00 GB",
    "freeMemory": "0.17 GB"
  },
  "node": {
    "version": "v24.12.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.4"
  },
  "frameworks": [
    {
      "name": "react",
      "version": "^19.2.3"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.16.3"
    },
 {
      "name": "drizzle",
      "version": "^0.39.3"
    }
  ],
  "betterAuth": {
    "version": "^1.4.7",
    "config": null
  }
}

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

Backend

Auth config (if applicable)

const auth = betterAuth({
	emailAndPassword: {
		enabled: true,
	},
	plugins: [
		organization({
			allowUserToCreateOrganization: false,
		}),
	],
	databaseHooks: {
		user: {
			create: {
				after: async (user, ctx) => {
					await auth.api.createOrganization({
						body: {
							name: `${user.name}'s Workspace`,
							slug: `${slugify(user.name)}-${randomStr(6)}`,
							logo: undefined,
							metadata: {
								personal: true
							},
							userId: user.id,
							keepCurrentActiveOrganization: false,
						},
					})
				}
			},
		}
	},
});

Additional context

No response

Originally created by @DevLubab on GitHub (Dec 16, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/6791 ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce I am trying to implement a flow where a generic user cannot create organizations manually, but the system creates a "Personal Organization" for them automatically upon signup via a database hook. When I set `allowUserToCreateOrganization: false` in the Organization plugin configuration, it prevents `auth.api.createOrganization` from being used within the `databaseHooks.user.create.after` hook. ### Current vs. Expected behavior Current Behavior: If `allowUserToCreateOrganization` is false, and we call `auth.api.createOrganization` in `databaseHooks.user.create.after`, it fails with `You are not allowed to create a new organization`. Expected Behavior: `auth.api.createOrganization` called from the server-side (without user headers) should bypass the `allowUserToCreateOrganization: false` restriction. ### What version of Better Auth are you using? 1.4.7 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.1.0: Mon Oct 20 19:32:56 PDT 2025; root:xnu-12377.41.6~2/RELEASE_ARM64_T8132", "release": "25.1.0", "cpuCount": 10, "cpuModel": "Apple M4", "totalMemory": "16.00 GB", "freeMemory": "0.17 GB" }, "node": { "version": "v24.12.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.4" }, "frameworks": [ { "name": "react", "version": "^19.2.3" } ], "databases": [ { "name": "pg", "version": "^8.16.3" }, { "name": "drizzle", "version": "^0.39.3" } ], "betterAuth": { "version": "^1.4.7", "config": null } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript const auth = betterAuth({ emailAndPassword: { enabled: true, }, plugins: [ organization({ allowUserToCreateOrganization: false, }), ], databaseHooks: { user: { create: { after: async (user, ctx) => { await auth.api.createOrganization({ body: { name: `${user.name}'s Workspace`, slug: `${slugify(user.name)}-${randomStr(6)}`, logo: undefined, metadata: { personal: true }, userId: user.id, keepCurrentActiveOrganization: false, }, }) } }, } }, }); ``` ### Additional context _No response_
GiteaMirror added the locked label 2026-04-13 06:52:28 -05:00
Author
Owner

@better-auth-agent[bot] commented on GitHub (Dec 16, 2025):

It looks like you’re running into an issue where the organization plugin’s restriction is also being applied to server‐side operations (in your database hook) even though your intent is for internal automation. In our design, the allowUserToCreateOrganization setting is meant to block organization creation that’s directly initiated by a user request, but ideally system‐initiated calls (without user headers) should bypass that check. One workaround is to ensure that when you call auth.api.createOrganization from your database hook you aren’t including any user authentication headers. You might try calling the API in a context that clearly indicates a server–side (or admin) call, so it’s not interpreted as a user action. If that isn’t feasible, as a short–term solution you could temporarily enable organization creation during the internal call. We’re aware that this behavior can be unintuitive and will evaluate improving the distinction in future updates.

Does this help clarify your next steps? Tag @better-auth-agent if you would like an answer from the Agent.

If you need more help, tag @better-auth-agent in a comment so I can respond.

DiagramDiscordGitHub

Diagram Join Star

<!-- gh-comment-id:3658679887 --> @better-auth-agent[bot] commented on GitHub (Dec 16, 2025): It looks like you’re running into an issue where the organization plugin’s restriction is also being applied to server‐side operations (in your database hook) even though your intent is for internal automation. In our design, the allowUserToCreateOrganization setting is meant to block organization creation that’s directly initiated by a user request, but ideally system‐initiated calls (without user headers) should bypass that check. One workaround is to ensure that when you call auth.api.createOrganization from your database hook you aren’t including any user authentication headers. You might try calling the API in a context that clearly indicates a server–side (or admin) call, so it’s not interpreted as a user action. If that isn’t feasible, as a short–term solution you could temporarily enable organization creation during the internal call. We’re aware that this behavior can be unintuitive and will evaluate improving the distinction in future updates. Does this help clarify your next steps? Tag @better-auth-agent if you would like an answer from the Agent. _If you need more help, tag @better-auth-agent in a comment so I can respond._ <!-- bot:webhook reply v1 --> [Diagram](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) • [Discord](https://discord.gg/fG2XXEuQX3) • [GitHub](https://github.com/Skyvern-AI/Skyvern) [![Diagram](https://img.shields.io/badge/Diagram-2b3137?style=flat-square)](https://repodiagrams.s3.eu-north-1.amazonaws.com/skyvern_ultra_detailed_interactive.html) [![Join](https://img.shields.io/badge/join-5865F2?logo=discord&logoColor=white&style=flat-square)](https://discord.gg/fG2XXEuQX3) [![Star](https://img.shields.io/badge/star-181717?logo=github&logoColor=white&style=flat-square)](https://github.com/Skyvern-AI/Skyvern)
Author
Owner

@DevLubab commented on GitHub (Dec 16, 2025):

Here is the solution to auto create organization on signup, and always make it active, without using the auth.api.

import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { organization } from "better-auth/plugins";
import { asc, desc, eq } from "drizzle-orm";
import { db } from "@/db"; 
import * as schema from "@/db/schema"; 
import { randomStr, slugify } from "@/utils"; 

export const auth = betterAuth({
    database: drizzleAdapter(db, {
        provider: "pg",
        schema: schema
    }),
    plugins: [
        organization({
            allowUserToCreateOrganization: false,
        }),
    ],
    databaseHooks: {
        session: {
            create: {
                // Set user org on connect
                before: async (session) => {
                    const memberships = await db
                        .select({
                            organizationId: schema.member.organizationId,
                            role: schema.member.role,
                        })
                        .from(schema.member)
                        .where(eq(schema.member.userId, session.userId))
                        .orderBy(asc(schema.member.createdAt));

                    const org = memberships.find((m) => m.role === "owner") || memberships[0];

                    return {
                        data: {
                            ...session,
                            activeOrganizationId: org?.organizationId ?? null,
                        },
                    };
                },
            },
        },
        user: {
            create: {
                after: async (user, ctx) => {
                    // Without setTimeout the user isn't created yet
                    // and the Member insert fails with foreign key violation.
                    setTimeout(async () => {
                        try {
                            await onUserSignup(user.name, user.id);
                        } catch (e) {
                            console.error("Error creating default org:", e);
                        }
                    }, 0);
                }
            },
        }
    },
});

async function onUserSignup(userName: string, userId: string) {
    const slug = `${slugify(userName)}-${randomStr(6)}`;
    const orgName = `${userName}'s Workspace`;
    const createdAt = new Date();

    // 1. Create Organization
    const [newOrg] = await db.insert(schema.organization).values({
        id: randomStr(16),
        name: orgName,
        slug: slug,
        logo: null,
        metadata: JSON.stringify({
            personal: true
        }),
        createdAt: createdAt,
    }).returning();

    // 2. Add Member
    await db.insert(schema.member).values({
        id: randomStr(16),
        organizationId: newOrg.id,
        userId: userId,
        role: "owner",
        createdAt: createdAt,
    });

    // 3. Update the current session to point to the new Org
    const [recentSession] = await db
        .select()
        .from(schema.session)
        .where(eq(schema.session.userId, userId))
        .orderBy(desc(schema.session.createdAt))
        .limit(1);

    if (recentSession) {
        await db
            .update(schema.session)
            .set({ activeOrganizationId: newOrg.id })
            .where(eq(schema.session.id, recentSession.id));
    }
}
<!-- gh-comment-id:3658849194 --> @DevLubab commented on GitHub (Dec 16, 2025): Here is the solution to auto create organization on signup, and always make it active, without using the auth.api. ```typescript import { betterAuth } from "better-auth"; import { drizzleAdapter } from "better-auth/adapters/drizzle"; import { organization } from "better-auth/plugins"; import { asc, desc, eq } from "drizzle-orm"; import { db } from "@/db"; import * as schema from "@/db/schema"; import { randomStr, slugify } from "@/utils"; export const auth = betterAuth({ database: drizzleAdapter(db, { provider: "pg", schema: schema }), plugins: [ organization({ allowUserToCreateOrganization: false, }), ], databaseHooks: { session: { create: { // Set user org on connect before: async (session) => { const memberships = await db .select({ organizationId: schema.member.organizationId, role: schema.member.role, }) .from(schema.member) .where(eq(schema.member.userId, session.userId)) .orderBy(asc(schema.member.createdAt)); const org = memberships.find((m) => m.role === "owner") || memberships[0]; return { data: { ...session, activeOrganizationId: org?.organizationId ?? null, }, }; }, }, }, user: { create: { after: async (user, ctx) => { // Without setTimeout the user isn't created yet // and the Member insert fails with foreign key violation. setTimeout(async () => { try { await onUserSignup(user.name, user.id); } catch (e) { console.error("Error creating default org:", e); } }, 0); } }, } }, }); async function onUserSignup(userName: string, userId: string) { const slug = `${slugify(userName)}-${randomStr(6)}`; const orgName = `${userName}'s Workspace`; const createdAt = new Date(); // 1. Create Organization const [newOrg] = await db.insert(schema.organization).values({ id: randomStr(16), name: orgName, slug: slug, logo: null, metadata: JSON.stringify({ personal: true }), createdAt: createdAt, }).returning(); // 2. Add Member await db.insert(schema.member).values({ id: randomStr(16), organizationId: newOrg.id, userId: userId, role: "owner", createdAt: createdAt, }); // 3. Update the current session to point to the new Org const [recentSession] = await db .select() .from(schema.session) .where(eq(schema.session.userId, userId)) .orderBy(desc(schema.session.createdAt)) .limit(1); if (recentSession) { await db .update(schema.session) .set({ activeOrganizationId: newOrg.id }) .where(eq(schema.session.id, recentSession.id)); } } ```
Author
Owner

@GautamBytes commented on GitHub (Dec 18, 2025):

Looking into it!

<!-- gh-comment-id:3669877528 --> @GautamBytes commented on GitHub (Dec 18, 2025): Looking into it!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#10630