[GH-ISSUE #8228] Schema generation removes magic link verifications & Magic link sign-in broken since 1.5.0 #28351

Closed
opened 2026-04-17 19:46:16 -05:00 by GiteaMirror · 13 comments
Owner

Originally created by @wottpal on GitHub (Mar 1, 2026).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/8228

Originally assigned to: @himself65 on GitHub.

Is this suited for github?

  • Yes, this is suited for github

To Reproduce

We've upgraded to 1.5.0 and ran a new schema generation which want's to remove the verifications table. Even if I bring it back manually and don't drop it from the database (= don't apply migration), magic sign in has stopped working, we get the following error upon magic link sign in:

next:dev    | 2026-03-01T05:22:54.919Z ERROR [Better Auth]: BetterAuthError [Error [BetterAuthError]: Model "verification" not found in schema]
next:dev    | # SERVER_ERROR:  [Error [BetterAuthError]: Model "verification" not found in schema]
next:dev    |  GET /api/auth/magic-link/verify?token=6cbc450e42e8850cd5d6b1808faee6c6&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 93ms (compile: 6ms, render: 87ms)

Also, it seems that it could also be related with usePlural: true (because it is searching for verification even though we use pluralization). Even if i try to manually give it the verification singular like below it doesn't work:

    schema: {
      ...schema,
      verification: schema.verifications,
    },
Image

Current vs. Expected behavior

  • I expect the verifications table not to be removed.
  • I expect magic sign-in's to continue working.

What version of Better Auth are you using?

1.5.0

System info

