{"record":{"id":"2969c9ba64173fed","repo":"Dokploy/dokploy","slug":"forbidden-2969c9","errorCode":"FORBIDDEN","errorMessage":"You've reached your plan's limit of ${limit} ${resourceLabels[resource]}. Upgrade your plan to add more.","messagePattern":"You've reached your plan's limit of (.+?) (.+?)\\. Upgrade your plan to add more\\.","errorType":"error_code","errorClass":"TRPCError","httpStatus":403,"severity":"warning","filePath":"apps/dokploy/server/api/utils/plan-limits.ts","lineNumber":69,"sourceCode":"\nconst resourceLabels: Record<PlanLimitResource, string> = {\n\torganization: \"organizations\",\n\tmember: \"users\",\n\tenvironment: \"environments per project\",\n\tvolumeBackup: \"volume backups per application\",\n\tdatabaseBackup: \"backups per database\",\n\tscheduledJob: \"scheduled jobs per service\",\n};\n\nconst assertLimitForPlan = (\n\tplan: \"hobby\" | \"startup\" | \"legacy\" | null,\n\tresource: PlanLimitResource,\n\tcurrentCount: number,\n) => {\n\tconst limit = PLAN_LIMITS[plan ?? \"legacy\"][resource];\n\n\tif (currentCount >= limit) {\n\t\tthrow new TRPCError({\n\t\t\tcode: \"FORBIDDEN\",\n\t\t\tmessage: `You've reached your plan's limit of ${limit} ${resourceLabels[resource]}. Upgrade your plan to add more.`,\n\t\t});\n\t}\n};\n\nexport const assertOrganizationLimit = async (userId: string) => {\n\tconst plan = await getCurrentPlanForUser(userId);\n\tconst organizations = await db.query.organization.findMany({\n\t\twhere: eq(organization.ownerId, userId),\n\t});\n\tassertLimitForPlan(plan, \"organization\", organizations.length);\n};\n\nexport const assertMemberLimit = async (organizationId: string) => {\n\tconst plan = await getCurrentPlan(organizationId);\n\tconst members = await db.query.member.findMany({\n\t\twhere: eq(member.organizationId, organizationId),","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/Dokploy/dokploy/blob/546686ea3587f12ec5652217dedd9f7960fb6d15/apps/dokploy/server/api/utils/plan-limits.ts#L51-L87","documentation":"Plan-limit assertion that fires when creating a resource would meet or exceed the configured PLAN_LIMITS cap for the organization's plan (falls back to 'legacy' plan when none is set). It is a usage-quota FORBIDDEN error, not an auth problem.","triggerScenarios":"Calling assertOrganizationLimit / assertMemberLimit / assertEnvironmentLimit / assertVolumeBackupLimit / assertDatabaseBackupLimit / assertScheduledJobLimit when currentCount >= limit for the plan (e.g. adding another member on a plan capped at N members).","commonSituations":"Free/starter plan quotas hit; legacy self-hosted installs mapped to legacy limits; counts include soft-deleted resources inflating the number; users unaware a resource counts against the plan.","solutions":["Upgrade the plan to raise the limit","Delete unused resources of that type to get under the cap","Verify currentCount (ensure stale/deleted records are purged or excluded) before upgrading","Cache/assert limits client-side to warn before submission"],"exampleFix":"// before\nawait assertMemberLimit(org.plan, currentMembers);\n// after\nif (currentMembers >= PLAN_LIMITS[org.plan ?? 'legacy'].member) throw new Error('Limit reached — upgrade plan');\nawait assertMemberLimit(org.plan, currentMembers);","handlingStrategy":"validation","validationCode":"const limit = PLAN_LIMITS[plan ?? 'legacy'][resource];\nif (currentCount >= limit) throw new Error(`Plan limit of ${limit} reached — upgrade or free up slots`);","typeGuard":"const isUnderLimit = (plan: string | null, resource: PlanLimitResource, count: number): boolean =>\n  count < PLAN_LIMITS[plan ?? 'legacy'][resource];","tryCatchPattern":"catch (e) { if (e instanceof TRPCError && e.code === 'FORBIDDEN' && e.message.includes(\"plan's limit\")) showUpgradeDialog(); throw e; }","preventionTips":["Show current usage vs limit in the UI before create actions","Purge unused resources when near quota","Assert limits client-side before submitting create forms"],"tags":["billing","quota","plan-limits","trpc"],"backgroundTag":"plan-quota-exceeded","analyzedSha":"546686ea3587f12ec5652217dedd9f7960fb6d15","analyzedAt":"2026-08-27T05:18:58.095Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}