toeverything/AFFiNE · error · WorkspaceIdRequiredToUpdateTeamSubscription
workspace_id_required_to_update_team_subscription
workspace_id_required_to_update_team_subscription
Error message
Workspace id is required to update team subscription.
What it means
Thrown by the cancelSubscription GraphQL mutation when the requested plan is SubscriptionPlan.Team but the nullable workspaceId argument is null/omitted. Team subscriptions are scoped to a workspace, so the service cannot resolve which workspace's subscription to cancel without it. The resolver rejects the call before it reaches SubscriptionService.cancelSubscription.
Source
Thrown at packages/backend/server/src/plugins/payment/resolver.ts:344
name: 'plan',
type: () => SubscriptionPlan,
nullable: true,
defaultValue: SubscriptionPlan.Pro,
})
plan: SubscriptionPlan,
@Args({ name: 'workspaceId', type: () => String, nullable: true })
workspaceId: string | null,
@Headers('idempotency-key') idempotencyKey?: string,
@Args('idempotencyKey', {
type: () => String,
nullable: true,
deprecationReason: 'use header `Idempotency-Key`',
})
_?: string
) {
if (plan === SubscriptionPlan.Team) {
if (!workspaceId) {
throw new WorkspaceIdRequiredToUpdateTeamSubscription();
}
return this.service.cancelSubscription(
{ workspaceId, plan },
idempotencyKey
);
}
return this.service.cancelSubscription(
{
userId: user.id,
// @ts-expect-error exam inside
plan,
},
idempotencyKey
);
}
View on GitHub (pinned to b4c8548c09)
Solutions
- Pass the target workspace's id as the workspaceId argument whenever plan is 'team' in the cancelSubscription mutation.
- In the client, derive workspaceId from the active workspace context (e.g. currentWorkspace.id) and disable the cancel button until a workspace is selected.
- If you hit this from a script/tool, check the GraphQL operation document and add the $workspaceId variable to the variables payload.
Example fix
# before
mutation {
cancelSubscription(plan: team, recurring: monthly)
}
# after
mutation Cancel($workspaceId: String!) {
cancelSubscription(plan: team, recurring: monthly, workspaceId: $workspaceId)
} Defensive patterns
Strategy: validation
Validate before calling
const needsWorkspace = plan === 'team';
if (needsWorkspace && !workspaceId) {
throw new Error('workspaceId is required to cancel a team subscription');
}
await gql.cancelSubscription({ plan, recurring, workspaceId }); Type guard
function isTeamCancelArgs(a: { plan: string; workspaceId?: string | null }): a is { plan: 'team'; workspaceId: string } {
return a.plan === 'team' && typeof a.workspaceId === 'string' && a.workspaceId.length > 0;
} Try / catch
catch (e) { if (gqlErrorCode(e) === 'workspace_id_required_to_update_team_subscription') { promptWorkspaceSelection(); return; } throw e; } Prevention
- Build mutation variables through one helper that adds workspaceId for plan 'team'.
- Disable plan actions in the UI until the workspace context is loaded.
- Add a client unit test asserting team-plan cancel/resume/recurring mutations always include workspaceId.
When it happens
Trigger: Calling cancelSubscription(plan: SubscriptionPlan.Team) (or the lowercase 'team' enum value) without the workspaceId argument; passing workspaceId: null; frontend sending an empty string that GraphQL coerces to null.
Common situations: UI has separate cancel flows for Pro (user-scoped, no workspace picker) and Team, and the Team branch forgets to thread the current workspace id; user's workspace context not yet loaded when the cancel button is clicked; a client built against the old API that never sent workspaceId.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/03312236b37f88b3.
Report an issue: GitHub.