{
  "system": {
    "platform": "darwin",
    "arch": "arm64",
    "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041",
    "release": "25.3.0",
    "cpuCount": 16,
    "cpuModel": "Apple M4 Max",
    "totalMemory": "128.00 GB",
    "freeMemory": "68.45 GB"
  },
  "node": {
    "version": "v24.14.0",
    "env": "development"
  },
  "packageManager": {
    "name": "bun",
    "version": "1.3.10"
  },
  "frameworks": [
    {
      "name": "next",
      "version": "^16.1.6"
    },
    {
      "name": "react",
      "version": "^19.2.4"
    }
  ],
  "databases": [
    {
      "name": "pg",
      "version": "^8.19.0"
    },
    {
      "name": "postgres",
      "version": "^3.4.8"
    },
    {
      "name": "drizzle",
      "version": "^0.45.1"
    }
  ],
  "betterAuth": {
    "version": "^1.5.0",
    "config": {
      "appName": "[REDACTED]",
      "baseURL": "http://localhost:3000",
      "secret": "[REDACTED]",
      "secondaryStorage": {},
      "rateLimit": {
        "storage": "secondary-storage"
      },
      "session": {
        "cookieCache": {
          "enabled": true,
          "maxAge": 300
        }
      },
      "user": {
        "additionalFields": {
          "firstName": {
            "type": "string",
            "required": true
          },
          "lastName": {
            "type": "string",
            "required": true
          },
          "creatorProfileId": {
            "type": "string",
            "required": false,
            "input": false,
            "unique": true
          }
        },
        "changeEmail": {
          "enabled": true
        }
      },
      "account": {
        "accountLinking": {
          "enabled": true,
          "allowDifferentEmails": true,
          "allowUnlinkingAll": true
        }
      },
      "socialProviders": {
        "google": {
          "clientId": "[REDACTED]",
          "clientSecret": "[REDACTED]"
        }
      },
      "hooks": {},
      "databaseHooks": {
        "session": {
          "update": {},
          "create": {}
        },
        "user": {
          "create": {},
          "update": {}
        }
      },
      "plugins": [
        {
          "name": "magic-link",
          "config": {
            "id": "magic-link",
            "endpoints": {},
            "rateLimit": [
              {
                "window": 60,
                "max": 5
              }
            ],
            "options": {}
          }
        },
        {
          "name": "organization",
          "config": {
            "id": "organization",
            "endpoints": {},
            "schema": {
              "organization": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": true,
                    "sortable": true
                  },
                  "slug": {
                    "type": "string",
                    "required": true,
                    "unique": true,
                    "sortable": true,
                    "index": true
                  },
                  "logo": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "url": {
                    "type": "string",
                    "required": false,
                    "input": true
                  }
                }
              },
              "member": {
                "modelName": "orgMember",
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "member"
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  }
                }
              },
              "invitation": {
                "modelName": "orgInvitation",
                "fields": {
                  "organizationId": {
                    "type": "string",
                    "required": true,
                    "references": {
                      "model": "organization",
                      "field": "id"
                    },
                    "index": true
                  },
                  "email": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "index": true
                  },
                  "role": {
                    "type": "string",
                    "required": false,
                    "sortable": true
                  },
                  "status": {
                    "type": "string",
                    "required": true,
                    "sortable": true,
                    "defaultValue": "pending"
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": true
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true
                  },
                  "inviterId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true
                  }
                }
              },
              "session": {
                "fields": {
                  "activeOrganizationId": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$Infer": {
              "Organization": {},
              "Invitation": {},
              "Member": {},
              "Team": {},
              "TeamMember": {},
              "ActiveOrganization": {}
            },
            "$ERROR_CODES": {
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION",
                "message": "You are not allowed to create a new organization"
              },
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": {
                "code": "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS",
                "message": "You have reached the maximum number of organizations"
              },
              "ORGANIZATION_ALREADY_EXISTS": {
                "code": "ORGANIZATION_ALREADY_EXISTS",
                "message": "Organization already exists"
              },
              "ORGANIZATION_SLUG_ALREADY_TAKEN": {
                "code": "ORGANIZATION_SLUG_ALREADY_TAKEN",
                "message": "Organization slug already taken"
              },
              "ORGANIZATION_NOT_FOUND": {
                "code": "ORGANIZATION_NOT_FOUND",
                "message": "Organization not found"
              },
              "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": {
                "code": "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION",
                "message": "User is not a member of the organization"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION",
                "message": "You are not allowed to update this organization"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION",
                "message": "You are not allowed to delete this organization"
              },
              "NO_ACTIVE_ORGANIZATION": {
                "code": "NO_ACTIVE_ORGANIZATION",
                "message": "No active organization"
              },
              "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": {
                "code": "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION",
                "message": "User is already a member of this organization"
              },
              "MEMBER_NOT_FOUND": {
                "code": "MEMBER_NOT_FOUND",
                "message": "Member not found"
              },
              "ROLE_NOT_FOUND": {
                "code": "ROLE_NOT_FOUND",
                "message": "Role not found"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM",
                "message": "You are not allowed to create a new team"
              },
              "TEAM_ALREADY_EXISTS": {
                "code": "TEAM_ALREADY_EXISTS",
                "message": "Team already exists"
              },
              "TEAM_NOT_FOUND": {
                "code": "TEAM_NOT_FOUND",
                "message": "Team not found"
              },
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": {
                "code": "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER",
                "message": "You cannot leave the organization as the only owner"
              },
              "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": {
                "code": "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER",
                "message": "You cannot leave the organization without an owner"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER",
                "message": "You are not allowed to delete this member"
              },
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION",
                "message": "You are not allowed to invite users to this organization"
              },
              "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": {
                "code": "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION",
                "message": "User is already invited to this organization"
              },
              "INVITATION_NOT_FOUND": {
                "code": "INVITATION_NOT_FOUND",
                "message": "Invitation not found"
              },
              "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": {
                "code": "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION",
                "message": "You are not the recipient of the invitation"
              },
              "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": {
                "code": "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION",
                "message": "Email verification required before accepting or rejecting invitation"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION",
                "message": "You are not allowed to cancel this invitation"
              },
              "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": {
                "code": "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION",
                "message": "Inviter is no longer a member of the organization"
              },
              "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE",
                "message": "You are not allowed to invite a user with this role"
              },
              "FAILED_TO_RETRIEVE_INVITATION": {
                "code": "FAILED_TO_RETRIEVE_INVITATION",
                "message": "Failed to retrieve invitation"
              },
              "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": {
                "code": "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS",
                "message": "You have reached the maximum number of teams"
              },
              "UNABLE_TO_REMOVE_LAST_TEAM": {
                "code": "UNABLE_TO_REMOVE_LAST_TEAM",
                "message": "Unable to remove last team"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER",
                "message": "You are not allowed to update this member"
              },
              "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": {
                "code": "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED",
                "message": "Organization membership limit reached"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION",
                "message": "You are not allowed to create teams in this organization"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION",
                "message": "You are not allowed to delete teams in this organization"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM",
                "message": "You are not allowed to update this team"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM",
                "message": "You are not allowed to delete this team"
              },
              "INVITATION_LIMIT_REACHED": {
                "code": "INVITATION_LIMIT_REACHED",
                "message": "Invitation limit reached"
              },
              "TEAM_MEMBER_LIMIT_REACHED": {
                "code": "TEAM_MEMBER_LIMIT_REACHED",
                "message": "Team member limit reached"
              },
              "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": {
                "code": "USER_IS_NOT_A_MEMBER_OF_THE_TEAM",
                "message": "User is not a member of the team"
              },
              "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": {
                "code": "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM",
                "message": "You are not allowed to list the members of this team"
              },
              "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": {
                "code": "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM",
                "message": "You do not have an active team"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER",
                "message": "You are not allowed to create a new member"
              },
              "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER",
                "message": "You are not allowed to remove a team member"
              },
              "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION",
                "message": "You are not allowed to access this organization as an owner"
              },
              "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": {
                "code": "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION",
                "message": "You are not a member of this organization"
              },
              "MISSING_AC_INSTANCE": {
                "code": "MISSING_AC_INSTANCE",
                "message": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information"
              },
              "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": {
                "code": "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE",
                "message": "You must be in an organization to create a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE",
                "message": "You are not allowed to create a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE",
                "message": "You are not allowed to update a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE",
                "message": "You are not allowed to delete a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE",
                "message": "You are not allowed to read a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE",
                "message": "You are not allowed to list a role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE",
                "message": "You are not allowed to get a role"
              },
              "TOO_MANY_ROLES": {
                "code": "TOO_MANY_ROLES",
                "message": "This organization has too many roles"
              },
              "INVALID_RESOURCE": {
                "code": "INVALID_RESOURCE",
                "message": "The provided permission includes an invalid resource"
              },
              "ROLE_NAME_IS_ALREADY_TAKEN": {
                "code": "ROLE_NAME_IS_ALREADY_TAKEN",
                "message": "That role name is already taken"
              },
              "CANNOT_DELETE_A_PRE_DEFINED_ROLE": {
                "code": "CANNOT_DELETE_A_PRE_DEFINED_ROLE",
                "message": "Cannot delete a pre-defined role"
              },
              "ROLE_IS_ASSIGNED_TO_MEMBERS": {
                "code": "ROLE_IS_ASSIGNED_TO_MEMBERS",
                "message": "Cannot delete a role that is assigned to members. Please reassign the members to a different role first"
              }
            },
            "options": {
              "ac": {
                "statements": {
                  "organization": [
                    "update",
                    "delete"
                  ],
                  "member": [
                    "create",
                    "update",
                    "delete"
                  ],
                  "invitation": [
                    "create",
                    "cancel"
                  ],
                  "team": [
                    "create",
                    "update",
                    "delete"
                  ],
                  "ac": [
                    "create",
                    "read",
                    "update",
                    "delete"
                  ]
                }
              },
              "roles": {
                "owner": {
                  "statements": {
                    "organization": [
                      "update",
                      "delete"
                    ],
                    "member": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "invitation": [
                      "create",
                      "cancel"
                    ],
                    "team": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "ac": [
                      "create",
                      "read",
                      "update",
                      "delete"
                    ]
                  }
                },
                "admin": {
                  "statements": {
                    "organization": [
                      "update"
                    ],
                    "invitation": [
                      "create",
                      "cancel"
                    ],
                    "member": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "team": [
                      "create",
                      "update",
                      "delete"
                    ],
                    "ac": [
                      "create",
                      "read",
                      "update",
                      "delete"
                    ]
                  }
                },
                "member": {
                  "statements": {
                    "organization": [],
                    "member": [],
                    "invitation": [],
                    "team": [],
                    "ac": [
                      "read"
                    ]
                  }
                },
                "viewer": {
                  "statements": {
                    "organization": [],
                    "member": [],
                    "invitation": [],
                    "team": []
                  }
                },
                "creator": {
                  "statements": {
                    "organization": [],
                    "member": [],
                    "invitation": [],
                    "team": []
                  }
                }
              },
              "invitationExpiresIn": 604800,
              "cancelPendingInvitationsOnReInvite": true,
              "membershipLimit": 9007199254740991,
              "schema": {
                "invitation": {
                  "modelName": "orgInvitation"
                },
                "member": {
                  "modelName": "orgMember"
                },
                "organization": {
                  "additionalFields": {
                    "url": {
                      "type": "string",
                      "required": false,
                      "input": true
                    }
                  }
                }
              },
              "organizationHooks": {}
            }
          }
        },
        {
          "name": "admin",
          "config": {
            "id": "admin",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "$ERROR_CODES": {
              "FAILED_TO_CREATE_USER": {
                "code": "FAILED_TO_CREATE_USER",
                "message": "Failed to create user"
              },
              "USER_ALREADY_EXISTS": {
                "code": "USER_ALREADY_EXISTS",
                "message": "User already exists."
              },
              "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": {
                "code": "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL",
                "message": "User already exists. Use another email."
              },
              "YOU_CANNOT_BAN_YOURSELF": {
                "code": "YOU_CANNOT_BAN_YOURSELF",
                "message": "You cannot ban yourself"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE",
                "message": "You are not allowed to change users role"
              },
              "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS",
                "message": "You are not allowed to create users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS",
                "message": "You are not allowed to list users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS",
                "message": "You are not allowed to list users sessions"
              },
              "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS",
                "message": "You are not allowed to ban users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS",
                "message": "You are not allowed to impersonate users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS",
                "message": "You are not allowed to revoke users sessions"
              },
              "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS",
                "message": "You are not allowed to delete users"
              },
              "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD",
                "message": "You are not allowed to set users password"
              },
              "BANNED_USER": {
                "code": "BANNED_USER",
                "message": "You have been banned from this application"
              },
              "YOU_ARE_NOT_ALLOWED_TO_GET_USER": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_GET_USER",
                "message": "You are not allowed to get user"
              },
              "NO_DATA_TO_UPDATE": {
                "code": "NO_DATA_TO_UPDATE",
                "message": "No data to update"
              },
              "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS",
                "message": "You are not allowed to update users"
              },
              "YOU_CANNOT_REMOVE_YOURSELF": {
                "code": "YOU_CANNOT_REMOVE_YOURSELF",
                "message": "You cannot remove yourself"
              },
              "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE",
                "message": "You are not allowed to set a non-existent role value"
              },
              "YOU_CANNOT_IMPERSONATE_ADMINS": {
                "code": "YOU_CANNOT_IMPERSONATE_ADMINS",
                "message": "You cannot impersonate admins"
              },
              "INVALID_ROLE_TYPE": {
                "code": "INVALID_ROLE_TYPE",
                "message": "Invalid role type"
              }
            },
            "schema": {
              "user": {
                "fields": {
                  "role": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banned": {
                    "type": "boolean",
                    "defaultValue": false,
                    "required": false,
                    "input": false
                  },
                  "banReason": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "banExpires": {
                    "type": "date",
                    "required": false,
                    "input": false
                  }
                }
              },
              "session": {
                "fields": {
                  "impersonatedBy": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "options": {
              "adminUserIds": [
                "user_TpCXtDU74dCr",
                "user_oopmG2v7mgRG",
                "user_zX1CZ1MqaHXq"
              ],
              "impersonationSessionDuration": 3600
            }
          }
        },
        {
          "name": "mcp",
          "config": {
            "id": "mcp",
            "hooks": {
              "after": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "oauthApplication": {
                "modelName": "oauthApplication",
                "fields": {
                  "name": {
                    "type": "string"
                  },
                  "icon": {
                    "type": "string",
                    "required": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false
                  },
                  "clientId": {
                    "type": "string",
                    "unique": true
                  },
                  "clientSecret": {
                    "type": "string",
                    "required": false
                  },
                  "redirectUrls": {
                    "type": "string"
                  },
                  "type": {
                    "type": "string"
                  },
                  "disabled": {
                    "type": "boolean",
                    "required": false,
                    "defaultValue": false
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthAccessToken": {
                "modelName": "oauthAccessToken",
                "fields": {
                  "accessToken": {
                    "type": "string",
                    "unique": true
                  },
                  "refreshToken": {
                    "type": "string",
                    "unique": true
                  },
                  "accessTokenExpiresAt": {
                    "type": "date"
                  },
                  "refreshTokenExpiresAt": {
                    "type": "date"
                  },
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "required": false,
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  }
                }
              },
              "oauthConsent": {
                "modelName": "oauthConsent",
                "fields": {
                  "clientId": {
                    "type": "string",
                    "references": {
                      "model": "oauthApplication",
                      "field": "clientId",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id",
                      "onDelete": "cascade"
                    },
                    "index": true
                  },
                  "scopes": {
                    "type": "string"
                  },
                  "createdAt": {
                    "type": "date"
                  },
                  "updatedAt": {
                    "type": "date"
                  },
                  "consentGiven": {
                    "type": "boolean"
                  }
                }
              }
            },
            "options": {
              "loginPage": "/auth/sign-up",
              "oidcConfig": {
                "loginPage": "/auth/sign-up"
              }
            }
          }
        },
        {
          "name": "api-key",
          "config": {
            "id": "api-key",
            "$ERROR_CODES": {
              "INVALID_METADATA_TYPE": {
                "code": "INVALID_METADATA_TYPE",
                "message": "metadata must be an object or undefined"
              },
              "REFILL_AMOUNT_AND_INTERVAL_REQUIRED": {
                "code": "REFILL_AMOUNT_AND_INTERVAL_REQUIRED",
                "message": "refillAmount is required when refillInterval is provided"
              },
              "REFILL_INTERVAL_AND_AMOUNT_REQUIRED": {
                "code": "REFILL_INTERVAL_AND_AMOUNT_REQUIRED",
                "message": "refillInterval is required when refillAmount is provided"
              },
              "USER_BANNED": {
                "code": "USER_BANNED",
                "message": "User is banned"
              },
              "UNAUTHORIZED_SESSION": {
                "code": "UNAUTHORIZED_SESSION",
                "message": "Unauthorized or invalid session"
              },
              "KEY_NOT_FOUND": {
                "code": "KEY_NOT_FOUND",
                "message": "API Key not found"
              },
              "KEY_DISABLED": {
                "code": "KEY_DISABLED",
                "message": "API Key is disabled"
              },
              "KEY_EXPIRED": {
                "code": "KEY_EXPIRED",
                "message": "API Key has expired"
              },
              "USAGE_EXCEEDED": {
                "code": "USAGE_EXCEEDED",
                "message": "API Key has reached its usage limit"
              },
              "KEY_NOT_RECOVERABLE": {
                "code": "KEY_NOT_RECOVERABLE",
                "message": "API Key is not recoverable"
              },
              "EXPIRES_IN_IS_TOO_SMALL": {
                "code": "EXPIRES_IN_IS_TOO_SMALL",
                "message": "The expiresIn is smaller than the predefined minimum value."
              },
              "EXPIRES_IN_IS_TOO_LARGE": {
                "code": "EXPIRES_IN_IS_TOO_LARGE",
                "message": "The expiresIn is larger than the predefined maximum value."
              },
              "INVALID_REMAINING": {
                "code": "INVALID_REMAINING",
                "message": "The remaining count is either too large or too small."
              },
              "INVALID_PREFIX_LENGTH": {
                "code": "INVALID_PREFIX_LENGTH",
                "message": "The prefix length is either too large or too small."
              },
              "INVALID_NAME_LENGTH": {
                "code": "INVALID_NAME_LENGTH",
                "message": "The name length is either too large or too small."
              },
              "METADATA_DISABLED": {
                "code": "METADATA_DISABLED",
                "message": "Metadata is disabled."
              },
              "RATE_LIMIT_EXCEEDED": {
                "code": "RATE_LIMIT_EXCEEDED",
                "message": "Rate limit exceeded."
              },
              "NO_VALUES_TO_UPDATE": {
                "code": "NO_VALUES_TO_UPDATE",
                "message": "No values to update."
              },
              "KEY_DISABLED_EXPIRATION": {
                "code": "KEY_DISABLED_EXPIRATION",
                "message": "Custom key expiration values are disabled."
              },
              "INVALID_API_KEY": {
                "code": "INVALID_API_KEY",
                "message": "Invalid API key."
              },
              "INVALID_USER_ID_FROM_API_KEY": {
                "code": "INVALID_USER_ID_FROM_API_KEY",
                "message": "The user id from the API key is invalid."
              },
              "INVALID_REFERENCE_ID_FROM_API_KEY": {
                "code": "INVALID_REFERENCE_ID_FROM_API_KEY",
                "message": "The reference id from the API key is invalid."
              },
              "INVALID_API_KEY_GETTER_RETURN_TYPE": {
                "code": "INVALID_API_KEY_GETTER_RETURN_TYPE",
                "message": "API Key getter returned an invalid key type. Expected string."
              },
              "SERVER_ONLY_PROPERTY": {
                "code": "SERVER_ONLY_PROPERTY",
                "message": "The property you're trying to set can only be set from the server auth instance only."
              },
              "FAILED_TO_UPDATE_API_KEY": {
                "code": "FAILED_TO_UPDATE_API_KEY",
                "message": "Failed to update API key"
              },
              "NAME_REQUIRED": {
                "code": "NAME_REQUIRED",
                "message": "API Key name is required."
              },
              "ORGANIZATION_ID_REQUIRED": {
                "code": "ORGANIZATION_ID_REQUIRED",
                "message": "Organization ID is required for organization-owned API keys."
              },
              "USER_NOT_MEMBER_OF_ORGANIZATION": {
                "code": "USER_NOT_MEMBER_OF_ORGANIZATION",
                "message": "You are not a member of the organization that owns this API key."
              },
              "INSUFFICIENT_API_KEY_PERMISSIONS": {
                "code": "INSUFFICIENT_API_KEY_PERMISSIONS",
                "message": "You do not have permission to perform this action on organization API keys."
              },
              "NO_DEFAULT_API_KEY_CONFIGURATION_FOUND": {
                "code": "NO_DEFAULT_API_KEY_CONFIGURATION_FOUND",
                "message": "No default api-key configuration found."
              },
              "ORGANIZATION_PLUGIN_REQUIRED": {
                "code": "ORGANIZATION_PLUGIN_REQUIRED",
                "message": "Organization plugin is required for organization-owned API keys. Please install and configure the organization plugin."
              }
            },
            "hooks": {
              "before": [
                {}
              ]
            },
            "endpoints": {},
            "schema": {
              "apikey": {
                "fields": {
                  "configId": {
                    "type": "string",
                    "required": true,
                    "defaultValue": "default",
                    "input": false,
                    "index": true
                  },
                  "name": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "start": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "referenceId": {
                    "type": "string",
                    "required": true,
                    "input": false,
                    "index": true
                  },
                  "prefix": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "key": {
                    "type": "string",
                    "required": true,
                    "input": false,
                    "index": true
                  },
                  "refillInterval": {
                    "type": "number",
                    "required": false,
                    "input": false
                  },
                  "refillAmount": {
                    "type": "number",
                    "required": false,
                    "input": false
                  },
                  "lastRefillAt": {
                    "type": "date",
                    "required": false,
                    "input": false
                  },
                  "enabled": {
                    "type": "boolean",
                    "required": false,
                    "input": false,
                    "defaultValue": true
                  },
                  "rateLimitEnabled": {
                    "type": "boolean",
                    "required": false,
                    "input": false,
                    "defaultValue": true
                  },
                  "rateLimitTimeWindow": {
                    "type": "number",
                    "required": false,
                    "input": false,
                    "defaultValue": 86400000
                  },
                  "rateLimitMax": {
                    "type": "number",
                    "required": false,
                    "input": false,
                    "defaultValue": 10
                  },
                  "requestCount": {
                    "type": "number",
                    "required": false,
                    "input": false,
                    "defaultValue": 0
                  },
                  "remaining": {
                    "type": "number",
                    "required": false,
                    "input": false
                  },
                  "lastRequest": {
                    "type": "date",
                    "required": false,
                    "input": false
                  },
                  "expiresAt": {
                    "type": "date",
                    "required": false,
                    "input": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": true,
                    "input": false
                  },
                  "updatedAt": {
                    "type": "date",
                    "required": true,
                    "input": false
                  },
                  "permissions": {
                    "type": "string",
                    "required": false,
                    "input": false
                  },
                  "metadata": {
                    "type": "string",
                    "required": false,
                    "input": true,
                    "transform": {}
                  }
                }
              }
            },
            "configurations": [
              {
                "apiKeyHeaders": [
                  "x-api-key"
                ],
                "enableMetadata": true,
                "enableSessionForAPIKeys": true,
                "defaultKeyLength": 64,
                "maximumPrefixLength": 32,
                "minimumPrefixLength": 1,
                "maximumNameLength": 32,
                "minimumNameLength": 1,
                "disableKeyHashing": false,
                "requireName": false,
                "storage": "database",
                "rateLimit": {
                  "enabled": true,
                  "timeWindow": 86400000,
                  "maxRequests": 10
                },
                "keyExpiration": {
                  "defaultExpiresIn": null,
                  "disableCustomExpiresTime": false,
                  "maxExpiresIn": 365,
                  "minExpiresIn": 1
                },
                "startingCharactersConfig": {
                  "shouldStore": true,
                  "charactersLength": 6
                },
                "fallbackToDatabase": false,
                "deferUpdates": false
              }
            ]
          }
        },
        {
          "name": "next-cookies",
          "config": {
            "id": "next-cookies",
            "hooks": {
              "before": [
                {}
              ],
              "after": [
                {}
              ]
            }
          }
        },
        {
          "name": "passkey",
          "config": {
            "id": "passkey",
            "endpoints": {},
            "schema": {
              "passkey": {
                "fields": {
                  "name": {
                    "type": "string",
                    "required": false
                  },
                  "publicKey": {
                    "type": "string",
                    "required": true
                  },
                  "userId": {
                    "type": "string",
                    "references": {
                      "model": "user",
                      "field": "id"
                    },
                    "required": true,
                    "index": true
                  },
                  "credentialID": {
                    "type": "string",
                    "required": true,
                    "index": true
                  },
                  "counter": {
                    "type": "number",
                    "required": true
                  },
                  "deviceType": {
                    "type": "string",
                    "required": true
                  },
                  "backedUp": {
                    "type": "boolean",
                    "required": true
                  },
                  "transports": {
                    "type": "string",
                    "required": false
                  },
                  "createdAt": {
                    "type": "date",
                    "required": false
                  },
                  "aaguid": {
                    "type": "string",
                    "required": false
                  }
                }
              }
            },
            "$ERROR_CODES": {
              "CHALLENGE_NOT_FOUND": {
                "code": "CHALLENGE_NOT_FOUND",
                "message": "Challenge not found"
              },
              "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": {
                "code": "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY",
                "message": "You are not allowed to register this passkey"
              },
              "FAILED_TO_VERIFY_REGISTRATION": {
                "code": "FAILED_TO_VERIFY_REGISTRATION",
                "message": "Failed to verify registration"
              },
              "PASSKEY_NOT_FOUND": {
                "code": "PASSKEY_NOT_FOUND",
                "message": "Passkey not found"
              },
              "AUTHENTICATION_FAILED": {
                "code": "AUTHENTICATION_FAILED",
                "message": "Authentication failed"
              },
              "UNABLE_TO_CREATE_SESSION": {
                "code": "UNABLE_TO_CREATE_SESSION",
                "message": "Unable to create session"
              },
              "FAILED_TO_UPDATE_PASSKEY": {
                "code": "FAILED_TO_UPDATE_PASSKEY",
                "message": "Failed to update passkey"
              }
            },
            "options": {
              "rpName": "[REDACTED]",
              "rpID": "localhost"
            }
          }
        }
      ],
      "advanced": {
        "database": {}
      },
      "experimental": {
        "joins": true
      }
    }
  }
}

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

