The Beta Launch requirement sounded simple: payments were not fully ready, so parents should be able to try the product for free first.
The implementation could easily go in the wrong direction.
The quickest solution would be to update every user’s subscription status to some kind of trial state. The pages would work. The permission checks would be easy. But I did not like that approach, because it writes a temporary operating decision into long-term business data.
Later, when reading the database, we would not know whether a user really entered a trial or was temporarily covered by Beta.
I preferred to treat Beta as a runtime plan.
The database still keeps the original subscription facts: free, trialing, active, canceled, and so on. When the server sees BETA_MODE=true, it computes the user’s effective access as beta_trial.
That keeps a few things clean:
- no new subscription status in the database
- no reset of
trialUsedAt - no overwrite of
currentPeriodEnd - normal subscription logic comes back when Beta is turned off
There is another important detail: restrictions cannot live only in the UI.
The subscription page can say “Coming soon”, but direct URLs should not bypass the rule. Actions like add child, withdraw, and checkout cannot rely on disabled buttons. The actual guard has to live in server actions and route handlers.
This was not a hard feature to code, but it needed clear boundaries.
Beta, rollouts, and operations campaigns are usually temporary. They should sit on top of business facts, not rewrite the facts themselves.