Skip to main content

Command Palette

Search for a command to run...

Open-sourcing parts of our React Native template engine

Updated
11 min readView as Markdown
S
I’m a Product Designer and Developer with 10+ years of experience designing and building digital products from early concepts to production. My work sits at the intersection of design and technology. I enjoy turning complex product requirements into simple, intuitive experiences while staying closely involved in the implementation process. Over the years, I’ve worked with startups, SaaS companies, and digital product teams across Europe, contributing to everything from product strategy and UX research to interface design, design systems, and frontend development.

We open-sourced parts of our React Native template engine last month. Not all of it. The theme engine, the ProductData config schema, the UI primitives, and the docs are public. The Stripe webhook, the license-grant fan-out, the admin routes, and the Supabase migrations aren't. And they're not going to be. This post is the specific breakdown of what went over the wall, what stayed inside, and the three questions we used to decide.

The short version: we open-sourced the parts that were already generic enough to be public: patterns other people could reuse without touching our billing surface. If the code answers a design question, it's public. If the code answers an entitlement question, it's private.

Two developers at a desk reviewing code on a shared monitor

Why we split it at all

We sell React Native app templates: Expo + Supabase + NativeWind, full source, one-time payment. Every template ships with its own storefront page on applighter.com: hero, feature grid, screenshot carousel, pricing card, FAQ. Each one gets a themed page with its own primary color, its own screens, its own copy.

Two years ago that meant near-duplicate Next.js pages with hardcoded colors. Then we built a config-driven system. Then customers asked us how it worked. Then we realized the interesting part (the "how") wasn't the part making us money. The part making us money was the license grant that lets someone download a ZIP after a Stripe checkout succeeds.

So we drew a line.

What we open-sourced

Three things went public:

1. The ProductData contract. Every template in Applighter is described by a single TypeScript interface in app/apps/lib/data/types.ts. Screenshots, features, pricing tiers, tech stack, testimonials, FAQs: all of it. When we add a new template, we write one config file like app/apps/config/weather-app.config.ts and register it in app/apps/config/index.ts:

export const appConfigs: Record<string, ProductData> = {
  "weather-app": weatherAppConfig,
  "fitness-app": fitnessAppConfig,
  "e-learning-app": eLearningAppConfig,
  "taxi-booking-app": taxiBookingAppConfig,
  "ai-voice-notes": aiVoiceNotesConfig,
  "chat-with-pdf": chatWithPdfConfig,
};

That's the whole registry. Adding a template is a config file plus one line. There's nothing proprietary about that pattern; half the marketing sites on the internet do a version of it. Making the schema public means anyone building a similar catalog site has a reference implementation.

2. The runtime theme engine. Each template on our site has its own primary color. Weather app is a saturated blue. Fitness app is a warmer orange. Taxi booking is nearly black. We used to ship a Tailwind class per template. Now the primary color is a hex string on the ProductData object, and app/apps/lib/theme.ts converts it to HSL at runtime and injects it into CSS custom properties that Tailwind reads through color-mix(). Every bg-primary/10 variant Just Works, per template, without generating extra CSS at build time.

That code is small, self-contained, and useful to anyone building a multi-tenant marketing site. Zero business logic. Full public.

3. The docs. The content/docs/ folder (installation, environment setup, folder structure, Supabase integration, Expo integration, UI component guide) has been public since day one. We didn't "open source" it so much as stop pretending we hadn't. If you're evaluating a template and you can't read the docs before paying, you're going to buy something else. The docs are the sales pitch.

A close-up of code on a laptop screen with syntax-highlighted TypeScript

What stayed proprietary

Four categories, in rough order of "how loudly we'd shout if this leaked."

1. The license-grant fan-out. When someone buys a template on Applighter, app/api/webhook/stripe/route.ts receives a checkout.session.completed event and calls into lib/grants.ts. That module's job is fan-out: if the customer bought a single template, they get one grant row. If they bought a bundle, resolveGrantTargets() walks the bundle_items table and writes a grant per member template. The free-claim path (for free templates like the Todo starter) goes through the same code, which is the whole point: one function, two entry points, no drift.

This is not code you can safely publish. It's the code that decides whether an authenticated user is allowed to hit the download endpoint. Every line is a lever an attacker could poke at. It stays private.

2. The Supabase migrations for entitlement tables. user_product_grants, product_licenses, bundle_items, template_screens: the row-level-security policies, the columns, the constraints. All private. The generic Supabase-auth-RLS-storage pattern is documented publicly; the specific schema that says "this user owns this template" is not. Publishing it would give any adversary a map of exactly what to try to bypass.