Backend

Auth config (if applicable)

import { betterAuth } from "better-auth"
export const auth = betterAuth({
  // …
  database: drizzleAdapter(db, {
    provider: "pg",
    usePlural: true,
    schema
  }),
  // …
});

Additional context

No response

Originally created by @wottpal on GitHub (Mar 1, 2026). Original GitHub issue: https://github.com/better-auth/better-auth/issues/8228 Originally assigned to: @himself65 on GitHub. ### Is this suited for github? - [x] Yes, this is suited for github ### To Reproduce We've upgraded to `1.5.0` and ran a new schema generation which want's to remove the `verifications` table. Even if I bring it back manually and don't drop it from the database (= don't apply migration), magic sign in has stopped working, we get the following error upon magic link sign in: ``` next:dev | 2026-03-01T05:22:54.919Z ERROR [Better Auth]: BetterAuthError [Error [BetterAuthError]: Model "verification" not found in schema] next:dev | # SERVER_ERROR: [Error [BetterAuthError]: Model "verification" not found in schema] next:dev | GET /api/auth/magic-link/verify?token=6cbc450e42e8850cd5d6b1808faee6c6&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 93ms (compile: 6ms, render: 87ms) ``` Also, it seems that it could also be related with `usePlural: true` (because it is searching for `verification` even though we use pluralization). Even if i try to manually give it the `verification` singular like below it doesn't work: ``` schema: { ...schema, verification: schema.verifications, }, ``` <img width="1116" height="662" alt="Image" src="https://github.com/user-attachments/assets/940011c3-2c1a-4bd2-ba25-0d9e6b013993" /> ### Current vs. Expected behavior * I expect the `verifications` table not to be removed. * I expect magic sign-in's to continue working. ### What version of Better Auth are you using? 1.5.0 ### System info ```bash { "system": { "platform": "darwin", "arch": "arm64", "version": "Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041", "release": "25.3.0", "cpuCount": 16, "cpuModel": "Apple M4 Max", "totalMemory": "128.00 GB", "freeMemory": "68.45 GB" }, "node": { "version": "v24.14.0", "env": "development" }, "packageManager": { "name": "bun", "version": "1.3.10" }, "frameworks": [ { "name": "next", "version": "^16.1.6" }, { "name": "react", "version": "^19.2.4" } ], "databases": [ { "name": "pg", "version": "^8.19.0" }, { "name": "postgres", "version": "^3.4.8" }, { "name": "drizzle", "version": "^0.45.1" } ], "betterAuth": { "version": "^1.5.0", "config": { "appName": "[REDACTED]", "baseURL": "http://localhost:3000", "secret": "[REDACTED]", "secondaryStorage": {}, "rateLimit": { "storage": "secondary-storage" }, "session": { "cookieCache": { "enabled": true, "maxAge": 300 } }, "user": { "additionalFields": { "firstName": { "type": "string", "required": true }, "lastName": { "type": "string", "required": true }, "creatorProfileId": { "type": "string", "required": false, "input": false, "unique": true } }, "changeEmail": { "enabled": true } }, "account": { "accountLinking": { "enabled": true, "allowDifferentEmails": true, "allowUnlinkingAll": true } }, "socialProviders": { "google": { "clientId": "[REDACTED]", "clientSecret": "[REDACTED]" } }, "hooks": {}, "databaseHooks": { "session": { "update": {}, "create": {} }, "user": { "create": {}, "update": {} } }, "plugins": [ { "name": "magic-link", "config": { "id": "magic-link", "endpoints": {}, "rateLimit": [ { "window": 60, "max": 5 } ], "options": {} } }, { "name": "organization", "config": { "id": "organization", "endpoints": {}, "schema": { "organization": { "fields": { "name": { "type": "string", "required": true, "sortable": true }, "slug": { "type": "string", "required": true, "unique": true, "sortable": true, "index": true }, "logo": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": true }, "metadata": { "type": "string", "required": false }, "url": { "type": "string", "required": false, "input": true } } }, "member": { "modelName": "orgMember", "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "userId": { "type": "string", "required": true, "references": { "model": "user", "field": "id" }, "index": true }, "role": { "type": "string", "required": true, "sortable": true, "defaultValue": "member" }, "createdAt": { "type": "date", "required": true } } }, "invitation": { "modelName": "orgInvitation", "fields": { "organizationId": { "type": "string", "required": true, "references": { "model": "organization", "field": "id" }, "index": true }, "email": { "type": "string", "required": true, "sortable": true, "index": true }, "role": { "type": "string", "required": false, "sortable": true }, "status": { "type": "string", "required": true, "sortable": true, "defaultValue": "pending" }, "expiresAt": { "type": "date", "required": true }, "createdAt": { "type": "date", "required": true }, "inviterId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true } } }, "session": { "fields": { "activeOrganizationId": { "type": "string", "required": false } } } }, "$Infer": { "Organization": {}, "Invitation": {}, "Member": {}, "Team": {}, "TeamMember": {}, "ActiveOrganization": {} }, "$ERROR_CODES": { "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_ORGANIZATION", "message": "You are not allowed to create a new organization" }, "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS": { "code": "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_ORGANIZATIONS", "message": "You have reached the maximum number of organizations" }, "ORGANIZATION_ALREADY_EXISTS": { "code": "ORGANIZATION_ALREADY_EXISTS", "message": "Organization already exists" }, "ORGANIZATION_SLUG_ALREADY_TAKEN": { "code": "ORGANIZATION_SLUG_ALREADY_TAKEN", "message": "Organization slug already taken" }, "ORGANIZATION_NOT_FOUND": { "code": "ORGANIZATION_NOT_FOUND", "message": "Organization not found" }, "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION": { "code": "USER_IS_NOT_A_MEMBER_OF_THE_ORGANIZATION", "message": "User is not a member of the organization" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_ORGANIZATION", "message": "You are not allowed to update this organization" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_ORGANIZATION", "message": "You are not allowed to delete this organization" }, "NO_ACTIVE_ORGANIZATION": { "code": "NO_ACTIVE_ORGANIZATION", "message": "No active organization" }, "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION": { "code": "USER_IS_ALREADY_A_MEMBER_OF_THIS_ORGANIZATION", "message": "User is already a member of this organization" }, "MEMBER_NOT_FOUND": { "code": "MEMBER_NOT_FOUND", "message": "Member not found" }, "ROLE_NOT_FOUND": { "code": "ROLE_NOT_FOUND", "message": "Role not found" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM", "message": "You are not allowed to create a new team" }, "TEAM_ALREADY_EXISTS": { "code": "TEAM_ALREADY_EXISTS", "message": "Team already exists" }, "TEAM_NOT_FOUND": { "code": "TEAM_NOT_FOUND", "message": "Team not found" }, "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER": { "code": "YOU_CANNOT_LEAVE_THE_ORGANIZATION_AS_THE_ONLY_OWNER", "message": "You cannot leave the organization as the only owner" }, "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER": { "code": "YOU_CANNOT_LEAVE_THE_ORGANIZATION_WITHOUT_AN_OWNER", "message": "You cannot leave the organization without an owner" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_MEMBER", "message": "You are not allowed to delete this member" }, "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_INVITE_USERS_TO_THIS_ORGANIZATION", "message": "You are not allowed to invite users to this organization" }, "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION": { "code": "USER_IS_ALREADY_INVITED_TO_THIS_ORGANIZATION", "message": "User is already invited to this organization" }, "INVITATION_NOT_FOUND": { "code": "INVITATION_NOT_FOUND", "message": "Invitation not found" }, "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION": { "code": "YOU_ARE_NOT_THE_RECIPIENT_OF_THE_INVITATION", "message": "You are not the recipient of the invitation" }, "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION": { "code": "EMAIL_VERIFICATION_REQUIRED_BEFORE_ACCEPTING_OR_REJECTING_INVITATION", "message": "Email verification required before accepting or rejecting invitation" }, "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_CANCEL_THIS_INVITATION", "message": "You are not allowed to cancel this invitation" }, "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION": { "code": "INVITER_IS_NO_LONGER_A_MEMBER_OF_THE_ORGANIZATION", "message": "Inviter is no longer a member of the organization" }, "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_INVITE_USER_WITH_THIS_ROLE", "message": "You are not allowed to invite a user with this role" }, "FAILED_TO_RETRIEVE_INVITATION": { "code": "FAILED_TO_RETRIEVE_INVITATION", "message": "Failed to retrieve invitation" }, "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS": { "code": "YOU_HAVE_REACHED_THE_MAXIMUM_NUMBER_OF_TEAMS", "message": "You have reached the maximum number of teams" }, "UNABLE_TO_REMOVE_LAST_TEAM": { "code": "UNABLE_TO_REMOVE_LAST_TEAM", "message": "Unable to remove last team" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_MEMBER", "message": "You are not allowed to update this member" }, "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED": { "code": "ORGANIZATION_MEMBERSHIP_LIMIT_REACHED", "message": "Organization membership limit reached" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_TEAMS_IN_THIS_ORGANIZATION", "message": "You are not allowed to create teams in this organization" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_TEAMS_IN_THIS_ORGANIZATION", "message": "You are not allowed to delete teams in this organization" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_THIS_TEAM", "message": "You are not allowed to update this team" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_THIS_TEAM", "message": "You are not allowed to delete this team" }, "INVITATION_LIMIT_REACHED": { "code": "INVITATION_LIMIT_REACHED", "message": "Invitation limit reached" }, "TEAM_MEMBER_LIMIT_REACHED": { "code": "TEAM_MEMBER_LIMIT_REACHED", "message": "Team member limit reached" }, "USER_IS_NOT_A_MEMBER_OF_THE_TEAM": { "code": "USER_IS_NOT_A_MEMBER_OF_THE_TEAM", "message": "User is not a member of the team" }, "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM": { "code": "YOU_CAN_NOT_ACCESS_THE_MEMBERS_OF_THIS_TEAM", "message": "You are not allowed to list the members of this team" }, "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM": { "code": "YOU_DO_NOT_HAVE_AN_ACTIVE_TEAM", "message": "You do not have an active team" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_NEW_TEAM_MEMBER", "message": "You are not allowed to create a new member" }, "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER": { "code": "YOU_ARE_NOT_ALLOWED_TO_REMOVE_A_TEAM_MEMBER", "message": "You are not allowed to remove a team member" }, "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_ALLOWED_TO_ACCESS_THIS_ORGANIZATION", "message": "You are not allowed to access this organization as an owner" }, "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION": { "code": "YOU_ARE_NOT_A_MEMBER_OF_THIS_ORGANIZATION", "message": "You are not a member of this organization" }, "MISSING_AC_INSTANCE": { "code": "MISSING_AC_INSTANCE", "message": "Dynamic Access Control requires a pre-defined ac instance on the server auth plugin. Read server logs for more information" }, "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE": { "code": "YOU_MUST_BE_IN_AN_ORGANIZATION_TO_CREATE_A_ROLE", "message": "You must be in an organization to create a role" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_A_ROLE", "message": "You are not allowed to create a role" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_A_ROLE", "message": "You are not allowed to update a role" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_A_ROLE", "message": "You are not allowed to delete a role" }, "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_READ_A_ROLE", "message": "You are not allowed to read a role" }, "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_A_ROLE", "message": "You are not allowed to list a role" }, "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_GET_A_ROLE", "message": "You are not allowed to get a role" }, "TOO_MANY_ROLES": { "code": "TOO_MANY_ROLES", "message": "This organization has too many roles" }, "INVALID_RESOURCE": { "code": "INVALID_RESOURCE", "message": "The provided permission includes an invalid resource" }, "ROLE_NAME_IS_ALREADY_TAKEN": { "code": "ROLE_NAME_IS_ALREADY_TAKEN", "message": "That role name is already taken" }, "CANNOT_DELETE_A_PRE_DEFINED_ROLE": { "code": "CANNOT_DELETE_A_PRE_DEFINED_ROLE", "message": "Cannot delete a pre-defined role" }, "ROLE_IS_ASSIGNED_TO_MEMBERS": { "code": "ROLE_IS_ASSIGNED_TO_MEMBERS", "message": "Cannot delete a role that is assigned to members. Please reassign the members to a different role first" } }, "options": { "ac": { "statements": { "organization": [ "update", "delete" ], "member": [ "create", "update", "delete" ], "invitation": [ "create", "cancel" ], "team": [ "create", "update", "delete" ], "ac": [ "create", "read", "update", "delete" ] } }, "roles": { "owner": { "statements": { "organization": [ "update", "delete" ], "member": [ "create", "update", "delete" ], "invitation": [ "create", "cancel" ], "team": [ "create", "update", "delete" ], "ac": [ "create", "read", "update", "delete" ] } }, "admin": { "statements": { "organization": [ "update" ], "invitation": [ "create", "cancel" ], "member": [ "create", "update", "delete" ], "team": [ "create", "update", "delete" ], "ac": [ "create", "read", "update", "delete" ] } }, "member": { "statements": { "organization": [], "member": [], "invitation": [], "team": [], "ac": [ "read" ] } }, "viewer": { "statements": { "organization": [], "member": [], "invitation": [], "team": [] } }, "creator": { "statements": { "organization": [], "member": [], "invitation": [], "team": [] } } }, "invitationExpiresIn": 604800, "cancelPendingInvitationsOnReInvite": true, "membershipLimit": 9007199254740991, "schema": { "invitation": { "modelName": "orgInvitation" }, "member": { "modelName": "orgMember" }, "organization": { "additionalFields": { "url": { "type": "string", "required": false, "input": true } } } }, "organizationHooks": {} } } }, { "name": "admin", "config": { "id": "admin", "hooks": { "after": [ {} ] }, "endpoints": {}, "$ERROR_CODES": { "FAILED_TO_CREATE_USER": { "code": "FAILED_TO_CREATE_USER", "message": "Failed to create user" }, "USER_ALREADY_EXISTS": { "code": "USER_ALREADY_EXISTS", "message": "User already exists." }, "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL": { "code": "USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL", "message": "User already exists. Use another email." }, "YOU_CANNOT_BAN_YOURSELF": { "code": "YOU_CANNOT_BAN_YOURSELF", "message": "You cannot ban yourself" }, "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE": { "code": "YOU_ARE_NOT_ALLOWED_TO_CHANGE_USERS_ROLE", "message": "You are not allowed to change users role" }, "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_CREATE_USERS", "message": "You are not allowed to create users" }, "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS", "message": "You are not allowed to list users" }, "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS": { "code": "YOU_ARE_NOT_ALLOWED_TO_LIST_USERS_SESSIONS", "message": "You are not allowed to list users sessions" }, "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_BAN_USERS", "message": "You are not allowed to ban users" }, "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_IMPERSONATE_USERS", "message": "You are not allowed to impersonate users" }, "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS": { "code": "YOU_ARE_NOT_ALLOWED_TO_REVOKE_USERS_SESSIONS", "message": "You are not allowed to revoke users sessions" }, "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_DELETE_USERS", "message": "You are not allowed to delete users" }, "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD": { "code": "YOU_ARE_NOT_ALLOWED_TO_SET_USERS_PASSWORD", "message": "You are not allowed to set users password" }, "BANNED_USER": { "code": "BANNED_USER", "message": "You have been banned from this application" }, "YOU_ARE_NOT_ALLOWED_TO_GET_USER": { "code": "YOU_ARE_NOT_ALLOWED_TO_GET_USER", "message": "You are not allowed to get user" }, "NO_DATA_TO_UPDATE": { "code": "NO_DATA_TO_UPDATE", "message": "No data to update" }, "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS": { "code": "YOU_ARE_NOT_ALLOWED_TO_UPDATE_USERS", "message": "You are not allowed to update users" }, "YOU_CANNOT_REMOVE_YOURSELF": { "code": "YOU_CANNOT_REMOVE_YOURSELF", "message": "You cannot remove yourself" }, "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE": { "code": "YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE", "message": "You are not allowed to set a non-existent role value" }, "YOU_CANNOT_IMPERSONATE_ADMINS": { "code": "YOU_CANNOT_IMPERSONATE_ADMINS", "message": "You cannot impersonate admins" }, "INVALID_ROLE_TYPE": { "code": "INVALID_ROLE_TYPE", "message": "Invalid role type" } }, "schema": { "user": { "fields": { "role": { "type": "string", "required": false, "input": false }, "banned": { "type": "boolean", "defaultValue": false, "required": false, "input": false }, "banReason": { "type": "string", "required": false, "input": false }, "banExpires": { "type": "date", "required": false, "input": false } } }, "session": { "fields": { "impersonatedBy": { "type": "string", "required": false } } } }, "options": { "adminUserIds": [ "user_TpCXtDU74dCr", "user_oopmG2v7mgRG", "user_zX1CZ1MqaHXq" ], "impersonationSessionDuration": 3600 } } }, { "name": "mcp", "config": { "id": "mcp", "hooks": { "after": [ {} ] }, "endpoints": {}, "schema": { "oauthApplication": { "modelName": "oauthApplication", "fields": { "name": { "type": "string" }, "icon": { "type": "string", "required": false }, "metadata": { "type": "string", "required": false }, "clientId": { "type": "string", "unique": true }, "clientSecret": { "type": "string", "required": false }, "redirectUrls": { "type": "string" }, "type": { "type": "string" }, "disabled": { "type": "boolean", "required": false, "defaultValue": false }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthAccessToken": { "modelName": "oauthAccessToken", "fields": { "accessToken": { "type": "string", "unique": true }, "refreshToken": { "type": "string", "unique": true }, "accessTokenExpiresAt": { "type": "date" }, "refreshTokenExpiresAt": { "type": "date" }, "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "required": false, "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" } } }, "oauthConsent": { "modelName": "oauthConsent", "fields": { "clientId": { "type": "string", "references": { "model": "oauthApplication", "field": "clientId", "onDelete": "cascade" }, "index": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id", "onDelete": "cascade" }, "index": true }, "scopes": { "type": "string" }, "createdAt": { "type": "date" }, "updatedAt": { "type": "date" }, "consentGiven": { "type": "boolean" } } } }, "options": { "loginPage": "/auth/sign-up", "oidcConfig": { "loginPage": "/auth/sign-up" } } } }, { "name": "api-key", "config": { "id": "api-key", "$ERROR_CODES": { "INVALID_METADATA_TYPE": { "code": "INVALID_METADATA_TYPE", "message": "metadata must be an object or undefined" }, "REFILL_AMOUNT_AND_INTERVAL_REQUIRED": { "code": "REFILL_AMOUNT_AND_INTERVAL_REQUIRED", "message": "refillAmount is required when refillInterval is provided" }, "REFILL_INTERVAL_AND_AMOUNT_REQUIRED": { "code": "REFILL_INTERVAL_AND_AMOUNT_REQUIRED", "message": "refillInterval is required when refillAmount is provided" }, "USER_BANNED": { "code": "USER_BANNED", "message": "User is banned" }, "UNAUTHORIZED_SESSION": { "code": "UNAUTHORIZED_SESSION", "message": "Unauthorized or invalid session" }, "KEY_NOT_FOUND": { "code": "KEY_NOT_FOUND", "message": "API Key not found" }, "KEY_DISABLED": { "code": "KEY_DISABLED", "message": "API Key is disabled" }, "KEY_EXPIRED": { "code": "KEY_EXPIRED", "message": "API Key has expired" }, "USAGE_EXCEEDED": { "code": "USAGE_EXCEEDED", "message": "API Key has reached its usage limit" }, "KEY_NOT_RECOVERABLE": { "code": "KEY_NOT_RECOVERABLE", "message": "API Key is not recoverable" }, "EXPIRES_IN_IS_TOO_SMALL": { "code": "EXPIRES_IN_IS_TOO_SMALL", "message": "The expiresIn is smaller than the predefined minimum value." }, "EXPIRES_IN_IS_TOO_LARGE": { "code": "EXPIRES_IN_IS_TOO_LARGE", "message": "The expiresIn is larger than the predefined maximum value." }, "INVALID_REMAINING": { "code": "INVALID_REMAINING", "message": "The remaining count is either too large or too small." }, "INVALID_PREFIX_LENGTH": { "code": "INVALID_PREFIX_LENGTH", "message": "The prefix length is either too large or too small." }, "INVALID_NAME_LENGTH": { "code": "INVALID_NAME_LENGTH", "message": "The name length is either too large or too small." }, "METADATA_DISABLED": { "code": "METADATA_DISABLED", "message": "Metadata is disabled." }, "RATE_LIMIT_EXCEEDED": { "code": "RATE_LIMIT_EXCEEDED", "message": "Rate limit exceeded." }, "NO_VALUES_TO_UPDATE": { "code": "NO_VALUES_TO_UPDATE", "message": "No values to update." }, "KEY_DISABLED_EXPIRATION": { "code": "KEY_DISABLED_EXPIRATION", "message": "Custom key expiration values are disabled." }, "INVALID_API_KEY": { "code": "INVALID_API_KEY", "message": "Invalid API key." }, "INVALID_USER_ID_FROM_API_KEY": { "code": "INVALID_USER_ID_FROM_API_KEY", "message": "The user id from the API key is invalid." }, "INVALID_REFERENCE_ID_FROM_API_KEY": { "code": "INVALID_REFERENCE_ID_FROM_API_KEY", "message": "The reference id from the API key is invalid." }, "INVALID_API_KEY_GETTER_RETURN_TYPE": { "code": "INVALID_API_KEY_GETTER_RETURN_TYPE", "message": "API Key getter returned an invalid key type. Expected string." }, "SERVER_ONLY_PROPERTY": { "code": "SERVER_ONLY_PROPERTY", "message": "The property you're trying to set can only be set from the server auth instance only." }, "FAILED_TO_UPDATE_API_KEY": { "code": "FAILED_TO_UPDATE_API_KEY", "message": "Failed to update API key" }, "NAME_REQUIRED": { "code": "NAME_REQUIRED", "message": "API Key name is required." }, "ORGANIZATION_ID_REQUIRED": { "code": "ORGANIZATION_ID_REQUIRED", "message": "Organization ID is required for organization-owned API keys." }, "USER_NOT_MEMBER_OF_ORGANIZATION": { "code": "USER_NOT_MEMBER_OF_ORGANIZATION", "message": "You are not a member of the organization that owns this API key." }, "INSUFFICIENT_API_KEY_PERMISSIONS": { "code": "INSUFFICIENT_API_KEY_PERMISSIONS", "message": "You do not have permission to perform this action on organization API keys." }, "NO_DEFAULT_API_KEY_CONFIGURATION_FOUND": { "code": "NO_DEFAULT_API_KEY_CONFIGURATION_FOUND", "message": "No default api-key configuration found." }, "ORGANIZATION_PLUGIN_REQUIRED": { "code": "ORGANIZATION_PLUGIN_REQUIRED", "message": "Organization plugin is required for organization-owned API keys. Please install and configure the organization plugin." } }, "hooks": { "before": [ {} ] }, "endpoints": {}, "schema": { "apikey": { "fields": { "configId": { "type": "string", "required": true, "defaultValue": "default", "input": false, "index": true }, "name": { "type": "string", "required": false, "input": false }, "start": { "type": "string", "required": false, "input": false }, "referenceId": { "type": "string", "required": true, "input": false, "index": true }, "prefix": { "type": "string", "required": false, "input": false }, "key": { "type": "string", "required": true, "input": false, "index": true }, "refillInterval": { "type": "number", "required": false, "input": false }, "refillAmount": { "type": "number", "required": false, "input": false }, "lastRefillAt": { "type": "date", "required": false, "input": false }, "enabled": { "type": "boolean", "required": false, "input": false, "defaultValue": true }, "rateLimitEnabled": { "type": "boolean", "required": false, "input": false, "defaultValue": true }, "rateLimitTimeWindow": { "type": "number", "required": false, "input": false, "defaultValue": 86400000 }, "rateLimitMax": { "type": "number", "required": false, "input": false, "defaultValue": 10 }, "requestCount": { "type": "number", "required": false, "input": false, "defaultValue": 0 }, "remaining": { "type": "number", "required": false, "input": false }, "lastRequest": { "type": "date", "required": false, "input": false }, "expiresAt": { "type": "date", "required": false, "input": false }, "createdAt": { "type": "date", "required": true, "input": false }, "updatedAt": { "type": "date", "required": true, "input": false }, "permissions": { "type": "string", "required": false, "input": false }, "metadata": { "type": "string", "required": false, "input": true, "transform": {} } } } }, "configurations": [ { "apiKeyHeaders": [ "x-api-key" ], "enableMetadata": true, "enableSessionForAPIKeys": true, "defaultKeyLength": 64, "maximumPrefixLength": 32, "minimumPrefixLength": 1, "maximumNameLength": 32, "minimumNameLength": 1, "disableKeyHashing": false, "requireName": false, "storage": "database", "rateLimit": { "enabled": true, "timeWindow": 86400000, "maxRequests": 10 }, "keyExpiration": { "defaultExpiresIn": null, "disableCustomExpiresTime": false, "maxExpiresIn": 365, "minExpiresIn": 1 }, "startingCharactersConfig": { "shouldStore": true, "charactersLength": 6 }, "fallbackToDatabase": false, "deferUpdates": false } ] } }, { "name": "next-cookies", "config": { "id": "next-cookies", "hooks": { "before": [ {} ], "after": [ {} ] } } }, { "name": "passkey", "config": { "id": "passkey", "endpoints": {}, "schema": { "passkey": { "fields": { "name": { "type": "string", "required": false }, "publicKey": { "type": "string", "required": true }, "userId": { "type": "string", "references": { "model": "user", "field": "id" }, "required": true, "index": true }, "credentialID": { "type": "string", "required": true, "index": true }, "counter": { "type": "number", "required": true }, "deviceType": { "type": "string", "required": true }, "backedUp": { "type": "boolean", "required": true }, "transports": { "type": "string", "required": false }, "createdAt": { "type": "date", "required": false }, "aaguid": { "type": "string", "required": false } } } }, "$ERROR_CODES": { "CHALLENGE_NOT_FOUND": { "code": "CHALLENGE_NOT_FOUND", "message": "Challenge not found" }, "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY": { "code": "YOU_ARE_NOT_ALLOWED_TO_REGISTER_THIS_PASSKEY", "message": "You are not allowed to register this passkey" }, "FAILED_TO_VERIFY_REGISTRATION": { "code": "FAILED_TO_VERIFY_REGISTRATION", "message": "Failed to verify registration" }, "PASSKEY_NOT_FOUND": { "code": "PASSKEY_NOT_FOUND", "message": "Passkey not found" }, "AUTHENTICATION_FAILED": { "code": "AUTHENTICATION_FAILED", "message": "Authentication failed" }, "UNABLE_TO_CREATE_SESSION": { "code": "UNABLE_TO_CREATE_SESSION", "message": "Unable to create session" }, "FAILED_TO_UPDATE_PASSKEY": { "code": "FAILED_TO_UPDATE_PASSKEY", "message": "Failed to update passkey" } }, "options": { "rpName": "[REDACTED]", "rpID": "localhost" } } } ], "advanced": { "database": {} }, "experimental": { "joins": true } } } } ``` ### Which area(s) are affected? (Select all that apply) Backend ### Auth config (if applicable) ```typescript import { betterAuth } from "better-auth" export const auth = betterAuth({ // … database: drizzleAdapter(db, { provider: "pg", usePlural: true, schema }), // … }); ``` ### Additional context _No response_
GiteaMirror added the lockedbug labels 2026-04-17 19:46:16 -05:00
Author
Owner

