Skip to main content

Get invitation details

Retrieve information about an invitation using its token:
const invite = await authClient.invite.get({
  token: "invitation-token",
});

Response structure

{
  "status": true,
  "inviter": {
    "email": "[email protected]",
    "name": "Admin User",
    "image": "https://example.com/avatar.jpg"
  },
  "invitation": {
    "email": "[email protected]",
    "createdAt": "2024-03-04T10:00:00.000Z",
    "role": "member",
    "newAccount": true
  }
}

Access control

For invitations with an email address, only the invited user can retrieve details:
// Only works if logged in as [email protected]
const invite = await authClient.invite.get({
  token: "private-invite-token",
});
If the logged-in user’s email doesn’t match the invite email, the request fails with INVALID_TOKEN.

Use cases

function InvitePreview({ token }: { token: string }) {
  const [invite, setInvite] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    authClient.invite.get({ token })
      .then(setInvite)
      .catch(() => alert("Invalid invitation"))
      .finally(() => setLoading(false));
  }, [token]);

  if (loading) return <p>Loading...</p>;
  if (!invite) return <p>Invitation not found</p>;

  return (
    <div>
      <h2>You've been invited!</h2>
      <p>{invite.inviter.name} invited you to join as {invite.invitation.role}</p>
      <p>
        {invite.invitation.newAccount
          ? "Create your account to accept"
          : "Sign in to accept this invitation"}
      </p>
    </div>
  );
}
async function validateInviteToken(token: string) {
  try {
    const invite = await authClient.invite.get({ token });
    return {
      valid: true,
      role: invite.invitation.role,
      inviterName: invite.inviter.name,
    };
  } catch (error) {
    return { valid: false };
  }
}

Cancel an invitation

Cancel an invitation you created. Only the user who created the invite can cancel it.
const result = await authClient.invite.cancel({
  token: "invitation-token",
});

// Result: { status: true, message: "Invite cancelled successfully" }

Requirements

  • You must be logged in
  • You must be the user who created the invitation
  • The invitation must have pending status
  • The canCancelInvite permission check must pass

Permission checks

The server respects your canCancelInvite configuration:
invite({
  canCancelInvite: async ({ inviterUser, invitation, ctx }) => {
    // Only admins can cancel admin invitations
    if (invitation.role === 'admin' && inviterUser.role !== 'admin') {
      return false;
    }
    return true;
  },
})

Cleanup behavior

Depending on your server configuration:
The invitation is marked as canceled but remains in the database:
UPDATE invite SET status = 'canceled' WHERE id = ?;

Example: Cancel button

function CancelInviteButton({ token }: { token: string }) {
  const [loading, setLoading] = useState(false);

  const handleCancel = async () => {
    if (!confirm("Are you sure you want to cancel this invitation?")) {
      return;
    }

    setLoading(true);
    try {
      await authClient.invite.cancel({ token });
      alert("Invitation cancelled");
    } catch (error) {
      if (error.errorCode === "INSUFFICIENT_PERMISSIONS") {
        alert("You don't have permission to cancel this invite");
      } else if (error.errorCode === "INVALID_TOKEN") {
        alert("This invitation is no longer valid");
      } else {
        alert("Failed to cancel invitation");
      }
    } finally {
      setLoading(false);
    }
  };

  return (
    <button onClick={handleCancel} disabled={loading}>
      {loading ? "Cancelling..." : "Cancel Invitation"}
    </button>
  );
}

Reject an invitation

Reject an invitation sent to you. Only available for private invites, and only the recipient can reject.
const result = await authClient.invite.reject({
  token: "invitation-token",
});

// Result: { status: true, message: "Invite rejected successfully" }

Requirements

  • You must be logged in
  • The invitation must be a private invite (with email)
  • Your email must match the invitation email
  • The invitation must have pending status
  • The canRejectInvite permission check must pass

Permission checks

invite({
  canRejectInvite: async ({ inviteeUser, invitation, ctx }) => {
    // Custom logic to control rejections
    return true;
  },
})

Public invites cannot be rejected

Attempting to reject a public invite fails:
try {
  await authClient.invite.reject({ token: "public-invite-token" });
} catch (error) {
  // error.errorCode === "CANT_REJECT_INVITE"
}

Cleanup behavior

Same as cancel - respects cleanupInvitesOnDecision configuration.

