# Implementing User Invitation Workflows with Logto: A Complete Developer's Guide

> Learn to implement user invitation workflows with Logto using magic links and a secure process. This guide covers database schemas, REST APIs, and React components for seamless integration.

- Repository: [Logto/logto](https://github.com/logto-io/logto)
- Tags: how-to-guide
- Published: 2026-07-04

---

**Logto provides a complete organization invitation system that enables administrators to invite users via email magic links and allows invitees to accept invitations through a secure workflow, implemented across database schemas, REST APIs, and React components.**

When building multi-tenant applications with the **logto-io/logto** identity platform, implementing **user invitation workflows** is essential for onboarding organization members securely. This guide examines the complete implementation, from the database migration that creates the `organization_invitations` table to the React components that handle magic-link consumption.

## Understanding the Invitation Architecture

Logto's invitation system operates through three integrated layers: a PostgreSQL schema storing invitation states, REST API endpoints handling lifecycle transitions, and React components providing the user interface.

### Database Schema and Migration

The foundation resides in [`packages/schemas/alterations/1.13.0-1704935001-add-organization-invitation-tables.ts`](https://github.com/logto-io/logto/blob/main/packages/schemas/alterations/1.13.0-1704935001-add-organization-invitation-tables.ts). This migration creates the `organization_invitations` table with columns for the inviter's identity, the invitee's email address, the target organization (tenant) ID, and a status enum supporting `Pending`, `Accepted`, `Expired`, and `Revoked` states. Each record includes a unique magic-link identifier used for secure token-based access.

### Backend API Endpoints

The core logic is encapsulated in the `OrganizationInvitationService` located in the `packages/core` directory. The service exposes several REST endpoints under `/api/tenants/:tenantId/invitations`:

- **POST** `/api/tenants/:tenantId/invitations` – Creates a new invitation record and triggers the email service to send a magic link
- **GET** `/api/invitations/:invitationId` – Retrieves invitation details for the acceptance page
- **PATCH** `/api/invitations/:invitationId/status` – Updates status to `Accepted`, `Revoked`, or handles expiration
- **DELETE** `/api/invitations/:invitationId` – Removes the invitation record entirely

### Frontend Component Hierarchy

The Console UI implements the workflow across several key components:

- **InviteMemberModal** ([`packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx)) – Admin interface for creating invitations
- **AcceptInvitation** ([`packages/console/src/pages/AcceptInvitation/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/console/src/pages/AcceptInvitation/index.tsx)) – Public-facing page for magic-link consumption
- **Invitations List** ([`packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx)) – Management interface for pending invitations
- **TenantInvitationDropdownItem** ([`packages/console/src/components/Topbar/TenantSelector/TenantInvitationDropdownItem/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/console/src/components/Topbar/TenantSelector/TenantInvitationDropdownItem/index.tsx)) – Global navigation indicator for pending invites

## Creating Organization Invitations (Admin Flow)

Administrators initiate invitations through the `InviteMemberModal` component, which posts to the tenant-scoped invitations endpoint. The request includes the invitee's email, assigned organization roles, and an optional expiration date.

```tsx
// packages/console/src/pages/TenantSettings/TenantMembers/InviteMemberModal/index.tsx
await cloudApi.post('/api/tenants/:tenantId/invitations', {
  data: {
    email: inviteeEmail,
    organizationRoles: selectedRoles,
    expireAt: expirationDate,
  },
  params: { tenantId: currentTenantId },
});
toast.success(t('tenant_members.messages.invitation_sent'));

```

The `OrganizationInvitationService` validates the request, inserts a record into `organization_invitations` with status `Pending`, and generates a magic-link URL containing the invitation ID. Logto's email service then dispatches the invitation to the specified address using templates defined in [`packages/phrases/src/locales/en/translation/admin-console/invitation.ts`](https://github.com/logto-io/logto/blob/main/packages/phrases/src/locales/en/translation/admin-console/invitation.ts).

## Accepting Invitations via Magic Links (Invitee Flow)

When an invitee clicks the magic link, the `AcceptInvitation` component loads the invitation details using a `useSWR` hook. The component first verifies the invitation status is `OrganizationInvitationStatus.Pending` before allowing acceptance.

```tsx
// packages/console/src/pages/AcceptInvitation/index.tsx
const { data: invitation } = useSWR<InvitationResponse>(
  isAuthenticated && invitationId && `/api/invitations/${invitationId}`,
  () => silentCloudApi.get('/api/invitations/:invitationId', { params: { invitationId } })
);

if (invitation && invitation.status === OrganizationInvitationStatus.Pending) {
  await cloudApi.patch('/api/invitations/:invitationId/status', {
    params: { invitationId: invitation.id },
    data: { status: 'Accepted' },
  });
  // redirect to the tenant after acceptance
  navigateTenant(invitation.organizationId);
}

```

If the invitation has already been accepted, revoked, or expired, the UI displays an error state (`invalid_invitation_status`) and prevents further action. Upon successful acceptance, the user is automatically redirected to the tenant dashboard with appropriate role-based permissions.

## Managing Invitation Lifecycles

Administrators can monitor and control pending invitations through the Invitations management page. Revoking an invitation updates its status to `Revoked`, preventing the magic link from functioning while preserving the audit trail.

```tsx
// packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx
await cloudApi.patch('/api/tenants/:tenantId/invitations/:invitationId/status', {
  params: { tenantId: currentTenantId, invitationId },
  data: { status: 'Revoked' },
});
toast.success(t('messages.invitation_revoked'));

```

Users can view their pending invitations globally through the top-bar navigation, which utilizes the `useUserInvitations` hook to fetch pending items.

```tsx
// packages/console/src/components/Topbar/TenantSelector/index.tsx
const { data: pendingInvitations } = useUserInvitations();
return pendingInvitations?.map(inv => (
  <TenantInvitationDropdownItem key={inv.id} data={inv} />
));

```

## Summary

- **Logto** implements a database-driven invitation system using the `organization_invitations` table with four distinct status states.
- The **backend API** provides tenant-scoped endpoints for creation, retrieval, and status updates, wrapped by the `OrganizationInvitationService`.
- **Magic links** serve as secure tokens for invitation acceptance, consumed by the `AcceptInvitation` page component.
- **Role assignment** occurs during invitation creation, allowing immediate access control upon acceptance.
- **Lifecycle management** includes revocation and expiration capabilities, with UI components disabling actions for non-pending invitations.

## Frequently Asked Questions

### How does Logto handle invitation expiration?

Logto checks the `expireAt` timestamp stored in the `organization_invitations` table during the acceptance flow. If the current time exceeds the expiration date, the system returns an expired status, and the `AcceptInvitation` component renders an error message preventing further action.

### Can administrators resend invitations to users?

Yes, administrators can create a new invitation for the same email address. The `InviteMemberModal` component allows POSTing a fresh invitation request, which generates a new magic-link ID and sends a replacement email. The previous invitation remains in the database but can be revoked to prevent confusion.

### What happens when an invitee clicks an expired or revoked invitation link?

The `AcceptInvitation` component fetches the invitation status via `GET /api/invitations/:invitationId`. If the status is not `Pending` (e.g., `Expired`, `Revoked`, or already `Accepted`), the UI displays a specific error state with the translation key `invalid_invitation_status`, informing the user that the link is no longer valid.

### How do administrators monitor pending invitations across tenants?

Administrators use the Invitations list page at [`packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx`](https://github.com/logto-io/logto/blob/main/packages/console/src/pages/TenantSettings/TenantMembers/Invitations/index.tsx), which queries the tenant-scoped invitations endpoint. The UI displays pending invitations with action buttons for revocation or deletion, while the `TenantInvitationDropdownItem` component provides a global notification mechanism for invitees viewing any page in the console.