@dosubot[bot] commented on GitHub (Mar 1, 2026):

This looks like a known issue related to model name resolution, particularly when using usePlural with experimental: { joins: true } (which I see you have enabled).

Key findings:

  1. Recent fix for usePlural bugs: PR #8117 was merged on Feb 28, 2026, fixing critical bugs with db.query key mismatches when usePlural is configured. Since you're on 1.5.0, you may be hitting the bug this PR addresses. Check if there's a newer patch version available.

  2. Known issue with usePlural + experimental joins: When using usePlural: true with experimental joins, there are compatibility issues. The maintainers recommend either:

    • Disabling experimental joins (experimental: { joins: false })
    • Or fully adopting the CLI-generated schema pattern
  3. Model name configuration: If you have a custom schema, you may need to configure the model name explicitly. The suggestion is to use capitalized Prisma model names:

    schema: {
      verification: { modelName: "Verification" }, // Capitalized
    }
    

Quick workarounds to try:

  1. Try setting experimental: { joins: false } temporarily to confirm this is the issue
  2. Update to the latest 1.5.x patch if one is available (PR #8117 fix was very recent)
  3. Add explicit modelName configuration for the verification model

The magic link plugin internally always uses the singular model name "verification" and relies on the adapter layer to handle the plural table name resolution - which is where the bug seems to occur.

To reply, just mention @dosu.


How did I do? Good | Irrelevant | Incorrect | Verbose | Hallucination | Report 🐛 | Other

Chat with Dosu Open in Cursor Join Discord Share on X

<!-- gh-comment-id:3979193452 --> @dosubot[bot] commented on GitHub (Mar 1, 2026): <!-- Answer --> This looks like a known issue related to model name resolution, particularly when using `usePlural` with `experimental: { joins: true }` (which I see you have enabled). **Key findings:** 1. **Recent fix for `usePlural` bugs**: [PR #8117](https://github.com/better-auth/better-auth/pull/8117) was merged on Feb 28, 2026, fixing critical bugs with `db.query` key mismatches when `usePlural` is configured. Since you're on `1.5.0`, you may be hitting the bug this PR addresses. Check if there's a newer patch version available. 2. **Known issue with `usePlural` + experimental joins**: [When using `usePlural: true` with experimental joins](https://github.com/better-auth/better-auth/issues/7814#issuecomment-3875742274), there are compatibility issues. The maintainers recommend either: - Disabling experimental joins (`experimental: { joins: false }`) - Or fully adopting the CLI-generated schema pattern 3. **Model name configuration**: If you have a custom schema, you may need to configure the model name explicitly. [The suggestion is to use capitalized Prisma model names](https://github.com/better-auth/better-auth/issues/1901#issuecomment-2883333084): ```typescript schema: { verification: { modelName: "Verification" }, // Capitalized } ``` **Quick workarounds to try:** 1. Try setting `experimental: { joins: false }` temporarily to confirm this is the issue 2. Update to the latest 1.5.x patch if one is available (PR #8117 fix was very recent) 3. Add explicit modelName configuration for the verification model The magic link plugin internally always uses the singular model name "verification" and relies on the adapter layer to handle the plural table name resolution - which is where the bug seems to occur. <!-- 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/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=great_response) | [Irrelevant](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=irrelevant_answer) | [Incorrect](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=incorrect_sources) | [Verbose](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=hallucination) | [Report 🐛](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/503c2b25-1934-4aea-811c-448be28ffa02?feedback_type=other)</sup> [![Chat with Dosu](https://dosu.dev/dosu-chat-badge.svg)](https://app.dosu.dev/cdda13d9-dd27-4d31-b09a-5d8bec92de21/ask?utm_source=github)&nbsp;[![Open in Cursor](https://dosu.dev/dosu-in-cursor.svg)](https://cursor.com/link/prompt?text=This%20looks%20like%20a%20known%20issue%20related%20to%20model%20name%20resolution%2C%20particularly%20when%20using%20%60usePlural%60%20with%20%60experimental%3A%20%7B%20joins%3A%20true%20%7D%60%20%28which%20I%20see%20you%20have%20enabled%29.%0A%0A%2A%2AKey%20findings%3A%2A%2A%0A%0A1.%20%2A%2ARecent%20fix%20for%20%60usePlural%60%20bugs%2A%2A%3A%20%5BPR%20%238117%5D%28https%3A//github.com/better-auth/better-auth/pull/8117%29%20was%20merged%20on%20Feb%2028%2C%202026%2C%20fixing%20critical%20bugs%20with%20%60db.query%60%20key%20mismatches%20when%20%60usePlural%60%20is%20configured.%20Since%20you%27re%20on%20%601.5.0%60%2C%20you%20may%20be%20hitting%20the%20bug%20this%20PR%20addresses.%20Check%20if%20there%27s%20a%20newer%20patch%20version%20available.%0A%0A2.%20%2A%2AKnown%20issue%20with%20%60usePlural%60%20%2B%20experimental%20joins%2A%2A%3A%20%5BWhen%20using%20%60usePlural%3A%20true%60%20with%20experimental%20joins%5D%28https%3A//github.com/better-auth/better-auth/issues/7814%23issuecomment-3875742274%29%2C%20there%20are%20compatibility%20issues.%20The%20maintainers%20recommend%20either%3A%0A%20%20%20-%20Disabling%20experimental%20joins%20%28%60experimental%3A%20%7B%20joins%3A%20false%20%7D%60%29%0A%20%20%20-%20Or%20fully%20adopting%20the%20CLI-generated%20schema%20pattern%0A%0A3.%20%2A%2AModel%20name%20configuration%2A%2A%3A%20If%20you%20have%20a%20custom%20schema%2C%20you%20may%20need%20to%20configure%20the%20model%20name%20explicitly.%20%5BThe%20suggestion%20is%20to%20use%20capitalized%20Prisma%20model%20names%5D%28https%3A//github.com/better-auth/better-auth/issues/1901%23issuecomment-2883333084%29%3A%0A%20%20%20%60%60%60typescript%0A%20%20%20schema%3A%20%7B%0A%20%20%20%20%20verification%3A%20%7B%20modelName%3A%20%22Verification%22%20%7D%2C%20//%20Capitalized%0A%20%20%20%7D%0A%20%20%20%60%60%60%0A%0A%2A%2AQuick%20workarounds%20to%20try%3A%2A%2A%0A1.%20Try%20setting%20%60experimental%3A%20%7B%20joins%3A%20false%20%7D%60%20temporarily%20to%20confirm%20this%20is%20the%20issue%0A2.%20Update%20to%20the%20latest%201.5.x%20patch%20if%20one%20is%20available%20%28PR%20%238117%20fix%20was%20very%20recent%29%0A3.%20Add%20explicit%20modelName%20configuration%20for%20the%20verification%20model%0A%0AThe%20magic%20link%20plugin%20internally%20always%20uses%20the%20singular%20model%20name%20%22verification%22%20and%20relies%20on%20the%20adapter%20layer%20to%20handle%20the%20plural%20table%20name%20resolution%20-%20which%20is%20where%20the%20bug%20seems%20to%20occur.)&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/8228)
Author
Owner

@wottpal commented on GitHub (Mar 1, 2026):

Further investigations:

  • Setting experimental: { joins: false } doesn't fix the issue (persists).
  • It seems as https://github.com/better-auth/better-auth/pull/8117 is already merged within 1.5.0, so this didn't help either (or maybe even introduced the issue).
  • If I add logging to internal getQueryModel.findOne I can see that it is not triggered upon magic link verification sign-in before the error occurs (it's indeed triggered in a lot of other contexts).
<!-- gh-comment-id:3979212913 --> @wottpal commented on GitHub (Mar 1, 2026): Further investigations: * Setting `experimental: { joins: false }` **doesn't fix the issue** (persists). * It seems as https://github.com/better-auth/better-auth/pull/8117 is already merged within `1.5.0`, so this didn't help either (or maybe even introduced the issue). * If I add logging to internal `getQueryModel.findOne` I can see that **it is not triggered** upon magic link verification sign-in before the error occurs (it's indeed triggered in a lot of other contexts).
Author
Owner

@ping-maxwell commented on GitHub (Mar 1, 2026):

@wottpal please give me your full auth config

<!-- gh-comment-id:3979223701 --> @ping-maxwell commented on GitHub (Mar 1, 2026): @wottpal please give me your full auth config
Author
Owner

@wottpal commented on GitHub (Mar 1, 2026):

@ping-maxwell happy to do so, but it might be a bit bloated 🤐

import { apiKey } from "@better-auth/api-key"
import { passkey } from "@better-auth/passkey"
import { type BaseModelNames, betterAuth, type LiteralUnion, type User } from "better-auth"
import { drizzleAdapter } from "better-auth/adapters/drizzle"
import { APIError, createAuthMiddleware } from "better-auth/api"
import { nextCookies } from "better-auth/next-js"
import { admin as adminPlugin, magicLink, mcp, organization } from "better-auth/plugins"
import { IMPERSONATION_COOKIE_TTL_SECONDS } from "@/app/(app)/app/(admin)/admin/users/atom"
import { config } from "@/config"
import * as schema from "@/drizzle/schema/auth"
import { env } from "@/env/server"
import { inngest } from "@/lib/inngest"
import { EventNames } from "@/lib/inngest/events"
import { getAbsoluteUrl } from "@/utils/url"
import { authCache } from "../cache/registry"
import { revalidate } from "../cache/revalidate"
import { db } from "../db"
import { newId } from "../db/utils/nanoid"
import { fbConversions } from "../facebook/conversions-client"
import { captureEvent, groupIdentify } from "../posthog/server"
import { redisAuthStorage } from "../redis"
import { passkeyRpId } from "./passkey-utils"
import { ac, roles } from "./roles"
import { MemberRole } from "./types"
import {
  getUserByEmailCached,
  getUserOrgsCached,
  isEmailAllowed,
  revalidateOrgOwnerCachesForUser,
} from "./utils"
import { generateToken } from "./verification-token"

export const auth = betterAuth({
  appName: config.site.name,
  baseURL: env.NEXT_PUBLIC_BASE_URL,
  secret: env.BETTER_AUTH_SECRET,
  database: drizzleAdapter(db, {
    provider: "pg",
    usePlural: true,
    schema,
    // DEBUG
    // debugLogs: !env.NEXT_PUBLIC_PRODUCTION_MODE,
  }),
  secondaryStorage: redisAuthStorage,
  rateLimit: {
    storage: "secondary-storage",
  },
  session: {
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // Keep session in cookie cache for 5 minutes
    },
  },
  user: {
    additionalFields: {
      firstName: {
        type: "string" as const,
        required: true,
      },
      lastName: {
        type: "string" as const,
        required: true,
      },
      creatorProfileId: {
        type: "string" as const,
        required: false,
        input: false,
        unique: true,
        // IMPORTANT: Needs to be commented out since a regression in Better Auth 1.14
        // references: { model: "creatorProfile", field: "id", onDelete: "set null" },
      },
    },
    changeEmail: {
      enabled: true,
      // TODO: Re-Enable Verification Email when fixed @ Better Auth
      // E.g. See https://github.com/better-auth/better-auth/issues/3424
      // sendChangeEmailVerification: async (data, request) => {
      //   console.log(`⏳ Triggering Inngest function: 'auth/change-email-verification.requested'…`)

      //   await inngest.send({
      //     name: EventNames.CHANGE_EMAIL_VERIFICATION_REQUESTED,
      //     data: {
      //       newEmail: data.newEmail,
      //       oldEmail: data.user.email,
      //       verificationUrl: data.url,
      //       userId: data.user.id,
      //     },
      //     user: {
      //       external_id: data.user.id,
      //       email: data.user.email,
      //     },
      //   })
      // },
    },
  },
  account: {
    accountLinking: {
      enabled: true,
      allowDifferentEmails: true,
      allowUnlinkingAll: true,
    },
  },
  socialProviders: {
    google: {
      clientId: env.GOOGLE_CLIENT_ID as string,
      clientSecret: env.GOOGLE_CLIENT_SECRET as string,
      mapProfileToUser: async (profile) => {
        const isAllowed = await isEmailAllowed(profile.email)
        if (!isAllowed) {
          throw new APIError("BAD_REQUEST", {
            message: "External sign-ups are disabled on this instance.",
          })
        }

        return {
          firstName: profile.given_name,
          lastName: profile.family_name || "", // IMPORTANT as family_name is not always set
        }
      },
    },
  },
  hooks: {
    before: createAuthMiddleware(async (ctx) => {
      const isMagicSignIn = ctx.path === "/sign-in/magic-link" && ctx.body?.mode === "signin"
      if (isMagicSignIn) {
        // Prevent sign-ins of external emails (in preview mode)
        const isAllowed = await isEmailAllowed(ctx.body?.email)
        if (!isAllowed) {
          throw new APIError("BAD_REQUEST", {
            message: "External sign-ins are disabled on this instance.",
          })
        }

        // Ensure magic link sign-ins have already signed-up before
        const email = ctx.body?.email
        if (!email) throw new APIError("BAD_REQUEST", { message: "Email is required" })
        if (!(await getUserByEmailCached(email)))
          throw new APIError("BAD_REQUEST", {
            message: "User not found. Please sign up first.",
          })
      }

      const isMagicSignUp = ctx.path === "/sign-in/magic-link" && ctx.body?.mode === "signup"
      if (isMagicSignUp) {
        // Prevent sign-ups of external emails (in preview mode)
        const isAllowed = await isEmailAllowed(ctx.body?.email)
        if (!isAllowed) {
          throw new APIError("BAD_REQUEST", {
            message: "External sign-ups are disabled on this instance.",
          })
        }

        // Prevent sign-up if user already exists
        const email = ctx.body?.email
        if (!email) throw new APIError("BAD_REQUEST", { message: "Email is required" })
        if (await getUserByEmailCached(email))
          throw new APIError("BAD_REQUEST", {
            message: "User already exists. Please sign in.",
          })
      }
    }),
  },
  databaseHooks: {
    session: {
      update: {
        after: async (session) => {
          // Update the last active organization ID in Redis
          try {
            // biome-ignore lint/suspicious/noExplicitAny: session is not correctly typed
            const activeOrganizationId = (session as any)?.activeOrganizationId
            if (activeOrganizationId) {
              const key = `last-active-org-id-${session.userId}`
              await redisAuthStorage.set(key, activeOrganizationId)
            }
          } catch (error) {
            console.error("Error updating session", error)
          }
        },
      },
      create: {
        before: async ({ userId, ...session }) => {
          // Fetch the last active organization via Redis
          const key = `last-active-org-id-${userId}`
          let lastActiveOrgId: string | null = null
          try {
            lastActiveOrgId = await redisAuthStorage.get(key)
          } catch (error) {
            console.error("Error fetching last active org id", error)
          }

          // Determine the active organization
          const organizations = await getUserOrgsCached(userId)
          if (organizations.length > 0) {
            let activeOrganizationId = organizations[0].id

            // Overwrite with the previously active org (if it exists)
            if (lastActiveOrgId && organizations.find((org) => org.id === lastActiveOrgId)) {
              activeOrganizationId = lastActiveOrgId
            }

            // Prevent demo org from being set as default active org
            if (organizations.length > 1 && activeOrganizationId === env.NEXT_PUBLIC_DEMO_ORG_ID) {
              activeOrganizationId =
                organizations.find((org) => org.id !== env.NEXT_PUBLIC_DEMO_ORG_ID)?.id ||
                activeOrganizationId
            }

            return {
              data: { ...session, userId, activeOrganizationId },
            }
          }

          // If no organization exists, return the session as is
          return { data: { ...session, userId } }
        },
      },
    },
    user: {
      create: {
        before: async (user) => {
          // NOTE: Hacky workaround to get firstName & lastName on magic link signup
          if (user.name.includes("⦀")) {
            const [firstName, lastName] = user.name.split("⦀")
            user.name = `${firstName} ${lastName}`
            type UserWithNames = User & { firstName: string; lastName: string }
            ;(user as UserWithNames).firstName = firstName
            ;(user as UserWithNames).lastName = lastName
          }
        },
        after: async (user, ctx) => {
          // Invalidate cache
          await revalidate(authCache, "userByEmail", [user.email])

          captureEvent({
            event: "user_created",
            distinctId: user.id,
            properties: {
              email: user.email,
              name: user.name,
            },
          })

          const userCreatedAt =
            user.createdAt instanceof Date
              ? user.createdAt.toISOString()
              : typeof user.createdAt === "string"
                ? user.createdAt
                : null

          const typedUserForInngest = user as typeof user & {
            firstName?: string
            lastName?: string
          }
          const normalizeOptionalUserName = (value: string | null | undefined) =>
            value === "" ? null : (value ?? null)

          try {
            await inngest.send({
              id: `auth:user.created:${user.id}`,
              name: EventNames.MARKETING_USER_CREATED,
              data: {
                userId: user.id,
                email: user.email,
                name: user.name,
                firstName: normalizeOptionalUserName(typedUserForInngest.firstName),
                lastName: normalizeOptionalUserName(typedUserForInngest.lastName),
                createdAt: userCreatedAt,
                image: user.image ?? null,
              },
              user: {
                external_id: user.id,
                email: user.email,
              },
            })
          } catch (error) {
            console.error("Failed to emit marketing user-created event to Inngest", {
              userId: user.id,
              error,
            })
          }

          // Track Facebook conversion - CompleteRegistration
          // Type assertion for Better Auth user with additional fields
          const typedUser = user as typeof user & { firstName: string; lastName: string }

          // Extract request context for better Facebook matching
          const ipAddress =
            ctx?.headers?.get("x-forwarded-for") ||
            ctx?.headers?.get("x-real-ip") ||
            ctx?.headers?.get("cf-connecting-ip") // Cloudflare
          const userAgent = ctx?.headers?.get("user-agent")

          fbConversions
            .trackCompleteRegistration({
              email: typedUser.email,
              userId: typedUser.id,
              firstName: typedUser.firstName,
              lastName: typedUser.lastName,
              eventUrl: `${env.NEXT_PUBLIC_BASE_URL}/auth/sign-up`,
              // Add request context for improved match quality
              ipAddress: ipAddress || undefined,
              userAgent: userAgent || undefined,
            })
            .catch(console.error)
        },
      },
      update: {
        after: async (user, context) => {
          await revalidateOrgOwnerCachesForUser(user.id)
        },
      },
    },
  },
  plugins: [
    magicLink({
      sendMagicLink: async (payload, request) => {
        console.log(`⏳ Triggering Inngest function: 'auth/magic-link.requested'…`)
        await inngest.send({
          name: EventNames.MAGIC_LINK_REQUESTED,
          data: payload,
          // For magic link requests, we only have the email (no user ID for new signups)
          user: {
            external_id: "anonymous", // Use a placeholder for new signups
            email: payload.email,
          },
        })
      },
      generateToken,
    }),
    organization({
      ac,
      roles,
      invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days
      cancelPendingInvitationsOnReInvite: true,
      membershipLimit: Number.MAX_SAFE_INTEGER,
      schema: {
        invitation: {
          modelName: "orgInvitation",
        },
        member: {
          modelName: "orgMember",
        },
        organization: {
          additionalFields: {
            url: {
              type: "string" as const,
              required: false,
              input: true,
            },
          },
        },
      },
      sendInvitationEmail: async (data) => {
        console.log(`⏳ Triggering Inngest function: 'org/invitation.created'…`)
        const isCreator = data.invitation.role === MemberRole.CREATOR

        // Construct the invitation acceptance URL based on role
        const id = data.invitation.id
        const acceptInvitationUrl = isCreator
          ? getAbsoluteUrl(`/setup/creator?invitationId=${id}`)
          : getAbsoluteUrl(`/auth/accept-invitation/${id}/${encodeURIComponent(data.email)}`)

        await inngest.send({
          name: EventNames.ORG_INVITATION_CREATED,
          data: {
            email: data.email,
            organizationName: data.organization.name,
            invitation: data.invitation,
            inviter: data.inviter.user,
            orgId: data.organization.id,
            userId: data.inviter.user.id,
            acceptInvitationUrl,
            withAutoSignIn: isCreator,
          },
          user: {
            external_id: data.inviter.user.id,
            email: data.inviter.user.email,
            organization_id: data.organization.id,
          },
        })
      },
      organizationHooks: {
        afterCreateOrganization: async ({ organization, user }) => {
          const { id: orgId, ...rest } = organization
          groupIdentify({
            groupKey: orgId,
            properties: rest,
          })
          captureEvent({
            event: "organization_created",
            distinctId: user.id,
            groups: {
              company: orgId,
            },
            properties: {
              organization,
            },
          })

          const orgCreatedAt =
            organization.createdAt instanceof Date
              ? organization.createdAt.toISOString()
              : typeof organization.createdAt === "string"
                ? organization.createdAt
                : null

          const typedUser = user as typeof user & {
            firstName?: string
            lastName?: string
          }

          try {
            await inngest.send({
              id: `auth:organization.created:${organization.id}`,
              name: EventNames.MARKETING_ORGANIZATION_CREATED,
              data: {
                orgId: organization.id,
                orgName: organization.name,
                orgSlug: organization.slug,
                orgCreatedAt,
                userId: user.id,
                email: user.email ?? null,
                name: user.name ?? null,
                firstName: typedUser.firstName ?? null,
                lastName: typedUser.lastName ?? null,
              },
              user: {
                external_id: user.id,
                ...(user.email ? { email: user.email } : {}),
                organization_id: organization.id,
              },
            })
          } catch (error) {
            console.error("Failed to emit marketing organization-created event to Inngest", {
              orgId: organization.id,
              userId: user.id,
              error,
            })
          }
        },
      },
    }),
    adminPlugin({
      // NOTE: Only works using `adminUserIds`, not `adminRoles`
      adminUserIds: env.SUPERADMIN_USER_IDS,
      impersonationSessionDuration: IMPERSONATION_COOKIE_TTL_SECONDS, // 1 hour
    }),
    // TODO: Replace with new OAuth 2.1 Provider (https://better-auth.com/docs/plugins/oauth-provider)
    mcp({
      loginPage: "/auth/sign-up",
      oidcConfig: {
        loginPage: "/auth/sign-up",
        generateClientId: () => newId("mcpClientId"),
        generateClientSecret: () => newId("mcpClientSecret"),
      },
    }),
    apiKey({
      apiKeyHeaders: ["x-api-key"],
      enableMetadata: true,
      // TODO: Potentially disable for security reasons (https://github.com/better-auth/better-auth/issues/5397)
      enableSessionForAPIKeys: true,
    }),
    nextCookies(),
    passkey({
      rpName: config.site.name,
      rpID: passkeyRpId,
    }),
  ],
  advanced: {
    database: {
      generateId({ model }: { model: LiteralUnion<BaseModelNames, string> }) {
        switch (model) {
          case "user":
            return newId("user")
          case "session":
            return newId("session")
          case "verification":
            return newId("verification")
          case "organization":
            return newId("organization")
          case "invitation":
            return newId("invitation")
          case "member":
            return newId("member")
          case "passkey":
            return newId("passkey")
          case "account":
            return newId("account")
          case "jwks":
            return newId("jwks")
          case "key":
            return newId("key")
          case "rate-limit":
            return newId("rate-limit")
          case "two-factor":
            return newId("two-factor")
          case "subscription":
            return newId("subscription")
          case "apiKey":
            return newId("apiKey")
          default:
            return newId("key")
        }
      },
    },
  },
  experimental: {
    joins: true, // See https://www.better-auth.com/docs/concepts/database#experimental-joins
  },
})

export type TAuth = typeof auth
<!-- gh-comment-id:3979270659 --> @wottpal commented on GitHub (Mar 1, 2026): @ping-maxwell happy to do so, but it might be a bit bloated 🤐 ```ts import { apiKey } from "@better-auth/api-key" import { passkey } from "@better-auth/passkey" import { type BaseModelNames, betterAuth, type LiteralUnion, type User } from "better-auth" import { drizzleAdapter } from "better-auth/adapters/drizzle" import { APIError, createAuthMiddleware } from "better-auth/api" import { nextCookies } from "better-auth/next-js" import { admin as adminPlugin, magicLink, mcp, organization } from "better-auth/plugins" import { IMPERSONATION_COOKIE_TTL_SECONDS } from "@/app/(app)/app/(admin)/admin/users/atom" import { config } from "@/config" import * as schema from "@/drizzle/schema/auth" import { env } from "@/env/server" import { inngest } from "@/lib/inngest" import { EventNames } from "@/lib/inngest/events" import { getAbsoluteUrl } from "@/utils/url" import { authCache } from "../cache/registry" import { revalidate } from "../cache/revalidate" import { db } from "../db" import { newId } from "../db/utils/nanoid" import { fbConversions } from "../facebook/conversions-client" import { captureEvent, groupIdentify } from "../posthog/server" import { redisAuthStorage } from "../redis" import { passkeyRpId } from "./passkey-utils" import { ac, roles } from "./roles" import { MemberRole } from "./types" import { getUserByEmailCached, getUserOrgsCached, isEmailAllowed, revalidateOrgOwnerCachesForUser, } from "./utils" import { generateToken } from "./verification-token" export const auth = betterAuth({ appName: config.site.name, baseURL: env.NEXT_PUBLIC_BASE_URL, secret: env.BETTER_AUTH_SECRET, database: drizzleAdapter(db, { provider: "pg", usePlural: true, schema, // DEBUG // debugLogs: !env.NEXT_PUBLIC_PRODUCTION_MODE, }), secondaryStorage: redisAuthStorage, rateLimit: { storage: "secondary-storage", }, session: { cookieCache: { enabled: true, maxAge: 5 * 60, // Keep session in cookie cache for 5 minutes }, }, user: { additionalFields: { firstName: { type: "string" as const, required: true, }, lastName: { type: "string" as const, required: true, }, creatorProfileId: { type: "string" as const, required: false, input: false, unique: true, // IMPORTANT: Needs to be commented out since a regression in Better Auth 1.14 // references: { model: "creatorProfile", field: "id", onDelete: "set null" }, }, }, changeEmail: { enabled: true, // TODO: Re-Enable Verification Email when fixed @ Better Auth // E.g. See https://github.com/better-auth/better-auth/issues/3424 // sendChangeEmailVerification: async (data, request) => { // console.log(`⏳ Triggering Inngest function: 'auth/change-email-verification.requested'…`) // await inngest.send({ // name: EventNames.CHANGE_EMAIL_VERIFICATION_REQUESTED, // data: { // newEmail: data.newEmail, // oldEmail: data.user.email, // verificationUrl: data.url, // userId: data.user.id, // }, // user: { // external_id: data.user.id, // email: data.user.email, // }, // }) // }, }, }, account: { accountLinking: { enabled: true, allowDifferentEmails: true, allowUnlinkingAll: true, }, }, socialProviders: { google: { clientId: env.GOOGLE_CLIENT_ID as string, clientSecret: env.GOOGLE_CLIENT_SECRET as string, mapProfileToUser: async (profile) => { const isAllowed = await isEmailAllowed(profile.email) if (!isAllowed) { throw new APIError("BAD_REQUEST", { message: "External sign-ups are disabled on this instance.", }) } return { firstName: profile.given_name, lastName: profile.family_name || "", // IMPORTANT as family_name is not always set } }, }, }, hooks: { before: createAuthMiddleware(async (ctx) => { const isMagicSignIn = ctx.path === "/sign-in/magic-link" && ctx.body?.mode === "signin" if (isMagicSignIn) { // Prevent sign-ins of external emails (in preview mode) const isAllowed = await isEmailAllowed(ctx.body?.email) if (!isAllowed) { throw new APIError("BAD_REQUEST", { message: "External sign-ins are disabled on this instance.", }) } // Ensure magic link sign-ins have already signed-up before const email = ctx.body?.email if (!email) throw new APIError("BAD_REQUEST", { message: "Email is required" }) if (!(await getUserByEmailCached(email))) throw new APIError("BAD_REQUEST", { message: "User not found. Please sign up first.", }) } const isMagicSignUp = ctx.path === "/sign-in/magic-link" && ctx.body?.mode === "signup" if (isMagicSignUp) { // Prevent sign-ups of external emails (in preview mode) const isAllowed = await isEmailAllowed(ctx.body?.email) if (!isAllowed) { throw new APIError("BAD_REQUEST", { message: "External sign-ups are disabled on this instance.", }) } // Prevent sign-up if user already exists const email = ctx.body?.email if (!email) throw new APIError("BAD_REQUEST", { message: "Email is required" }) if (await getUserByEmailCached(email)) throw new APIError("BAD_REQUEST", { message: "User already exists. Please sign in.", }) } }), }, databaseHooks: { session: { update: { after: async (session) => { // Update the last active organization ID in Redis try { // biome-ignore lint/suspicious/noExplicitAny: session is not correctly typed const activeOrganizationId = (session as any)?.activeOrganizationId if (activeOrganizationId) { const key = `last-active-org-id-${session.userId}` await redisAuthStorage.set(key, activeOrganizationId) } } catch (error) { console.error("Error updating session", error) } }, }, create: { before: async ({ userId, ...session }) => { // Fetch the last active organization via Redis const key = `last-active-org-id-${userId}` let lastActiveOrgId: string | null = null try { lastActiveOrgId = await redisAuthStorage.get(key) } catch (error) { console.error("Error fetching last active org id", error) } // Determine the active organization const organizations = await getUserOrgsCached(userId) if (organizations.length > 0) { let activeOrganizationId = organizations[0].id // Overwrite with the previously active org (if it exists) if (lastActiveOrgId && organizations.find((org) => org.id === lastActiveOrgId)) { activeOrganizationId = lastActiveOrgId } // Prevent demo org from being set as default active org if (organizations.length > 1 && activeOrganizationId === env.NEXT_PUBLIC_DEMO_ORG_ID) { activeOrganizationId = organizations.find((org) => org.id !== env.NEXT_PUBLIC_DEMO_ORG_ID)?.id || activeOrganizationId } return { data: { ...session, userId, activeOrganizationId }, } } // If no organization exists, return the session as is return { data: { ...session, userId } } }, }, }, user: { create: { before: async (user) => { // NOTE: Hacky workaround to get firstName & lastName on magic link signup if (user.name.includes("⦀")) { const [firstName, lastName] = user.name.split("⦀") user.name = `${firstName} ${lastName}` type UserWithNames = User & { firstName: string; lastName: string } ;(user as UserWithNames).firstName = firstName ;(user as UserWithNames).lastName = lastName } }, after: async (user, ctx) => { // Invalidate cache await revalidate(authCache, "userByEmail", [user.email]) captureEvent({ event: "user_created", distinctId: user.id, properties: { email: user.email, name: user.name, }, }) const userCreatedAt = user.createdAt instanceof Date ? user.createdAt.toISOString() : typeof user.createdAt === "string" ? user.createdAt : null const typedUserForInngest = user as typeof user & { firstName?: string lastName?: string } const normalizeOptionalUserName = (value: string | null | undefined) => value === "" ? null : (value ?? null) try { await inngest.send({ id: `auth:user.created:${user.id}`, name: EventNames.MARKETING_USER_CREATED, data: { userId: user.id, email: user.email, name: user.name, firstName: normalizeOptionalUserName(typedUserForInngest.firstName), lastName: normalizeOptionalUserName(typedUserForInngest.lastName), createdAt: userCreatedAt, image: user.image ?? null, }, user: { external_id: user.id, email: user.email, }, }) } catch (error) { console.error("Failed to emit marketing user-created event to Inngest", { userId: user.id, error, }) } // Track Facebook conversion - CompleteRegistration // Type assertion for Better Auth user with additional fields const typedUser = user as typeof user & { firstName: string; lastName: string } // Extract request context for better Facebook matching const ipAddress = ctx?.headers?.get("x-forwarded-for") || ctx?.headers?.get("x-real-ip") || ctx?.headers?.get("cf-connecting-ip") // Cloudflare const userAgent = ctx?.headers?.get("user-agent") fbConversions .trackCompleteRegistration({ email: typedUser.email, userId: typedUser.id, firstName: typedUser.firstName, lastName: typedUser.lastName, eventUrl: `${env.NEXT_PUBLIC_BASE_URL}/auth/sign-up`, // Add request context for improved match quality ipAddress: ipAddress || undefined, userAgent: userAgent || undefined, }) .catch(console.error) }, }, update: { after: async (user, context) => { await revalidateOrgOwnerCachesForUser(user.id) }, }, }, }, plugins: [ magicLink({ sendMagicLink: async (payload, request) => { console.log(`⏳ Triggering Inngest function: 'auth/magic-link.requested'…`) await inngest.send({ name: EventNames.MAGIC_LINK_REQUESTED, data: payload, // For magic link requests, we only have the email (no user ID for new signups) user: { external_id: "anonymous", // Use a placeholder for new signups email: payload.email, }, }) }, generateToken, }), organization({ ac, roles, invitationExpiresIn: 60 * 60 * 24 * 7, // 7 days cancelPendingInvitationsOnReInvite: true, membershipLimit: Number.MAX_SAFE_INTEGER, schema: { invitation: { modelName: "orgInvitation", }, member: { modelName: "orgMember", }, organization: { additionalFields: { url: { type: "string" as const, required: false, input: true, }, }, }, }, sendInvitationEmail: async (data) => { console.log(`⏳ Triggering Inngest function: 'org/invitation.created'…`) const isCreator = data.invitation.role === MemberRole.CREATOR // Construct the invitation acceptance URL based on role const id = data.invitation.id const acceptInvitationUrl = isCreator ? getAbsoluteUrl(`/setup/creator?invitationId=${id}`) : getAbsoluteUrl(`/auth/accept-invitation/${id}/${encodeURIComponent(data.email)}`) await inngest.send({ name: EventNames.ORG_INVITATION_CREATED, data: { email: data.email, organizationName: data.organization.name, invitation: data.invitation, inviter: data.inviter.user, orgId: data.organization.id, userId: data.inviter.user.id, acceptInvitationUrl, withAutoSignIn: isCreator, }, user: { external_id: data.inviter.user.id, email: data.inviter.user.email, organization_id: data.organization.id, }, }) }, organizationHooks: { afterCreateOrganization: async ({ organization, user }) => { const { id: orgId, ...rest } = organization groupIdentify({ groupKey: orgId, properties: rest, }) captureEvent({ event: "organization_created", distinctId: user.id, groups: { company: orgId, }, properties: { organization, }, }) const orgCreatedAt = organization.createdAt instanceof Date ? organization.createdAt.toISOString() : typeof organization.createdAt === "string" ? organization.createdAt : null const typedUser = user as typeof user & { firstName?: string lastName?: string } try { await inngest.send({ id: `auth:organization.created:${organization.id}`, name: EventNames.MARKETING_ORGANIZATION_CREATED, data: { orgId: organization.id, orgName: organization.name, orgSlug: organization.slug, orgCreatedAt, userId: user.id, email: user.email ?? null, name: user.name ?? null, firstName: typedUser.firstName ?? null, lastName: typedUser.lastName ?? null, }, user: { external_id: user.id, ...(user.email ? { email: user.email } : {}), organization_id: organization.id, }, }) } catch (error) { console.error("Failed to emit marketing organization-created event to Inngest", { orgId: organization.id, userId: user.id, error, }) } }, }, }), adminPlugin({ // NOTE: Only works using `adminUserIds`, not `adminRoles` adminUserIds: env.SUPERADMIN_USER_IDS, impersonationSessionDuration: IMPERSONATION_COOKIE_TTL_SECONDS, // 1 hour }), // TODO: Replace with new OAuth 2.1 Provider (https://better-auth.com/docs/plugins/oauth-provider) mcp({ loginPage: "/auth/sign-up", oidcConfig: { loginPage: "/auth/sign-up", generateClientId: () => newId("mcpClientId"), generateClientSecret: () => newId("mcpClientSecret"), }, }), apiKey({ apiKeyHeaders: ["x-api-key"], enableMetadata: true, // TODO: Potentially disable for security reasons (https://github.com/better-auth/better-auth/issues/5397) enableSessionForAPIKeys: true, }), nextCookies(), passkey({ rpName: config.site.name, rpID: passkeyRpId, }), ], advanced: { database: { generateId({ model }: { model: LiteralUnion<BaseModelNames, string> }) { switch (model) { case "user": return newId("user") case "session": return newId("session") case "verification": return newId("verification") case "organization": return newId("organization") case "invitation": return newId("invitation") case "member": return newId("member") case "passkey": return newId("passkey") case "account": return newId("account") case "jwks": return newId("jwks") case "key": return newId("key") case "rate-limit": return newId("rate-limit") case "two-factor": return newId("two-factor") case "subscription": return newId("subscription") case "apiKey": return newId("apiKey") default: return newId("key") } }, }, }, experimental: { joins: true, // See https://www.better-auth.com/docs/concepts/database#experimental-joins }, }) export type TAuth = typeof auth ```
Author
Owner

@Ventinc commented on GitHub (Mar 1, 2026):

SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend).

import { db } from '@/drizzle/index.js';
import { drizzleAdapter } from '@better-auth/drizzle-adapter';
import { betterAuth } from 'better-auth';
import { magicLink } from 'better-auth/plugins';

export const auth = betterAuth({
  baseURL: 'http://localhost:3000/',
  database: drizzleAdapter(db, {
    provider: 'pg',
  }),
  plugins: [
    magicLink({
      sendMagicLink(data, ctx) {
        console.log(data, ctx);
      },
    }),
  ],
});

And everytime I use signIn.magicLink({email: 'test@example.com', ...})

I have this error:

Image

I use the beta version of drizzle, I don't think it's the problem but at least you know what I use

<!-- gh-comment-id:3979282295 --> @Ventinc commented on GitHub (Mar 1, 2026): SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend). ```ts import { db } from '@/drizzle/index.js'; import { drizzleAdapter } from '@better-auth/drizzle-adapter'; import { betterAuth } from 'better-auth'; import { magicLink } from 'better-auth/plugins'; export const auth = betterAuth({ baseURL: 'http://localhost:3000/', database: drizzleAdapter(db, { provider: 'pg', }), plugins: [ magicLink({ sendMagicLink(data, ctx) { console.log(data, ctx); }, }), ], }); ``` And everytime I use signIn.magicLink({email: 'test@example.com', ...}) I have this error: <img width="856" height="333" alt="Image" src="https://github.com/user-attachments/assets/98edee32-200f-499e-aabd-4ca9c9e5b4d4" /> I use the beta version of drizzle, I don't think it's the problem but at least you know what I use
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

Hi, can you check if this get fixed?

npm i https://pkg.pr.new/better-auth@8242

<!-- gh-comment-id:3979478202 --> @himself65 commented on GitHub (Mar 1, 2026): Hi, can you check if this get fixed? npm i https://pkg.pr.new/better-auth@8242
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend).

