gitroomhq/postiz-app · error · SubscriptionException

Subscription required: section ${item[1]}, action ${item[0]}

Error message

Subscription required: section ${item[1]}, action ${item[0]}

What it means

A NestJS guard evaluated the request against the subscription policy (CASL-style handlers) and at least one handler failed, so a SubscriptionException is thrown carrying the section/action that is not covered by the user's plan. This is a paywall/entitlement error, not a crash.

Source

Thrown at apps/backend/src/services/auth/permissions/permissions.guard.ts:57

    if (!policyHandlers || !policyHandlers.length) {
      return true;
    }

    // eslint-disable-next-line @typescript-eslint/ban-ts-comment
    // @ts-expect-error
    const { org }: { org: Organization } = request;

    const refreshChannelId = typeof request.query?.refresh === 'string' ? request.query.refresh : undefined;

    // @ts-ignore
    const ability = await this._authorizationService.check(org.id, org.createdAt, org.users[0].role, policyHandlers, refreshChannelId);

    const item = policyHandlers.find(
      (handler) => !this.execPolicyHandler(handler, ability)
    );

    if (item) {
      throw new SubscriptionException({
        section: item[1],
        action: item[0],
      });
    }

    return true;
  }

  private execPolicyHandler(handler: AbilityPolicy, ability: AppAbility) {
    return ability.can(handler[0], handler[1]);
  }
}

View on GitHub (pinned to 0f1647f749)

Solutions

  1. Verify the user's actual subscription tier vs. the endpoint's required policy handlers
  2. Redirect/notify the user to upgrade for the reported section+action rather than retrying
  3. If the plan should allow it, check that the ability is built from fresh subscription data (re-fetch, clear stale cache)
  4. Ensure the frontend mirrors the same limits so the blocked call is never made

Example fix

// before — frontend calls directly
await api.createChannel(payload);

// after — check plan limit first, prompt upgrade
if (channels.length >= plan.limits.channels) {
  showUpgradeDialog('channels', 'create');
} else {
  await api.createChannel(payload);
}
Defensive patterns

Strategy: validation

Validate before calling

const ability = buildAbilityFor(currentPlan);
if (!ability.can(action, section)) {
  showUpgradeDialog(section, action);
  return;
}
await api.call(payload);

Type guard

null

Try / catch

try {
  await api.call(payload);
} catch (e) {
  if (e instanceof SubscriptionException) {
    showUpgradeDialog(e.section, e.action);
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: A user on a free/cheaper plan invokes an API whose handler is decorated with policy handlers requiring a higher tier, e.g. adding more channels than the plan allows (section=channels, action=create) or accessing an analytics section.

Common situations: Free-tier user exceeds limits; subscription expired/downgraded but cached JWT still reflects old plan; new feature gated by a policy handler but frontend doesn't hide the UI; stale ability built from an outdated subscription record.

Related errors


AI-assisted analysis of gitroomhq/postiz-app@0f1647f749 (2026-08-27). Data as JSON: /api/errors/7f4a324bd61bfdcf. Report an issue: GitHub.