[Bug] Organization invitation fails when teamId is undefined - empty string violates foreign key constraint #1717

Closed
opened 2026-03-13 08:58:11 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @kristianeboe on GitHub (Aug 15, 2025).

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

Bug Description

When creating an organization invitation without specifying a teamId, Better Auth converts an empty teamIds array to an empty string instead of null, causing a foreign key constraint violation.

Environment

  • Better Auth version: 1.3.6
  • Database: PostgreSQL (via Neon)
  • ORM: Drizzle
  • Teams enabled: Yes

Steps to Reproduce

  1. Enable teams in organization plugin configuration
  2. Create an invitation without specifying a teamId
  3. Better Auth tries to insert empty string "" into team_id column
  4. Database rejects with foreign key constraint violation

Root Cause

In the source code, line 739 in the organization plugin:

teamId: invitation.teamIds.join(",")

When teamIds is an empty array [], join(",") returns an empty string "" instead of null.

Suggested Fix

Change line 739 from:

teamId: invitation.teamIds.join(",")

To:

teamId: invitation.teamIds.length > 0 ? invitation.teamIds.join(",") : null

Code Example

// This fails with the current implementation
await auth.api.createInvitation({
  body: {
    email: "user@example.com",
    role: "member",
    organizationId: "org-123",
    // teamId not provided - should be null, not empty string
  },
  headers: await headers(),
});

Database Schema

The invitation table correctly defines teamId as nullable:

teamId: t.text().references(() => teamTable.id, { onDelete: "cascade" }),

Workaround

We've temporarily implemented a custom invitation creation function that properly handles teamId as null when not provided.

Additional Context

This affects any application using Better Auth's organization plugin with teams enabled when inviting users without team assignment.

Current vs. Expected behavior

Expected Behavior

When no teamId is provided, the team_id column should be set to NULL to properly handle the optional foreign key relationship.

Actual Behavior

Better Auth converts empty teamIds array to empty string, causing this error:

What version of Better Auth are you using?

1.3.6

System info

System:
    OS: macOS 15.6
    CPU: (10) arm64 Apple M1 Max
    Memory: 342.56 MB / 32.00 GB
    Shell: 5.9 - /bin/zsh
  Browsers:
    Chrome: 139.0.7258.68
    Edge: 124.0.2478.51
    Safari: 18.6

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  emailAndPassword: {  
    enabled: true
  },
  organization({
        // Enable teams with configuration
        teams: {
          enabled: true,
          maximumTeams: 10,
          maximumMembersPerTeam: 50,
          allowRemovingAllTeams: true,
        },
        // Organization creation settings
        allowUserToCreateOrganization: true,
        organizationLimit: 5,
        membershipLimit: 100,
        invitationLimit: 50,
        invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
        cancelPendingInvitationsOnReInvite: false,
        // Custom email sender
        async sendInvitationEmail(data) {
          try {
            console.log("Sending organization invitation email:", data);

            const inviteLink = `${options.baseUrl}/org-invite/${data.id}`;

            await options.sendOrganizationInvitationEmail({
              email: data.email,
              organizationName: data.organization.name,
              inviterName:
                data.inviter.user.name || data.inviter.user.email || "Someone",
              inviteUrl: inviteLink,
            });

            console.log("Organization invitation email sent successfully");
          } catch (error) {
            console.error(
              "Failed to send organization invitation email:",
              error,
            );
            // Don't throw to prevent blocking the invitation creation
          }
        },
        schema: {
          organization: {
            additionalFields: {
              // Define our custom fields for Better Auth
              plan: {
                type: "string",
                required: false,
                input: false, // Don't allow input from API
              },
              creditBalance: {
                type: "number",
                required: false,
                input: false, // Don't allow input from API
              },
              updatedAt: {
                type: "date",
                required: false,
                input: false, // Don't allow input from API
              },
              deletedAt: {
                type: "date",
                required: false,
                input: false, // Don't allow input from API
              },
            },
          },
        },
      }),
});

