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
- Verify the user's actual subscription tier vs. the endpoint's required policy handlers
- Redirect/notify the user to upgrade for the reported section+action rather than retrying
- If the plan should allow it, check that the ability is built from fresh subscription data (re-fetch, clear stale cache)
- 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
- Mirror plan limits in the frontend so blocked calls are never made
- Rebuild the ability from fresh subscription data on each request
- Handle SubscriptionException as an upgrade prompt, never a retry
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.