---
title: "I Almost Built an Entitlement System for a Feature That Was Already a Page"
canonical: https://dxdev.com/blog/reuse-the-gate-feature-gating-via-minpkg/
datePublished: 2026-04-10
---
A group of tournament pages needed tier-aware navigation. My first plan sketched a feature catalog, a checks layer, and a table mapping organizations to allowed features. That is a familiar reflex: someone says "put these behind a plan" and a new subsystem starts to feel inevitable. Before building it, I looked for whether an existing structure already decided how the interface should present those pages.

The answer was yes. An earlier navigation change had given every page a `minPkg` field. The nav builder used that field to render an upgrade prompt rather than a live link when an organization's package did not cover the page. That was already the right UI mechanism for this work, even though it came from a navigation change rather than a pricing project.

Each item in the navigation list corresponded to a page in the same site structure. For the interface question, "what tier should this page advertise" and "what tier should this navigation item show" were the same question, and the structure already held the answer. That did not remove the separate need for server-side checks wherever an action itself needed protection.

## What I wrote instead of a new system

The UI change was a small set of guarded assignments in the site-structure setup, one per page, each assigning a tier value to `.minPkg`:

```js
if (pages.example) pages.example.minPkg = 'tier-name';
```

That avoided a new navigation table or second UI catalog that would have to stay in sync with the first. The implementation remained a prototype branch rather than a production release, which made the following ordering issue safe to find before it reached users.

## The line that did nothing

Most of the tier assignments worked the first time. One dynamically added page received no tier value, silently.

One page was not static. The base site structure added it later through a separate feature-registration call. My tier-assignment block ran before that call, so the guarded assignment quietly skipped a page object that did not exist yet. The later registration created the page without the navigation-tier value. The page came into existence after its own UI rule had already been attempted.

There was also a separate server-side feature-access mechanism for actions that did not map cleanly onto one navigation page. Its exact checks were intentionally separate from the navigation field. Where a page and a protected action overlapped, the UI and server paths still needed to agree on the business rule, but neither should be treated as a replacement for the other.

The fix was to apply the same tier assignment after the feature-registration call, when the page object existed. The broader lesson was to apply declarative configuration after the structure it configures has been assembled, then verify dynamically added entries explicitly.

## What did not ship

This work remained on an experimental branch and did not become a production entitlement system. That is worth stating plainly: it is a case study in finding a reusable navigation primitive and catching an ordering defect before release, not proof that the prototype became the platform's final access model.

## Related

- [I Was About to Write a daily_digest.py. The Platform Already Shipped It.](dont-build-infrastructure-the-platform-already-ships): the same instinct to look for an existing primitive before building a parallel system
- [The Bundle-Ceiling Model: Separating Subscription and Per-Event Cost](bundle-ceiling-saas-pricing-decouple-subscription-from-per-event): the pricing-design context that made tier-aware navigation relevant