Example: Reject button

function RejectInviteButton({ token }: { token: string }) {
  const handleReject = async () => {
    if (!confirm("Are you sure you want to reject this invitation?")) {
      return;
    }

    try {
      await authClient.invite.reject({ token });
      alert("Invitation rejected");
    } catch (error) {
      if (error.errorCode === "CANT_REJECT_INVITE") {
        alert("You cannot reject this invitation");
      } else if (error.errorCode === "INVALID_TOKEN") {
        alert("This invitation is no longer valid");
      } else {
        alert("Failed to reject invitation");
      }
    }
  };

  return <button onClick={handleReject}>Reject Invitation</button>;
}

Invitation lifecycle

Understanding invitation states:

Status values

pending
Initial state. Invitation is active and can be accepted.
used
Invitation was successfully accepted and reached max uses.
canceled
Creator cancelled the invitation.
rejected
Recipient rejected the invitation (private invites only).

Status transitions

Only pending invitations can be:
  • Accepted (activated)
  • Canceled by creator
  • Rejected by recipient
Once an invitation changes to used, canceled, or rejected, it cannot be used or modified.

Example: Invitation management dashboard

import { useState, useEffect } from "react";
import { authClient } from "@/lib/auth-client";

function InviteManagement() {
  const [invites, setInvites] = useState([]);

  // Note: You'll need to implement a server endpoint to list invites
  // The invite plugin doesn't include a list endpoint by default

  const handleCancel = async (token: string) => {
    try {
      await authClient.invite.cancel({ token });
      // Refresh list
      setInvites(invites.filter(i => i.token !== token));
    } catch (error) {
      alert("Failed to cancel invitation");
    }
  };

  return (
    <div>
      <h2>Pending Invitations</h2>
      <table>
        <thead>
          <tr>
            <th>Email</th>
            <th>Role</th>
            <th>Created</th>
            <th>Uses</th>
            <th>Actions</th>
          </tr>
        </thead>
        <tbody>
          {invites.map(invite => (
            <tr key={invite.token}>
              <td>{invite.email || "(Public invite)"}</td>
              <td>{invite.role}</td>
              <td>{new Date(invite.createdAt).toLocaleDateString()}</td>
              <td>{invite.usedCount} / {invite.maxUses}</td>
              <td>
                <button onClick={() => handleCancel(invite.token)}>
                  Cancel
                </button>
              </td>
            </tr>
          ))}
        </tbody>
      </table>
    </div>
  );
}
The invite plugin provides individual invite operations but doesn’t include a “list all invites” endpoint. You’ll need to query the database directly or create a custom endpoint for this functionality.

Server-side operations

You can also manage invites from your server:
import { auth } from "./auth";

// Get invite details
const invite = await auth.api.getInvite({
  query: { token: "invitation-token" },
  headers: request.headers,
});

// Cancel invite
const result = await auth.api.cancelInvite({
  body: { token: "invitation-token" },
  headers: request.headers,
});

// Reject invite
const result = await auth.api.rejectInvite({
  body: { token: "invitation-token" },
  headers: request.headers,
});

Cleanup strategies

Choose how to handle completed invitations:

Keep historical data (default)

invite({
  cleanupInvitesOnDecision: false,
  cleanupInvitesAfterMaxUses: false,
})
Maintains full audit trail of all invitations and their usage.

Automatic cleanup

invite({
  cleanupInvitesOnDecision: true, // Delete when canceled/rejected
  cleanupInvitesAfterMaxUses: true, // Delete when fully used
})
Reduces database size by removing completed invitations.

Manual cleanup

Implement a cron job to delete old invitations:
// Run daily
await db.delete(invite)
  .where(
    and(
      eq(invite.status, 'used'),
      lt(invite.expiresAt, new Date(Date.now() - 30 * 24 * 60 * 60 * 1000))
    )
  );

Error codes

INVALID_TOKEN
Token doesn’t exist, is expired, or invitation status is not pending.
INSUFFICIENT_PERMISSIONS
User lacks permission to perform the operation.
CANT_REJECT_INVITE
Either the invite is public, or the user’s email doesn’t match the invite email.
INVITER_NOT_FOUND
The user who created the invitation no longer exists.

Next steps

Hooks and callbacks

Respond to invitation lifecycle events

Email integration

Configure email sending for invitations

Build docs developers (and LLMs) love