Additional context

No response

Originally created by @kristianeboe on GitHub (Aug 15, 2025). ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce ## Bug Description When creating an organization invitation without specifying a `teamId`, Better Auth converts an empty `teamIds` array to an empty string instead of `null`, causing a foreign key constraint violation. ## Environment - Better Auth version: 1.3.6 - Database: PostgreSQL (via Neon) - ORM: Drizzle - Teams enabled: Yes ## Steps to Reproduce 1. Enable teams in organization plugin configuration 2. Create an invitation without specifying a `teamId` 3. Better Auth tries to insert empty string `""` into `team_id` column 4. Database rejects with foreign key constraint violation ## Root Cause In the source code, line 739 in the organization plugin: ```javascript teamId: invitation.teamIds.join(",") ``` When `teamIds` is an empty array `[]`, `join(",")` returns an empty string `""` instead of `null`. ## Suggested Fix Change line 739 from: ```javascript teamId: invitation.teamIds.join(",") ``` To: ```javascript teamId: invitation.teamIds.length > 0 ? invitation.teamIds.join(",") : null ``` ## Code Example ```typescript // This fails with the current implementation await auth.api.createInvitation({ body: { email: "user@example.com", role: "member", organizationId: "org-123", // teamId not provided - should be null, not empty string }, headers: await headers(), }); ``` ## Database Schema The invitation table correctly defines `teamId` as nullable: ```typescript teamId: t.text().references(() => teamTable.id, { onDelete: "cascade" }), ``` ## Workaround We've temporarily implemented a custom invitation creation function that properly handles `teamId` as `null` when not provided. ## Additional Context This affects any application using Better Auth's organization plugin with teams enabled when inviting users without team assignment. ### Current vs. Expected behavior ## Expected Behavior When no `teamId` is provided, the `team_id` column should be set to `NULL` to properly handle the optional foreign key relationship. ## Actual Behavior Better Auth converts empty `teamIds` array to empty string, causing this error: ### What version of Better Auth are you using? 1.3.6 ### System info ```bash System: OS: macOS 15.6 CPU: (10) arm64 Apple M1 Max Memory: 342.56 MB / 32.00 GB Shell: 5.9 - /bin/zsh Browsers: Chrome: 139.0.7258.68 Edge: 124.0.2478.51 Safari: 18.6 ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ emailAndPassword: { enabled: true }, organization({ // Enable teams with configuration teams: { enabled: true, maximumTeams: 10, maximumMembersPerTeam: 50, allowRemovingAllTeams: true, }, // Organization creation settings allowUserToCreateOrganization: true, organizationLimit: 5, membershipLimit: 100, invitationLimit: 50, invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days cancelPendingInvitationsOnReInvite: false, // Custom email sender async sendInvitationEmail(data) { try { console.log("Sending organization invitation email:", data); const inviteLink = `${options.baseUrl}/org-invite/${data.id}`; await options.sendOrganizationInvitationEmail({ email: data.email, organizationName: data.organization.name, inviterName: data.inviter.user.name || data.inviter.user.email || "Someone", inviteUrl: inviteLink, }); console.log("Organization invitation email sent successfully"); } catch (error) { console.error( "Failed to send organization invitation email:", error, ); // Don't throw to prevent blocking the invitation creation } }, schema: { organization: { additionalFields: { // Define our custom fields for Better Auth plan: { type: "string", required: false, input: false, // Don't allow input from API }, creditBalance: { type: "number", required: false, input: false, // Don't allow input from API }, updatedAt: { type: "date", required: false, input: false, // Don't allow input from API }, deletedAt: { type: "date", required: false, input: false, // Don't allow input from API }, }, }, }, }), }); ``` ### Additional context _No response_
GiteaMirror added the bug label 2026-03-13 08:58:11 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#1717