{"record":{"id":"7f4a324bd61bfdcf","repo":"gitroomhq/postiz-app","slug":"subscription-required-section-item-1-action","errorCode":null,"errorMessage":"Subscription required: section ${item[1]}, action ${item[0]}","messagePattern":"Subscription required: section (.+?), action (.+?)","errorType":"exception","errorClass":"SubscriptionException","httpStatus":null,"severity":"error","filePath":"apps/backend/src/services/auth/permissions/permissions.guard.ts","lineNumber":57,"sourceCode":"    if (!policyHandlers || !policyHandlers.length) {\n      return true;\n    }\n\n    // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n    // @ts-expect-error\n    const { org }: { org: Organization } = request;\n\n    const refreshChannelId = typeof request.query?.refresh === 'string' ? request.query.refresh : undefined;\n\n    // @ts-ignore\n    const ability = await this._authorizationService.check(org.id, org.createdAt, org.users[0].role, policyHandlers, refreshChannelId);\n\n    const item = policyHandlers.find(\n      (handler) => !this.execPolicyHandler(handler, ability)\n    );\n\n    if (item) {\n      throw new SubscriptionException({\n        section: item[1],\n        action: item[0],\n      });\n    }\n\n    return true;\n  }\n\n  private execPolicyHandler(handler: AbilityPolicy, ability: AppAbility) {\n    return ability.can(handler[0], handler[1]);\n  }\n}\n","sourceCodeStart":39,"sourceCodeEnd":70,"githubUrl":"https://github.com/gitroomhq/postiz-app/blob/0f1647f7491a217d43eb5ae7a480484bdf0aff3e/apps/backend/src/services/auth/permissions/permissions.guard.ts#L39-L70","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before — frontend calls directly\nawait api.createChannel(payload);\n\n// after — check plan limit first, prompt upgrade\nif (channels.length >= plan.limits.channels) {\n  showUpgradeDialog('channels', 'create');\n} else {\n  await api.createChannel(payload);\n}","handlingStrategy":"validation","validationCode":"const ability = buildAbilityFor(currentPlan);\nif (!ability.can(action, section)) {\n  showUpgradeDialog(section, action);\n  return;\n}\nawait api.call(payload);","typeGuard":"null","tryCatchPattern":"try {\n  await api.call(payload);\n} catch (e) {\n  if (e instanceof SubscriptionException) {\n    showUpgradeDialog(e.section, e.action);\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["authorization","subscription","paywall","rbac"],"backgroundTag":"subscription-limit-exceeded","analyzedSha":"0f1647f7491a217d43eb5ae7a480484bdf0aff3e","analyzedAt":"2026-08-27T12:09:55.020Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}