A feature flag for a feature that doesn't exist yet
Foodproof is free today. Not “free with a hidden paid plan somewhere”, actually free, everything unlocked. And yet, the paywall code is already sitting inside the app you can download from the App Store right now. Here’s why, and how it’s built.
A single boolean
It all comes down to a four-line file:
// src/config/features.ts
export const FEATURES = {
SUBSCRIPTION_ENABLED: false,
} as const;
That flag is read directly in five different screens across the app, wherever a limitation or a premium badge might appear:
{FEATURES.SUBSCRIPTION_ENABLED && isPremium && (
<PremiumBadge />
)}
{FEATURES.SUBSCRIPTION_ENABLED && !isPremium && history.length > maxHistoryItems && (
<UpgradePrompt />
)}
As long as SUBSCRIPTION_ENABLED is false, none of these blocks ever render. The code exists, ships in the same builds everyone downloads, but produces zero output. No risk of showing a half-finished paywall to a real user.
The detail that matters: nothing is wired up behind it
What makes this an honest story rather than just a config trick is that RevenueCat, the service we plan to use for managing subscriptions, isn’t even integrated yet. No react-native-purchases in the dependencies, no RevenueCat import anywhere in the code.
The store that’s supposed to carry the user’s subscription status has a status that’s… hardcoded:
const useSubscriptionStore = create<SubscriptionState>()(
persist(
(set) => ({
isPremium: true, // TODO: wire up RevenueCat here
plan: "free",
// ...
})
)
);
isPremium: true for everyone, with a comment openly admitting it’s unfinished. And since the global flag is false, that value doesn’t currently affect the experience at all: the code blocks reading it never render anyway. It’s a placeholder waiting for its moment.
Why build this before there’s even a way to pay
The alternative would have been to keep the paywall code in a separate branch and merge it all at once the day RevenueCat is ready. On a solo project, that option has a real downside: the longer a branch lives alongside main, the more it drifts, and the riskier that final merge becomes, a single moment where everything can break at once.
By developing the paywall directly inside the same builds as everything else, behind a flag that cleanly turns it off, every screen touching premium UI gets exercised continuously, in the same context as the code around it. The day RevenueCat gets wired up, only two things need to change: swap the hardcoded isPremium value for the real subscription status, and flip SUBSCRIPTION_ENABLED to true. No giant branch merge, no moment where nobody’s sure anything still works.
It’s as much a product decision as a technical one: ship the infrastructure before the feature, so that when the feature arrives, it’s nothing more than a switch to flip.