Omnibase

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 inviteUrl you 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

  1. Verify the caller has tenant#update_user_role permission
  2. Remove user from their current role's user_ids array
  3. Delete old permission relationships for this tenant
  4. Add user to new role's user_ids array
  5. 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

  1. The tenant_user record is deleted
  2. All permission relationships for this user in this tenant are removed
  3. If this was their active tenant, another tenant becomes active
  4. 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

ActionRequired Permission
Create invitationtenant#invite_user
Accept invitation(Authenticated + matching email)
List memberstenant#view_users
Update member roletenant#update_user_role
Remove membertenant#remove_user

Built-in Role Permissions

Roleinvite_userview_usersupdate_user_roleremove_user
OwnerYesYesYesYes
AdminYesYesYesYes
MemberNoNoNoNo

Error Handling

Common Errors

ErrorCauseSolution
403 ForbiddenUser lacks invite_user permissionEnsure user is owner/admin or has custom role with permission
400 Bad RequestInvalid email formatValidate email before sending
409 ConflictUser already a memberCheck membership before inviting
ErrorCauseSolution
404 Not FoundInvalid or expired tokenRequest a new invitation
400 Bad RequestEmail mismatchUser must authenticate with the invited email
409 ConflictInvitation already usedRequest a new invitation
410 GoneInvitation expiredRequest 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);
  }
}

On this page