Chapter 11: Native Plugins — Camera & Haptics
Access the real camera and haptic engine on iOS through JavaScript.
This is where Capacitor earns its keep. In the browser, you get a basic file picker for photos. On native iOS, you get the full camera experience — viewfinder, photo library access, and permission prompts. We'll also add haptic feedback — the subtle vibrations that make iOS apps feel tactile and responsive.
What You'll Build
- Camera integration — take photos directly or choose from the photo library
- iOS permission handling with
Info.plistconfiguration - A platform-aware photo picker (native camera on iOS, file input on web)
- Haptic feedback on key interactions (creating entries, deleting, tab switching)
- A
useHapticshook for reusable haptic triggers
What You'll Learn
- How Capacitor plugins bridge JavaScript to native Swift code
- iOS permission model and
Info.plistusage strings - Platform detection (
Capacitor.isNativePlatform()) - The Haptics API and its feedback types
- Graceful degradation — features that work on native but don't break on web
Step 1: Install the Plugins
npm install @capacitor/camera @capacitor/haptics
npx cap sync
cap sync is important here — it copies the new native plugin code into the Xcode project and runs pod install to link the Swift libraries.
If you get any terminal errors during this step, paste the full error output directly into Claude Code. It can read error messages and fix them faster than you can search Stack Overflow.
Step 2: Configure iOS Permissions
iOS requires you to explain why your app needs camera and photo library access. These strings appear in the system permission dialog.
Open ios/App/App/Info.plist in Xcode (or a text editor) and add three permission keys inside the top-level <dict>:
- NSCameraUsageDescription — Set this to a string like "GratitudeTree needs camera access to take photos for your journal entries."
- NSPhotoLibraryUsageDescription — Set this to a string like "GratitudeTree needs photo library access to add photos to your journal entries."
- NSPhotoLibraryAddUsageDescription — Set this to a string like "GratitudeTree needs to save photos you take to your photo library."
What each permission does
| Key | When it triggers |
|---|---|
NSCameraUsageDescription | First time the app tries to open the camera |
NSPhotoLibraryUsageDescription | First time the app tries to read from the photo library |
NSPhotoLibraryAddUsageDescription | First time the app tries to save a photo to the library |
If you forget these strings and try to use the camera, iOS will crash your app immediately. Apple is strict about this — every permission must have a human-readable reason. The App Store will also reject apps with missing usage descriptions.
You can also ask Claude Code to handle this for you:
$ claude "Add the required iOS permissions for camera and photo library access to our Capa..."Step 3: Build the Camera Service
Send this prompt to Claude Code to create the camera service:
$ claude "Create a camera service at src/lib/camera.ts that wraps Capacitor's Camera plugi..."The platform detection pattern
The most important pattern in Capacitor development is checking Capacitor.isNativePlatform() before calling native APIs. Your app works on both web and iOS:
- On iOS: Uses the real camera, shows the native photo picker
- On web: Falls back to a standard file input dialog
This means your app never breaks — it just has fewer features on the web.
Camera options explained
| Option | Value | Why |
|---|---|---|
quality | 80 | 80% JPEG quality — good balance of size and clarity |
resultType | DataUrl | Returns a base64 data URL we can preview and upload |
source | Camera or Photos | Camera opens the viewfinder; Photos opens the library |
width/height | 1200 | Max dimensions — Capacitor resizes automatically |
Step 4: Build the Haptics Service
Haptic feedback makes interactions feel physical. Send this prompt to Claude Code to create the haptics service:
$ claude "Create a haptics service at src/lib/haptics.ts that wraps Capacitor's Haptics pl..."Haptic types on iOS
| Type | Feels like | Use for |
|---|---|---|
| Impact Light | Gentle tap | Tab switches, toggles |
| Impact Medium | Firm press | Saving, confirming |
| Impact Heavy | Strong thud | Shake interaction reveal |
| Notification Success | Double tap pattern | Entry saved, login success |
| Notification Warning | Triple tap pattern | Delete confirmation |
| Notification Error | Buzz pattern | Error, failed action |
The if (!isNative) return guard is critical — calling haptics on the web would throw an error. This way, all haptic calls are safe on any platform.
Step 5: Update the Create Page for Native Camera
Send this prompt to Claude Code to update the Create page with native camera support:
$ claude "Update the Create page to use our native camera service instead of the basic fil..."The two-button pattern (Take Photo + Library) matches how native iOS apps handle photo input. Claude Code will also add an uploadPhotoBlob function to storage.ts that uploads a pre-compressed blob, since the Capacitor camera plugin already compresses at 80% quality and 1200x1200.
Step 6: Add Haptics Throughout the App
Send this prompt to Claude Code to add haptic feedback across the app:
$ claude "Add haptic feedback to key interactions throughout the app — a light tap on tab ..."The haptic philosophy
Don't overdo haptics. Apple's Human Interface Guidelines recommend:
- DO: Use haptics for meaningful state changes (save, delete, navigate)
- DON'T: Use haptics for every tap or scroll
- DO: Match haptic intensity to action importance (light for tabs, heavy for shake reveal)
- DON'T: Use haptics as a substitute for visual feedback
$ claude "I'm adding haptic feedback to my Capacitor iOS app. I have light taps on tab swi..."Step 7: Test on the Simulator
npx cap sync
Then build and run from Xcode. Test:
- Camera button — on the Simulator, it opens the photo library (the Simulator has no real camera). On a physical device, it opens the actual camera viewfinder.
- Library button — opens the photo picker with sample photos
- Tab switching — you should feel a subtle haptic tap (physical device only — the Simulator doesn't simulate haptics)
- Save entry — success haptic after saving
- Delete confirmation — warning haptic when the delete prompt appears
The iOS Simulator doesn't support haptic feedback. To feel the haptics, you'll need to run the app on a real iPhone. We'll cover deploying to a physical device in a later chapter.
Step 8: Commit Your Progress
git add .
git commit -m "Add native camera, photo library, and haptic feedback via Capacitor plugins"
git push
Checkpoint
Your app should now:
- The camera plugin lets users take photos on native iOS
- The photo library picker works for choosing existing photos
- Permission prompts appear with clear usage descriptions
- The app falls back to file input on the web (no crashes)
- Haptic feedback fires on tab switches, entry creation, and delete
- Haptics are silent on web — no errors, no broken behavior
What's Next
You have camera and haptics working. In the next chapter, we build the hero feature — shake the phone to discover a random past memory. It's the interaction that makes GratitudeTree feel magical.
Follow @parvsondhi for build threads, tips, and updates on this tutorial.