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 when tryCheckSeat reports the workspace's active member count has reached or exceeded its seatLimit. It is the hard gate that prevents adding new members (or accepting invites) once the billing plan's seat quota is exhausted.

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 26c515e050)

Solutions

  1. Call tryCheckSeat(workspaceId, true) before issuing the invite and surface the result to the user.
  2. Upgrade the workspace plan to raise memberLimit, or remove inactive members first.
  3. If the count is stale, trigger quotaState.reconcileWorkspaceQuotaState to refresh before retrying.

Example fix

// before
await this.quota.checkSeat(workspaceId, true);

// after
const hasSeat = await this.quota.tryCheckSeat(workspaceId, true);
if (!hasSeat) {
  throw new MemberQuotaExceeded('Upgrade your plan to add more members.');
}
Defensive patterns

Strategy: validation

Validate before calling

const hasSeat = await quota.tryCheckSeat(workspaceId, true);
if (!hasSeat) {
  return { canInvite: false, reason: 'seat-quota-exceeded', upgradeUrl };
}

Type guard

// n/a

Try / catch

try {
  await quota.checkSeat(workspaceId, true);
} catch (e) {
  if (e instanceof MemberQuotaExceeded) {
    // surface upgrade prompt instead of failing the invite form
    return res.status(402).json({ code: 'member_quota_exceeded', upgradeUrl });
  }
  throw e;
}

Prevention

When it happens

Trigger: Inviting or accepting an invite when (memberCount - excludeSelf) >= memberLimit; member count reconciled upward by quota state sync just before an invite; plan downgrade reduced memberLimit below current memberCount.

Common situations: Self-host or cloud workspace on a Free/Pro plan hitting the seat cap; owner tries to invite after a teammate's pending invite was accepted; automation scripts inviting batches without pre-checking seats.

Related errors


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