a0.dev generates the app shell, but real iOS features like camera, push notifications, and location come from Expo modules. Here is a step-by-step guide to adding native capabilities to your generated React Native app the right way.
a0.dev is effective at generating screens, navigation, and UI from a prompt. But real device capabilities — camera, location, notifications, secure storage — come from native modules, not from prompt text alone.
Because a0.dev output is a standard Expo project, the natural way to add these features is through Expo's ecosystem of modules and config plugins.
This guide shows how to layer genuine iOS functionality onto a generated app without breaking the Expo workflow.
The mindset shift is important: a0.dev builds the app's structure, and you extend it with well-supported native modules the same way any Expo developer would.
Start by naming the exact native capability. Camera access, photo library, geolocation, push notifications, biometric authentication, and secure storage are the most common.
Each of these maps to a specific Expo module. Rather than writing native Swift, you install a JavaScript-facing module that wraps the native iOS API.
Be precise about what you need. Reading the photo library is different from capturing with the camera, and each has its own iOS permission implications.
This clarity determines which module you install, which permissions you declare, and what your app config needs, so it is worth getting right before touching code.
With the capability identified, install the corresponding Expo module into your project. Expo maintains first-party modules for most common device features.
Use the recommended installation command for your Expo version so the module version matches your SDK. Version mismatches are a frequent source of runtime errors.
After installing, import the module in the relevant screen that a0.dev generated. Because the output is standard React Native, this integration looks like any Expo project.
Keep changes small and test after each addition. Adding one module at a time makes it far easier to isolate a problem if something breaks.
iOS requires an explicit reason for accessing sensitive capabilities. If you skip this, your app can crash on access or get rejected by App Review.
In an Expo project, you declare these usage descriptions in your app config (app.json or app.config.js), often via the module's config plugin or the iOS infoPlist section.
Write clear, honest purpose strings. Apple expects the description to explain why your app needs the camera, location, or notifications, and vague strings can trigger rejection.
These strings end up in the compiled app's Info.plist. Getting them right at the config stage means EAS Build produces a compliant binary automatically.
Declaring a permission is not the same as being granted it. At runtime, iOS shows the user a prompt, and they can deny it.
Your code must request permission at an appropriate moment and handle both outcomes. Ask for camera access when the user taps a camera button, not on app launch.
Always handle denial gracefully. Show a helpful message explaining why the feature needs the permission and how to enable it in Settings, rather than letting the app fail silently.
Generated a0.dev screens usually will not include this logic, so adding proper permission handling is one of the most valuable manual improvements you make.
Some native modules need changes to the underlying native project. Expo handles this through config plugins and the prebuild step.
When you build with EAS, Expo generates the native iOS project from your JavaScript config, applying any config plugins automatically. You usually do not hand-edit native files.
If a module documents a config plugin, add it to your app config. This is how the module injects the native setup it needs during the build.
Understanding this flow prevents confusion when a module works in a plain JavaScript context but requires a fresh native build to function fully. Some capabilities simply will not appear until you rebuild.
Native features must be tested on real hardware. A preview or simulator does not fully exercise camera, notifications, or biometric flows.
Build a development or preview build with EAS and install it on your iPhone. This gives you a build that includes the native modules you added.
Exercise the real flows: grant permission, deny permission, background the app, and trigger the feature repeatedly. Real devices surface issues that no preview will.
Push notifications especially require a proper build and Apple's push infrastructure, so plan to test them on a real device with the correct configuration rather than in a lightweight preview.
Expo modules cover a broad range of native features, but not everything. Some highly specialized or brand-new iOS APIs may not have a ready-made module.
When no module exists, you can write a custom native module in Swift or Objective-C and wire it in — but that pulls you into native development, which is exactly what a0.dev abstracts away.
This is the honest boundary of the approach. React Native plus Expo covers a large share of app needs, but a truly native, platform-first feature can require native code.
Weigh that trade-off deliberately. If your app depends heavily on cutting-edge native capabilities, consider whether a React Native foundation is the right long-term choice.
Add native features incrementally and test each one on a device before moving on. This discipline prevents a pile of interacting changes you cannot debug.
Always pair a native capability with correct permission strings and graceful denial handling. These are the two things generated code most often omits.
Match module versions to your Expo SDK and rebuild when a config plugin requires it. Version and build-state mismatches cause a large share of native issues.
And when in doubt, consult Expo's official module documentation. It stays current with SDK changes and is the authoritative reference for installation, config, and platform-specific caveats.
Consider a common case: your a0.dev app has a profile screen with a placeholder avatar, and you want users to take a photo.
First, install Expo's camera or image-picker module for your SDK version. Then decide whether you need live capture or simply picking from the library, because they carry different permissions.
Next, add the matching usage description string to your app config so iOS knows why the app wants the camera. Without it, the feature will not work in a real build.
Finally, wire the button on that generated screen to request permission, launch the capture flow, and handle the case where the user declines. That end-to-end path — module, permission string, runtime request, denial handling — is the pattern you repeat for every native feature.
Once you start editing generated screens to add native features, you introduce a tension: further prompting could overwrite your manual changes.
A practical approach is to treat generation as an early-phase activity. Use prompts heavily while the structure is still forming, then shift to manual code once you begin wiring real native modules.
Use version control from the start. Committing before each significant prompt or manual edit gives you a safe point to return to if a change goes wrong.
This discipline lets you enjoy fast generation early and stable, hand-maintained code later, without the two working against each other.
It can scaffold UI, but real device capabilities come from Expo modules you install and configure. You typically add the module, declare iOS permissions in app config, and handle runtime permission requests in code.
Usually not. Expo provides first-party modules for common features like camera, location, and notifications, so you use JavaScript-facing APIs. Only unusual or brand-new native capabilities without an existing module require custom Swift or Objective-C.
Some modules need native project changes applied through config plugins and prebuild. These take effect when EAS generates the native project during a build, so you may need a fresh development or production build for the feature to work fully.
In your app config (app.json or app.config.js), often via the module's config plugin or the iOS infoPlist section. They compile into the app's Info.plist, and they must clearly explain why the app needs the capability.
You can write a custom native module in Swift or Objective-C, but that moves you into native development. If your app depends heavily on cutting-edge native APIs, reconsider whether a React Native foundation is the right long-term fit.