Teams and departments
Nested teams whose maintainers manage membership and take join requests, and the department tree with heads that policies and approvals follow.
A growing organization stops thinking in individual permissions. People work in teams (Platform, Site Reliability, the Payments squad) and sit in a reporting structure (Engineering, then Platform under it). Access should follow the team, the person who runs a team should be able to add a newcomer without filing a ticket with IT, and a policy should be able to say "anyone in Engineering". alone cannot do that: they are flat, and only administrators with authority over their change them.
Better IAM adds two structures inside a :
- Teams: working units that nest, have maintainers who manage their membership, can take join requests, and give their members access through roles bound to the team.
- Departments: the reporting structure. Each person belongs to one department, a department can name a head, and heads can become everyone's managers so manager approvals follow the org chart.
How a team grants access
Every team owns a backing group (team.groupId, named team:{slug}). You give a team access by binding
to that group, exactly as you would for any group:
const platform = await iam.api.teams.create(admin, {
tenantId,
name: 'Platform',
joinPolicy: 'request',
maintainerIds: [bob.id],
});
const sre = await iam.api.teams.create(admin, {
tenantId,
name: 'Site Reliability',
slug: 'sre',
parentId: platform.id,
});
await iam.api.bindings.create(admin, {
tenantId,
roleId: deployer.id,
subjectType: 'group',
subjectId: platform.groupId,
});The teams module keeps the backing group in step with the team: it holds the team's live members and the members of
every team below it. Alice, added to Site Reliability, therefore holds Deployer through Platform. Because access flows
through an ordinary group, everything that reads groups reads teams: ,
, access reviews, , role mining,
, and principal.groups.
Only the teams API changes who is in a backing group. The groups API refuses with TEAM_MANAGED, access packages,
invitations, and onboarding flows cannot name a backing group, and configuration as code keeps team groups out of
its groups and bindings kinds.
Teams and departments as code
Configuration as code has a teams kind (matched by slug, with parent, department, join
settings, maintainers and members by email, and the roles the team holds by name) and a departments kind (matched by
name, with code, parent, head, cost center, and people by email). Listing a team's maintainers or members makes its
permanent direct members match exactly; temporary memberships and join requests are left alone. Apply creates parents
before children and, with prune, deletes children first.
{
"version": 1,
"departments": [{ "name": "Engineering", "code": "ENG", "head": "alice@acme.test" }],
"teams": [
{
"name": "Platform",
"slug": "platform",
"department": "Engineering",
"maintainers": ["bob@acme.test"],
"members": ["alice@acme.test"],
"roles": ["Deployer"]
}
]
}Maintainers
A team's members are members or maintainers. Maintainers of a team, or of any team above it, manage membership from
their own session without an administrator permission: they add, update, and remove members (teams.candidates lists
people they can add), and decide join requests. Their calls go through the same transaction as an administrator's, so
separation-of-duties rules and enforced invariants still apply, and the audit trail records via: team-maintainer.
// Bob maintains Platform, so he also manages Site Reliability below it.
await iam.api.teams.addMember(bobSession, { tenantId, teamId: sre.id, identityId: carol.id });Administrators hold iam:teams:update and, as with groups.addMember, authority over what the team (and the teams
above it) hold. Maintainers skip that authority check: making someone a maintainer delegates the team's access to them.
Set memberManagement: 'admins' on a team whose membership only administrators should change, and remember when you
bind a role to a team that its maintainers can hand that role to anyone they add.
Members see their own team (teams.get, teams.listMembers) without iam:teams:read.
Team sync from your directory
Most organizations already keep team rosters in their identity provider. Give a team syncGroupIds (up to ten
ordinary groups, such as the groups Okta or Microsoft Entra push over SCIM) and its
membership follows them: everyone in a source group is a member marked source: 'sync', leaving the group removes
them, and a temporary group membership makes a temporary team membership. Each SCIM push updates the team in the same
transaction. People added by hand, and maintainers, stay as they are; synced members are changed through the source
group.
Join requests
A team with joinPolicy: 'request' takes join requests. teams.listMine shows a person their teams, their requests,
and the teams they can ask to join; teams.requestToJoin sends the request, and the team's maintainers get a
team-join-request email. A maintainer or administrator answers with teams.approveRequest (optionally for a limited
time) or teams.denyRequest, and the requester gets a team-join-decided email. Requests lapse after fourteen days;
nobody decides their own.
Departments
const engineering = await iam.api.departments.create(admin, {
tenantId,
name: 'Engineering',
code: 'ENG',
headId: alice.id,
costCenter: 'CC-100',
});
const platformDept = await iam.api.departments.create(admin, {
tenantId,
name: 'Platform',
parentId: engineering.id,
headId: bob.id,
});
await iam.api.departments.assign(admin, {
tenantId,
departmentId: platformDept.id,
identityIds: [carol.id, erin.id],
});A person belongs to one department at a time, so assigning moves them. Teams can be filed under a department
(departmentId). Two operations connect the org chart to the rest of the system:
importFromAttributeplaces everyone whose string identity attribute (for exampledepartment, filled by SCIM provisioning) names a department, by name or code, and can create the departments that are missing. Run it withdryRun: truefirst.syncManagersmakes each person's department head their manager, and a head's the nearest head above, so approvals routed to managers (manager approval on eligible bindings and access packages, manager-reviewed certifications) follow the org chart.
In policies
| Key | Type | Value |
|---|---|---|
principal.teams | list | IDs of the person's teams and of every team above them |
principal.departments | list | The person's department ID and the IDs of every department above it |
principal.departmentId | identifier | The person's own department; absent without one |
{
"version": 1,
"statements": [
{
"effect": "allow",
"actions": ["documents:read"],
"resources": ["document/*"],
"conditions": { "ArrayContains": { "principal.departments": ["<engineering id>"] } }
},
{
"effect": "allow",
"actions": ["documents:write"],
"resources": ["document/*"],
"conditions": { "StringEquals": { "principal.departmentId": "${resource.departmentId}" } }
}
]
}The keys are loaded only when a document names them, and describe people in their own organization: an assumed role sees empty lists.
Leaving
Offboarding removes a person from every team (teamsLeft) and hands the departments they head to the successor
(departmentsReassigned). Deleting an identity ends its memberships and clears the departments it headed.
In the console
Directory โ Teams lists your teams and the teams you can ask to join, shows every team as a tree, and opens a page per team with members, join requests, the roles the team holds (its own and inherited), and settings. Maintainers see the same page with the membership tools only. Directory โ Departments shows the org chart, imports departments from attributes, and syncs managers. Member pages show a person's teams and department.
Next steps
Better IAM is created by Sean Filimon
Last updated
Onboardingnew
Checklists for new members and new tenants, defined by the platform and customized by each organization and project.
Billing and spendnew
Usage meters, rate cards, and spend by person, team, department, project and organization, with budgets, plans and subscriptions, and Stripe-style invoicing.