RowShield

Help / Plans & billing

Entitlements: one pure function decides every limit

All plansLast reviewed 2026-08-23

Every limit in RowShield — project counts, scan intervals, alert channels, seats — comes from a single pure function called entitlementsFor. It takes the organisation’s plan and subscription status and returns what that organisation is entitled to right now. No hidden lookups, no environment-dependent behaviour, no special cases buried in route handlers.

Purity is the point. Because the answer depends only on its inputs, it can be unit tested without a payment provider or a network, and because both the dashboard and the API call the same function, the two cannot disagree about what a plan allows.

One function, two callers

The interface consults entitlements when rendering: the connect form knows the project ceiling before you fill it in, and the alerts page offers only channels your plan includes. The worker applies the same numbers on schedule — the poller computes each organisation’s effective scan interval from the identical call, so a plan change takes effect without redeploying anything. Same inputs, same outputs, same instant.

const ent = entitlementsFor(
  { plan: 'indie', subscriptionStatus: org.subscriptionStatus },
  { billingEnabled: true },
);
// ent.maxProjects === 3
// ent.minPollingIntervalMin === 60
// ent.alertChannels === ['email', 'slack']
// ent.paymentFailing === false

Limits feel like signposts, not walls

When an action exceeds an entitlement, the response names the limit, explains it in a sentence, and identifies the cheapest plan that would allow the action. Nothing is silently truncated: a fourth project on Indie is refused with a message, not accepted and hidden.

Scan frequency works by clamping rather than rewriting. A project configured for five-minute polls on a plan whose floor is hourly is simply scanned hourly — the setting is kept untouched, so it takes effect again the moment the plan allows it.

Fail-open when billing is not configured

With no payment credentials present — every local development run and every continuous-integration job — billingEnabled is false and entitlements resolve to the full Team set, ungated. A paywall that depended on a secret being present would make the application unusable out of the box and fail in the wrong direction; the code chooses deliberately to fail open in development and closed nowhere.

Related questions

If the interface and API call the same function, can I trust the UI to show real limits?
Yes. There is no second source of truth to drift out of date. What the interface displays is what the API enforces, because both read the same return value.
Where is the function defined?
In packages/billing, alongside the plan catalogue. It imports no SDK and touches no network, which is why the whole paywall can be reviewed — or tested — in one sitting.

Did this answer your question? If not, tell us what is missing — article corrections go straight to the person who maintains it.

RowShield checks 9 rule classes continuously. This article describes shipped behaviour only.