Upgrade from v1
Use this guide to move an existing NativeExpress 1.x app to v2.0.0 while preserving your custom code and data.
This migration is optional. New projects already use v2 and do not need these steps.
Before you start
- Get the v2.0.0 source with your existing repository access.
- Review the tech stack and development tooling. v2 requires Node.js 22 or later.
- Review schema migrations if your app uses Supabase.
Save a working baseline
Commit your current app and create an upgrade branch. Keep a working build and its configuration. Check your custom screens, sign-in, purchases, push notifications and stored data before making changes, then use the same list to verify the upgrade.
Back up your database and storage objects. Rehearse database changes on a staging project with representative data, including any custom tables and policies.
Update dependencies and styling
Compare your configuration with v2.0.0 before installing dependencies. Merge the
changes to package.json, yarn.lock, metro.config.js, babel.config.js and
tsconfig.json, keeping any configuration your app needs.
| In your 1.x app | What to update |
|---|---|
| Expo and React Native dependencies | Use the v2 dependency set for Expo SDK 57, React Native 0.86, React 19.2 and TypeScript 6. Keep compatible dependencies you added. |
| NativeWind | Replace its Metro wrapper with withUniwindConfig. Remove nativewind/babel and the NativeWind JSX import source from Babel. |
tailwind.config.js | Move custom theme values into src/global.css and src/theme/themes/mono.css. Tailwind v4 configuration is defined in CSS. |
nativewind-env.d.ts | Remove the file and its tsconfig.json entry. Use v2’s uniwind-types.d.ts. |
@/components/ui/... | Replace the removed GlueStack wrappers with React Native primitives or heroui-native components. |
Screens using SafeAreaView | Follow v2’s safe-area setup in src/app/_layout.tsx and use inset utilities such as <View className="pt-safe">. The boilerplate’s lint rules disallow SafeAreaView imports. |
| Custom fonts and colors | Preserve your branding while adapting it to the v2 theme. The supplied fonts are Geist and Instrument Serif. |
| Type-only imports | Use import type; v2 enables verbatimModuleSyntax. |
Keep the complete theme variable set in both light and dark modes. Use DESIGN.md
and the bundled Uniwind and HeroUI Native skills when adapting screens.
The root layout also adds providers and Stack.Protected route guards, so merge
your routes into that setup and check signed-in and signed-out navigation.
The demo product screens, Product model, product repository and hooks are removed.
Keep your implementations and translation keys if your app still uses them.
Update integrations and AI
Review each integration’s imports, arguments and return values.
| Existing code | v2 replacement |
|---|---|
@/hooks/analytics/* | analytics from @/services/analytics; replace ConditionalPostHogProvider with AnalyticsProvider. See analytics usage. |
@/lib/one-signal | Functions from @/services/push. Permission is requested through onboarding. See push notifications. |
RevenueCatContext purchase methods | The context now exposes isPro, isConfigured, identifyUser and logout. packages, customerInfo, purchasePackage and restorePermission are removed. |
| Paywall presentation | Use presentPaywall from @/services/paywall for the onboarding_end and quota_exceeded placements. Keep PaywallProvider in the root layout. |
| Direct Sentry calls | Use logError from @/lib/logger. See monitoring. |
src/lib/env.ts | Read your app’s process.env.EXPO_PUBLIC_* variables directly. |
call-llm and useCallLLM | Adapt callers to the Chat, Create or Scan feature you need. These have different request and response shapes. |
The Paywall component at @/components/paywall/Paywall provides purchase and
restore UI. It takes an onClose callback and an optional onPurchaseError callback.
If you enable Superwall, complete its purchase and subscription integration
before testing purchases.
The AI functions use OpenRouter. Set OPENROUTER_API_KEY as a Supabase function
secret and configure models in supabase/functions/_utils/ai.config.json.
The new functions do not accept a user’s own provider key.
Pro access depends on an active RevenueCat entitlement. Set
config.js → purchases.proEntitlementId and the server’s PRO_ENTITLEMENT_ID to
the same entitlement; both default to pro. Configure REVENUECAT_SECRET_KEY
before testing Pro-only AI features. See function secrets
and quota for the settings shared by the app and server.
Prepare a migration for your database
v2 ships one initial migration, supabase/migrations/20260904000000_initial_schema.sql,
for new projects. It replaces the initial migration shipped with 1.x.
Do not apply the v2 initial migration to a database that already ran the 1.x migrations or reset it to make it fit. Keep your migration history and write a new migration for the changes your app needs.
Compare your actual schema with the released v2 migration. Review these changes:
| Area | What to preserve or add |
|---|---|
| Custom data | Keep your tables, relationships, triggers and policies. The 1.x data, categories and data_categories tables are absent from the new initial schema; their data is not converted to AI records. |
| AI features | Add the threads, messages, generations and scans tables, their indexes and owner policies when adopting the corresponding features. |
| Storage | Add the private chat-media bucket and its policies. Review existing avatar paths before adopting policies that require avatars/{userId}.<ext> inside the avatars bucket. |
| Usage | Add count_monthly_usage with its ownership check and execution grants. The released function permits authenticated and service_role, with access revoked from public and anon. |
Keep your custom bucket configuration if you need it, or migrate its objects and
references before switching to v2’s fixed avatars bucket. The new app does not
read EXPO_PUBLIC_SUPABASE_BUCKET_NAME.
Apply your migration to staging, then regenerate src/lib/db/database.types.ts
from that project. Follow type definitions. Deploy the
new edge functions after their database dependencies exist.
Keep call-llm deployed while any installed app versions still call it.
Rebuild the app
Preserve your bundle identifiers, Android package name, app scheme and EAS project
ID when merging config.js and app.config.js. Check your Google sign-in settings
and any native plugins you added.
The v2 eas.json selects an environment for each build profile and uses remote
versioning with production autoIncrement. Keep the version sequence required by
your existing store apps. Set their submission identifiers and credentials.
Add your EXPO_PUBLIC_* variables to the matching EAS environments; a local
.env.local file does not configure a cloud build. See production builds.
Run yarn install and create a new development build for each platform you ship.
Preserve any native changes before regenerating the native projects.
Verify
Run yarn ci and review the testing guide, then
check the upgraded app on your supported platforms:
- Follow your baseline feature list, including custom screens in light and dark modes.
- Install over an existing test build and check that sign-in survives. v2 migrates the existing Supabase session from AsyncStorage to SecureStore.
- Check existing database records and images, then confirm that one test account cannot access another account’s private data.
- Test purchases and restores with sandbox products. Confirm that Pro access agrees in the app and the AI functions.
- Test chat, an attachment, a scan and a free-quota refusal if you adopted those features.
Repeat the rehearsed database, function and app release steps in production only after staging passes. Keep the previous app and backend behavior available for installed clients until your rollout no longer depends on them.