Docs / Authentication / Roles overview
Roles overview
Keel has two independent role systems: per-team roles that scope what you can do inside a team, and one global role that gates your own internal admin panel. They live in different tables and never mix.
Per-team roles
Three roles, defined as teamRoleEnum in lib/db/schema/team.ts:
- owner, full control of the team, including deletion and billing.
- admin, manages members and settings, but can't delete the team.
- member, the default for anyone who joins.
A user's role is stored per team on team_members.role, not on the user. The same account can be an owner of one team and a member of another, the role only means something in the context of a specific team_members row.
The global role
The admin plugin in lib/auth.ts adds a role column to the user table (lib/db/schema/auth-schema.ts) with two values: user (the default) and superadmin. This is unrelated to team membership, it exists only to gate the /admin/* panel. It's named superadmin rather than admin specifically so it never gets confused with the per-team admin role in UI and copy.
Two 'admin' words, two meanings
Team admin is a per-team role on team_members.role. Global superadmin is on user.role and covers the whole platform. A team admin has no access to /admin/*, and a superadmin has no special standing inside any team they aren't a member of.
How a role gets assigned
- Team creation makes you the owner. Every new user gets a personal team from the
createPersonalTeamhook inlib/teams.ts, which inserts theirteam_membersrow withrole: "owner". - Invites carry a role. When an invite is created it's stamped with a role (defaults to
member, see theinvitestable). At accept time,app/api/invites/[token]/accept/route.tsinserts the newteam_membersrow usinginvite.role, so the invitee joins at exactly the role the inviter chose. - Superadmin is set manually. New accounts default to
user; promoting someone tosuperadminis a deliberate DB-level change, not something any self-serve flow grants.
For how these roles are actually enforced, the can() permission matrix and outranks() rank checks, see Permissions.