A React Native camera is one component, one hook and one Info.plist string.
Putting a React Native camera on screen is a short job. You install expo-camera, render CameraView, ask with useCameraPermissions, and capture with takePictureAsync. The long job is everything around it: the iOS usage strings that decide whether a build ever reaches a tester, the cache file you get back instead of an image, and the fact that no iOS simulator has a lens. This page is the camera chapter of the wider app development guide.
It covers which package to use and what it already does. Then the permission keys iOS and Android want, and where they go. Then the file after the shutter, and how to test a camera when the simulator cannot see.
See which strings your screen needsThe short version
Use expo-camera, then spend the time on the permission strings and the file.
For a camera in React Native the default answer is expo-camera: a CameraView component, a permission hook, stills, video and barcode scanning in one package, with no native code of your own to write. Reach for react-native-vision-camera only when you need to run your own code on every frame.
Two things then decide whether the feature ships. iOS refuses to let an app touch the camera without a usage description in Info.plist. The photo it hands back is a file in a cache directory, not a record in your app. Neither problem is hard. Both get skipped.
The package is expo-camera, and it scans as well as shoots
expo-camera is the Expo camera module, and the API is small. CameraView is the preview: give it a facing prop and a style and the lens renders inside your own layout. useCameraPermissions hands back the current permission and a function to ask for it, so a camera screen is usually a permission check, a view and a button. takePictureAsync saves the picture to the app cache directory and returns an object with a uri, a width, a height and a format. recordAsync and stopRecording do video.
The part people miss is that the same view scans. Add an onBarcodeScanned handler and list the code types you care about in barcodeScannerSettings, which accepts values including qr, ean13, pdf417 and code128, and scanned codes arrive as plain strings. A stock count screen or a ticket door needs no scanning library on top of it.
This is also the module an AI builder tends to pick for you. Asked for an app with a camera in it, Newly installs expo-camera and writes the CameraView and useCameraPermissions pattern. Its Deploy tab later lists a missing camera usage description as a blocking issue before the App Store step. Worth knowing, because the package decides which of the rules below apply to you.
One behaviour to plan around: on the web the module returns image data as a base64 string, because a browser has no local file paths. Upload code written against a file uri works on the phone and quietly breaks in a browser preview.
The permission strings are what stop a build
Apple's rule is one line long. The NSCameraUsageDescription key is required if your app uses APIs that access the device camera, and the string it holds is the sentence people read in the permission dialog. Leave it out and the app fails at the moment it asks, which in practice means it dies in front of a reviewer rather than in front of you.
The camera module lists a second iOS key, NSMicrophoneUsageDescription, because the same component records sound with video. Its config plugin ships defaults for both, and the defaults are generic: allow this app to access your camera, allow this app to access your microphone. Replace them with a real sentence. App Review reads the string, and so does the person deciding whether to tap Allow. Note too that a readiness check looking only for the camera key will pass a screen that records sound with no microphone string, so add that one yourself.
Android wants android.permission.CAMERA, which the camera plugin adds for you, and android.permission.RECORD_AUDIO if you capture sound. In an Expo project both sides live in app.json: iOS strings under expo.ios.infoPlist, Android permissions under expo.android.permissions. One more iOS habit to design for: the system asks once. After someone declines, asking again does nothing, so the screen needs a state that explains what is missing and sends them to Settings.
Apple, NSCameraUsageDescription
Try it
What your camera screen has to declare
Tick what the screen actually does. The keys go in app.json: iOS under expo.ios.infoPlist, Android under expo.android.permissions.
expo.ios.infoPlist
NSCameraUsageDescription
expo.android.permissions
android.permission.CAMERA
Every iOS string is shown to the person in the permission dialog, so write a real sentence about your app rather than keeping the default.
The photo after the shutter is a file, not a record
Taking photos in an app is the easy half. takePictureAsync gives you a uri into the app cache directory, and a cache is not storage. The operating system is allowed to clear it, so a uri written into your database can point at nothing a fortnight later. The first thing the screen should do with a capture is move it or upload it.
Which means the interesting design is not the camera, it is the queue. A photo taken in a basement or a loading bay has to sit on the device until there is signal, retry without creating duplicates, and survive the app being killed in between. That is ordinary mobile app offline sync work, and it is where camera features actually fail.
It also changes what a row looks like. A photo is rarely the record, it is evidence attached to one. So the row holds the local uri, the remote url once it exists, an upload state, and the id of the thing photographed. Getting that shape right is mobile app database design, and fixing it after two hundred photos are already sitting on phones is miserable.
Size is the other quiet cost. A full resolution still runs to several megabytes, and a list showing twenty of them will stutter on a cheap Android phone. Capture at a lower quality, or keep a small copy for lists and the original for the one screen that needs detail.
You cannot test a camera on an iOS simulator
Apple's own Simulator documentation lists audio and video input, the camera and the microphone, among the hardware features it does not simulate. A camera screen in an iOS simulator gives you the layout and the permission flow and nothing through the lens. An Android emulator can be pointed at a webcam, which is better, and still not a phone camera in a dark warehouse.
So decide the test path before you build the screen. On iOS that means a TestFlight build on a real device, which needs your own Apple Developer account. On Android you can hand a tester a standalone APK, or push a build to Google Play internal testing. Either is quicker than it sounds, and usually the fastest way to see a real frame.
Then test the things a simulator can never show you: a permission somebody declined last week, a torch in a dark room, autofocus on a crumpled label, a photo that takes three seconds to write, and a phone at nine percent battery. If you want to see a camera as one part of a job rather than the job itself, here is a camera doing real work.
What each way of getting a photo into your app gives you
| Approach | Preview inside your own screen | Scans a code into your app | Your code runs on every frame | What you have to add |
|---|---|---|---|---|
| The phone camera app plus a shared folder | No | No | No | nothing, and the photo arrives with no record attached |
| expo-image-picker, the system camera or library | No | No | No | the camera and photo library strings |
| expo-camera CameraView | Yes | Yes | No | the camera string, plus microphone for sound |
| react-native-vision-camera | Yes | Yes | Yes | a custom native build, so no Expo Go |
Building the screen without a native toolchain
None of the above needs a Mac full of native tooling. An Expo project gives you the camera, the permission plumbing and a build service, and what is left is your own screens and your own upload logic.
Newly is an AI app builder. You describe the app in plain English and it writes a real React Native and Expo project you own. It runs that project on a cloud iPhone or Android simulator while it builds, then ships it. Plans are $25 a month and there is no free plan. For a camera feature the part that matters is the end of the pipe, because that is where a real lens finally gets involved. iOS goes out through TestFlight and App Store Connect using your own Apple Developer account. Android goes to Google Play internal testing from the Deploy tab, or out as a standalone APK with the JavaScript bundled. The code comes out one way, with npm i -g @newly/cli and then newly pull, and there is no GitHub sync.
Whatever you build it with, write the permission strings and the upload queue into the first version. They are the two pieces that look optional on a simulator and decide whether the feature works on a wet Tuesday in a car park.
Questions people ask about the camera in React Native
expo-camera for almost everything. It gives you a CameraView component, a useCameraPermissions hook, stills through takePictureAsync, video through recordAsync, and barcode scanning in the same view. Use react-native-vision-camera when you need your own code on every frame, for example live machine learning, and accept that it needs a custom native build.
Describe the screen, including what the photo is for
Write down what gets photographed, what the photo has to attach to, and what happens when there is no signal, then build the camera around that answer instead of around the lens.
Start building