import { db } from '@/drizzle/index.js';
import { drizzleAdapter } from '@better-auth/drizzle-adapter';
import { betterAuth } from 'better-auth';
import { magicLink } from 'better-auth/plugins';

export const auth = betterAuth({
baseURL: 'http://localhost:3000/',
database: drizzleAdapter(db, {
provider: 'pg',
}),
plugins: [
magicLink({
sendMagicLink(data, ctx) {
console.log(data, ctx);
},
}),
],
});
And everytime I use signIn.magicLink({email: 'test@example.com', ...})

I have this error:

Image I use the beta version of drizzle, I don't think it's the problem but at least you know what I use

SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend).

import { db } from '@/drizzle/index.js';
import { drizzleAdapter } from '@better-auth/drizzle-adapter';
import { betterAuth } from 'better-auth';
import { magicLink } from 'better-auth/plugins';

export const auth = betterAuth({
baseURL: 'http://localhost:3000/',
database: drizzleAdapter(db, {
provider: 'pg',
}),
plugins: [
magicLink({
sendMagicLink(data, ctx) {
console.log(data, ctx);
},
}),
],
});
And everytime I use signIn.magicLink({email: 'test@example.com', ...})

I have this error:

Image I use the beta version of drizzle, I don't think it's the problem but at least you know what I use