3. Partner integrations. modules/services/OutrankArticleService.ts receives AI-generated draft articles from a third-party content provider. modules/services/SlackService.ts posts sales and support events to internal channels. app/api/webhook/rewardful/route.ts receives affiliate referrals from Rewardful. app/api/rn/* is a partner API for RapidNative, which sits behind bearer-token auth and signature verification via lib/verifyRapidNativeRequest.ts. None of this is code anyone else could use; it's glue between us and named counterparties, plus the credentials to talk to them.

4. Admin routes. app/api/admin/* includes impersonation, template request triage, and support queue management. There's nothing clever in here. It's a bunch of if (!isAdmin) return 403 and a bunch of DB reads. But "boring code that enforces boundaries" is exactly the code you don't publish, because the moment it becomes interesting is the moment someone finds a hole in it.

A padlock resting on a circuit board

The comparison

Three different template projects, three different splits:

Project What's public What's private Business model
Applighter (open-core hybrid) Theme engine, ProductData schema, UI primitives, docs Stripe webhook, license grants, RLS schema, admin, partner APIs One-time template sales; access gated by Stripe
Ignite (fully open) Everything: CLI, generators, boilerplate Nothing Agency lead-gen; you pay for their consulting
Shipnative (fully closed) Marketing site only The whole boilerplate + updates Subscription for source + updates

None of these are wrong. They're three coherent answers to a real question. The interesting thing is what happens when you're honest about which parts of your codebase actually generate revenue and which parts are just plumbing.

For Ignite, the boilerplate is a marketing loss-leader for the agency behind it. For Shipnative, the boilerplate is the product, and every leaked copy is a lost subscription. For us, the templates are the product, but the engine around them is just how we render marketing pages. It doesn't need to be secret to protect revenue. The Stripe webhook does.

The three questions we asked

Before every file, before every folder, we asked three things.

Question 1: If a competitor copied this line-for-line, would they win?

Nobody wins by copying our theme engine. There are ten thousand hex-to-HSL converters on npm. The theme engine is not a moat. If someone forks it and uses it in their own marketing site, that's fine: the moat is the templates themselves, plus everything that surrounds them (documentation, discoverability, support, updates).

But if someone copied our grant fan-out, including the SQL constraints on user_product_grants, they'd have an exact map of how our entitlement checks work. That's not a competitive edge for them; it's an attack surface for us. Different question, different answer.

Question 2: Is this code interesting to future customers, or only to us?

The ProductData schema is genuinely interesting. It's a real example of how to model a template catalog in a way that survives fifty templates without needing a rewrite. A prospective customer reading app/apps/config/weather-app.config.ts learns something about how we think about product data. That's a positive signal in a purchase decision.

The internal Slack service that posts "$29 sale from user X" to a channel is not interesting to anyone but us. Publishing it doesn't help customers evaluate the product. Keeping it private doesn't hurt them.

Question 3: Is this the piece the payment guards?

This one is the tie-breaker. Anything that sits on the "you paid, therefore you get" edge stays private. Everything else is negotiable.

That's why app/api/webhook/stripe/route.ts and lib/grants.ts are permanently closed, and why the migration files in supabase/migrations/ that define entitlement columns are private even though most of our other migrations could be shared. The moment a file is part of the license-check chain, it stops being open-source-eligible.

A small team collaborating over sticky notes on a whiteboard

What this means if you're buying a template

The practical answer: it means you can read the config for any template, like the AI Voice Notes template, before you buy. You can read the docs. You can see how our theme engine renders the color you'll get. You can inspect the schema shape you'd be paying for. Nothing about the sales page is a mystery.

What you buy is different: the full source of a template. Screens, hooks, Expo Router config, Supabase schema for that specific template's features, EAS build config, everything you need to fork and ship your own app. That code, the actual template ZIP behind the paywall, is what you're paying for. The engine that renders the storefront isn't.

What it means if you're building something similar

If you're running a template-selling business, a boilerplate business, or any small product where you're wondering what to open-source:

  • Publish everything that answers a design question ("how does the theme work?", "what's the config shape?", "how do you structure the folders?"). Those are marketing assets.

  • Keep private everything that answers an entitlement question ("is this user allowed to download this file?"). Those are business assets.

  • The line is not about "clever code" vs "boring code." Some of your most valuable-to-protect code is boring: a fifty-line handler that decides who can access what. Some of your most technically interesting code isn't worth protecting at all.

  • Don't split "for optics." If you can't articulate why a specific file is private, publish it.

We wrote the full comparison of React Native boilerplates earlier this year. The pattern holds across the industry: nobody's business model is protected by hiding UI code, but plenty of businesses are protected by hiding the code that guards their payment surface. Get honest about which is which and the split writes itself.

What we're doing next

Three things on the near roadmap:

  • A public applighter/theme-engine package on npm, extracted from app/apps/lib/theme.ts, standalone, MIT-licensed. Zero dependencies except a peer on Tailwind.

  • A CLI scaffolder (npx create-applighter-app <template-slug>) for the free templates. The paid templates stay behind the login wall.

  • Publishing our ProductData interface as its own package so people building marketing sites with a similar shape don't have to reinvent the type.

None of that changes the split. The public parts are the parts that were never protecting anything. The private parts are the parts that are.

If this is the kind of internals writeup you find useful, we plan to do more of these on the Applighter engineering blog.

FAQ

Q: Why not open-source the whole thing? A: Because the parts of the codebase that enforce "did this person pay" don't belong in public. Publishing them would give an attacker a map of our entitlement checks. It wouldn't help any prospective customer either; they want to see templates, not our Stripe webhook.

Q: What license did you use for the open-sourced parts? A: MIT for the theme engine and the ProductData schema when they're published as separate packages. The docs are Creative Commons. We chose permissive licenses because we want people to use these patterns freely. The parts we published were already generic; adding an aggressive license would just be posturing.

Q: Can I read the private code if I buy a template? A: You can read the template source. You can't read the billing infrastructure, the admin panel, or the partner APIs. Those aren't part of what you're buying; they're part of how we run the store. Every template ships with its own full source: screens, hooks, Supabase migrations for that template's schema, EAS build config.

Q: Does this affect existing customers? A: No. Nothing about what you download changes. The engine we open-sourced was already running our marketing site, not shipping inside templates. If anything, more code is public now than was public a month ago.

Q: What if I want to build a template store like Applighter? A: Fork the public parts, write your own Stripe webhook and entitlement schema, don't publish those. If you do end up building one, we'd rather compete on template quality than on who has the fancier config system.