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

  1. Pass the target workspace's id as the workspaceId argument whenever plan is 'team' in the cancelSubscription mutation.
  2. In the client, derive workspaceId from the active workspace context (e.g. currentWorkspace.id) and disable the cancel button until a workspace is selected.
  3. 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

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.