Team Invitations
Invite users to your organization and manage team memberships
Team Invitations
OmniBase provides a complete invitation workflow for onboarding team members to your organization.
UI Components
OmniBase provides pre-built shadcn components for team management in @omnibase/shadcn:
Invitation Flow
┌───────────┐ ┌───────────┐ ┌───────────┐ ┌───────────┐
│ Inviter │────▶│ Create │────▶│ Send │────▶│ Invitee │
│ (Admin) │ │ Invite │ │ Email │ │ Accepts │
└───────────┘ └───────────┘ └───────────┘ └───────────┘
│ │
│ │
▼ ▼
┌───────────────┐ ┌───────────────┐
│ Token Created │ │ User Added to │
│ (7 day expiry)│ │ Tenant + Role │
└───────────────┘ └───────────────┘Creating Invitations
Users with the tenant#invite_user permission can invite new members.
Basic Invitation
import { Configuration, V1TenantsApi } from '@omnibase/core-js';
const tenantsApi = new V1TenantsApi(config);
const { data: invite } = await tenantsApi.createInvite({
createTenantUserInviteRequest: {
email: 'newuser@example.com',
role: 'member',
inviteUrl: 'https://app.example.com/auth/onboarding',
},
});
console.log('Token:', invite.data.token);
console.log('Expires:', invite.data.expires_at);Automatic Email Delivery
OmniBase automatically sends an invitation email to the invitee when you create an invite. The email includes:
- The organization name
- The assigned role
- A link to accept the invitation (using the
inviteUrlyou provide)
Make sure your OmniBase instance has email configured. The invite URL you provide will have the token appended as a query parameter: {inviteUrl}?token={token}
The invitation email template can be customized. See the Email Templates guide for details.
Accepting Invitations
The invited user must be authenticated before accepting. Their email must match the invitation email.
Accept an Invitation
const { data } = await tenantsApi.acceptInvite({
acceptInviteRequest: {
token: inviteToken,
},
});
// User is now a member of the tenant
console.log('Joined tenant:', data.data.tenant.name);
console.log('Assigned role:', data.data.role);
console.log('JWT Token:', data.data.token);What Happens on Acceptance
Token Validation
OmniBase verifies the token is valid, not expired, and not already used.
Email Verification
The authenticated user's email must match the invitation email.
Membership Creation
A tenant_user record is created linking the user to the tenant with the assigned role.
Permission Assignment
Permission relationships are created based on the role's permissions.
Invite Marked Used
The invitation is marked as used and cannot be reused.
Tenant Activation
The new tenant becomes the user's active tenant, and a JWT token is returned.
The invitee's email must exactly match the authenticated user's email. Invitations expire after 7 days and can only be used once.
Invitation Acceptance UI
Here's how to handle invitation acceptance in a Next.js app:
// app/auth/onboarding/page.tsx
import { V1TenantsApi, Configuration } from '@omnibase/core-js';
import { redirect } from 'next/navigation';
export default async function OnboardingPage({
searchParams,
}: {
searchParams: { token?: string };
}) {
const { token } = searchParams;
if (token) {
// User is accepting an invitation
return <AcceptInviteForm token={token} />;
}
// User is creating a new organization
return <CreateTenantForm />;
}
async function AcceptInviteForm({ token }: { token: string }) {
async function acceptInvite() {
'use server';
const config = new Configuration({
basePath: process.env.OMNIBASE_API_URL,
});
const tenantsApi = new V1TenantsApi(config);
await tenantsApi.acceptInvite({
acceptInviteRequest: { token },
});
redirect('/');
}
return (
<form action={acceptInvite}>
<h1>Accept Invitation</h1>
<p>Click below to join the organization.</p>
<button type="submit">Accept & Join</button>
</form>
);
}Managing Members
List Tenant Members
const { data: members } = await tenantsApi.listTenantUsers();
for (const member of members.data) {
console.log(`${member.email}: ${member.role}`);
console.log(` User ID: ${member.user_id}`);
console.log(` Joined: ${member.joined_at}`);
}Updating Member Roles
Users with the tenant#update_user_role permission can change member roles.
await tenantsApi.updateTenantUserRole({
updateTenantUserRoleRequest: {
userId: targetUserId,
role: 'admin',
},
});Changing a user's role immediately updates their permissions. The change takes effect on their next request.
Role Update Flow
- Verify the caller has
tenant#update_user_rolepermission - Remove user from their current role's
user_idsarray - Delete old permission relationships for this tenant
- Add user to new role's
user_idsarray - Create new permission relationships based on the new role
Removing Members
Users with the tenant#remove_user permission can remove members.
await tenantsApi.removeTenantUser({
removeTenantUserRequest: {
userId: targetUserId,
},
});What Happens on Removal
- The
tenant_userrecord is deleted - All permission relationships for this user in this tenant are removed
- If this was their active tenant, another tenant becomes active
- If they have no tenants left, their identity metadata is updated
Owners cannot be removed from a tenant. The tenant must be deleted instead, or ownership must be transferred first.
Permission Requirements
| Action | Required Permission |
|---|---|
| Create invitation | tenant#invite_user |
| Accept invitation | (Authenticated + matching email) |
| List members | tenant#view_users |
| Update member role | tenant#update_user_role |
| Remove member | tenant#remove_user |
Built-in Role Permissions
| Role | invite_user | view_users | update_user_role | remove_user |
|---|---|---|---|---|
| Owner | Yes | Yes | Yes | Yes |
| Admin | Yes | Yes | Yes | Yes |
| Member | No | No | No | No |
Error Handling
Common Errors
| Error | Cause | Solution |
|---|---|---|
403 Forbidden | User lacks invite_user permission | Ensure user is owner/admin or has custom role with permission |
400 Bad Request | Invalid email format | Validate email before sending |
409 Conflict | User already a member | Check membership before inviting |
| Error | Cause | Solution |
|---|---|---|
404 Not Found | Invalid or expired token | Request a new invitation |
400 Bad Request | Email mismatch | User must authenticate with the invited email |
409 Conflict | Invitation already used | Request a new invitation |
410 Gone | Invitation expired | Request a new invitation |
Example Error Handling
try {
await tenantsApi.acceptInvite({
acceptInviteRequest: { token },
});
} catch (error) {
if (error.response?.status === 404) {
console.error('Invalid or expired invitation');
} else if (error.response?.status === 400) {
console.error('Email does not match invitation');
} else if (error.response?.status === 410) {
console.error('Invitation has expired');
} else {
console.error('Failed to accept invitation:', error);
}
}Related Guides
- Tenant Lifecycle — Create and manage tenants
- RBAC — Configure roles and permissions
- Role Configuration — Configure who can invite
- Data Isolation — How member data is scoped