Keel

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 createPersonalTeam hook in lib/teams.ts, which inserts their team_members row with role: "owner".
  • Invites carry a role. When an invite is created it's stamped with a role (defaults to member, see the invites table). At accept time, app/api/invites/[token]/accept/route.ts inserts the new team_members row using invite.role, so the invitee joins at exactly the role the inviter chose.
  • Superadmin is set manually. New accounts default to user; promoting someone to superadmin is 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.

Edit this page on GitHub →Last updated Jul 22, 2026