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
- Call tryCheckSeat(workspaceId, true) before issuing the invite and surface the result to the user.
- Upgrade the workspace plan to raise memberLimit, or remove inactive members first.
- 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
- Call tryCheckSeat before rendering the invite button so the UI can show seat availability.
- Reconcile quota state (reconcileWorkspaceQuotaState) before the seat check if counts may be stale.
- Prompt for plan upgrade when memberCount approaches memberLimit.
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.