toeverything/AFFiNE · error · MemberQuotaExceeded

member_quota_exceeded

member_quota_exceeded

Error message

You have exceeded your workspace member quota.

What it means

Thrown by QuotaService.checkSeat (packages/backend/server/src/core/quota/service.ts:118). tryCheckSeat compares the workspace memberCount (minus 1 when excludeSelf) against the plan's memberLimit; when there is no free seat, checkSeat throws MemberQuotaExceeded, guarding invite and invitation-accept flows.

Solutions

  1. Free a seat by removing or deactivating a workspace member.
  2. Upgrade the workspace plan so memberLimit rises.
  3. Call tryCheckSeat(workspaceId) before the invite and surface an upgrade prompt when it returns false.

Example fix

// before
await inviteService.invite(workspaceId, email);

// after
if (!(await quota.tryCheckSeat(workspaceId))) {
  return showUpgradeRequired('member_quota_exceeded');
}
await inviteService.invite(workspaceId, email);
Defensive patterns

Strategy: validation

Validate before calling

if (!(await quota.tryCheckSeat(workspaceId))) {
  return { ok: false, reason: 'member_quota_exceeded' };
}

Type guard

const isMemberQuotaExceeded = (e: unknown): e is MemberQuotaExceeded =>
  e instanceof MemberQuotaExceeded;

Try / catch

try {
  await invites.accept(inviteId);
} catch (e) {
  if (e instanceof MemberQuotaExceeded) return res.status(402).json({ code: e.code });
  throw e;
}

Prevention

When it happens

Trigger: Posting an invite, or accepting a pending invitation, when the workspace already has memberLimit members and excludeSelf does not apply.

Common situations: Free/small plan ceilings, a plan downgraded without pruning members, or bulk CSV invites exceeding remaining seats.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/a6bc660940e6299e. Report an issue: GitHub.

Appendix: source

Thrown at packages/backend/server/src/core/quota/service.ts:118

      await this.quotaState.reconcileWorkspaceQuotaState(workspaceId);

    return {
      memberCount: state.memberCount,
      memberLimit: state.seatLimit,
    };
  }

  async tryCheckSeat(workspaceId: string, excludeSelf = false) {
    const quota = await this.getWorkspaceSeatQuota(workspaceId);

    return quota.memberCount - (excludeSelf ? 1 : 0) < quota.memberLimit;
  }

  async checkSeat(workspaceId: string, excludeSelf = false) {
    const available = await this.tryCheckSeat(workspaceId, excludeSelf);

    if (!available) {
      throw new MemberQuotaExceeded();
    }
  }

  formatWorkspaceQuota(
    quota: Omit<WorkspaceQuotaType, 'humanReadable'>
  ): WorkspaceQuotaHumanReadableType {
    return {
      name: quota.name,
      blobLimit: formatSize(quota.blobLimit),
      storageQuota: formatSize(quota.storageQuota),
      storageQuotaUsed: formatSize(quota.usedStorageQuota),
      historyPeriod: formatDate(quota.historyPeriod),
      memberLimit: quota.memberLimit.toString(),
      memberCount: quota.memberCount.toString(),
      overcapacityMemberCount: quota.overcapacityMemberCount.toString(),
    };
  }

View on GitHub (pinned to b4c8548c09)