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
- Free a seat by removing or deactivating a workspace member.
- Upgrade the workspace plan so memberLimit rises.
- 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
- Run tryCheckSeat before every invite/accept mutation.
- Show remaining seats in the member management UI so over-invites are visible early.
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)