DEV Community

Chris F A
Chris F A

Posted on

We open-sourced our React Native template engine (but not the part that makes money)

TL;DR

  • We open-sourced our theme engine, ProductData config schema, UI primitives, and docs
  • We kept the Stripe webhook, license-grant fan-out, entitlement migrations, and admin routes private, permanently
  • The rule: code that answers a design question goes public; code that answers an entitlement question stays private
  • Three questions decided every file: would a competitor win by copying it, is it interesting to customers, and is it the piece the payment guards

We open-sourced parts of our React Native template engine last month. Not all of it. This post is the specific breakdown of what went over the wall, what stayed inside, and how we decided.

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: 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

1. The ProductData contract. Every template 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. Adding a new template is one config file plus one registry line:

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,
};
Enter fullscreen mode Exit fullscreen mode

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 catalog site has a reference implementation.

2. The runtime theme engine. Each template has its own primary color. 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. Small, self-contained, zero business logic. Full public.

3. The docs. Installation, environment setup, folder structure, Supabase integration, Expo integration, UI component guide. Public since day one. 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.

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, 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: single template purchase → one grant row. Bundle purchase → resolveGrantTargets() walks the bundle_items table and writes a grant per member template. The free-claim path 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 decides whether an authenticated user is allowed to hit the download endpoint. Every line is a lever an attacker could poke at.

2. The entitlement migrations. user_product_grants, product_licenses, bundle_items, template_screens: the RLS policies, columns, and constraints. The generic Supabase-auth-RLS-storage pattern is documented publicly on our blog; 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. A content-provider webhook, an internal Slack service, an affiliate webhook, and a partner API behind bearer-token auth and signature verification. 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. Impersonation, template request triage, support queue management. Nothing clever in here. It's a bunch of if (!isAdmin) return 403 and 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.

The comparison

Three 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. 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

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 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.

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

The ProductData schema is genuinely interesting. A prospective customer reading a config file 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?

The tie-breaker. Anything that sits on the "you paid, therefore you get" edge stays private. Everything else is negotiable. The moment a file is part of the license-check chain, it stops being open-source-eligible.

What this means if you're buying a template

You can read the config for any template in the Applighter catalog before you buy. You can read the docs. You can see how the theme engine renders the color you'll get. Nothing about the sales page is a mystery.

What you buy is different: the full source of a template. Screens, hooks, Expo Router config, the Supabase schema for that template's features, EAS build config. The actual 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

  • Publish everything that answers a design question ("how does the theme work?", "what's the config shape?"). 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 "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.
  • Don't split "for optics." If you can't articulate why a specific file is private, publish it.

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's next

  • A public applighter/theme-engine package on npm, standalone, MIT-licensed, zero dependencies except a peer on Tailwind
  • A CLI scaffolder (npx create-applighter-app <template-slug>) for the free templates
  • Publishing the ProductData interface as its own package

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 you've drawn (or dodged) a similar open/closed line in your own product, drop a comment: what did you publish, and what would you never?

Top comments (0)