Members & Invitations
WPHammer organizes everything around teams. Each team has an owner, members with assigned roles, and pending invitations for users who have not yet joined.
Team membership
The Team model tracks members through a many-to-many relationship with the User model. The pivot table stores each member's role (a TeamRole enum value) and timestamps.
Key relationships on the Team model:
owner()— the user who created the teammembers()— all users with a role in the teaminvitations()— allTeamInvitationrecords for the teampendingInvitations()— invitations that have not been accepted and have not expired
A user can belong to multiple teams and has a current_team_id that determines which team context they are working in. The switchTeam() method on the User model changes this context.
Inviting members
The InviteTeamMemberAction handles the invitation flow:
- Validates that the email is not already a team member
- Checks that no pending invitation exists for the same email
- If the user already has a WPHammer account, they are directly attached to the team with the specified role
- If the user does not exist, a
TeamInvitationis created with a 7-day expiration and aTeamInvitationMailis sent via the queue
Only users with the canManageMembers permission (Owner and Admin roles) can invite new members. The invitableRoles() method on the TeamRole enum defines which roles can be assigned via invitation — Owner cannot be assigned this way.
Accepting invitations
The AcceptTeamInvitationAction processes invitation acceptance:
- Validates the invitation has not already been accepted
- Validates the invitation has not expired
- Confirms the accepting user's email matches the invitation
- Attaches the user to the team with the invited role
- Sets
accepted_aton the invitation - Auto-switches the user to the new team if they do not have a current team
The TeamInvitationController handles the acceptance URL (/teams/invitations/{invitation}/accept). If the user is not logged in, they are redirected to register with the invitation email pre-filled. The invitation reference is stored in the session so it can be completed after registration.
Invitation states
Each TeamInvitation record tracks its lifecycle:
isPending()— not yet accepted and not expiredisAccepted()— theaccepted_attimestamp is setisExpired()— theexpires_attimestamp has passed
The invitation stores the invited_by user ID, so you can see who sent each invitation.
Removing members
The RemoveTeamMemberAction detaches a user from the team. It prevents removing the team owner. If the removed user had the team set as their current team, they are automatically switched to another team they belong to.
Transferring ownership
The TransferOwnershipAction transfers the owner role to another team member. The new owner must currently hold at least an Admin role. The previous owner is downgraded to Admin.
Only the current team owner can initiate a transfer, as enforced by the TeamPolicy@transferOwnership method.
Server scoping
Members can optionally be scoped to specific servers within a team. When scoped, the member can only access sites on their assigned servers. The isScopedToTeam() and scopedServerIds() methods on the User model check and return scoping restrictions.
Scoping is managed from the team members page, where an Admin or Owner can open the scope modal and assign specific servers to a member.
Related
- Roles & Permissions — role hierarchy and permission details
- Forge API Configuration — team-level Forge settings
- Two-Factor Authentication — securing individual accounts