●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●NATIVE — It unlocks native capabilities React Native cannot reach: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, Siri Intents, and HealthKit●RN — Standard Rork builds cross-platform apps with React Native (Expo), a good fit when you want something working fast●CHOICE — Pick React Native for speed, or Rork Max when you need Apple hardware and OS integration●PRICE — Rork is free to start with paid plans from $25/mo; Rork Max is $200/mo●FLOW — Describe the app you want in plain language and Rork produces working code you can ship to the stores●MAX — Rork Max generates native Swift apps for iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●NATIVE — It unlocks native capabilities React Native cannot reach: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, Siri Intents, and HealthKit●RN — Standard Rork builds cross-platform apps with React Native (Expo), a good fit when you want something working fast●CHOICE — Pick React Native for speed, or Rork Max when you need Apple hardware and OS integration●PRICE — Rork is free to start with paid plans from $25/mo; Rork Max is $200/mo●FLOW — Describe the app you want in plain language and Rork produces working code you can ship to the stores
Before You Pay $200/mo for Rork Max, Map How Far Expo Reaches in Three Tiers
Wanting widgets or Live Activities makes Rork Max tempting, but most of those features are reachable from the Expo setup that standard Rork generates. Here is how I sort each Apple-native feature into three tiers—reachable in Expo, reachable with a custom module, or where Max is the pragmatic answer—and verify which tier my app is in before paying.
"I want a home screen widget. That means I have to move to Rork Max at $200 a month, right?" An indie developer friend who has run apps for years asked me this last month. The short answer was no—not at that point. Widgets are on the reachable side of the line from the Expo (React Native) setup that standard Rork generates.
Lay out the native features Rork Max advertises—AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, Siri Intents, HealthKit, HomeKit, NFC, App Clips, on-device Core ML—and the impression is that React Native can't reach any of them. But once you actually start building, the boundary sits closer than the list suggests. Many of these are reachable in plain Expo, or with a small custom module. The real question is whether you can tell which ones genuinely require Max before you pay.
Reading the list as "feature present or absent" leads you astray
A product page's feature list exists to say "Max can do this," not "React Native cannot." Confuse the two and you end up paying $200 a month for a feature that took one day in plain Expo.
Change your unit of judgment from "does the feature exist" to "how much implementation distance is there between my app and that feature," and the picture shifts immediately. As an indie developer who has run wallpaper and relaxation apps on an Expo-leaning stack, my rough sense is that about 70% of native features land on the "solvable inside the Expo world" side. Another 20% are "reachable once you write a single custom native module." Only the final 10% is where Max is truly the pragmatic answer.
So let's sort the features Max lists into three tiers by implementation distance.
Sorting features into three tiers
Tier
Meaning
Features around here
Call
Tier 1
Reachable in plain Expo via a config plugin or maintained library
Reachable by adding an extension target or custom native module to an Expo prebuild (development build required)
Home screen widgets, Live Activities/Dynamic Island, App Clips, App Intents/Siri, on-device Core ML, Apple Foundation Models
Depends on effort. Worth staying on Expo if it scopes to a few days per feature
Tier 3
Hard to reach at production quality in Expo; native Swift generation (Rork Max) is the pragmatic answer
Full RoomPlan/LiDAR scanning, Metal 3D games, simultaneous shipping to Apple Watch/TV/Vision Pro
Seriously consider Max the moment it's a requirement
The widget question above was Tier 2. It stops being "I have to move" and becomes "will a few days do it, or is it less painful to hand it to Max?" That reframing is where the subscription decision actually starts.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A decision table that sorts Apple-native features into three tiers: reachable in Expo, reachable with a custom module, or where Rork Max is the pragmatic answer
✦A four-step method using expo-apple-targets and an EAS development build to confirm which tier your app is in on a real device—before you subscribe
✦A minimal config-plugin example for generating a widget extension through Expo prebuild, plus the three traps that waste the most time
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Confirm which tier your app is in—on a real device, before paying
The tier table is only a map. The fastest way to find where your app sits on it is to check on a real device. The method I use has four steps.
First, look for a config plugin or maintained library for the feature you want. If npx expo install succeeds and adding it to the plugins array in app.json is all it takes, that's Tier 1.
# First check for Tier 1. If this works, you reach it in plain Exponpx expo install expo-notifications react-native-nfc-manager# After adding config plugins to app.json, generate the iOS project with prebuildnpx expo prebuild -p ios --clean
Second, if a library isn't enough and you need an extension target (like a widget) or a native module, make an EAS development build and put it on a device. Custom native code does not run in Expo Go, so switching to a development build here is the key point.
# Verify custom native code with a development build, not Expo Goeas build --profile development --platform ios# If you can build locally (Mac + Xcode), this also worksnpx expo run:ios --device
Third, when a custom module is needed, estimate the effort. How many days—including research time—to wrap one feature with the Expo Modules API? If it fits in a few days, the feature is Tier 2 and worth keeping on Expo. Once the estimate creeps past two weeks, treat that feature as effectively Tier 3.
Fourth, only if it still doesn't reach, or if simultaneous shipping to multiple Apple devices is a requirement, does Max become a candidate. Do it in the reverse order—subscribe to Max first, then decide what to build—and the $200 tends to become insurance you never use.
Generating a widget extension through Expo prebuild (a minimal Tier 2 build)
Using widgets, the canonical Tier 2 case, here is the minimal way to add an extension target while staying on Expo. The Expo config plugin generates the WidgetKit extension target at prebuild time, and you write only the SwiftUI inside.
First, declare the plugin and the extension target location in app.json.
Next, define the extension target. Writing the target type in targets/widget/expo-target.config.js lets prebuild wire the widget extension into the Xcode project.
// targets/widget/expo-target.config.jsmodule.exports = { type: "widget", name: "MyWallpaperWidget", // Share an App Group with the main app to pass the selected wallpaper ID entitlements: { "com.apple.security.application-groups": ["group.net.dolice.mywallpaper"], },};
Write the widget body in SwiftUI. Passing state from the React Native side through the App Group's UserDefaults is the least fragile hand-off.
// targets/widget/MyWallpaperWidget.swiftimport WidgetKitimport SwiftUIstruct Provider: TimelineProvider { func placeholder(in context: Context) -> Entry { Entry(date: .now, title: "—") } func getSnapshot(in context: Context, completion: @escaping (Entry) -> Void) { completion(loadEntry()) } func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> Void) { // Re-read the latest wallpaper name the main app wrote, once an hour let next = Calendar.current.date(byAdding: .hour, value: 1, to: .now)! completion(Timeline(entries: [loadEntry()], policy: .after(next))) } private func loadEntry() -> Entry { let defaults = UserDefaults(suiteName: "group.net.dolice.mywallpaper") let title = defaults?.string(forKey: "currentWallpaper") ?? "None" return Entry(date: .now, title: title) }}struct Entry: TimelineEntry { let date: Date; let title: String }struct MyWallpaperWidget: Widget { var body: some WidgetConfiguration { StaticConfiguration(kind: "MyWallpaperWidget", provider: Provider()) { entry in VStack(alignment: .leading) { Text("Today's wallpaper").font(.caption).foregroundStyle(.secondary) Text(entry.title).font(.headline) } .padding() .containerBackground(.fill.tertiary, for: .widget) } .configurationDisplayName("Wallpaper Widget") .supportedFamilies([.systemSmall, .systemMedium]) }}
On the React Native side, all you write is one thin bridge that, when the wallpaper changes, writes to the App Group and calls the reloadAllTimelines equivalent. At this granularity, research included, you reach a real device in a few days. That is the concrete feel of "Tier 2, but worth staying on Expo."
Three traps that waste the most time
Here are the spots where time actually drains, flagged up front.
First, if the App Group identifier differs by even one character between the app and the widget, the data silently becomes nil. The build passes, the widget renders, but the content is empty—so the cause is hard to spot. Make the whole thing, group. prefix included, a constant referenced from both sides.
Second, misjudging "it doesn't work" by trying to run it in Expo Go. Extensions with custom native code never run in Expo Go. Conclude "Expo can't do this" without making a development build, and you misread Tier 2 as Tier 3 and walk into an unnecessary subscription.
Third, a missing appleTeamId. App Groups and extension targets need a Team ID for signing, so if it's blank, prebuild passes but device installs get rejected. Keep the Team ID in app.json aligned with your EAS credentials.
Running both, instead of committing to one side
Sorting into three tiers also reveals an option: you don't have to push the whole app to one side. Across the apps I run under Dolice Labs, I design on the assumption that each feature gets routed separately—"this one as a custom Expo module," "that one shipped to Max later." Keep the core experience on Expo, add just the widget as a small Tier 2 piece, and if a Tier 3 requirement like LiDAR scanning shows up later, send only that feature to Max for evaluation.
The benefit of this view is that it turns the subscription call from one heavy decision—migrating the whole app—into a light, repeatable one: which tier's tool implements this single feature. In practice, every time I add a feature I write down once where it falls on the tier table above. It costs a minute, but it reliably stops me from reaching for $200 on impression alone.
Two clear conditions where I still choose Max
After sorting with the three tiers, there are two conditions where I think "now it's Max."
One is when a Tier 3 feature sits at the center of the requirements. Full LiDAR room scanning, spinning 3D with Metal—bridging these into Expo keeps getting heavier to maintain. Fighting on Max's turf, where it generates native Swift directly, lowers long-term operating cost.
The other is when there's a business plan to ship to Apple Watch, TV, and Vision Pro alongside iPhone, simultaneously. Multi-device shipping is less likely to break when it rides on native generation from the start than when stacked on top of React Native. Put the other way: if neither condition applies, filling in Tier 1 and Tier 2 with Expo fits the scale of indie development better.
When you're unsure, pull out just one feature you want and run the four steps above all the way to a real device. Once you know where your app sits on the map, paying—or not paying—$200 a month becomes a decision you make from hands-on evidence rather than impression. I hope it helps anyone standing at the same fork.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.