Prompt file imported from kcooper81/email-signature-manager (
.windsurf/workflows/paygates.md). Copyright stays with the author.
Before Making Paygate Changes
-
Read the pricing and paygates document first:
Read PRICING-AND-PAYGATES.md -
Check the current plan definitions:
Read apps/web/src/lib/billing/plans.ts -
Check the gate components:
Read apps/web/src/components/billing/upgrade-prompt.tsx -
Check the server-side plan guard:
Read apps/web/src/lib/billing/plan-guard.ts -
Check the field allowlists:
Read apps/web/src/lib/api/field-allowlists.ts
Adding a New Gated Feature
Frontend (UI)
- Add to
PlanFeaturesinterface inplans.ts - Add to each plan in
PLANSobject - Add to
featureRequirementsinupgrade-prompt.tsx - Use
<FeatureGate feature="newFeature">in the UI
Backend (API Routes)
- Add to
PlanFeaturesinterface inplans.ts - Add to each plan in
PLANSobject - In the API route:
import { getOrgPlan, checkFeature, planDenied } from '@/lib/billing/plan-guard'; const orgPlan = await getOrgPlan(supabase, organizationId); if (!checkFeature(orgPlan, 'newFeature')) { return planDenied('Feature Name', 'professional'); } - For limit checks:
import { getOrgPlan, checkLimit, limitDenied } from '@/lib/billing/plan-guard'; const orgPlan = await getOrgPlan(supabase, organizationId); if (!checkLimit(orgPlan, 'maxSomething', currentCount)) { return limitDenied('Resource', currentCount, orgPlan.plan.features.maxSomething); }
PUT Route Security
For PUT routes, always use field allowlists:
- Add an allowlist to
apps/web/src/lib/api/field-allowlists.ts - Use
pickAllowed(body, ALLOWLIST)instead of spreading...body
Modifying Limits
- Update limit values in
PLANSobject inplans.ts - Update
limitRequirementsinupgrade-prompt.tsx(if applicable) - All server-side
checkLimit()calls automatically use new limits - Update
PRICING-AND-PAYGATES.mddocumentation
Testing
- Turn OFF "Bypass Pay Gates (Dev)" toggle
- Test as Free plan user:
- Verify HubSpot, bulk ops, compliance blocks, directory sync, and scheduled deployments are accessible
- Verify disclaimers limited to 2 templates, 1 rule
- Verify Presets and Audit tabs are locked on disclaimers page
- Verify HR Sync page shows upgrade prompt
- Verify Automation page shows upgrade prompt
- Verify Brand pages show upgrade prompt
- Test as Professional plan user:
- Verify MS365, full analytics, and multiple templates are unlocked
- Verify HR Sync page loads fully
- Verify up to 5 lifecycle workflows allowed
- Verify disclaimer presets and audit trail accessible
- Test as Enterprise user:
- Verify brand governance accessible
- Verify unlimited lifecycle workflows
- Verify webhook actions allowed
- Verify MSP cascade features work
- Test dev bypass:
- Set NEXT_PUBLIC_BYPASS_PAY_GATES=true
- Verify all routes work regardless of plan
- Test API security:
- Verify PUT routes strip unauthorized fields (organization_id, is_system)
- Verify webhook URLs reject private/localhost addresses
- Verify cron route returns 500 when CRON_SECRET is unset