Native Features
Access device capabilities like camera, GPS, and push notifications using Capacitor plugins.
What you'll accomplish
Implement camera, geolocation, and storage features in your Capacitor app.
Prerequisites
- Completed iOS and/or Android setup
- Running Capacitor app
How Plugins Work
Capacitor plugins bridge JavaScript and native code:
Your JavaScript Code
↓
Capacitor Plugin API
↓
Native Implementation (Swift/Kotlin)
↓
Device Hardware/OS Features
You write JavaScript - the plugin handles the native complexity.
Installing Plugins
All Capacitor plugins follow the same pattern:
bash# 1. Install with npm npm install @capacitor/plugin-name # 2. Sync to native projects npx cap sync
Then import and use in your code:
typescriptimport { PluginName } from '@capacitor/plugin-name'; await PluginName.doSomething();
Camera
Take photos and access the photo library.
Installation
bashnpm install @capacitor/camera npx cap sync
iOS Configuration
Add to ios/App/App/Info.plist:
xml<key>NSCameraUsageDescription</key> <string>We need camera access to take photos</string> <key>NSPhotoLibraryUsageDescription</key> <string>We need photo library access to select images</string> <key>NSPhotoLibraryAddUsageDescription</key> <string>We need to save photos to your library</string>
Android Configuration
Add to android/app/src/main/AndroidManifest.xml:
xml<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
Usage
typescriptimport { Camera, CameraResultType, CameraSource } from '@capacitor/camera'; // Take a photo async function takePhoto() { const image = await Camera.getPhoto({ quality: 90, allowEditing: true, resultType: CameraResultType.Uri, source: CameraSource.Camera, }); // image.webPath is a URL you can use in <img src=""> return image.webPath; } // Pick from gallery async function pickFromGallery() { const image = await Camera.getPhoto({ quality: 90, resultType: CameraResultType.Uri, source: CameraSource.Photos, }); return image.webPath; } // Get as base64 async function getPhotoBase64() { const image = await Camera.getPhoto({ quality: 90, resultType: CameraResultType.Base64, source: CameraSource.Prompt, // Let user choose camera or gallery }); return `data:image/jpeg;base64,${image.base64String}`; }
React Component Example
tsx'use client'; import { useState } from 'react'; import { Camera, CameraResultType } from '@capacitor/camera'; export function PhotoCapture() { const [photo, setPhoto] = useState<string | null>(null); async function handleTakePhoto() { try { const image = await Camera.getPhoto({ quality: 90, allowEditing: true, resultType: CameraResultType.Uri, }); setPhoto(image.webPath || null); } catch (error) { console.error('Camera error:', error); } } return ( <div> <button onClick={handleTakePhoto}>Take Photo</button> {photo && <img src={photo} alt="Captured" />} </div> ); }
Geolocation
Get the user's location.
Installation
bashnpm install @capacitor/geolocation npx cap sync
iOS Configuration
Add to ios/App/App/Info.plist:
xml<key>NSLocationWhenInUseUsageDescription</key> <string>We need your location to show nearby places</string> <key>NSLocationAlwaysUsageDescription</key> <string>We need background location for navigation</string>
Android Configuration
Add to android/app/src/main/AndroidManifest.xml:
xml<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Usage
typescriptimport { Geolocation } from '@capacitor/geolocation'; // Get current position async function getCurrentLocation() { const position = await Geolocation.getCurrentPosition(); return { lat: position.coords.latitude, lng: position.coords.longitude, accuracy: position.coords.accuracy, }; } // Watch position changes function watchLocation(callback: (lat: number, lng: number) => void) { const watchId = Geolocation.watchPosition( { enableHighAccuracy: true }, (position, err) => { if (position) { callback(position.coords.latitude, position.coords.longitude); } } ); // Return cleanup function return () => Geolocation.clearWatch({ id: watchId }); } // Check/request permissions async function checkPermissions() { const status = await Geolocation.checkPermissions(); if (status.location !== 'granted') { const request = await Geolocation.requestPermissions(); return request.location === 'granted'; } return true; }
React Hook Example
tsx'use client'; import { useState, useEffect } from 'react'; import { Geolocation } from '@capacitor/geolocation'; export function useLocation() { const [location, setLocation] = useState<{ lat: number; lng: number } | null>(null); const [error, setError] = useState<string | null>(null); useEffect(() => { let watchId: string; async function startWatching() { try { await Geolocation.requestPermissions(); watchId = await Geolocation.watchPosition( { enableHighAccuracy: true }, (position, err) => { if (err) { setError(err.message); return; } if (position) { setLocation({ lat: position.coords.latitude, lng: position.coords.longitude, }); } } ); } catch (e) { setError('Failed to get location'); } } startWatching(); return () => { if (watchId) { Geolocation.clearWatch({ id: watchId }); } }; }, []); return { location, error }; }
Local Storage
Persist data locally with the Preferences plugin (key-value storage).
Installation
bashnpm install @capacitor/preferences npx cap sync
Usage
typescriptimport { Preferences } from '@capacitor/preferences'; // Save data async function saveUser(user: { id: string; name: string }) { await Preferences.set({ key: 'user', value: JSON.stringify(user), }); } // Load data async function loadUser() { const { value } = await Preferences.get({ key: 'user' }); return value ? JSON.parse(value) : null; } // Remove data async function clearUser() { await Preferences.remove({ key: 'user' }); } // Clear all data async function clearAll() { await Preferences.clear(); } // Get all keys async function getAllKeys() { const { keys } = await Preferences.keys(); return keys; }
React Hook Example
tsx'use client'; import { useState, useEffect, useCallback } from 'react'; import { Preferences } from '@capacitor/preferences'; export function useStorage<T>(key: string, initialValue: T) { const [value, setValue] = useState<T>(initialValue); const [loading, setLoading] = useState(true); // Load on mount useEffect(() => { async function load() { const { value: stored } = await Preferences.get({ key }); if (stored) { setValue(JSON.parse(stored)); } setLoading(false); } load(); }, [key]); // Save function const save = useCallback(async (newValue: T) => { setValue(newValue); await Preferences.set({ key, value: JSON.stringify(newValue), }); }, [key]); return { value, save, loading }; }
Push Notifications
Receive push notifications.
Installation
bashnpm install @capacitor/push-notifications npx cap sync
iOS Configuration
Push notifications require:
- Apple Developer account
- Push notification capability in Xcode
- APNs configuration
Android Configuration
Requires Firebase Cloud Messaging setup:
- Create Firebase project
- Add
google-services.jsontoandroid/app/ - Configure Gradle files
Usage
typescriptimport { PushNotifications } from '@capacitor/push-notifications'; // Request permission and register async function initPushNotifications() { // Request permission const permission = await PushNotifications.requestPermissions(); if (permission.receive !== 'granted') { console.log('Push notification permission denied'); return; } // Register with APNs/FCM await PushNotifications.register(); // Listen for registration PushNotifications.addListener('registration', (token) => { console.log('Push registration token:', token.value); // Send token to your server }); // Listen for errors PushNotifications.addListener('registrationError', (error) => { console.error('Push registration error:', error); }); // Listen for notifications received PushNotifications.addListener('pushNotificationReceived', (notification) => { console.log('Push received:', notification); }); // Listen for notification taps PushNotifications.addListener('pushNotificationActionPerformed', (notification) => { console.log('Push tapped:', notification); // Navigate to relevant screen }); }
Note
Push notifications require additional backend setup. You'll need a server to send notifications through APNs (iOS) or FCM (Android).
Your Mobile App Needs a Backend
Native features like camera, GPS, and local storage happen on the device. But most apps also need cloud services:
- User accounts and authentication - login, sessions, password reset
- Cloud data storage and sync - persist data across devices
- Push notifications from your server - not just local notifications
- In-app purchases and subscriptions - monetization
Backend Options for Capacitor Apps
| Approach | Best for | Time to implement |
|---|---|---|
| Firebase | Prototypes, simple apps | Hours |
| Supabase | Open-source preference, PostgreSQL | Days |
| StackForDevs | SaaS apps with billing/multi-tenancy | Hours |
| Custom backend (AWS/GCP/etc.) | Full control, specific requirements | Weeks |
For apps that need subscription billing and multi-tenancy (like a B2B mobile app), StackForDevs provides these as APIs you call from your Capacitor app—the same way you'd call any REST API from your frontend code.
Your Capacitor app is just a web app with native wrappers. Call APIs the same way you would in any web app:
typescript// Example: Authenticate user via backend API async function login(email: string, password: string) { const response = await fetch('https://your-backend.com/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), }); const { token, user } = await response.json(); // Store token locally using Capacitor Preferences await Preferences.set({ key: 'auth_token', value: token }); return user; }
The backend handles the complexity (auth, billing, notifications), and your mobile app focuses on the user experience.
Share
Use the native share dialog.
Installation
bashnpm install @capacitor/share npx cap sync
Usage
typescriptimport { Share } from '@capacitor/share'; // Share text async function shareText() { await Share.share({ title: 'Check this out!', text: 'I found this amazing app', url: 'https://example.com', dialogTitle: 'Share with friends', }); } // Check if sharing is available async function canShare() { const { value } = await Share.canShare(); return value; }
Haptics
Provide tactile feedback.
Installation
bashnpm install @capacitor/haptics npx cap sync
Usage
typescriptimport { Haptics, ImpactStyle } from '@capacitor/haptics'; // Light tap feedback async function lightTap() { await Haptics.impact({ style: ImpactStyle.Light }); } // Medium impact async function mediumImpact() { await Haptics.impact({ style: ImpactStyle.Medium }); } // Heavy impact async function heavyImpact() { await Haptics.impact({ style: ImpactStyle.Heavy }); } // Vibration async function vibrate() { await Haptics.vibrate({ duration: 300 }); } // Notification feedback async function notificationFeedback() { await Haptics.notification({ type: 'SUCCESS' }); }
Platform Detection
Run different code on different platforms.
typescriptimport { Capacitor } from '@capacitor/core'; // Check platform const platform = Capacitor.getPlatform(); // Returns: 'ios', 'android', or 'web' // Check if running natively const isNative = Capacitor.isNativePlatform(); // Platform-specific code if (Capacitor.getPlatform() === 'ios') { // iOS-specific logic } else if (Capacitor.getPlatform() === 'android') { // Android-specific logic } else { // Web fallback } // Check if plugin is available const hasCamera = Capacitor.isPluginAvailable('Camera');
Common Issues
Frequently Asked Questions
Make sure you:
- Ran
npm install @capacitor/plugin-name - Ran
npx cap sync - Rebuilt the native app
For iOS, also run:
bashcd ios && pod install && cd ..
Check that you added the required Info.plist (iOS) or AndroidManifest.xml (Android) entries. Each plugin's documentation specifies required permissions.
Some features have platform differences. Check the plugin documentation for platform-specific notes. Use Capacitor.isPluginAvailable() to check.
Some plugins have web implementations, others don't. Check if the plugin supports web in its documentation. You may need to implement web fallbacks.
- You need a real device (not simulator/emulator for full testing)
- Configure APNs (iOS) or FCM (Android)
- Use tools like Firebase Console to send test notifications
Verify Your Understanding
You've completed this lesson if you can:
Next: Deploy your app to the Apple App Store and Google Play Store.