Have you run the db migration script?

<!-- gh-comment-id:3979478993 --> @himself65 commented on GitHub (Mar 1, 2026): > SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend). > > import { db } from '@/drizzle/index.js'; > import { drizzleAdapter } from '@better-auth/drizzle-adapter'; > import { betterAuth } from 'better-auth'; > import { magicLink } from 'better-auth/plugins'; > > export const auth = betterAuth({ > baseURL: 'http://localhost:3000/', > database: drizzleAdapter(db, { > provider: 'pg', > }), > plugins: [ > magicLink({ > sendMagicLink(data, ctx) { > console.log(data, ctx); > }, > }), > ], > }); > And everytime I use signIn.magicLink({email: '[test@example.com](mailto:test@example.com)', ...}) > > I have this error: > > <img alt="Image" width="856" height="333" src="https://private-user-images.githubusercontent.com/16818589/556506134-98edee32-200f-499e-aabd-4ca9c9e5b4d4.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzIzNTM1MTMsIm5iZiI6MTc3MjM1MzIxMywicGF0aCI6Ii8xNjgxODU4OS81NTY1MDYxMzQtOThlZGVlMzItMjAwZi00OTllLWFhYmQtNGNhOWM5ZTViNGQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzAxVDA4MjAxM1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlmNWQ2MTgxMGI2NmQ4ODNlODQ5YjYzNWU3MjZmOTU4YjllMzQwYTlmYTU4YTIxMzQ1YTllN2FiOTJlZmFhMTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.1-ty-nl0bjs1yDJegMb743uPqZS0N-AGdpl4fHg1QP0"> > I use the beta version of drizzle, I don't think it's the problem but at least you know what I use > SignIn with magicLink doesn't work for me too. I just installed better-auth on a new project (Hono as backend and Tanstack Start as frontend). > > import { db } from '@/drizzle/index.js'; > import { drizzleAdapter } from '@better-auth/drizzle-adapter'; > import { betterAuth } from 'better-auth'; > import { magicLink } from 'better-auth/plugins'; > > export const auth = betterAuth({ > baseURL: 'http://localhost:3000/', > database: drizzleAdapter(db, { > provider: 'pg', > }), > plugins: [ > magicLink({ > sendMagicLink(data, ctx) { > console.log(data, ctx); > }, > }), > ], > }); > And everytime I use signIn.magicLink({email: '[test@example.com](mailto:test@example.com)', ...}) > > I have this error: > > <img alt="Image" width="856" height="333" src="https://private-user-images.githubusercontent.com/16818589/556506134-98edee32-200f-499e-aabd-4ca9c9e5b4d4.png?jwt=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTUiLCJleHAiOjE3NzIzNTM1MTMsIm5iZiI6MTc3MjM1MzIxMywicGF0aCI6Ii8xNjgxODU4OS81NTY1MDYxMzQtOThlZGVlMzItMjAwZi00OTllLWFhYmQtNGNhOWM5ZTViNGQ0LnBuZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPUFLSUFWQ09EWUxTQTUzUFFLNFpBJTJGMjAyNjAzMDElMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjYwMzAxVDA4MjAxM1omWC1BbXotRXhwaXJlcz0zMDAmWC1BbXotU2lnbmF0dXJlPTlmNWQ2MTgxMGI2NmQ4ODNlODQ5YjYzNWU3MjZmOTU4YjllMzQwYTlmYTU4YTIxMzQ1YTllN2FiOTJlZmFhMTcmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0In0.1-ty-nl0bjs1yDJegMb743uPqZS0N-AGdpl4fHg1QP0"> > I use the beta version of drizzle, I don't think it's the problem but at least you know what I use Have you run the db migration script?
Author
Owner

@Ventinc commented on GitHub (Mar 1, 2026):

Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above

<!-- gh-comment-id:3979512735 --> @Ventinc commented on GitHub (Mar 1, 2026): Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above
Author
Owner

@wottpal commented on GitHub (Mar 1, 2026):

Hi, can you check if this get fixed?

npm i https://pkg.pr.new/better-auth@8242

Hey @himself65, thanks for the quick try! It brings up another error now:

app:dev     | 2026-03-01T09:46:52.678Z ERROR [Better Auth]: TypeError TypeError: expiresAt.getTime is not a function
app:dev     |     at ignore-listed frames
app:dev     | # SERVER_ERROR:  TypeError: expiresAt.getTime is not a function
app:dev     |     at ignore-listed frames
app:dev     |  GET /api/auth/magic-link/verify?token=c166a54e5ed72980de7ee10cdb27c113&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 313ms (compile: 2ms, render: 311ms)

Just for clarification, is the verifications table supposed to be not used anymore with secondary storage?


@Ventinc I would recommend you opening another issue.

<!-- gh-comment-id:3979597331 --> @wottpal commented on GitHub (Mar 1, 2026): > Hi, can you check if this get fixed? > > npm i https://pkg.pr.new/better-auth@8242 Hey @himself65, thanks for the quick try! It brings up another error now: ``` app:dev | 2026-03-01T09:46:52.678Z ERROR [Better Auth]: TypeError TypeError: expiresAt.getTime is not a function app:dev | at ignore-listed frames app:dev | # SERVER_ERROR: TypeError: expiresAt.getTime is not a function app:dev | at ignore-listed frames app:dev | GET /api/auth/magic-link/verify?token=c166a54e5ed72980de7ee10cdb27c113&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 313ms (compile: 2ms, render: 311ms) ``` Just for clarification, is the `verifications` table supposed to be not used anymore with secondary storage? *** @Ventinc I would recommend you opening another issue.
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above

import Database from "better-sqlite3";
import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugins";
import { getMigrations } from "better-auth/db/migration";

const sqliteDb = new Database(":memory:");

const options = {
	baseURL: "http://localhost:3000",
	database: sqliteDb,
	plugins: [
		magicLink({
			sendMagicLink(data: any, ctx: any) {
				console.log("Magic link sent:", data);
			},
		}),
	],
};

const auth = betterAuth(options);

async function main() {
	const { runMigrations } = await getMigrations({
		...auth.options,
		database: sqliteDb,
	});
	await runMigrations();
	console.log("Migrations done");

	// Try sending magic link
	try {
		const result = await auth.api.signInMagicLink({
			body: {
				email: "test@test.com",
			},
			headers: new Headers({
				origin: "http://localhost:3000",
			}),
		});
		console.log("Magic link sign-in result:", result);
	} catch (e) {
		console.error("Error sending magic link:", e);
	}
}

main().catch(console.error);
/opt/homebrew/bin/pnpm run run

> test-better-auth-1-5@1.0.0 run /Users/himself65/Code/test-better-auth-1-5
> tsx test.ts

Migrations done
Magic link sent: {
  email: 'test@test.com',
  url: 'http://localhost:3000/api/auth/magic-link/verify?token=LGmOdIuDuUVedtGrQnESmQrwwtbsUzUz&callbackURL=%2F',
  token: 'LGmOdIuDuUVedtGrQnESmQrwwtbsUzUz'
}
Magic link sign-in result: { status: true }

Process finished with exit code 0

<!-- gh-comment-id:3979775985 --> @himself65 commented on GitHub (Mar 1, 2026): > Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above ```ts import Database from "better-sqlite3"; import { betterAuth } from "better-auth"; import { magicLink } from "better-auth/plugins"; import { getMigrations } from "better-auth/db/migration"; const sqliteDb = new Database(":memory:"); const options = { baseURL: "http://localhost:3000", database: sqliteDb, plugins: [ magicLink({ sendMagicLink(data: any, ctx: any) { console.log("Magic link sent:", data); }, }), ], }; const auth = betterAuth(options); async function main() { const { runMigrations } = await getMigrations({ ...auth.options, database: sqliteDb, }); await runMigrations(); console.log("Migrations done"); // Try sending magic link try { const result = await auth.api.signInMagicLink({ body: { email: "test@test.com", }, headers: new Headers({ origin: "http://localhost:3000", }), }); console.log("Magic link sign-in result:", result); } catch (e) { console.error("Error sending magic link:", e); } } main().catch(console.error); ``` ```shell /opt/homebrew/bin/pnpm run run > test-better-auth-1-5@1.0.0 run /Users/himself65/Code/test-better-auth-1-5 > tsx test.ts Migrations done Magic link sent: { email: 'test@test.com', url: 'http://localhost:3000/api/auth/magic-link/verify?token=LGmOdIuDuUVedtGrQnESmQrwwtbsUzUz&callbackURL=%2F', token: 'LGmOdIuDuUVedtGrQnESmQrwwtbsUzUz' } Magic link sign-in result: { status: true } Process finished with exit code 0 ```
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

Hi, can you check if this get fixed?
npm i https://pkg.pr.new/better-auth@8242

Hey @himself65, thanks for the quick try! It brings up another error now:

app:dev     | 2026-03-01T09:46:52.678Z ERROR [Better Auth]: TypeError TypeError: expiresAt.getTime is not a function
app:dev     |     at ignore-listed frames
app:dev     | # SERVER_ERROR:  TypeError: expiresAt.getTime is not a function
app:dev     |     at ignore-listed frames
app:dev     |  GET /api/auth/magic-link/verify?token=c166a54e5ed72980de7ee10cdb27c113&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 313ms (compile: 2ms, render: 311ms)

Just for clarification, is the verifications table supposed to be not used anymore with secondary storage?

@Ventinc I would recommend you opening another issue.

Yes, for secondary storage, there's no table, it's actually a json or string json

<!-- gh-comment-id:3979777232 --> @himself65 commented on GitHub (Mar 1, 2026): > > Hi, can you check if this get fixed? > > npm i https://pkg.pr.new/better-auth@8242 > > Hey [@himself65](https://github.com/himself65), thanks for the quick try! It brings up another error now: > > ``` > app:dev | 2026-03-01T09:46:52.678Z ERROR [Better Auth]: TypeError TypeError: expiresAt.getTime is not a function > app:dev | at ignore-listed frames > app:dev | # SERVER_ERROR: TypeError: expiresAt.getTime is not a function > app:dev | at ignore-listed frames > app:dev | GET /api/auth/magic-link/verify?token=c166a54e5ed72980de7ee10cdb27c113&callbackURL=http%3A%2F%2Flocalhost%3A3000%2Fapp 500 in 313ms (compile: 2ms, render: 311ms) > ``` > > Just for clarification, is the `verifications` table supposed to be not used anymore with secondary storage? > > [@Ventinc](https://github.com/Ventinc) I would recommend you opening another issue. Yes, for secondary storage, there's no table, it's actually a json or string json
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above

Please open another issue with reproduceable repo. For now I think it's okay

<!-- gh-comment-id:3979777893 --> @himself65 commented on GitHub (Mar 1, 2026): > Yes, the update does not fix the problem (mine), in my studio I have the verification created but after that I have a 500 error with the error above Please open another issue with reproduceable repo. For now I think it's okay
Author
Owner

@himself65 commented on GitHub (Mar 1, 2026):

Can you check npm i https://pkg.pr.new/better-auth@8247

<!-- gh-comment-id:3979824772 --> @himself65 commented on GitHub (Mar 1, 2026): Can you check `npm i https://pkg.pr.new/better-auth@8247`
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#28351