# Capawesome > Capawesome offers enterprise-grade solutions and services designed for teams building cross-platform apps with Capacitor. Capawesome is an open source organization on [GitHub](https://github.com/capawesome-team) that provides enterprise-grade solutions and services for teams developing cross-platform applications with [Capacitor](https://github.com/ionic-team/capacitor). Capawesome currently maintains more than 50 high-quality [Capacitor Plugins](plugins/index.md) with more than **1,000,000 monthly downloads**. Besides that, Capawesome offers a [Cloud](cloud/index.md) solution for Capacitor Live Updates and a [Consulting](consulting.md) service for Capacitor-related projects. # Plugins # Plugins This is a list of our Capacitor plugins: - [Accelerometer](https://capawesome.io/plugins/accelerometer/index.md) - [Age Signals](https://capawesome.io/plugins/age-signals/index.md) - [Android Battery Optimization](https://capawesome.io/plugins/android-battery-optimization/index.md) - [Android Dark Mode Support](https://capawesome.io/plugins/android-dark-mode-support/index.md) - [Android Edge-to-Edge Support](https://capawesome.io/plugins/android-edge-to-edge-support/index.md) - [Android Foreground Service](https://capawesome.io/plugins/android-foreground-service/index.md) - [App Review](https://capawesome.io/plugins/app-review/index.md) - [App Shortcuts](https://capawesome.io/plugins/app-shortcuts/index.md) - [App Update](https://capawesome.io/plugins/app-update/index.md) - [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) - [Asset Manager](https://capawesome.io/plugins/asset-manager/index.md) - [Audio Player](https://capawesome.io/plugins/audio-player/index.md) - [Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) - [Background Task](https://capawesome.io/plugins/background-task/index.md) - [Badge](https://capawesome.io/plugins/badge/index.md) - [Barometer](https://capawesome.io/plugins/barometer/index.md) - [Biometrics](https://capawesome.io/plugins/biometrics/index.md) - [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) - [Cloudinary](https://capawesome.io/plugins/cloudinary/index.md) - [Contacts](https://capawesome.io/plugins/contacts/index.md) - [Datetime Picker](https://capawesome.io/plugins/datetime-picker/index.md) - [File Compressor](https://capawesome.io/plugins/file-compressor/index.md) - [File Opener](https://capawesome.io/plugins/file-opener/index.md) - [File Picker](https://capawesome.io/plugins/file-picker/index.md) - [Firebase Analytics](https://capawesome.io/plugins/firebase/analytics/index.md) - [Firebase App](https://capawesome.io/plugins/firebase/app/index.md) - [Firebase App Check](https://capawesome.io/plugins/firebase/app-check/index.md) - [Firebase Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) - [Firebase Crashlytics](https://capawesome.io/plugins/firebase/crashlytics/index.md) - [Firebase Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) - [Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) - [Firebase Cloud Storage](https://capawesome.io/plugins/firebase/cloud-storage/index.md) - [Firebase Performance Monitoring](https://capawesome.io/plugins/firebase/performance-monitoring/index.md) - [Firebase Remote Config](https://capawesome.io/plugins/firebase/remote-config/index.md) - [Geocoder](https://capawesome.io/plugins/geocoder/index.md) - [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) - [libSQL](https://capawesome.io/plugins/libsql/index.md) - [Live Update](https://capawesome.io/plugins/live-update/index.md) - [Managed Configurations](https://capawesome.io/plugins/managed-configurations/index.md) - [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) - [ML Kit Document Scanner](https://capawesome.io/plugins/mlkit/document-scanner/index.md) - [ML Kit Face Detection](https://capawesome.io/plugins/mlkit/face-detection/index.md) - [ML Kit Face Mesh Detection](https://capawesome.io/plugins/mlkit/face-mesh-detection/index.md) - [ML Kit Selfie Segmentation](https://capawesome.io/plugins/mlkit/selfie-segmentation/index.md) - [ML Kit Translation](https://capawesome.io/plugins/mlkit/translation/index.md) - [Media Session](https://capawesome.io/plugins/media-session/index.md) - [NFC](https://capawesome.io/plugins/nfc/index.md) - [OAuth](https://capawesome.io/plugins/oauth/index.md) - [Pedometer](https://capawesome.io/plugins/pedometer/index.md) - [Photo Editor](https://capawesome.io/plugins/photo-editor/index.md) - [PostHog](https://capawesome.io/plugins/posthog/index.md) - [Printer](https://capawesome.io/plugins/printer/index.md) - [Purchases](https://capawesome.io/plugins/purchases/index.md) - [RealtimeKit](https://capawesome.io/plugins/realtimekit/index.md) - [Screen Orientation](https://capawesome.io/plugins/screen-orientation/index.md) - [Screenshot](https://capawesome.io/plugins/screenshot/index.md) - [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) - [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) - [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/index.md) - [Share Target](https://capawesome.io/plugins/share-target/index.md) - [Square Mobile Payments](https://capawesome.io/plugins/square-mobile-payments/index.md) - [SQLite](https://capawesome.io/plugins/sqlite/index.md) - [Superwall](https://capawesome.io/plugins/superwall/index.md) - [Torch](https://capawesome.io/plugins/torch/index.md) - [Wifi](https://capawesome.io/plugins/wifi/index.md) - [Zip](https://capawesome.io/plugins/zip/index.md) # @capawesome-team/capacitor-accelerometer Capacitor plugin to capture the acceleration force along the x, y, and z axes. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for accelerometer measurements. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - ⚡ **Real-time measurements**: Continuous accelerometer data with event listeners. - 📊 **High precision**: Accurate x, y, and z-axis acceleration measurements in G's. - 🔒 **Permission handling**: Built-in permission management for sensor access. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-accelerometer` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-accelerometer npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSMotionUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the user's contacts: ``` NSMotionUsageDescription The app needs to access the motion activity. ``` ## Usage ``` import { Accelerometer } from '@capawesome-team/capacitor-accelerometer'; const getMeasurement = async () => { const measurement = await Accelerometer.getMeasurement(); console.log("X: ", measurement.x); console.log("Y: ", measurement.y); console.log("Z: ", measurement.z); }; const isAvailable = async () => { const result = await Accelerometer.isAvailable(); return result.isAvailable; }; const startMeasurementUpdates = async () => { await Accelerometer.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Accelerometer.stopMeasurementUpdates(); }; const checkPermissions = async () => { const result = await Accelerometer.checkPermissions(); return result; }; const requestPermissions = async () => { const result = await Accelerometer.requestPermissions(); return result; }; const removeAllListeners = async () => { await Accelerometer.removeAllListeners(); }; ``` ## API - [`getMeasurement()`](#getmeasurement) - [`isAvailable()`](#isavailable) - [`startMeasurementUpdates()`](#startmeasurementupdates) - [`stopMeasurementUpdates()`](#stopmeasurementupdates) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('measurement', ...)`](#addlistenermeasurement-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getMeasurement() ``` getMeasurement() => Promise ``` Get the latest measurement. This method returns the most recent measurement from the accelerometer sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the accelerometer sensor is available on the device. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startMeasurementUpdates() ``` startMeasurementUpdates() => Promise ``` Starts emitting `measurement` events. **Since:** 0.1.0 ______________________________________________________________________ ### stopMeasurementUpdates() ``` stopMeasurementUpdates() => Promise ``` Stops emitting `measurement` events. **Since:** 0.1.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check if the app has permission to access the accelerometer sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to access the accelerometer sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('measurement', ...) ``` addListener(eventName: 'measurement', listenerFunc: (event: MeasurementEvent) => void) => Promise ``` Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'measurement'` | | **`listenerFunc`** | `(event: Measurement) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### Measurement | Prop | Type | Description | Since | | ------- | -------- | ----------------------------------------------------- | ----- | | **`x`** | `number` | The x-axis acceleration in G's (gravitational force). | 0.1.0 | | **`y`** | `number` | The y-axis acceleration in G's (gravitational force). | 0.1.0 | | **`z`** | `number` | The z-axis acceleration in G's (gravitational force). | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------ | ----- | | **`isAvailable`** | `boolean` | Whether the accelerometer sensor is available on the device. | 0.1.0 | #### PermissionStatus | Prop | Type | Description | Since | | ------------------- | ------------------------------ | --------------------------------------- | ----- | | **`accelerometer`** | `AccelerometerPermissionState` | The permission status of accelerometer. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GetMeasurementResult `Measurement` #### AccelerometerPermissionState `PermissionState | 'limited'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### MeasurementEvent `Measurement` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accelerometer/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accelerometer/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accelerometer/LICENSE). # @capawesome/capacitor-age-signals Capacitor plugin to use the [Play Age Signals API](https://developer.android.com/google/play/age-signals/overview) (Android) and [DeclaredAgeRange](https://developer.apple.com/documentation/declaredagerange/) (iOS) to request age signals about the user. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for age verification. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🔍 **Age Verification**: Request user age signals using Play Age Signals API (Android) and DeclaredAgeRange (iOS). - 👨‍👩‍👧‍👦 **Parental Controls**: Support for supervised accounts with parental approval status. - 🧪 **Testing Support**: Built-in `FakeAgeSignalsManager` integration for testing different age verification scenarios (Android). - 🌍 **Compliance Ready**: Built for US state age verification requirements (effective January 1, 2026). - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.3.x | >=8.x.x | Active support | | 0.2.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-age-signals` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-age-signals npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app's `variables.gradle` file to change the default version of the dependency: - `$androidPlayAgeSignalsVersion` version of `com.google.android.play:age-signals` (default: `0.0.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. **Note**: The `FakeAgeSignalsManager` testing API is included in the main `age-signals` library, so no additional dependency is required for testing. ### iOS #### Entitlements To use the DeclaredAgeRange API, you must enable the `com.apple.developer.declared-age-range` entitlement in your app's entitlements file by adding the following key: ``` com.apple.developer.declared-age-range ``` Check out the [Apple documentation](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.contacts.notes) for more information. ## Configuration No configuration required for this plugin. ## Usage ``` import { AgeSignals } from '@capawesome/capacitor-age-signals'; const checkAgeSignals = async () => { const result = await AgeSignals.checkAgeSignals(); console.log('User Status:', result.userStatus); console.log('Age Lower:', result.ageLower); console.log('Age Upper:', result.ageUpper); }; const checkEligibility = async () => { const result = await AgeSignals.checkEligibility(); console.log('Is Eligible:', result.isEligible); }; ``` ## Testing The plugin includes support for the `FakeAgeSignalsManager` API on Android, which allows you to simulate different age signals scenarios in your tests without requiring live responses from Google Play. ### Android Testing **Important**: Due to a known issue in versions 0.0.1 and 0.0.2 of the Age Signals API, you may encounter a `java.lang.VerifyError` when calling the builder method of `AgeSignalsResult` in unit tests. As a workaround, run your tests as Android instrumented tests within the `androidTest` source set. #### Example: Testing a Verified Adult User ``` import { AgeSignals, UserStatus } from '@capawesome/capacitor-age-signals'; // Enable the fake manager await AgeSignals.setUseFakeManager({ useFake: true }); // Set up a verified adult user await AgeSignals.setNextAgeSignalsResult({ userStatus: UserStatus.Verified, }); // Check age signals - will return the fake result const result = await AgeSignals.checkAgeSignals(); console.log(result.userStatus); // 'VERIFIED' ``` #### Example: Testing a Supervised User (13-17 years old) ``` import { AgeSignals, UserStatus } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); await AgeSignals.setNextAgeSignalsResult({ userStatus: UserStatus.Supervised, ageLower: 13, ageUpper: 17, installId: 'fake_install_id', }); const result = await AgeSignals.checkAgeSignals(); console.log(result.userStatus); // 'SUPERVISED' console.log(result.ageLower); // 13 console.log(result.ageUpper); // 17 console.log(result.installId); // 'fake_install_id' ``` #### Example: Testing Parental Approval Scenarios ``` import { AgeSignals, UserStatus } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); // Test pending approval await AgeSignals.setNextAgeSignalsResult({ userStatus: UserStatus.SupervisedApprovalPending, ageLower: 13, ageUpper: 17, mostRecentApprovalDate: '2025-02-01', installId: 'fake_install_id', }); const result = await AgeSignals.checkAgeSignals(); console.log(result.userStatus); // 'SUPERVISED_APPROVAL_PENDING' console.log(result.mostRecentApprovalDate); // '2025-02-01' ``` #### Example: Testing Error Scenarios ``` import { AgeSignals, ErrorCode } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); // Simulate a network error await AgeSignals.setNextAgeSignalsException({ errorCode: ErrorCode.NetworkError, }); try { await AgeSignals.checkAgeSignals(); } catch (error) { console.log('Caught network error:', error); } ``` #### Disabling the Fake Manager ``` import { AgeSignals } from '@capawesome/capacitor-age-signals'; // Switch back to the production manager await AgeSignals.setUseFakeManager({ useFake: false }); // This will now use the real Age Signals API const result = await AgeSignals.checkAgeSignals(); ``` ## API - [`checkAgeSignals(...)`](#checkagesignals) - [`checkEligibility()`](#checkeligibility) - [`setUseFakeManager(...)`](#setusefakemanager) - [`setNextAgeSignalsResult(...)`](#setnextagesignalsresult) - [`setNextAgeSignalsException(...)`](#setnextagesignalsexception) - [Interfaces](#interfaces) - [Enums](#enums) ### checkAgeSignals(...) ``` checkAgeSignals(options?: CheckAgeSignalsOptions | undefined) => Promise ``` Request the user's age signals. | Param | Type | | ------------- | ------------------------ | | **`options`** | `CheckAgeSignalsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### checkEligibility() ``` checkEligibility() => Promise ``` Check if the user is eligible for age-gated features. Only available on iOS. **Returns:** `Promise` **Since:** 0.3.1 ______________________________________________________________________ ### setUseFakeManager(...) ``` setUseFakeManager(options: SetUseFakeManagerOptions) => Promise ``` Enable or disable the fake age signals manager for testing. Only available on Android. | Param | Type | | ------------- | -------------------------- | | **`options`** | `SetUseFakeManagerOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### setNextAgeSignalsResult(...) ``` setNextAgeSignalsResult(options: SetNextAgeSignalsResultOptions) => Promise ``` Set the next age signals result to be returned by the fake manager. Only available on Android. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SetNextAgeSignalsResultOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### setNextAgeSignalsException(...) ``` setNextAgeSignalsException(options: SetNextAgeSignalsExceptionOptions) => Promise ``` Set the next exception to be thrown by the fake manager. Only available on Android. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `SetNextAgeSignalsExceptionOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### Interfaces #### CheckAgeSignalsResult | Prop | Type | Description | Since | | ---------------------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`userStatus`** | `UserStatus` | The user's verification status. | 0.0.1 | | **`ageLower`** | `number` | The (inclusive) lower bound of a supervised user's age range. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED`. | 0.0.1 | | **`ageUpper`** | `number` | The (inclusive) upper bound of a supervised user's age range. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED` and the user's age is under 18. | 0.0.1 | | **`mostRecentApprovalDate`** | `string` | The effective from date of the most recent significant change that was approved. When an app is installed, the date of the most recent significant change prior to install is used. Only available when `userStatus` is `SUPERVISED_APPROVAL_PENDING` or `SUPERVISED_APPROVAL_DENIED`. Only available on Android. | 0.0.1 | | **`installId`** | `string` | An ID assigned to supervised user installs by Google Play, used for the purposes of notifying you of revoked app approval. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED`. Only available on Android. | 0.0.1 | #### CheckAgeSignalsOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----- | | **`ageGates`** | `number[]` | The age ranges that the user falls into. The provided array must contain at least 2 and at most 3 ages. Only available on iOS. | `[13, 15, 18]` | 0.0.2 | #### CheckEligibilityResult | Prop | Type | Description | Since | | ---------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`isEligible`** | `boolean` | Whether the user is eligible for age-gated features. Returns `true` if the user is in an applicable region that requires additional age-related obligations. Always returns `false` on macOS. | 0.3.1 | #### SetUseFakeManagerOptions | Prop | Type | Description | Default | Since | | ------------- | --------- | -------------------------------------------------------- | ------- | ----- | | **`useFake`** | `boolean` | Whether to use the fake age signals manager for testing. | `false` | 0.3.1 | #### SetNextAgeSignalsResultOptions | Prop | Type | Description | Since | | ---------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`userStatus`** | `UserStatus` | The user's verification status. | 0.3.1 | | **`ageLower`** | `number` | The (inclusive) lower bound of a supervised user's age range. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED`. | 0.3.1 | | **`ageUpper`** | `number` | The (inclusive) upper bound of a supervised user's age range. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED` and the user's age is under 18. | 0.3.1 | | **`mostRecentApprovalDate`** | `string` | The effective from date of the most recent significant change that was approved. When an app is installed, the date of the most recent significant change prior to install is used. Only available when `userStatus` is `SUPERVISED_APPROVAL_PENDING` or `SUPERVISED_APPROVAL_DENIED`. | 0.3.1 | | **`installId`** | `string` | An ID assigned to supervised user installs by Google Play, used for the purposes of notifying you of revoked app approval. Only available when `userStatus` is `SUPERVISED`, `SUPERVISED_APPROVAL_PENDING`, or `SUPERVISED_APPROVAL_DENIED`. | 0.3.1 | #### SetNextAgeSignalsExceptionOptions | Prop | Type | Description | Since | | --------------- | ----------- | ------------------------------------------------ | ----- | | **`errorCode`** | `ErrorCode` | The error code to be thrown by the fake manager. | 0.3.1 | ### Enums #### UserStatus | Members | Value | Description | Since | | ------------------------------- | ------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Verified`** | `'VERIFIED'` | The user is over 18. Google verified the user's age using a commercially reasonable method such as a government-issued ID, credit card, or facial age estimation. | 0.0.1 | | **`Supervised`** | `'SUPERVISED'` | The user has a supervised Google Account managed by a parent who sets their age. Use `ageLower` and `ageUpper` to determine the user's age range. | 0.0.1 | | **`SupervisedApprovalPending`** | `'SUPERVISED_APPROVAL_PENDING'` | The user has a supervised Google Account, and their supervising parent has not yet approved one or more pending significant changes. Use `ageLower` and `ageUpper` to determine the user's age range. Use `mostRecentApprovalDate` to determine the last significant change that was approved. | 0.0.1 | | **`SupervisedApprovalDenied`** | `'SUPERVISED_APPROVAL_DENIED'` | The user has a supervised Google Account, and their supervising parent denied approval for one or more significant changes. Use `ageLower` and `ageUpper` to determine the user's age range. Use `mostRecentApprovalDate` to determine the last significant change that was approved. | 0.0.1 | | **`Unknown`** | `'UNKNOWN'` | The user is not verified or supervised in applicable jurisdictions and regions. These users could be over or under 18. To obtain an age signal from Google Play, ask the user to visit the Play Store to resolve their status. | 0.0.1 | | **`Empty`** | `'EMPTY'` | All other users return this value. | 0.0.1 | #### ErrorCode | Members | Value | Description | Since | | --------------------------------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ApiNotAvailable`** | `'API_NOT_AVAILABLE'` | The Play Age Signals API is not available. The Play Store app version installed on the device might be old. | 0.0.1 | | **`PlayStoreNotFound`** | `'PLAY_STORE_NOT_FOUND'` | No Play Store app is found on the device. | 0.0.1 | | **`NetworkError`** | `'NETWORK_ERROR'` | No available network is found. | 0.0.1 | | **`PlayServicesNotFound`** | `'PLAY_SERVICES_NOT_FOUND'` | Play Services is not available or its version is too old. | 0.0.1 | | **`CannotBindToService`** | `'CANNOT_BIND_TO_SERVICE'` | Binding to the service in the Play Store has failed. This can be due to having an old Play Store version installed on the device or device memory is overloaded. | 0.0.1 | | **`PlayStoreVersionOutdated`** | `'PLAY_STORE_VERSION_OUTDATED'` | The Play Store app needs to be updated. | 0.0.1 | | **`PlayServicesVersionOutdated`** | `'PLAY_SERVICES_VERSION_OUTDATED'` | Play Services needs to be updated. | 0.0.1 | | **`ClientTransientError`** | `'CLIENT_TRANSIENT_ERROR'` | There was a transient error in the client device. | 0.0.1 | | **`AppNotOwned`** | `'APP_NOT_OWNED'` | The app was not installed by Google Play. | 0.0.1 | | **`InternalError`** | `'INTERNAL_ERROR'` | Unknown internal error. | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/age-signals/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/age-signals/LICENSE). # @capawesome-team/capacitor-android-battery-optimization Capacitor plugin for Android to manage battery optimization settings, request exemptions, and enhance app performance under Doze and App Standby modes. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-android-battery-optimization` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-android-battery-optimization npx cap sync ``` ### Android This API requires the following permissions be added to your `AndroidManifest.xml` before the `application` tag if you want to request direct exemption from Power Management features: ``` ``` ⚠️ **Attention**: Google Play policies prohibit apps from requesting direct exemption from Power Management features in Android 6.0+ (Doze and App Standby) unless the core function of the app is adversely affected. [Source](https://developer.android.com/training/monitoring-device-state/doze-standby.html#support_for_other_use_cases) ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | | ------- | | | ## Usage ``` import { Capacitor } from '@capacitor/core'; import { BatteryOptimization } from '@capawesome-team/capacitor-android-battery-optimization'; const isBatteryOptimizationEnabled = async () => { if (Capacitor.getPlatform() !== 'android') { return false; } const { enabled } = await BatteryOptimization.isBatteryOptimizationEnabled(); return enabled; }; const openBatteryOptimizationSettings = async () => { if (Capacitor.getPlatform() !== 'android') { return; } await BatteryOptimization.openBatteryOptimizationSettings(); }; const requestIgnoreBatteryOptimization = async () => { if (Capacitor.getPlatform() !== 'android') { return; } await BatteryOptimization.requestIgnoreBatteryOptimization(); }; ``` ## API - [`isBatteryOptimizationEnabled()`](#isbatteryoptimizationenabled) - [`openBatteryOptimizationSettings()`](#openbatteryoptimizationsettings) - [`requestIgnoreBatteryOptimization()`](#requestignorebatteryoptimization) - [Interfaces](#interfaces) ### isBatteryOptimizationEnabled() ``` isBatteryOptimizationEnabled() => Promise ``` Returns whether or not battery optimization is enabled. Only available on Android. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openBatteryOptimizationSettings() ``` openBatteryOptimizationSettings() => Promise ``` Opens the battery optimization settings page. Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### requestIgnoreBatteryOptimization() ``` requestIgnoreBatteryOptimization() => Promise ``` Requests the battery optimization ignore permission. This method needs the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` manifest permission. Use this method only if your app meets an acceptable use case (see Google Play Policy). Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### IsBatteryOptimizationEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | ----------------------------------------------- | ----- | | **`enabled`** | `boolean` | Whether or not battery optimization is enabled. | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-battery-optimization/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-battery-optimization/LICENSE). # @capawesome/capacitor-android-dark-mode-support Capacitor plugin for seamless Android dark mode support. Enhance user experience with `prefers-color-scheme` CSS media feature compatibility. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-android-dark-mode-support` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-android-dark-mode-support npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxWebkitVersion` version of `androidx.webkit:webkit` (default: `1.9.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage The plugin only needs to be installed. It enables the correct behavior of the [`prefers-color-scheme`](https://developer.mozilla.org/en-US/docs/Web/CSS/@media/prefers-color-scheme) CSS media feature. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-dark-mode-support/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-dark-mode-support/LICENSE). # @capawesome/capacitor-android-edge-to-edge-support Capacitor plugin to support [edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) display on Android with advanced features like setting the background color of the status bar and navigation bar. | Before | After | Before | After | | ------ | ----- | ------ | ----- | | | | | | **Attention:** Despite its name, this plugin doesn't enable edge-to-edge mode by default. Instead, it preserves the traditional app behavior by applying proper insets to the webview, preventing Android's edge-to-edge changes from affecting apps that haven't been designed to support it. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-android-edge-to-edge-support` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-android-edge-to-edge-support npx cap sync ``` ### Android #### Capacitor SystemBars Plugin Make sure to disable the built-in insets handling of the [Capacitor SystemBars](https://capacitorjs.com/docs/apis/system-bars) plugin in your [Capacitor Configuration](https://capacitorjs.com/docs/config) file: ``` { "plugins": { "SystemBars": { "insetsHandling": "disable" } } } ``` Please note that this plugin is part of the Capacitor core and the insets handling is **always enabled by default**. #### Capacitor Keyboard Plugin If you are using the [Capacitor Keyboard](https://capacitorjs.com/docs/apis/keyboard) plugin, make sure to set the `resizeOnFullScreen` property to `false` (default) in your [Capacitor Configuration](https://capacitorjs.com/docs/config) file: ``` { "plugins": { "Keyboard": { "resizeOnFullScreen": false } } } ``` Otherwise, the web view will be resized to fit the screen, which may cause issues with this plugin, see [this comment](https://github.com/capawesome-team/capacitor-plugins/issues/490#issuecomment-2826435796). ## Configuration | Prop | Type | Description | Since | | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------------- | ----- | | **`backgroundColor`** | `string` | The hexadecimal color to set as the background color of the status bar and navigation bar. | 7.1.0 | | **`navigationBarColor`** | `string` | The hexadecimal color to set as the background color of the navigation bar area. Only available on Android. | 8.0.0 | | **`statusBarColor`** | `string` | The hexadecimal color to set as the background color of the status bar area. Only available on Android. | 8.0.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "EdgeToEdge": { "backgroundColor": "#ffffff", "navigationBarColor": "#000000", "statusBarColor": "#ffffff" } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { EdgeToEdge: { backgroundColor: "#ffffff", navigationBarColor: "#000000", statusBarColor: "#ffffff", }, }, }; export default config; ``` ## Usage The plugin **only needs to be installed**. It applies insets to the web view to support edge-to-edge display on Android. The plugin also provides a method to set the background color of the status bar and navigation bar. It's recommended to use this method in combination with the [SystemBars](https://capacitorjs.com/docs/apis/system-bars) plugin. ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; import { SystemBars, SystemBarsStyle } from '@capacitor/core'; const enable = async () => { await EdgeToEdge.enable(); }; const disable = async () => { await EdgeToEdge.disable(); }; const getInsets = async () => { const result = await EdgeToEdge.getInsets(); console.log('Insets:', result); }; const setDarkStyle = async () => { await SystemBars.setStyle({ style: SystemBarsStyle.Dark }); await EdgeToEdge.setBackgroundColor({ color: '#000000' }); }; const setLightStyle = async () => { await SystemBars.setStyle({ style: SystemBarsStyle.Light }); await EdgeToEdge.setBackgroundColor({ color: '#FFFFFF' }); }; ``` ## API - [`disable()`](#disable) - [`enable()`](#enable) - [`getInsets()`](#getinsets) - [`setBackgroundColor(...)`](#setbackgroundcolor) - [`setNavigationBarColor(...)`](#setnavigationbarcolor) - [`setStatusBarColor(...)`](#setstatusbarcolor) - [Interfaces](#interfaces) ### disable() ``` disable() => Promise ``` Disable the edge-to-edge mode. Only available on Android. **Since:** 7.2.0 ______________________________________________________________________ ### enable() ``` enable() => Promise ``` Enable the edge-to-edge mode. Only available on Android. **Since:** 7.2.0 ______________________________________________________________________ ### getInsets() ``` getInsets() => Promise ``` Return the insets that are currently applied to the webview. Only available on Android. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### setBackgroundColor(...) ``` setBackgroundColor(options: SetBackgroundColorOptions) => Promise ``` Set the background color of the status bar and navigation bar. Only available on Android. | Param | Type | | ------------- | --------------------------- | | **`options`** | `SetBackgroundColorOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### setNavigationBarColor(...) ``` setNavigationBarColor(options: SetNavigationBarColorOptions) => Promise ``` Set the background color of the navigation bar area. Only available on Android. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SetNavigationBarColorOptions` | **Since:** 8.0.0 ______________________________________________________________________ ### setStatusBarColor(...) ``` setStatusBarColor(options: SetStatusBarColorOptions) => Promise ``` Set the background color of the status bar area. Only available on Android. | Param | Type | | ------------- | -------------------------- | | **`options`** | `SetStatusBarColorOptions` | **Since:** 8.0.0 ______________________________________________________________________ ### Interfaces #### GetInsetsResult | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------------------------------------- | ----- | | **`bottom`** | `number` | The bottom inset that was applied to the webview. Only available on Android. | 7.2.0 | | **`left`** | `number` | The left inset that was applied to the webview. Only available on Android. | 7.2.0 | | **`right`** | `number` | The right inset that was applied to the webview. Only available on Android. | 7.2.0 | | **`top`** | `number` | The top inset that was applied to the webview. Only available on Android. | 7.2.0 | #### SetBackgroundColorOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------------------------------ | ----- | | **`color`** | `string` | The hexadecimal color to set as the background color of the status bar and navigation bar. | 7.0.0 | #### SetNavigationBarColorOptions | Prop | Type | Description | Since | | ----------- | -------- | -------------------------------------------------------------------------------- | ----- | | **`color`** | `string` | The hexadecimal color to set as the background color of the navigation bar area. | 8.0.0 | #### SetStatusBarColorOptions | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------------------------------------------------- | ----- | | **`color`** | `string` | The hexadecimal color to set as the background color of the status bar area. | 8.0.0 | ## FAQ ### What about Capacitor 8's built-in edge-to-edge support? Capacitor 8 introduced native edge-to-edge functionality through the internal `SystemBars` plugin. While this covers many common scenarios, this plugin addresses additional edge cases that aren't yet fully resolved in the Capacitor core implementation. We plan to deprecate this plugin once all edge cases are properly handled in Capacitor core. ### Is this plugin compatible with Capacitor's SystemBars API? Yes, this plugin is partially compatible with the new [SystemBars API](https://capacitorjs.com/docs/apis/system-bars) introduced in Capacitor 8. For example, methods like `setStyle()` from the SystemBars API are supported and can be used alongside this plugin without conflicts. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-edge-to-edge-support/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-edge-to-edge-support/LICENSE). # @capawesome-team/capacitor-android-foreground-service Capacitor plugin to run a foreground service on Android. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-android-foreground-service` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-android-foreground-service npx cap sync ``` ### Android This API requires the following permissions be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` **Attention**: Replace `FOREGROUND_SERVICE_LOCATION` with the foreground service types you want to use (see [Foreground service types](https://developer.android.com/develop/background-work/services/fg-service-types)). See [ServiceType](#servicetype) for the available types. You also need to add the following receiver and service **inside** the `application` tag in your `AndroidManifest.xml`: ``` ``` **Attention**: Replace `location` with the foreground service types you want to use (see [Foreground service types](https://developer.android.com/develop/background-work/services/fg-service-types)). See [ServiceType](#servicetype) for the available types. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | | ------- | | | ## Usage ``` import { Capacitor } from '@capacitor/core'; import { ForegroundService } from '@capawesome-team/capacitor-android-foreground-service'; const startForegroundService = async () => { await ForegroundService.startForegroundService({ id: 1, title: 'Title', body: 'Body', smallIcon: 'ic_stat_icon_config_sample', buttons: [ { title: 'Button 1', id: 1, }, { title: 'Button 2', id: 2, }, ], silent: false, notificationChannelId: 'default', }); }; const updateForegroundService = async () => { await ForegroundService.updateForegroundService({ id: 1, title: 'Title', body: 'Body', smallIcon: 'ic_stat_icon_config_sample', }); }; const stopForegroundService = async () => { await ForegroundService.stopForegroundService(); }; const createNotificationChannel = async () => { await ForegroundService.createNotificationChannel({ id: 'default', name: 'Default', description: 'Default channel', importance: Importance.Default, }); }; const deleteNotificationChannel = async () => { await ForegroundService.deleteNotificationChannel({ id: 'default', }); }; ``` ## API - [`moveToForeground()`](#movetoforeground) - [`startForegroundService(...)`](#startforegroundservice) - [`updateForegroundService(...)`](#updateforegroundservice) - [`stopForegroundService()`](#stopforegroundservice) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`checkManageOverlayPermission()`](#checkmanageoverlaypermission) - [`requestManageOverlayPermission()`](#requestmanageoverlaypermission) - [`createNotificationChannel(...)`](#createnotificationchannel) - [`deleteNotificationChannel(...)`](#deletenotificationchannel) - [`addListener('buttonClicked', ...)`](#addlistenerbuttonclicked-) - [`addListener('notificationTapped', ...)`](#addlistenernotificationtapped-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### moveToForeground() ``` moveToForeground() => Promise ``` Moves the app to the foreground. On Android SDK 23+, the user must grant the manage overlay permission. You can use the `requestManageOverlayPermission()` method to request the permission and the `checkManageOverlayPermission()` method to check if the permission is granted. Only available on Android. **Since:** 0.3.0 ______________________________________________________________________ ### startForegroundService(...) ``` startForegroundService(options: StartForegroundServiceOptions) => Promise ``` Starts the foreground service. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `StartForegroundServiceOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### updateForegroundService(...) ``` updateForegroundService(options: UpdateForegroundServiceOptions) => Promise ``` Updates the notification details of the running foreground service. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `StartForegroundServiceOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### stopForegroundService() ``` stopForegroundService() => Promise ``` Stops the foreground service. Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permission to display notifications. On **Android**, this method only needs to be called on Android 13+. Only available on Android. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to display notifications. On **Android**, this method only needs to be called on Android 13+. Only available on Android. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### checkManageOverlayPermission() ``` checkManageOverlayPermission() => Promise ``` Check if the overlay permission is granted. Only available on Android. **Returns:** `Promise` **Since:** 0.3.0 ______________________________________________________________________ ### requestManageOverlayPermission() ``` requestManageOverlayPermission() => Promise ``` Request the manage overlay permission. Only available on Android. **Returns:** `Promise` **Since:** 0.3.0 ______________________________________________________________________ ### createNotificationChannel(...) ``` createNotificationChannel(options: CreateNotificationChannelOptions) => Promise ``` Create a notification channel. If not invoked, the plugin creates a channel with name and description set to "Default". Only available for Android (SDK 26+). | Param | Type | | ------------- | ---------------------------------- | | **`options`** | `CreateNotificationChannelOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### deleteNotificationChannel(...) ``` deleteNotificationChannel(options: DeleteNotificationChannelOptions) => Promise ``` Delete a notification channel. Only available for Android (SDK 26+). | Param | Type | | ------------- | ---------------------------------- | | **`options`** | `DeleteNotificationChannelOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### addListener('buttonClicked', ...) ``` addListener(eventName: 'buttonClicked', listenerFunc: ButtonClickedEventListener) => Promise ``` Called when a notification button is clicked. Only available on iOS. | Param | Type | | ------------------ | ---------------------------- | | **`eventName`** | `'buttonClicked'` | | **`listenerFunc`** | `ButtonClickedEventListener` | **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### addListener('notificationTapped', ...) ``` addListener(eventName: 'notificationTapped', listenerFunc: NotificationTappedEventListener) => Promise ``` Called when the foreground service notification is tapped. Only available on Android. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'notificationTapped'` | | **`listenerFunc`** | `NotificationTappedEventListener` | **Returns:** `Promise` **Since:** 8.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.2.0 ______________________________________________________________________ ### Interfaces #### StartForegroundServiceOptions | Prop | Type | Description | Default | Since | | --------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`body`** | `string` | The body of the notification, shown below the title. | | 0.0.1 | | **`buttons`** | `NotificationButton[]` | The buttons to show on the notification. Only available on Android (SDK 24+). | | 0.2.0 | | **`id`** | `number` | The notification identifier. | | 0.0.1 | | **`serviceType`** | `ServiceType` | The foreground service type. Only available on Android (SDK 29+). | | 6.2.0 | | **`smallIcon`** | `string` | The status bar icon for the notification. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. | | 0.0.1 | | **`title`** | `string` | The title of the notification. | | 0.0.1 | | **`silent`** | `boolean` | If true, will only alert (sound/vibration) on the first notification. Subsequent updates will be silent. | `false` | 6.1.0 | | **`notificationChannelId`** | `string` | The notification channel identifier. | | 6.1.0 | #### NotificationButton | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------------------------------------------------------------------- | ----- | | **`title`** | `string` | The button title. | 0.2.0 | | **`id`** | `number` | The button identifier. This is used to identify the button when the `buttonClicked` event is emitted. | 0.2.0 | #### PermissionStatus | Prop | Type | Description | Since | | ------------- | ----------------- | --------------------------------------------- | ----- | | **`display`** | `PermissionState` | Permission state of displaying notifications. | 5.0.0 | #### ManageOverlayPermissionResult | Prop | Type | Description | Since | | ------------- | --------- | ----------------------------------------------------------------------------- | ----- | | **`granted`** | `boolean` | Whether the permission is granted. This is always `true` on Android SDK < 23. | 0.3.0 | #### CreateNotificationChannelOptions | Prop | Type | Description | Since | | ----------------- | ------------ | ------------------------------------------------------------------- | ----- | | **`description`** | `string` | The description of this channel (presented to the user). | 6.1.0 | | **`id`** | `string` | The channel identifier. | 6.1.0 | | **`importance`** | `Importance` | The level of interruption for notifications posted to this channel. | 6.1.0 | | **`name`** | `string` | The name of this channel (presented to the user). | 6.1.0 | #### DeleteNotificationChannelOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------- | ----- | | **`id`** | `string` | The channel identifier. | 6.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ButtonClickedEvent | Prop | Type | Description | Since | | -------------- | -------- | ---------------------- | ----- | | **`buttonId`** | `number` | The button identifier. | 0.2.0 | #### NotificationTappedEvent | Prop | Type | Description | Since | | -------------------- | -------- | ---------------------------- | ----- | | **`notificationId`** | `number` | The notification identifier. | 8.1.0 | ### Type Aliases #### UpdateForegroundServiceOptions `StartForegroundServiceOptions` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### ButtonClickedEventListener `(event: ButtonClickedEvent): void` #### NotificationTappedEventListener `(event: NotificationTappedEvent): void` ### Enums #### ServiceType | Members | Value | Description | Since | | ---------------- | ----- | ----------------------------------------------------------------------------------------------- | ----- | | **`Location`** | `8` | Long-running use cases that require location access, such as navigation and location sharing. | 6.2.0 | | **`Microphone`** | `128` | Continue microphone capture from the background, such as voice recorders or communication apps. | 6.2.0 | #### Importance | Members | Value | Since | | ------------- | ----- | ----- | | **`Min`** | `1` | 6.1.0 | | **`Low`** | `2` | 6.1.0 | | **`Default`** | `3` | 6.1.0 | | **`High`** | `4` | 6.1.0 | | **`Max`** | `5` | 6.1.0 | ## FAQ ### Why can the user dismiss the notification? Android 14 has changed the behavior to allow users to dismiss such notifications, see [Changes to how users experience non-dismissible notifications](https://developer.android.com/about/versions/14/behavior-changes-all#non-dismissable-notifications). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-foreground-service/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-foreground-service/LICENSE). # @capawesome/capacitor-app-review Capacitor plugin that allows users to submit app store reviews and ratings. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-app-review` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-app-review npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidPlayReviewVersion` version of `com.google.android.play:review` (default: `2.0.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Usage ``` import { AppReview } from '@capawesome/capacitor-app-review'; const openAppStore = async () => { await AppReview.openAppStore(); }; const requestReview = async () => { await AppReview.requestReview(); }; ``` ## API - [`openAppStore(...)`](#openappstore) - [`requestReview()`](#requestreview) - [Interfaces](#interfaces) ### openAppStore(...) ``` openAppStore(options?: OpenAppStoreOptions | undefined) => Promise ``` Open the App Store page for the current app and, if possible, open the dialog to leave a review. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `OpenAppStoreOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### requestReview() ``` requestReview() => Promise ``` Request an in-app review. **Attention**: On iOS, review requests are limited to 3 requests per year. Only available on Android and iOS (14+). **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### OpenAppStoreOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`appId`** | `string` | The app ID of the app to open in the App Store. On **iOS**, this is the Apple ID of your app (e.g. `123456789`). You can find the ID in the URL of your app store entry (e.g. `https://apps.apple.com/app/id123456789`). Only available on iOS. | 6.0.1 | ## Testing In order to test the In-App Review functionality, you need to follow the instructions provided by the respective platform: - [Android](https://developer.android.com/guide/playcore/in-app-review/test) - [iOS](https://developer.apple.com/documentation/storekit/skstorereviewcontroller/3566727-requestreview#4278434) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-review/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-review/LICENSE). # @capawesome/capacitor-app-shortcuts Capacitor plugin to manage app shortcuts and quick actions. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-app-shortcuts` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome/capacitor-app-shortcuts npx cap sync ``` ## Configuration | Prop | Type | Description | Since | | --------------- | ------------ | ------------------------------------------------------------------------------------------- | ----- | | **`shortcuts`** | `Shortcut[]` | The list of app shortcuts that should be set by default. Only available on Android and iOS. | 7.2.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "AppShortcuts": { "shortcuts": [{ id: 'feedback', title: 'Feedback' }] } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { AppShortcuts: { shortcuts: [{ id: 'feedback', title: 'Feedback' }], }, }, }; export default config; ``` ### iOS On iOS, you must add the following to your app's `AppDelegate.swift`: ``` + import CapawesomeCapacitorAppShortcuts @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + if let shortcutItem = launchOptions?[.shortcutItem] as? UIApplicationShortcutItem { + NotificationCenter.default.post(name: NSNotification.Name(AppShortcutsPlugin.notificationName), object: nil, userInfo: [AppShortcutsPlugin.userInfoShortcutItemKey: shortcutItem]) + return true + } return true } + func application(_ application: UIApplication, performActionFor shortcutItem: UIApplicationShortcutItem, completionHandler: @escaping (Bool) -> Void) { + NotificationCenter.default.post(name: NSNotification.Name(AppShortcutsPlugin.notificationName), object: nil, userInfo: [AppShortcutsPlugin.userInfoShortcutItemKey: shortcutItem]) + completionHandler(true) + } ``` ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/app-shortcuts/example). | Android | iOS | | ------- | --- | | | | ## Usage ``` import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const clear = async () => { await AppShortcuts.clear(); }; const get = async () => { const result = await AppShortcuts.get(); return result.shortcuts; }; const set = async () => { await AppShortcuts.set({ shortcuts: [ { id: 'feedback', title: 'Feedback', description: 'Send us your feedback', }, { id: 'rate', title: 'Rate', description: 'Rate our app', } ], }); }; const addListener = async () => { AppShortcuts.addListener('click', (event) => { console.log('Shortcut clicked:', event.id); }); }; ``` ## API - [`clear()`](#clear) - [`get()`](#get) - [`set(...)`](#set) - [`addListener('click', ...)`](#addlistenerclick-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) ### clear() ``` clear() => Promise ``` Remove all app shortcuts. Only available on Android and iOS. **Since:** 6.0.0 ______________________________________________________________________ ### get() ``` get() => Promise ``` Get all app shortcuts. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### set(...) ``` set(options: SetOptions) => Promise ``` Create or update app shortcuts. Only available on Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `SetOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### addListener('click', ...) ``` addListener(eventName: 'click', listenerFunc: (event: ClickEvent) => void) => Promise ``` Called when an app shortcut is clicked. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------- | | **`eventName`** | `'click'` | | **`listenerFunc`** | `(event: ClickEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### GetResult | Prop | Type | Description | Since | | --------------- | ------------ | -------------------------- | ----- | | **`shortcuts`** | `Shortcut[]` | The list of app shortcuts. | 6.0.0 | #### Shortcut | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`description`** | `string` | The description. On **Android**, the launcher shows this instead of the short title when it has enough space. **Attention**: On **iOS**, the icon and the description must be used together. | 6.0.0 | | **`id`** | `string` | The unique identifier. | 6.0.0 | | **`title`** | `string` | The display name. | 6.0.0 | | **`icon`** | \`string | number\` | The icon to display. On **Android**, the icon can be one of the following: - An integer value of the [R.drawable](https://developer.android.com/reference/android/R.drawable) enum (e.g. `17301547`). - A string that represents the name of the drawable resource (e.g. `"alert_dark_frame"`). - A base64 encoded image string (e.g. `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QC..."`). On **iOS**, the icon can be one of the following: - The constant integer value of the [UIApplicationShortcutIcon.IconType](https://developer.apple.com/documentation/uikit/uiapplicationshortcuticon/icontype) enum (e.g. `6`). - A system symbol name (e.g. `star.fill`). - Name of the image asset from the asset catalogue. | | **`androidIcon`** | \`string | number\` | The icon to display on Android. The icon can be one of the following: - An integer value of the [R.drawable](https://developer.android.com/reference/android/R.drawable) enum (e.g. `17301547`). - A string that represents the name of the drawable resource (e.g. `"alert_dark_frame"`). - A base64 encoded image string (e.g. `"data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAASABIAAD/4QC..."`). | | **`iosIcon`** | \`string | number\` | The icon to display on iOS. The icon can be one of the following: - The constant integer value of the [UIApplicationShortcutIcon.IconType](https://developer.apple.com/documentation/uikit/uiapplicationshortcuticon/icontype) enum (e.g. `6`). - A system symbol name (e.g. `star.fill`). - Name of the image asset from the asset catalogue. | #### SetOptions | Prop | Type | Description | Since | | --------------- | ------------ | -------------------------- | ----- | | **`shortcuts`** | `Shortcut[]` | The list of app shortcuts. | 6.0.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ClickEvent | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------------------------------------------- | ----- | | **`shortcutId`** | `string` | The unique identifier of the app shortcut that was clicked. | 6.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-shortcuts/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-shortcuts/LICENSE). # @capawesome/capacitor-app-update Capacitor plugin that assists with native app updates. It supports retrieving app update information on **Android** and **iOS** and supports [in-app updates](https://developer.android.com/guide/playcore/in-app-updates) on **Android**. > Check out the [Capacitor Live Update](https://capawesome.io/plugins/live-update/) plugin to update your app remotely in real-time without submitting a new version to the app store. 🚀 ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for app updates. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📱 **App update information**: Retrieves current and available app versions. - ⚡ **Immediate in-app updates**: Performs immediate updates on Android. - 📲 **Flexible in-app updates**: Supports flexible update flows on Android. - 📈 **Update priority**: Supports update priority levels on Android. - 🏪 **App store navigation**: Opens the app store entry for manual updates. - 📊 **Update state tracking**: Monitors flexible update progress with listeners. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-app-update` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-app-update npx cap sync ``` ### Android Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidPlayAppUpdateVersion` version of `com.google.android.play:app-update` (default: `2.1.0`) - `$androidPlayServicesBaseVersion` version of `com.google.android.gms:play-services-base` (default: `18.9.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { Capacitor } from '@capacitor/core'; import { AppUpdate } from '@capawesome/capacitor-app-update'; const getCurrentAppVersion = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (Capacitor.getPlatform() === 'android') { return result.currentVersionCode; } else { return result.currentVersionName; } }; const getAvailableAppVersion = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (Capacitor.getPlatform() === 'android') { return result.availableVersionCode; } else { return result.availableVersionName; } }; const openAppStore = async () => { await AppUpdate.openAppStore(); }; const performImmediateUpdate = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (result.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (result.immediateUpdateAllowed) { await AppUpdate.performImmediateUpdate(); } }; const startFlexibleUpdate = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (result.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (result.flexibleUpdateAllowed) { await AppUpdate.startFlexibleUpdate(); } }; const completeFlexibleUpdate = async () => { await AppUpdate.completeFlexibleUpdate(); }; ``` ## API - [`getAppUpdateInfo(...)`](#getappupdateinfo) - [`openAppStore(...)`](#openappstore) - [`performImmediateUpdate()`](#performimmediateupdate) - [`startFlexibleUpdate()`](#startflexibleupdate) - [`completeFlexibleUpdate()`](#completeflexibleupdate) - [`addListener('onFlexibleUpdateStateChange', ...)`](#addlisteneronflexibleupdatestatechange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### getAppUpdateInfo(...) ``` getAppUpdateInfo(options?: GetAppUpdateInfoOptions | undefined) => Promise ``` Returns app update informations. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `GetAppUpdateInfoOptions` | **Returns:** `Promise` ______________________________________________________________________ ### openAppStore(...) ``` openAppStore(options?: OpenAppStoreOptions | undefined) => Promise ``` Opens the app store entry of the app in the Play Store (Android) or App Store (iOS). Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `OpenAppStoreOptions` | ______________________________________________________________________ ### performImmediateUpdate() ``` performImmediateUpdate() => Promise ``` Performs an immediate in-app update. Only available on Android. **Returns:** `Promise` ______________________________________________________________________ ### startFlexibleUpdate() ``` startFlexibleUpdate() => Promise ``` Starts a flexible in-app update. Only available on Android. **Returns:** `Promise` ______________________________________________________________________ ### completeFlexibleUpdate() ``` completeFlexibleUpdate() => Promise ``` Completes a flexible in-app update by restarting the app. Only available on Android. ______________________________________________________________________ ### addListener('onFlexibleUpdateStateChange', ...) ``` addListener(eventName: 'onFlexibleUpdateStateChange', listenerFunc: (state: FlexibleUpdateState) => void) => Promise ``` Adds a flexbile in-app update state change listener. Only available on Android. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'onFlexibleUpdateStateChange'` | | **`listenerFunc`** | `(state: FlexibleUpdateState) => void` | **Returns:** `Promise` ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. ______________________________________________________________________ ### Interfaces #### AppUpdateInfo | Prop | Type | Description | Since | | --------------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`currentVersionName`** | `string` | The current version name of the app. On **Android**, this is the `versionName` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file. Only available on Android and iOS. | 5.1.0 | | **`availableVersionName`** | `string` | The available version name of the update. On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file. Only available on iOS. | 5.1.0 | | **`currentVersionCode`** | `string` | The current version code of the app. On **Android**, this is the `versionCode` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file. Only available on Android and iOS. | 5.1.0 | | **`availableVersionCode`** | `string` | The available version code of the update. On **Android**, this is the `versionCode` from the `android/app/build.gradle` file. Only available on Android. | 5.1.0 | | **`availableVersionReleaseDate`** | `string` | Release date of the update in ISO 8601 (UTC) format. Only available on iOS. | | | **`updateAvailability`** | `AppUpdateAvailability` | The app update availability. Only available on Android and iOS. | | | **`updatePriority`** | `number` | In-app update priority for this update, as defined by the developer in the Google Play Developer API. Only available on Android. | | | **`immediateUpdateAllowed`** | `boolean` | `true` if an immediate update is allowed, otherwise `false`. Only available on Android. | | | **`flexibleUpdateAllowed`** | `boolean` | `true` if a flexible update is allowed, otherwise `false`. Only available on Android. | | | **`clientVersionStalenessDays`** | `number` | Number of days since the Google Play Store app on the user's device has learnt about an available update if an update is available or in progress. Only available on Android. | | | **`installStatus`** | `FlexibleUpdateInstallStatus` | Flexible in-app update install status. Only available on Android. | | | **`minimumOsVersion`** | `string` | The minimum version of the operating system required for the app to run in iOS. Only available on iOS. | | #### GetAppUpdateInfoOptions | Prop | Type | Description | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`country`** | `string` | The two-letter country code for the store you want to search. See http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 for a list of ISO Country Codes. Only available on iOS. | #### OpenAppStoreOptions | Prop | Type | Description | Since | | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`androidPackageName`** | `string` | The package name of the app to open in the Play Store. On **Android**, this is the application ID of your app (e.g. `com.example.app`). You can find the ID in the `android/app/build.gradle` file. If not provided, the current app's package name will be used. Only available on Android. | 7.2.0 | | **`appId`** | `string` | The app ID of the app to open in the App Store. On **iOS**, this is the Apple ID of your app (e.g. `123456789`). You can find the ID in the URL of your app store entry (e.g. `https://apps.apple.com/app/id123456789`). Only available on iOS. | 6.1.0 | #### AppUpdateResult | Prop | Type | | ---------- | --------------------- | | **`code`** | `AppUpdateResultCode` | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### FlexibleUpdateState | Prop | Type | Description | | -------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | **`installStatus`** | `FlexibleUpdateInstallStatus` | Flexible in-app update install status. | | **`bytesDownloaded`** | `number` | Returns the number of bytes downloaded so far. `undefined` if the install status is other than `DOWNLOADING`. | | **`totalBytesToDownload`** | `number` | Returns the total number of bytes to be downloaded for this update. `undefined` if the install status is other than `DOWNLOADING`. | ### Enums #### AppUpdateAvailability | Members | Value | | -------------------------- | ----- | | **`UNKNOWN`** | `0` | | **`UPDATE_NOT_AVAILABLE`** | `1` | | **`UPDATE_AVAILABLE`** | `2` | | **`UPDATE_IN_PROGRESS`** | `3` | #### FlexibleUpdateInstallStatus | Members | Value | | ----------------- | ----- | | **`UNKNOWN`** | `0` | | **`PENDING`** | `1` | | **`DOWNLOADING`** | `2` | | **`INSTALLING`** | `3` | | **`INSTALLED`** | `4` | | **`FAILED`** | `5` | | **`CANCELED`** | `6` | | **`DOWNLOADED`** | `11` | #### AppUpdateResultCode | Members | Value | Description | | ------------------- | ----- | ------------------------------------------------------------------------------------------- | | **`OK`** | `0` | The user has accepted the update. | | **`CANCELED`** | `1` | The user has denied or cancelled the update. | | **`FAILED`** | `2` | Some other error prevented either the user from providing consent or the update to proceed. | | **`NOT_AVAILABLE`** | `3` | No update available. | | **`NOT_ALLOWED`** | `4` | Update type not allowed. | | **`INFO_MISSING`** | `5` | App update info missing. You must call `getAppUpdateInfo()` before requesting an update. | ## Test with internal app-sharing The Android Developers documentation describes how to test [in-app updates](https://developer.android.com/guide/playcore/in-app-updates) using [internal app sharing](https://developer.android.com/guide/playcore/in-app-updates/test). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-update/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-update/LICENSE). ## Credits This plugin is based on the [Capacitor App Update](https://github.com/capawesome-team/capacitor-app-update) plugin. Thanks to everyone who contributed to the project! # @capawesome/capacitor-apple-sign-in Unofficial Capacitor plugin to sign-in with Apple.[1](#fn:1) ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for Apple Sign-In. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 🍎 **Native Sign-In**: Uses native AuthenticationServices on iOS. - 🌐 **WebView OAuth**: Implements Apple OAuth flow on Android without external dependencies. - 📧 **Scope support**: Request email and full name on all platforms. - 🔐 **Nonce & state**: Supports nonce for replay protection and state for CSRF protection. - 🪶 **Lightweight**: Just a single dependency and zero unnecessary bloat. - 🤝 **Compatibility**: Compatible with the [Google Sign-In](https://capawesome.io/plugins/google-sign-in) and [OAuth](https://capawesome.io/plugins/oauth) plugins. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.1.x | >=8.x.x | Active support | ## Guides - [How to Sign In with Apple using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-apple-using-capacitor/) ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-apple-sign-in` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-apple-sign-in npx cap sync ``` ### iOS Add the **Sign in with Apple** capability to your app in Xcode: 1. Open your app target in Xcode. 1. Go to the **Signing & Capabilities** tab. 1. Click **+ Capability** and add **Sign in with Apple**. ### Web The plugin loads the [Apple JS SDK](https://developer.apple.com/documentation/sign_in_with_apple/sign_in_with_apple_js) automatically. Make sure you have configured your Apple Service ID with the correct redirect URL and web domain in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list/serviceId). ## Configuration No configuration required for this plugin. ## Usage ``` import { AppleSignIn, SignInScope } from '@capawesome/capacitor-apple-sign-in'; const initialize = async () => { await AppleSignIn.initialize({ clientId: 'com.example.app.signin', }); }; const signIn = async () => { const result = await AppleSignIn.signIn({ scopes: [SignInScope.Email, SignInScope.FullName], redirectUrl: 'https://example.com/callback', nonce: 'random-nonce', state: 'random-state', }); return result; }; ``` ## API - [`initialize(...)`](#initialize) - [`signIn(...)`](#signin) - [Interfaces](#interfaces) - [Enums](#enums) ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the plugin. This method must be called before `signIn()` on **Android** and **Web**. Only available on Android and Web. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### signIn(...) ``` signIn(options?: SignInOptions | undefined) => Promise ``` Sign in with Apple. | Param | Type | | ------------- | --------------- | | **`options`** | `SignInOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### InitializeOptions | Prop | Type | Description | Since | | -------------- | -------- | ---------------------------------------- | ----- | | **`clientId`** | `string` | The Apple Service ID to use for sign-in. | 0.1.0 | #### SignInResult | Prop | Type | Description | Since | | ----------------------- | ---------------- | ------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | | **`authorizationCode`** | `string` | The authorization code. | 0.1.0 | | **`idToken`** | `string` | The ID token (JWT). | 0.1.0 | | **`user`** | `string` | The stable Apple user identifier. On **Android** and **Web**, this is decoded from the JWT `sub` claim. | 0.1.0 | | **`email`** | \`string | null\` | The user's email address. On **iOS**, this is only provided on the first sign-in. | | **`givenName`** | \`string | null\` | The user's given name. On **iOS**, this is only provided on the first sign-in. | | **`familyName`** | \`string | null\` | The user's family name. On **iOS**, this is only provided on the first sign-in. | | **`state`** | `string` | The state value from the sign-in request. Only available on Android and Web. | 0.1.0 | | **`realUserStatus`** | `RealUserStatus` | The real user status. Only available on iOS. | 0.1.0 | #### SignInOptions | Prop | Type | Description | Since | | ----------------- | --------------- | ----------------------------------------------------------------------------- | ----- | | **`redirectUrl`** | `string` | The OAuth redirect URL to use for sign-in. Only available on Android and Web. | 0.1.0 | | **`scopes`** | `SignInScope[]` | The scopes to request during sign-in. | 0.1.0 | | **`nonce`** | `string` | A nonce for replay protection. | 0.1.0 | | **`state`** | `string` | A state value for CSRF protection. Only available on Android and Web. | 0.1.0 | ### Enums #### RealUserStatus | Members | Value | Description | Since | | ----------------- | --------------- | ------------------------------------------------------------- | ----- | | **`LikelyReal`** | `'LIKELY_REAL'` | The user appears to be a real person. | 0.1.0 | | **`Unknown`** | `'UNKNOWN'` | The system can't determine whether the user is a real person. | 0.1.0 | | **`Unsupported`** | `'UNSUPPORTED'` | The real user status is not supported on this platform. | 0.1.0 | #### SignInScope | Members | Value | Description | Since | | -------------- | ------------- | --------------------------------- | ----- | | **`Email`** | `'EMAIL'` | Request the user's email address. | 0.1.0 | | **`FullName`** | `'FULL_NAME'` | Request the user's full name. | 0.1.0 | ## Security This plugin handles the OAuth flow and returns tokens to your app. To keep your integration secure, be aware of the following: - **Server-side token verification is required.** The `idToken` (JWT) is **not** verified client-side. Your backend **must** verify the JWT signature using [Apple's public keys](https://appleid.apple.com/auth/keys) before trusting any claims (e.g. `user`, `email`). Never use client-side token data for authorization decisions without server-side verification. - **Validate the `state` parameter.** The plugin passes the `state` value through to the result but does **not** validate it. Your app must compare the returned `state` against the value it originally sent to protect against CSRF attacks. - **Android uses a WebView-based OAuth flow.** Unlike iOS (which uses native `AuthenticationServices`), the Android implementation renders Apple's sign-in page in a WebView. Unlike a system browser flow, the WebView is controlled by the app and has access to page content. Ensure your `redirectUrl` uses HTTPS and points to a domain you control. ## FAQ ### What's the difference between this plugin and other Apple Sign-In plugins? This plugin is purpose-built for Apple Sign-In and focuses on providing a clean and modern API with the latest platform features. Here are some of the key differences: - **Cross-platform**: Supports Android, iOS, and Web. - **Lightweight**: No unnecessary dependencies. Just Apple Sign-In, nothing else. - **No deprecated APIs**: Uses the latest platform APIs (AuthenticationServices on iOS). - **Scope support**: Request email and full name on all platforms. - **Error codes**: Provides typed error codes for proper error handling. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/apple-sign-in/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/apple-sign-in/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Apple Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-asset-manager Capacitor plugin to access native asset files. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for asset management. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📂 **Asset copying**: Copy files or directories from app bundle to data directory. - 📄 **File listing**: List all files in asset directories. - 📖 **File reading**: Read asset files with Base64 or UTF-8 encoding. - 🔄 **Bundle access**: Direct access to native app bundle assets. - 🗂️ **Directory operations**: Handle both individual files and entire directories. - 📦 **Multiple encodings**: Support for Base64 and UTF-8 file reading. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-asset-manager` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome/capacitor-asset-manager npx cap sync ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { Directory, Filesystem } from '@capacitor/filesystem'; import { AssetManager, Encoding } from '@capawesome/capacitor-asset-manager'; const copy = async () => { const { uri } = await Filesystem.getUri({ directory: Directory.Cache, path: 'index.html' }); await AssetManager.copy({ from: 'public/index.html', to: uri }); }; const list = async () => { await AssetManager.list({ path: 'public' }); }; const read = async () => { const { data } = await AssetManager.read({ encoding: Encoding.Utf8, path: 'capacitor.config.json' }); return JSON.parse(data); }; ``` ## API - [`copy(...)`](#copy) - [`list(...)`](#list) - [`read(...)`](#read) - [Interfaces](#interfaces) - [Enums](#enums) ### copy(...) ``` copy(options: CopyOptions) => Promise ``` Copy a file or directory from the app bundle to the app's data directory. Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `CopyOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### list(...) ``` list(options?: ListOptions | undefined) => Promise ``` List files in a directory. Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `ListOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### read(...) ``` read(options: ReadOptions) => Promise ``` Read a file from the app bundle. **Attention**: Reading large files can cause out of memory (OOM) issues. It is therefore recommended to copy files to the app's data directory using the `copy` method and read them from there using the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch). Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `ReadOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### Interfaces #### CopyOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`from`** | `string` | The source path to copy from. | 7.0.0 | | **`to`** | `string` | The destination path to copy to. **Tip**: Generate this path using the [`getUri(...)`](https://capacitorjs.com/docs/apis/filesystem#geturi) method of the Capacitor Filesystem plugin. | 7.0.0 | #### ListResult | Prop | Type | Description | Since | | ----------- | ---------- | ----------------------------------- | ----- | | **`files`** | `string[]` | The list of files in the directory. | 7.0.0 | #### ListOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path to list files from. If not specified, the root path will be used. | 7.0.0 | #### ReadResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------ | ----- | | **`data`** | `string` | The content of the file. | 7.0.0 | #### ReadOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | ------------------------------------------ | ---------- | ----- | | **`encoding`** | `Encoding` | The encoding to use when reading the file. | `'base64'` | 7.0.0 | | **`path`** | `string` | The path to read the file from. | | 7.0.0 | ### Enums #### Encoding | Members | Value | Since | | ------------ | ---------- | ----- | | **`Base64`** | `'base64'` | 7.0.0 | | **`Utf8`** | `'utf8'` | 7.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/asset-manager/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/asset-manager/LICENSE). # @capawesome-team/capacitor-audio-player Capacitor plugin to play audio with background support. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for audio playback. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🌙 **Background Mode**: Play audio even when the app is in the background. - 🎵 **Audio Focus Management**: Automatically manages audio focus on Android to pause other audio sources during playback. - ⏯️ **Full Control**: Play, pause, resume, stop, seek, and adjust volume. - 🔂 **Loop Support**: Loop audio playback for continuous sound. - 🔊 **Volume Control**: Precise volume control from 0-100. - ⏩ **Playback Speed**: Adjustable playback rate with pitch preservation. - 🗂️ **Web Assets**: Support for web asset paths alongside file URIs and remote URLs. - 🤝 **Compatibility**: Compatible with the [Audio Recorder](https://capawesome.io/plugins/audio-recorder/), [Media Session](https://capawesome.io/plugins/media-session/), [Speech Recognition](https://capawesome.io/plugins/speech-recognition/) and [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/) plugins. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.2.x | 7.x.x | Deprecated | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-audio-player` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-audio-player npx cap sync ``` ### iOS #### Capabilities If you want to play audio in the background, ensure `Background Modes` capability is enabled with `Audio, AirPlay, and Picture in Picture` in your Xcode project. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. ## Usage ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; import { Capacitor } from '@capacitor/core'; import { Filesystem } from '@capacitor/filesystem'; const playFromWebAsset = async () => { await AudioPlayer.play({ src: '/assets/audio.mp3', loop: false, volume: 100, position: 0 }); }; const playFromNativeFile = async () => { const { uri } = await Filesystem.getUri({ directory: FilesystemDirectory.Documents, path: 'audio.mp3', }); await AudioPlayer.play({ uri, loop: false, volume: 100, position: 0 }); }; const playFromBlob = async () => { const assetUrl = 'https://www.example.com/audio.mp3'; const response = await fetch(assetUrl); const blob = await response.blob(); await AudioPlayer.play({ blob, loop: false, volume: 100, position: 0 }); }; const pause = async () => { await AudioPlayer.pause(); }; const resume = async () => { await AudioPlayer.resume(); }; const stop = async () => { await AudioPlayer.stop(); }; const seekTo = async () => { await AudioPlayer.seekTo({ position: 30_000 }); // Seek to 30 seconds }; const setVolume = async () => { await AudioPlayer.setVolume({ volume: 50 }); // Set volume to 50% }; const getCurrentPosition = async () => { const { position } = await AudioPlayer.getCurrentPosition(); console.log('Current position:', position); }; const getDuration = async () => { const { duration } = await AudioPlayer.getDuration(); console.log('Duration:', duration); }; const isPlaying = async () => { const { isPlaying } = await AudioPlayer.isPlaying(); console.log('Is playing:', isPlaying); }; ``` ## API - [`getCurrentPosition()`](#getcurrentposition) - [`getDuration()`](#getduration) - [`isPlaying()`](#isplaying) - [`pause()`](#pause) - [`play(...)`](#play) - [`resume()`](#resume) - [`seekTo(...)`](#seekto) - [`setRate(...)`](#setrate) - [`setVolume(...)`](#setvolume) - [`stop(...)`](#stop) - [`addListener('stop', ...)`](#addlistenerstop-) - [Interfaces](#interfaces) ### getCurrentPosition() ``` getCurrentPosition() => Promise ``` Get the current position of the audio playback in milliseconds. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getDuration() ``` getDuration() => Promise ``` Get the duration of the audio playback in milliseconds. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### isPlaying() ``` isPlaying() => Promise ``` Check whether the audio is currently playing. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### pause() ``` pause() => Promise ``` Pause the audio playback. **Since:** 0.0.1 ______________________________________________________________________ ### play(...) ``` play(options: PlayOptions) => Promise ``` Play the audio playback. | Param | Type | | ------------- | ------------- | | **`options`** | `PlayOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### resume() ``` resume() => Promise ``` Resume the audio playback. **Since:** 0.0.1 ______________________________________________________________________ ### seekTo(...) ``` seekTo(options: SeekToOptions) => Promise ``` Seek to a specific position in the audio playback. | Param | Type | | ------------- | --------------- | | **`options`** | `SeekToOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setRate(...) ``` setRate(options: SetRateOptions) => Promise ``` Set the playback rate for the audio playback. This only affects the current playback session and is not persisted. Only available on Android (SDK 23+), iOS and Web. | Param | Type | | ------------- | ---------------- | | **`options`** | `SetRateOptions` | **Since:** 8.2.0 ______________________________________________________________________ ### setVolume(...) ``` setVolume(options: SetVolumeOptions) => Promise ``` Set the volume level for the audio playback. This only affects the current playback session and is not persisted. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetVolumeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### stop(...) ``` stop(options?: StopOptions | undefined) => Promise ``` Stop the audio playback. | Param | Type | | ------------- | ------------- | | **`options`** | `StopOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### addListener('stop', ...) ``` addListener(eventName: 'stop', listenerFunc: () => void) => Promise ``` Called when the audio has stopped playing. | Param | Type | | ------------------ | ------------ | | **`eventName`** | `'stop'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### Interfaces #### GetCurrentPositionResult | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------- | ----- | | **`position`** | `number` | The current position of the audio playback in milliseconds. | 0.0.1 | #### GetDurationResult | Prop | Type | Description | Since | | -------------- | -------- | --------------------------------------------------- | ----- | | **`duration`** | `number` | The duration of the audio playback in milliseconds. | 0.0.1 | #### IsPlayingResult | Prop | Type | Description | Since | | --------------- | --------- | --------------------------------------- | ----- | | **`isPlaying`** | `boolean` | Whether the audio is currently playing. | 0.0.1 | #### PlayOptions | Prop | Type | Description | Default | Since | | -------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`blob`** | `Blob` | The audio file to play. If both `blob` and `src` are provided, `blob` takes priority. Only available on Web. | | 0.0.1 | | **`loop`** | `boolean` | Whether to loop the audio playback. | | 0.0.1 | | **`position`** | `number` | The position to start playback from (in milliseconds). | | 0.0.1 | | **`rate`** | `number` | The playback rate to use. Values between 0.5 and 2.0 are recommended. Other values may not be supported on all devices. Only available on Android (SDK 23+), iOS and Web. | `1.0` | 8.2.0 | | **`src`** | `string` | The path to the web asset file to play. If both `blob` and `src` are provided, `blob` takes priority. If both `uri` and `src` are provided, `uri` takes priority. Both web assets and remote URLs are supported on all platforms. | | 0.1.2 | | **`uri`** | `string` | The URI or path of the audio file to play. If both `uri` and `src` are provided, `uri` takes priority. Only available on Android and iOS. | | 0.0.1 | | **`volume`** | `number` | The volume level to set (0-100). | `100` | 0.0.1 | #### SeekToOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------ | ----- | | **`position`** | `number` | The position to seek to (in milliseconds). | 0.0.1 | #### SetRateOptions | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ----- | | **`rate`** | `number` | The playback rate to set. Values between 0.5 and 2.0 are recommended. Other values may not be supported on all devices. | 8.2.0 | #### SetVolumeOptions | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------- | ----- | | **`volume`** | `number` | The volume level to set (0-100). | 0.0.1 | #### StopOptions | Prop | Type | Description | Default | Since | | ---------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deactivateAudioSession`** | `boolean` | Whether to deactivate the audio session when stopping playback. Set to `false` if you intend to call `play()` again shortly after stopping, to avoid `CoreMediaErrorDomain -16042` errors on iOS or audio focus issues on Android. Only available on Android and iOS. | `true` | 8.3.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ## Troubleshooting ##### `CoreMediaErrorDomain -16042` error on iOS when calling `play()` after `stop()` When `stop()` is called, the audio session is deactivated by default. If `play()` is called shortly after, `AVAudioSession.setActive(true)` can fail with `CoreMediaErrorDomain -16042`, breaking all subsequent playback. To avoid this, set `deactivateAudioSession` to `false` in the `stop()` options: ``` await AudioPlayer.stop({ deactivateAudioSession: false }); ``` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-player/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-player/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-player/LICENSE). # @capawesome-team/capacitor-audio-recorder Capacitor plugin for seamless audio recording using the device's microphone. Supports Android, iOS, and Web with advanced features and high performance. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for audio recording. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - ⏯️ **Full Control**: Start, pause, resume, cancel and stop recording. - 🚀 **Performance**: Record long audio sessions without any performance issues. - 🔑 **Permissions**: Check and request microphone permissions. - 🔊 **Events**: Listen for events like `recordingError`, `recordingPaused` or `recordingStopped`. - 🌙 **Background Mode**: Record audio even when the app is in the background. - 🤝 **Compatibility**: Compatible with the [Audio Player](https://capawesome.io/plugins/audio-player/), [Speech Recognition](https://capawesome.io/plugins/speech-recognition/) and [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/) plugins. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Guides - [Announcing the Capacitor Audio Recorder Plugin](https://capawesome.io/blog/announcing-the-capacitor-audio-recorder-plugin/) - [Exploring the Capacitor Audio Recorder API](https://capawesome.io/blog/exploring-the-capacitor-audio-recorder-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-audio-recorder` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-audio-recorder npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Capabilities If you want to record audio in the background, ensure `Background Modes` capability is enabled with `Audio, AirPlay, and Picture in Picture` in your Xcode project. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. #### Privacy Descriptions Add the `NSMicrophoneUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the user's contacts: ``` NSMicrophoneUsageDescription We need access to your microphone to record audio. ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { AudioRecorder, AudioSessionCategoryOption, AudioSessionMode } from '@capawesome-team/capacitor-audio-recorder'; import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; import { Directory, Filesystem } from '@capacitor/filesystem'; const startRecording = async () => { await AudioRecorder.startRecording({ audioSessionCategoryOptions: [AudioSessionCategoryOption.DuckOthers], audioSessionMode: AudioSessionMode.Measurement, bitRate: 192000, sampleRate: 44100 }); }; const startRecordingToCustomLocation = async () => { const { uri } = await Filesystem.getUri({ directory: Directory.Data, path: 'recordings/my-recording.aac' }); await AudioRecorder.startRecording({ uri }); }; const stopRecording = async () => { // Stop recording and get the audio blob or URI const { blob, uri } = await AudioRecorder.stopRecording(); // Play the audio if (blob) { // Only available on Web await AudioPlayer.play({ blob }); } else if (uri) { // Only available on Android and iOS await AudioPlayer.play({ uri }); } }; const pauseRecording = async () => { await AudioRecorder.pauseRecording(); }; const resumeRecording = async () => { await AudioRecorder.resumeRecording(); }; const cancelRecording = async () => { await AudioRecorder.cancelRecording(); }; const getRecordingStatus = async () => { const { status } = await AudioRecorder.getRecordingStatus(); console.log('Recording status:', status); }; const checkPermissions = async () => { const { recordAudio } = await AudioRecorder.checkPermissions(); console.log('Record audio permission:', recordAudio); }; const requestPermissions = async () => { const { recordAudio } = await AudioRecorder.requestPermissions(); console.log('Record audio permission:', recordAudio); }; const addRecordingErrorListener = async () => { await AudioRecorder.addListener('recordingError', (event) => { console.error('Recording error:', event.message); }); }; const addRecordingPausedListener = async () => { await AudioRecorder.addListener('recordingPaused', () => { console.log('Recording paused'); }); }; const addRecordingStoppedListener = async () => { await AudioRecorder.addListener('recordingStopped', (event) => { console.log('Recording stopped:', event.uri); }); }; ``` ## API - [`cancelRecording()`](#cancelrecording) - [`getRecordingStatus()`](#getrecordingstatus) - [`pauseRecording()`](#pauserecording) - [`resumeRecording()`](#resumerecording) - [`startRecording(...)`](#startrecording) - [`stopRecording()`](#stoprecording) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('recordingError', ...)`](#addlistenerrecordingerror-) - [`addListener('recordingPaused', ...)`](#addlistenerrecordingpaused-) - [`addListener('recordingStopped', ...)`](#addlistenerrecordingstopped-) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### cancelRecording() ``` cancelRecording() => Promise ``` Cancel the recording. **Since:** 7.0.0 ______________________________________________________________________ ### getRecordingStatus() ``` getRecordingStatus() => Promise ``` Check if the device supports audio recording. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### pauseRecording() ``` pauseRecording() => Promise ``` Pause the recording. This method is only available on Android (SDK 24+), iOS and Web. **Since:** 7.0.0 ______________________________________________________________________ ### resumeRecording() ``` resumeRecording() => Promise ``` Resume the recording. This method is only available on Android (SDK 24+), iOS and Web. **Since:** 7.0.0 ______________________________________________________________________ ### startRecording(...) ``` startRecording(options?: StartRecordingOptions | undefined) => Promise ``` Start recording audio in AAC format. | Param | Type | | ------------- | ----------------------- | | **`options`** | `StartRecordingOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### stopRecording() ``` stopRecording() => Promise ``` Stop recording audio. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for audio recording. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permissions for audio recording. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### addListener('recordingError', ...) ``` addListener(eventName: 'recordingError', listenerFunc: (event: RecordingErrorEvent) => void) => Promise ``` Called when an error occurs during recording. The recording will be cancelled. Only available on iOS. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'recordingError'` | | **`listenerFunc`** | `(event: RecordingErrorEvent) => void` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### addListener('recordingPaused', ...) ``` addListener(eventName: 'recordingPaused', listenerFunc: () => void) => Promise ``` Called when the recording is paused (e.g. when the recording is interrupted by a phone call). | Param | Type | | ------------------ | ------------------- | | **`eventName`** | `'recordingPaused'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 7.1.0 ______________________________________________________________________ ### addListener('recordingStopped', ...) ``` addListener(eventName: 'recordingStopped', listenerFunc: (event: RecordingStoppedEvent) => void) => Promise ``` Called when the recording is stopped. **Note**: This will not be called if the recording is cancelled or paused or if an error occurs. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'recordingStopped'` | | **`listenerFunc`** | `(event: RecordingStoppedEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### GetRecordingStatusResult | Prop | Type | Description | Default | Since | | ------------ | ----------------- | ----------------------------- | -------------------------- | ----- | | **`status`** | `RecordingStatus` | The current recording status. | `RecordingStatus.Inactive` | 7.0.0 | #### StartRecordingOptions | Prop | Type | Description | Default | Since | | --------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----- | | **`audioSessionCategoryOptions`** | `AudioSessionCategoryOption[]` | The audio session category options for recording. Only available on iOS. | `['duckOthers']` | 7.5.0 | | **`audioSessionMode`** | `AudioSessionMode` | The audio session mode for recording. Only available on iOS. | `AudioSessionMode.Default` | 7.4.0 | | **`bitRate`** | `number` | The audio bitrate in bytes per second. This option is only available on Android and iOS. | `192000` | 7.2.0 | | **`sampleRate`** | `number` | The audio sample rate in Hz. This option is only available on Android and iOS. | `44100` | 7.1.0 | | **`uri`** | `string` | The URI where the recorded audio should be saved. If not provided, the recording is saved to a temporary location in the cache directory. **Tip:** Generate this path using the `getUri(...)` method of the Capacitor Filesystem plugin. Only available on Android and iOS. | | 8.1.0 | #### StopRecordingResult | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The recorded audio as a Blob. Only available on Web. | 7.0.0 | | **`duration`** | `number` | The duration of the recorded audio in milliseconds. | 7.1.0 | | **`uri`** | `string` | The URI of the recorded audio. Only available on Android and iOS. | 7.0.0 | #### PermissionStatus | Prop | Type | Description | Since | | ----------------- | ----------------- | ----------------------------------------- | ----- | | **`recordAudio`** | `PermissionState` | The permission state for audio recording. | 7.0.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### RecordingErrorEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 7.0.0 | #### RecordingStoppedEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The recorded audio as a Blob. Only available on Web. | 7.0.0 | | **`duration`** | `number` | The duration of the recorded audio in milliseconds. | 7.1.0 | | **`uri`** | `string` | The URI of the recorded audio. Only available on Android and iOS. | 7.0.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### RecordingStatus | Members | Value | Description | Since | | --------------- | ------------- | -------------------------- | ----- | | **`Inactive`** | `'INACTIVE'` | The recording is inactive. | 7.0.0 | | **`Recording`** | `'RECORDING'` | The recording is active. | 7.0.0 | | **`Paused`** | `'PAUSED'` | The recording is paused. | 7.0.0 | #### AudioSessionCategoryOption | Members | Value | Description | Since | | ------------------------------------------ | ---------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----- | | **`AllowAirPlay`** | `'ALLOW_AIR_PLAY'` | Option to stream audio from this session to AirPlay devices. | 7.5.0 | | **`AllowBluetooth`** | `'ALLOW_BLUETOOTH'` | Option to make Bluetooth hands-free devices appears as available input routes. | 7.5.0 | | **`AllowBluetoothA2DP`** | `'ALLOW_BLUETOOTH_A2DP'` | Option to stream audio from this session to Bluetooth devices that support the Advanced Audio Distribution Profile (A2DP). | 7.5.0 | | **`DefaultToSpeaker`** | `'DEFAULT_TO_SPEAKER'` | Option to make audio from this session to default to the built-in speaker instead of the receiver. | 7.5.0 | | **`DuckOthers`** | `'DUCK_OTHERS'` | Option to reduce the audio volume of other active sessions when audio from this session is in play. | 7.5.0 | | **`InterruptSpokenAudioAndMixWithOthers`** | `'INTERRUPT_SPOKEN_AUDIO_AND_MIX_WITH_OTHERS'` | Option to pause spoken audio of other sessions when audio from this session is in play. | 7.5.0 | | **`MixWithOthers`** | `'MIX_WITH_OTHERS'` | Option to mix audio with audio from other active sessions in other apps. | 7.5.0 | | **`overrideMutedMicrophoneInterruption`** | `'OVERRIDE_MUTED_MICROPHONE_INTERRUPTION'` | Option that indicates if the system interrupts the audio session when it mutes the built-in microphone. | 7.5.0 | #### AudioSessionMode | Members | Value | Description | Since | | -------------------- | ------------------- | ----------------------------------------------------------------------------- | ----- | | **`Default`** | `'DEFAULT'` | Default mode that doesn't enable additional audio session features. | 7.4.0 | | **`GameChat`** | `'GAME_CHAT'` | Mode for chat communication over VoIP or internet, optimized for low latency. | 7.4.0 | | **`Measurement`** | `'MEASUREMENT'` | Mode for high-quality measurement recordings with maximum dynamic range. | 7.4.0 | | **`SpokenAudio`** | `'SPOKEN_AUDIO'` | Mode for speech recording and transcription with optimized voice processing. | 7.4.0 | | **`VideoChat`** | `'VIDEO_CHAT'` | Mode for two-way video chat communications. | 7.4.0 | | **`VideoRecording`** | `'VIDEO_RECORDING'` | Mode for recording video content with high-quality audio. | 7.4.0 | | **`VoiceChat`** | `'VOICE_CHAT'` | Mode for voice chat communications. | 7.4.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-recorder/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-recorder/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-recorder/LICENSE). # @capawesome/capacitor-background-task Capacitor plugin for running background tasks. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-background-task` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome/capacitor-background-task npx cap sync ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { App } from '@capacitor/app'; import { BackgroundTask } from '@capawesome/capacitor-background-task'; App.addListener('appStateChange', async ({ isActive }) => { if (isActive) { return; } // The app state has been changed to inactive. // Start the background task by calling `beforeExit`. const taskId = await BackgroundTask.beforeExit(async () => { // Run your code... // Finish the background task as soon as everything is done. BackgroundTask.finish({ taskId }); }); }); ``` ## API - [`beforeExit(...)`](#beforeexit) - [`finish(...)`](#finish) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### beforeExit(...) ``` beforeExit(cb: () => void) => Promise ``` Call this method when the app moves to the background. It allows the app to continue running a task in the background. On **iOS** this method should be finished in less than 30 seconds. Only available on Android and iOS. | Param | Type | | -------- | ------------ | | **`cb`** | `() => void` | **Returns:** `Promise` ______________________________________________________________________ ### finish(...) ``` finish(options: FinishOptions) => void ``` Finish the current background task. The OS will put the app to sleep. Only available on Android and iOS. | Param | Type | | ------------- | --------------- | | **`options`** | `FinishOptions` | ______________________________________________________________________ ### Interfaces #### FinishOptions | Prop | Type | | ------------ | ------------ | | **`taskId`** | `CallbackID` | ### Type Aliases #### CallbackID `string` ## Quirks ### iOS On **iOS** the [UIKit framework](https://developer.apple.com/documentation/uikit) is used. Read more about the implementation and any limitations [here](https://developer.apple.com/documentation/uikit/app_and_environment/scenes/preparing_your_ui_to_run_in_the_background/extending_your_app_s_background_execution_time). ### Android There is currently no ready implementation on **Android**. It's planned to add the support in the near future. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/background-task/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/background-task/LICENSE). ## Credits This plugin is based on the [Capacitor Background Task](https://github.com/capawesome-team/capacitor-background-task) plugin. Thanks to everyone who contributed to the project! # @capawesome/capacitor-badge Capacitor plugin to access and update the badge number of the app icon. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for app icon badge management. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web (PWA). - 🔢 **Badge management**: Get, set, increase, decrease, and clear badge counts. - 💾 **Persistent badges**: Badge count persists after reboot or app restart. - 🔄 **Auto-clear option**: Automatically reset counter when resuming the app. - 🔐 **Permission handling**: Check and request badge display permissions. - ⚙️ **Configurable**: Customize persistence and auto-clear behaviors. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-badge` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-badge npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$shortcutBadgerVersion` version of `me.leolin:ShortcutBadger` (default: `1.1.22`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Privacy manifest Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Privacy Manifest](https://capacitorjs.com/docs/ios/privacy-manifest) (usually `ios/App/PrivacyInfo.xcprivacy`): ``` NSPrivacyAccessedAPITypes NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons CA92.1 ``` ## Configuration These configuration values are available: | Prop | Type | Description | Default | | --------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | **`persist`** | `boolean` | Configure whether the plugin should restore the counter after a reboot or app restart. Only available on Android and iOS. | `true` | | **`autoClear`** | `boolean` | Configure whether the plugin should reset the counter after resuming the application. On **iOS**, this will also clear all notifications. Only available on Android and iOS. | `false` | ### Examples In `capacitor.config.json`: ``` { "plugins": { "Badge": { "persist": true, "autoClear": false } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { Badge: { persist: true, autoClear: false, }, }, }; export default config; ``` ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { Badge } from '@capawesome/capacitor-badge'; const get = async () => { const result = await Badge.get(); return result.count; }; const set = async (count: number) => { await Badge.set({ count }); }; const increase = async () => { await Badge.increase(); }; const decrease = async () => { await Badge.decrease(); }; const clear = async () => { await Badge.clear(); }; const isSupported = async () => { const result = await Badge.isSupported(); return result.isSupported; }; const checkPermissions = async () => { const result = await Badge.checkPermissions(); }; const requestPermissions = async () => { const result = await Badge.requestPermissions(); }; ``` ## API - [`get()`](#get) - [`set(...)`](#set) - [`increase()`](#increase) - [`decrease()`](#decrease) - [`clear()`](#clear) - [`isSupported()`](#issupported) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### get() ``` get() => Promise ``` Get the badge count. The badge count won't be lost after a reboot or app restart. Default: `0`. **Returns:** `Promise` ______________________________________________________________________ ### set(...) ``` set(options: SetBadgeOptions) => Promise ``` Set the badge count. | Param | Type | | ------------- | ----------------- | | **`options`** | `SetBadgeOptions` | ______________________________________________________________________ ### increase() ``` increase() => Promise ``` Increase the badge count. ______________________________________________________________________ ### decrease() ``` decrease() => Promise ``` Decrease the badge count. ______________________________________________________________________ ### clear() ``` clear() => Promise ``` Clear the badge count. On **iOS**, this will remove the badge and also clear all notifications. ______________________________________________________________________ ### isSupported() ``` isSupported() => Promise ``` Check if the badge count is supported. **Returns:** `Promise` ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permission to display badge. **Returns:** `Promise` ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to display badge. **Returns:** `Promise` ______________________________________________________________________ ### Interfaces #### GetBadgeResult | Prop | Type | | ----------- | -------- | | **`count`** | `number` | #### SetBadgeOptions | Prop | Type | Description | | ----------- | -------- | -------------------------------------------------------------------------------------------------------------------- | | **`count`** | `number` | The badge count to set. On **iOS**, setting the count to `0` will remove the badge and also clear all notifications. | #### IsSupportedResult | Prop | Type | | ----------------- | --------- | | **`isSupported`** | `boolean` | #### PermissionStatus | Prop | Type | Description | | ------------- | ----------------- | ----------------------------------------- | | **`display`** | `PermissionState` | Permission state of displaying the badge. | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ## Quirks On **Android** not all launchers support badges. This plugin uses [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger). All supported launchers are listed [there](https://github.com/leolin310148/ShortcutBadger#supported-launchers). On **Web**, the app must run as an installed PWA (in the taskbar or dock). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/badge/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/badge/LICENSE). ## Credits This plugin is based on the [Capacitor Badge](https://github.com/capawesome-team/capacitor-badge) plugin. Thanks to everyone who contributed to the project! # @capawesome-team/capacitor-barometer Capacitor plugin to obtain the static air pressure, which is measured in hectopascals (hPa). ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for barometer measurements. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📏 **Precise Measurements**: Get accurate air pressure readings in hectopascals (hPa). - 🔄 **Real-time Updates**: Continuously monitor barometer changes with event listeners. - 🔍 **Device Detection**: Check if barometer sensor is available on the device. - 🔑 **Permissions**: Check and request barometer sensor permissions. - 📊 **Additional Data**: Get relative altitude measurements. - ⏱️ **Timestamps**: Each measurement includes precise timing information. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Guides - [Announcing the Capacitor Barometer Plugin](https://capawesome.io/blog/announcing-the-capacitor-barometer-plugin/) - [Exploring the Capacitor Barometer API](https://capawesome.io/blog/exploring-the-capacitor-barometer-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-barometer` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-barometer npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSMotionUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the user's contacts: ``` NSMotionUsageDescription The app needs to access the motion activity. ``` ## Usage ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const getMeasurement = async () => { const { measurement } = await Barometer.getMeasurement(); console.log('Pressure:', measurement.pressure, 'hPa'); console.log('Relative Altitude:', measurement.relativeAltitude, 'm'); console.log('Timestamp:', new Date(measurement.timestamp)); }; const isAvailable = async () => { const result = await Barometer.isAvailable(); console.log('Barometer is available:', result.isAvailable); }; const startMeasurementUpdates = async () => { Barometer.addListener('measurement', (event) => { console.log('New measurement:', event); }); await Barometer.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Barometer.stopMeasurementUpdates(); Barometer.removeAllListeners(); }; const checkPermissions = async () => { const status = await Barometer.checkPermissions(); console.log('Barometer permission status:', status.barometer); }; const requestPermissions = async () => { const status = await Barometer.requestPermissions(); console.log('Barometer permission status after request:', status.barometer); }; ``` ## API - [`getMeasurement()`](#getmeasurement) - [`isAvailable()`](#isavailable) - [`startMeasurementUpdates()`](#startmeasurementupdates) - [`stopMeasurementUpdates()`](#stopmeasurementupdates) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('measurement', ...)`](#addlistenermeasurement-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getMeasurement() ``` getMeasurement() => Promise ``` Get the latest measurement. This method returns the most recent measurement from the barometer sensor. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the barometer sensor is available on the device. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### startMeasurementUpdates() ``` startMeasurementUpdates() => Promise ``` Starts emitting `measurement` events. Only available on Android and iOS. **Since:** 7.0.0 ______________________________________________________________________ ### stopMeasurementUpdates() ``` stopMeasurementUpdates() => Promise ``` Stops emitting `measurement` events. Only available on Android and iOS. **Since:** 7.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check if the app has permission to access the barometer sensor. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to access the barometer sensor. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### addListener('measurement', ...) ``` addListener(eventName: 'measurement', listenerFunc: (event: MeasurementEvent) => void) => Promise ``` Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'measurement'` | | **`listenerFunc`** | `(event: Measurement) => void` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 7.0.0 ______________________________________________________________________ ### Interfaces #### Measurement | Prop | Type | Description | Since | | ---------------------- | -------- | ----------------------------------------------------------------- | ----- | | **`pressure`** | `number` | The pressure in hPa (hectopascal). | 7.0.0 | | **`relativeAltitude`** | `number` | The relative altitude in meters. Only available on iOS. | 7.0.0 | | **`timestamp`** | `number` | The timestamp of the measurement in milliseconds since the epoch. | 7.0.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------------ | ----- | | **`isAvailable`** | `boolean` | Indicates whether the barometer sensor is available on the device. | 7.0.0 | #### PermissionStatus | Prop | Type | Description | Since | | --------------- | -------------------------- | ----------------------------------- | ----- | | **`barometer`** | `BarometerPermissionState` | The permission status of barometer. | 7.0.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GetMeasurementResult `Measurement` #### BarometerPermissionState `PermissionState | 'limited'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### MeasurementEvent `Measurement` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barometer/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barometer/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barometer/LICENSE). # @capawesome-team/capacitor-biometrics Capacitor plugin to request biometric authentication, such as using face recognition or fingerprint recognition. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for biometric authentication. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 👁️ **Fingerprint, Face and Iris**: Supports fingerprint, face and iris recognition. - 🔑 **Device Credential**: Optionally allow the user to authenticate using their device's credential (e.g., PIN, password) if biometric authentication is not available or fails. - 🚨 **Error Codes**: Provides detailed error codes for better error handling. - ✨ **Customizable**: Customize the authentication prompt with a title, subtitle, and button text. - 🤝 **Compatibility**: Compatible with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/) plugin. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.5.x | >=8.x.x | Active support | ## Guides - [Announcing the Capacitor Biometrics Plugin](https://capawesome.io/blog/announcing-the-capacitor-biometrics-plugin/) - [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/) - [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/) ## Videos - [How to use Biometrics in a Capacitor Mobile App](https://youtu.be/ixUvTX6n7x8?si=27_X3KiLGAkI9-vJ) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-biometrics` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-biometrics npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxBiometricVersion` version of `androidx.biometric:biometric` (default: `1.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Privacy Descriptions Add the `NSFaceIDUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the biometric authentication: ``` NSFaceIDUsageDescription This app uses Face ID for authentication. ``` ## Usage ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { // If the user successfully authenticates, the promise resolves. // If the user cancels the authentication or if an error occurs, the promise rejects. try { await Biometrics.authenticate({ title: 'Authentication Required', subtitle: 'Please authenticate to continue', cancelButtonText: 'Cancel', iosFallbackButtonText: 'Use Passcode', allowDeviceCredential: true, }); } catch (error) { if (error.code === ErrorCode.USER_CANCELED) { console.log('User canceled the authentication.'); } else if (error.code === ErrorCode.NOT_ENROLLED) { console.log('No biometric authentication enrolled.'); } else if (error.code === ErrorCode.NOT_AVAILABLE) { console.log('Biometric authentication not available.'); } else { console.log('Another error occurred:', error); } } }; const cancelAuthentication = async () => { await Biometrics.cancelAuthentication(); }; const enroll = async () => { await Biometrics.enroll(); }; const getBiometricStrengthLevel = async () => { const { strengthLevel } = await Biometrics.getBiometricStrengthLevel(); return strengthLevel; }; const hasDeviceCredential = async () => { const { hasDeviceCredential } = await Biometrics.hasDeviceCredential(); return hasDeviceCredential; }; const isAvailable = async () => { const { isAvailable } = await Biometrics.isAvailable(); return isAvailable; }; const isEnrolled = async () => { const { isEnrolled } = await Biometrics.isEnrolled(); return isEnrolled; }; ``` ## API - [`authenticate(...)`](#authenticate) - [`cancelAuthentication()`](#cancelauthentication) - [`enroll()`](#enroll) - [`getAuthenticationType()`](#getauthenticationtype) - [`getBiometricStrengthLevel()`](#getbiometricstrengthlevel) - [`getBiometricType()`](#getbiometrictype) - [`hasDeviceCredential()`](#hasdevicecredential) - [`isAvailable()`](#isavailable) - [`isEnrolled()`](#isenrolled) - [Interfaces](#interfaces) - [Enums](#enums) ### authenticate(...) ``` authenticate(options?: AuthenticateOptions | undefined) => Promise ``` Authenticates the user locally using the device's biometric authentication. This method will show a prompt to the user asking them to authenticate using their biometrics (e.g., fingerprint, face recognition). If the user successfully authenticates, the promise resolves. If the user cancels the authentication or if an error occurs, the promise rejects. It is recommended to check if biometrics is available and enrolled using the `isAvailable()` and `isEnrolled()` methods before calling this method. On **iOS**, the first time the user is prompted to authenticate, they will be asked for permission to use biometrics. If the user denies permission, the promise will reject with an error. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `AuthenticateOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### cancelAuthentication() ``` cancelAuthentication() => Promise ``` Cancel the ongoing authentication session and dismisses the prompt. This method is only available on Android (SDK 29+) and iOS. **Since:** 7.0.0 ______________________________________________________________________ ### enroll() ``` enroll() => Promise ``` Prompt the user to enroll their biometrics. This method is only available on Android. **Since:** 7.0.0 ______________________________________________________________________ ### getAuthenticationType() ``` getAuthenticationType() => Promise ``` Check whether the user authenticated using a device credential or a biometric credential. Only available on Android. **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### getBiometricStrengthLevel() ``` getBiometricStrengthLevel() => Promise ``` Returns the biometric strength level of the device. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getBiometricType() ``` getBiometricType() => Promise ``` Returns the type of biometric authentication available on the device. If multiple biometric types are available, returns the highest priority type based on the following order: Face > Iris > Fingerprint. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.5.1 ______________________________________________________________________ ### hasDeviceCredential() ``` hasDeviceCredential() => Promise ``` Check whether or not the device's credential (e.g., PIN, password) has been set up by the current user of the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether or not biometrics is available on the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isEnrolled() ``` isEnrolled() => Promise ``` Check whether or not biometrics is available on the device and has been configured by the current user of the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### AuthenticateOptions | Prop | Type | Description | Default | Since | | ------------------------------ | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------- | ----- | | **`allowDeviceCredential`** | `boolean` | Whether or not to allow the user to authenticate using their device's credential (e.g., PIN, password) if biometric authentication is not available or fails. You can check if the device's credential is set up using the `hasDeviceCredential()` method. | `false` | 0.1.0 | | **`androidBiometricStrength`** | `BiometricStrength` | The Android biometric strength to use for authentication. You can check the supported biometric strength level of the device using the `getBiometricStrengthLevel()` method. **Note**: On Android API Level 28 and 29, this will always be set to `AndroidBiometricStrength.WEAK` regardless of the value passed in if `allowDeviceCredential` is set to `true`. This is a known limitation. Only available on Android. | `AndroidBiometricStrength.WEAK` | 0.1.0 | | **`cancelButtonText`** | `string` | The negative button text of the authentication prompt. | | 0.1.0 | | **`iosFallbackButtonText`** | `string` | The fallback button text of the authentication prompt. Only available on iOS. | | 0.1.0 | | **`subtitle`** | `string` | The subtitle of the authentication prompt. | | 0.1.0 | | **`title`** | `string` | The title of the authentication prompt. | | 0.1.0 | #### GetAuthenticationTypeResult | Prop | Type | Description | Since | | ------------------------ | -------------------- | -------------------------------------------- | ----- | | **`authenticationType`** | `AuthenticationType` | The type of authentication used by the user. | 0.2.0 | #### GetBiometricStrengthLevelResult | Prop | Type | Description | Since | | ------------------- | ------------------- | ----------------------------------------------------- | ----- | | **`strengthLevel`** | `BiometricStrength` | The supported biometric strength level of the device. | 0.1.0 | #### GetBiometricTypeResult | Prop | Type | Description | Since | | ------------------- | --------------- | ------------------------------------------------------------- | ----- | | **`biometricType`** | `BiometricType` | The type of biometric authentication available on the device. | 0.5.1 | #### HasDeviceCredentialResult | Prop | Type | Description | Since | | ------------------------- | --------- | --------------------------------------------------------------------------------------------------------------- | ----- | | **`hasDeviceCredential`** | `boolean` | Whether or not the device's credential (e.g., PIN, password) has been set up by the current user of the device. | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not biometric authentication is available on the device. | 0.1.0 | #### IsEnrolledResult | Prop | Type | Description | Since | | ---------------- | --------- | --------------------------------------------------------------------------------------------------------------- | ----- | | **`isEnrolled`** | `boolean` | Whether or not biometrics is supported by the device and has been configured by the current user of the device. | 0.1.0 | ### Enums #### BiometricStrength | Members | Value | Since | | ------------ | ---------- | ----- | | **`Strong`** | `'STRONG'` | 0.1.0 | | **`Weak`** | `'WEAK'` | 0.1.0 | #### AuthenticationType | Members | Value | Description | Since | | ---------------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Biometric`** | `'BIOMETRIC'` | The user authenticated using a biometric credential. | 0.2.0 | | **`DeviceCredential`** | `'DEVICE_CREDENTIAL'` | The user authenticated using a device credential (e.g., PIN, password). | 0.2.0 | | **`Unknown`** | `'UNKNOWN'` | The user authenticated via an unknown method. This value may be returned on older Android versions due to partial incompatibility with a newer API. | 0.2.0 | #### BiometricType | Members | Value | Description | Since | | ----------------- | --------------- | --------------------------------------------------------------------- | ----- | | **`Face`** | `'FACE'` | Face authentication (Face ID on iOS, face recognition on Android). | 0.5.1 | | **`Fingerprint`** | `'FINGERPRINT'` | Fingerprint authentication (Touch ID on iOS, fingerprint on Android). | 0.5.1 | | **`Iris`** | `'IRIS'` | Iris authentication. Only available on Android. | 0.5.1 | | **`None`** | `'NONE'` | No biometric authentication available on the device. | 0.5.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/biometrics/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/biometrics/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/biometrics/LICENSE). # @capawesome-team/capacitor-bluetooth-low-energy Capacitor plugin for Bluetooth Low Energy (BLE) communication in the central and peripheral role with advanced features like headless tasks, foreground services, and more. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for Bluetooth Low Energy communication. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🔄 **Central Role**: Communicate with BLE peripherals as a central device. - 📳 **Peripheral Role**: Act as a BLE peripheral to communicate with other central devices. - 🦾 **Headless Task**: Add custom native code for specific events. - 🌙 **Foreground Service**: Keep the connection alive even when the app is in the background. - 🔌 **Auto Reconnection**: Automatically reconnect to peripherals when the connection is lost. - ⏳ **Command Queue**: Queue up incoming commands to prevent operation failures. - 📱 **Multiple Devices**: Connect to multiple devices at the same time. - 🛠️ **Utils**: Utility functions to make your life easier. - ⚔️ **Battle-Tested**: Used in more than 300 projects. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Testimonials > We migrated PadelBand, a sports tech app, from the Capacitor Community BLE plugin to this one and the difference is remarkable. The reliable background support and the ability to run custom native code with headless tasks made all the difference for our use case. Highly recommended! -- [PadelBand](https://padel-band.com) Development Team ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-heart-rate-monitor-app). | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor Bluetooth Low Energy Plugin](https://capawesome.io/blog/announcing-the-capacitor-bluetooth-low-energy-plugin/) - [How to Build a Heart Rate Monitor with Capacitor](https://capawesome.io/blog/how-to-build-a-heart-rate-monitor-with-capacitor/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-bluetooth-low-energy` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-bluetooth-low-energy npx cap sync ``` ### Android #### Features Add the following element to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` Set the `android:required` attribute to `true` if your app can't function, or isn't designed to function, when Bluetooth Low Energy is not available on the device. If your app can function without Bluetooth Low Energy, set the `android:required` attribute to `false`. This will allow your app to be installed on devices that do not support Bluetooth Low Energy. #### Permissions This API requires the following elements be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` You can read more about Bluetooth permissions in the [Android documentation](https://developer.android.com/develop/connectivity/bluetooth/bt-permissions). #### Services You also need to add the following service **inside** the `application` tag in your `AndroidManifest.xml` (usually `android/app/src/main/AndroidManifest.xml`): ``` ``` #### Headless Task If you want to run your own native code when a specific event occurs, you can create a headless task. For this, you need to create a Java class with the name `BluetoothLowEnergyHeadlessTask` in the same package as your `MainActivity`. Then implement the following methods: ``` import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattDescriptor; import androidx.annotation.NonNull; public class BluetoothLowEnergyHeadlessTask { public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic) { // Your code here } public void onCharacteristicChanged(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) { // Your code here } public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, int status) { // Your code here } public void onCharacteristicWrite(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattCharacteristic characteristic, int status) { // Your code here } public void onConnectionStateChange(@NonNull BluetoothGatt gatt, int status, int newState) { // Your code here } public void onDescriptorRead(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) { // Your code here } public void onDescriptorWrite(@NonNull BluetoothGatt gatt, @NonNull BluetoothGattDescriptor descriptor, int status) { // Your code here } public void onMtuChanged(@NonNull BluetoothGatt gatt, int mtu, int status) { // Your code here } public void onReadRemoteRssi(@NonNull BluetoothGatt gatt, int rssi, int status) { // Your code here } public void onServiceChanged(@NonNull BluetoothGatt gatt) { // Your code here } public void onServicesDiscovered(@NonNull BluetoothGatt gatt, int status) { // Your code here } } ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSBluetoothAlwaysUsageDescription` key to the `Info.plist` file (usually `ios/App/App/Info.plist`), which tells the user why the app needs access to Bluetooth peripherals: ``` NSBluetoothAlwaysUsageDescription The app needs access to Bluetooth peripherals to communicate with Bluetooth devices. ``` #### Capabilities If you want your app to maintain Bluetooth Low Energy connections in the background, ensure the `Background Modes` capability is enabled with `bluetooth-central` in your Xcode project. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. ## Configuration No configuration required for this plugin. ## Usage ``` import { BluetoothLowEnergy, BluetoothLowEnergyUtils, ConnectionPriority } from '@capawesome-team/capacitor-bluetooth-low-energy'; const connect = async () => { await BluetoothLowEnergy.connect({ deviceId: '00:00:00:00:00:00' }); }; const createBond = async () => { await BluetoothLowEnergy.createBond({ deviceId: '00:00:00:00:00:00' }); }; const disconnect = async () => { await BluetoothLowEnergy.disconnect({ deviceId: '00:00:00:00:00:00' }); }; const discoverServices = async () => { await BluetoothLowEnergy.discoverServices({ deviceId: '00:00:00:00:00:00' }); }; const getConnectedDevices = async () => { const result = await BluetoothLowEnergy.getConnectedDevices(); return result.devices; }; const getServices = async () => { const result = await BluetoothLowEnergy.getServices({ deviceId: '00:00:00:00:00:00' }); return result.services; }; const initialize = async () => { await BluetoothLowEnergy.initialize({ mode: 'central' }); }; const isAvailable = async () => { const result = await BluetoothLowEnergy.isAvailable(); return result.isAvailable; }; const isBonded = async () => { const result = await BluetoothLowEnergy.isBonded({ deviceId: '00:00:00:00:00:00' }); return result.bonded; }; const isEnabled = async () => { const result = await BluetoothLowEnergy.isEnabled(); return result.enabled; }; const openAppSettings = async () => { await BluetoothLowEnergy.openAppSettings(); }; const openBluetoothSettings = async () => { await BluetoothLowEnergy.openBluetoothSettings(); }; const openLocationSettings = async () => { await BluetoothLowEnergy.openLocationSettings(); }; const readCharacteristic = async () => { const result = await BluetoothLowEnergy.readCharacteristic({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', }); return result.value; }; const readDescriptor = async () => { const result = await BluetoothLowEnergy.readDescriptor({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', descriptorId: '00002902-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', }); return result.value; }; const readRssi = async () => { const result = await BluetoothLowEnergy.readRssi({ deviceId: '00:00:00:00:00:00' }); return result.rssi; }; const requestConnectionPriority = async () => { await BluetoothLowEnergy.requestConnectionPriority({ connectionPriority: ConnectionPriority.BALANCED, deviceId: '00:00:00:00:00:00', }); }; const requestMtu = async () => { await BluetoothLowEnergy.requestMtu({ deviceId: '00:00:00:00:00:00', mtu: 512, }); }; const setCharacteristicValue = async () => { await BluetoothLowEnergy.setCharacteristicValue({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', serviceId: '00001800-0000-1000-8000-00805f9b34fb', value: [1, 2, 3], }); }; const startAdvertising = async () => { await BluetoothLowEnergy.startAdvertising({ manufacturerData: { 0xffff: [1, 2, 3] }, name: 'MyDevice', services: [ { id: '0000180A-0000-1000-8000-00805F9B34FB', characteristics: [ { id: '00002A29-0000-1000-8000-00805F9B34FB', descriptors: [], // Descriptors are ignored for now permissions: { read: true, write: true, }, properties: { read: true, write: true, notify: true, indicate: true, }, }, ], }, ], }); }; const startCharacteristicNotifications = async () => { await BluetoothLowEnergy.startCharacteristicNotifications({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', }); }; const startForegroundService = async () => { await BluetoothLowEnergy.startForegroundService({ body: 'Body', id: 1, smallIcon: 'smallIcon', title: 'Title', }); }; const startScan = async () => { await BluetoothLowEnergy.startScan(); }; const stopAdvertising = async () => { await BluetoothLowEnergy.stopAdvertising(); }; const stopCharacteristicNotifications = async () => { await BluetoothLowEnergy.stopCharacteristicNotifications({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', }); }; const stopForegroundService = async () => { await BluetoothLowEnergy.stopForegroundService(); }; const stopScan = async () => { await BluetoothLowEnergy.stopScan(); }; const writeCharacteristic = async () => { await BluetoothLowEnergy.writeCharacteristic({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', value: [1, 2, 3], }); }; const writeDescriptor = async () => { await BluetoothLowEnergy.writeDescriptor({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', descriptorId: '00002902-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', value: [1, 2, 3], }); }; const checkPermissions = async () => { const result = await BluetoothLowEnergy.checkPermissions(); return result; }; const requestPermissions = async () => { const result = await BluetoothLowEnergy.requestPermissions(); return result; }; const addListener = () => { BluetoothLowEnergy.addListener('characteristicChanged', (event) => { console.log('Characteristic changed', event); }); BluetoothLowEnergy.addListener('characteristicWriteRequest', async (event) => { console.log('Characteristic write request', event); }); BluetoothLowEnergy.addListener('deviceConnected', (event) => { console.log('Device connected', event); }); BluetoothLowEnergy.addListener('deviceDisconnected', (event) => { console.log('Device disconnected', event); }); BluetoothLowEnergy.addListener('deviceScanned', (event) => { console.log('Device scanned', event); }); }; const removeAllListeners = () => { BluetoothLowEnergy.removeAllListeners(); }; const convertBytesToHex = (bytes: number[]) => { return BluetoothLowEnergyUtils.convertBytesToHex({ bytes }); }; ``` ## API - [@capawesome-team/capacitor-bluetooth-low-energy](#capawesome-teamcapacitor-bluetooth-low-energy) - [Features](#features) - [Testimonials](#testimonials) - [Compatibility](#compatibility) - [Demo](#demo) - [Guides](#guides) - [Installation](#installation) - [Android](#android) - [Features](#features-1) - [Permissions](#permissions) - [Services](#services) - [Headless Task](#headless-task) - [Proguard](#proguard) - [iOS](#ios) - [Privacy Descriptions](#privacy-descriptions) - [Capabilities](#capabilities) - [Configuration](#configuration) - [Usage](#usage) - [API](#api) - [connect(...)](#connect) - [createBond(...)](#createbond) - [disconnect(...)](#disconnect) - [discoverServices(...)](#discoverservices) - [getConnectedDevices()](#getconnecteddevices) - [getServices(...)](#getservices) - [initialize(...)](#initialize) - [isAvailable()](#isavailable) - [isBonded(...)](#isbonded) - [isEnabled()](#isenabled) - [isLocationEnabled()](#islocationenabled) - [openAppSettings()](#openappsettings) - [openBluetoothSettings()](#openbluetoothsettings) - [openLocationSettings()](#openlocationsettings) - [readCharacteristic(...)](#readcharacteristic) - [readDescriptor(...)](#readdescriptor) - [readRssi(...)](#readrssi) - [requestConnectionPriority(...)](#requestconnectionpriority) - [requestMtu(...)](#requestmtu) - [setCharacteristicValue(...)](#setcharacteristicvalue) - [startAdvertising(...)](#startadvertising) - [startCharacteristicNotifications(...)](#startcharacteristicnotifications) - [startForegroundService(...)](#startforegroundservice) - [startScan(...)](#startscan) - [stopAdvertising()](#stopadvertising) - [stopCharacteristicNotifications(...)](#stopcharacteristicnotifications) - [stopForegroundService()](#stopforegroundservice) - [stopScan()](#stopscan) - [writeCharacteristic(...)](#writecharacteristic) - [writeDescriptor(...)](#writedescriptor) - [checkPermissions()](#checkpermissions) - [requestPermissions(...)](#requestpermissions) - [addListener('characteristicChanged', ...)](#addlistenercharacteristicchanged-) - [addListener('characteristicWriteRequest', ...)](#addlistenercharacteristicwriterequest-) - [addListener('deviceConnected', ...)](#addlistenerdeviceconnected-) - [addListener('deviceDisconnected', ...)](#addlistenerdevicedisconnected-) - [addListener('deviceScanned', ...)](#addlistenerdevicescanned-) - [removeAllListeners()](#removealllisteners) - [Interfaces](#interfaces) - [ConnectOptions](#connectoptions) - [CreateBondOptions](#createbondoptions) - [DisconnectOptions](#disconnectoptions) - [DiscoverServiceOptions](#discoverserviceoptions) - [GetConnectedDevicesResult](#getconnecteddevicesresult) - [Device](#device) - [GetServicesResult](#getservicesresult) - [Service](#service) - [Characteristic](#characteristic) - [Descriptor](#descriptor) - [CharacteristicPermissions](#characteristicpermissions) - [CharacteristicProperties](#characteristicproperties) - [GetServicesOptions](#getservicesoptions) - [InitializeOptions](#initializeoptions) - [IsAvailableResult](#isavailableresult) - [IsBondedResult](#isbondedresult) - [IsBondedOptions](#isbondedoptions) - [IsEnabledResult](#isenabledresult) - [IsLocationEnabledResult](#islocationenabledresult) - [ReadCharacteristicResult](#readcharacteristicresult) - [ReadCharacteristicOptions](#readcharacteristicoptions) - [ReadDescriptorResult](#readdescriptorresult) - [ReadDescriptorOptions](#readdescriptoroptions) - [ReadRssiResult](#readrssiresult) - [ReadRssiOptions](#readrssioptions) - [RequestConnectionPriorityOptions](#requestconnectionpriorityoptions) - [RequestMtuOptions](#requestmtuoptions) - [SetCharacteristicValueOptions](#setcharacteristicvalueoptions) - [StartAdvertisingOptions](#startadvertisingoptions) - [StartCharacteristicNotificationsOptions](#startcharacteristicnotificationsoptions) - [StartForegroundServiceOptions](#startforegroundserviceoptions) - [StartScanOptions](#startscanoptions) - [StopCharacteristicNotificationsOptions](#stopcharacteristicnotificationsoptions) - [WriteCharacteristicOptions](#writecharacteristicoptions) - [WriteDescriptorOptions](#writedescriptoroptions) - [PermissionStatus](#permissionstatus) - [BluetoothLowEnergyPluginPermission](#bluetoothlowenergypluginpermission) - [PluginListenerHandle](#pluginlistenerhandle) - [CharacteristicChangedEvent](#characteristicchangedevent) - [CharacteristicWriteRequestEvent](#characteristicwriterequestevent) - [DeviceConnectedEvent](#deviceconnectedevent) - [DeviceDisconnectedEvent](#devicedisconnectedevent) - [DeviceScannedEvent](#devicescannedevent) - [Type Aliases](#type-aliases) - [PermissionState](#permissionstate) - [BluetoothLowEnergyPermissionType](#bluetoothlowenergypermissiontype) - [Enums](#enums) - [ConnectionPriority](#connectionpriority) - [Utils](#utils) - [Changelog](#changelog) - [Breaking Changes](#breaking-changes) - [License](#license) ### connect(...) ``` connect(options: ConnectOptions) => Promise ``` Connect to a BLE device. Only available on Android and iOS. | Param | Type | | ------------- | ---------------- | | **`options`** | `ConnectOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### createBond(...) ``` createBond(options: CreateBondOptions) => Promise ``` Create a bond with the BLE device. Only available on Android. | Param | Type | | ------------- | ------------------- | | **`options`** | `CreateBondOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### disconnect(...) ``` disconnect(options: DisconnectOptions) => Promise ``` Disconnect from the BLE device. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `DisconnectOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### discoverServices(...) ``` discoverServices(options: DiscoverServiceOptions) => Promise ``` Discover services provided by the device. On **iOS**, this operation may take up to 30 seconds. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `DiscoverServiceOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### getConnectedDevices() ``` getConnectedDevices() => Promise ``` Get a list of connected devices. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getServices(...) ``` getServices(options: GetServicesOptions) => Promise ``` Get a list of services provided by the device. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `GetServicesOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Initialize the plugin. This method must be called before any other method. On **iOS**, this will prompt the user for Bluetooth permissions. On **Android** and **Web**, this does nothing. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether or not Bluetooth Low Energy is available on the device. **Returns:** `Promise` **Since:** 7.3.0 ______________________________________________________________________ ### isBonded(...) ``` isBonded(options: IsBondedOptions) => Promise ``` Check if the device is bonded. Only available on Android. | Param | Type | | ------------- | ----------------- | | **`options`** | `IsBondedOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Check if Bluetooth is enabled. On **iOS**, requires the plugin to be initialized. Returns `false` if not initialized. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isLocationEnabled() ``` isLocationEnabled() => Promise ``` Check if location services are enabled. Only available on Android. **Returns:** `Promise` **Since:** 7.7.0 ______________________________________________________________________ ### openAppSettings() ``` openAppSettings() => Promise ``` Open the Bluetooth settings on the device. Only available on Android and iOS. **Since:** 6.0.0 ______________________________________________________________________ ### openBluetoothSettings() ``` openBluetoothSettings() => Promise ``` Open the Bluetooth settings on the device. Only available on Android. **Since:** 6.0.0 ______________________________________________________________________ ### openLocationSettings() ``` openLocationSettings() => Promise ``` Open the location settings on the device. Only available on Android. **Since:** 6.0.0 ______________________________________________________________________ ### readCharacteristic(...) ``` readCharacteristic(options: ReadCharacteristicOptions) => Promise ``` Read the value of a characteristic. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `ReadCharacteristicOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### readDescriptor(...) ``` readDescriptor(options: ReadDescriptorOptions) => Promise ``` Read the value of a descriptor. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `ReadDescriptorOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### readRssi(...) ``` readRssi(options: ReadRssiOptions) => Promise ``` Read the RSSI value of the device. Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `ReadRssiOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### requestConnectionPriority(...) ``` requestConnectionPriority(options: RequestConnectionPriorityOptions) => Promise ``` Request a connection priority. Only available on Android. | Param | Type | | ------------- | ---------------------------------- | | **`options`** | `RequestConnectionPriorityOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### requestMtu(...) ``` requestMtu(options: RequestMtuOptions) => Promise ``` Request an MTU size. Only available on Android. | Param | Type | | ------------- | ------------------- | | **`options`** | `RequestMtuOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### setCharacteristicValue(...) ``` setCharacteristicValue(options: SetCharacteristicValueOptions) => Promise ``` Set the value of a characteristic. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `SetCharacteristicValueOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### startAdvertising(...) ``` startAdvertising(options: StartAdvertisingOptions) => Promise ``` Start advertising as a BLE device. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `StartAdvertisingOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### startCharacteristicNotifications(...) ``` startCharacteristicNotifications(options: StartCharacteristicNotificationsOptions) => Promise ``` Start listening for characteristic value changes. This will emit the `characteristicChanged` event when a value changes. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------------------- | | **`options`** | `StartCharacteristicNotificationsOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### startForegroundService(...) ``` startForegroundService(options?: StartForegroundServiceOptions | undefined) => Promise ``` Start the foreground service and show a notification. This method should be called when the app is moved to the background to keep the Bluetooth connections alive. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `StartForegroundServiceOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### startScan(...) ``` startScan(options?: StartScanOptions | undefined) => Promise ``` Start scanning for BLE devices. This will emit the `deviceScanned` event when a device is found. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `StartScanOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### stopAdvertising() ``` stopAdvertising() => Promise ``` Stop advertising as a BLE device. Only available on Android and iOS. **Since:** 7.2.0 ______________________________________________________________________ ### stopCharacteristicNotifications(...) ``` stopCharacteristicNotifications(options: StopCharacteristicNotificationsOptions) => Promise ``` Stop listening for characteristic value changes. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------------------- | | **`options`** | `StopCharacteristicNotificationsOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### stopForegroundService() ``` stopForegroundService() => Promise ``` Stop the foreground service and remove the notification. This method should be called when the app is moved to the foreground since the foreground service is no longer needed. Only available on Android. **Since:** 6.0.0 ______________________________________________________________________ ### stopScan() ``` stopScan() => Promise ``` Stop scanning for BLE devices. Only available on Android and iOS. **Since:** 6.0.0 ______________________________________________________________________ ### writeCharacteristic(...) ``` writeCharacteristic(options: WriteCharacteristicOptions) => Promise ``` Write a value to a characteristic. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `WriteCharacteristicOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### writeDescriptor(...) ``` writeDescriptor(options: WriteDescriptorOptions) => Promise ``` Write a value to a descriptor. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `WriteDescriptorOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for the plugin. Only available on Android. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(permissions?: BluetoothLowEnergyPluginPermission | undefined) => Promise ``` Request permissions for the plugin. Only available on Android. | Param | Type | | ----------------- | ------------------------------------ | | **`permissions`** | `BluetoothLowEnergyPluginPermission` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('characteristicChanged', ...) ``` addListener(eventName: 'characteristicChanged', listenerFunc: (event: CharacteristicChangedEvent) => void) => Promise ``` Called when a characteristic value changes. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------------------- | | **`eventName`** | `'characteristicChanged'` | | **`listenerFunc`** | `(event: CharacteristicChangedEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('characteristicWriteRequest', ...) ``` addListener(eventName: 'characteristicWriteRequest', listenerFunc: (event: CharacteristicWriteRequestEvent) => void) => Promise ``` Called when a characteristic write request is received. Only available on Android. | Param | Type | | ------------------ | -------------------------------------------------- | | **`eventName`** | `'characteristicWriteRequest'` | | **`listenerFunc`** | `(event: CharacteristicWriteRequestEvent) => void` | **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### addListener('deviceConnected', ...) ``` addListener(eventName: 'deviceConnected', listenerFunc: (event: DeviceConnectedEvent) => void) => Promise ``` Called when a device is connected. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'deviceConnected'` | | **`listenerFunc`** | `(event: DeviceConnectedEvent) => void` | **Returns:** `Promise` **Since:** 7.1.0 ______________________________________________________________________ ### addListener('deviceDisconnected', ...) ``` addListener(eventName: 'deviceDisconnected', listenerFunc: (event: DeviceDisconnectedEvent) => void) => Promise ``` Called when a device is disconnected. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'deviceDisconnected'` | | **`listenerFunc`** | `(event: DeviceDisconnectedEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('deviceScanned', ...) ``` addListener(eventName: 'deviceScanned', listenerFunc: (event: DeviceScannedEvent) => void) => Promise ``` Called when an error occurs during the scan session. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'deviceScanned'` | | **`listenerFunc`** | `(event: DeviceScannedEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### ConnectOptions | Prop | Type | Description | Default | Since | | ------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`autoConnect`** | `boolean` | Whether to directly connect to the remote device (false) or to automatically connect as soon as the remote device becomes available (true). Only available on Android. | `false` | 7.1.0 | | **`autoReconnect`** | `boolean` | Whether to enable automatic reconnection to the peripheral when the connection is lost. Only available on Android and iOS (17.0+). | `false` | 7.6.0 | | **`deviceId`** | `string` | The address of the device to connect to. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the connect operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `10000` | 6.0.0 | #### CreateBondOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deviceId`** | `string` | The address of the device to create a bond with. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the create bond operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `10000` | 6.0.0 | #### DisconnectOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deviceId`** | `string` | The address of the device to disconnect from. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the disconnect operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### DiscoverServiceOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deviceId`** | `string` | The address of the device to discover services for. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the discover services operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `20000` | 6.0.0 | #### GetConnectedDevicesResult | Prop | Type | Description | Since | | ------------- | ---------- | ------------------------------ | ----- | | **`devices`** | `Device[]` | An array of connected devices. | 6.0.0 | #### Device | Prop | Type | Description | Since | | ---------- | -------- | --------------------------------- | ----- | | **`id`** | `string` | The UUID of the connected device. | 6.0.0 | | **`name`** | `string` | The name of the connected device. | 6.0.0 | #### GetServicesResult | Prop | Type | Description | Since | | -------------- | ----------- | -------------------------------------------- | ----- | | **`services`** | `Service[]` | An array of services provided by the device. | 6.0.0 | #### Service | Prop | Type | Description | Since | | --------------------- | ------------------ | ----------------------------------- | ----- | | **`id`** | `string` | The UUID of the service. | 6.0.0 | | **`characteristics`** | `Characteristic[]` | The characteristics of the service. | 6.0.0 | #### Characteristic | Prop | Type | Description | Since | | ----------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The UUID of the characteristic. | 6.0.0 | | **`descriptors`** | `Descriptor[]` | The descriptors of the characteristic. **Note**: This property is currently ignored when advertising a characteristic. | 6.0.0 | | **`permissions`** | `CharacteristicPermissions` | The permissions of the characteristic. Only available on Android. | 7.2.0 | | **`properties`** | `CharacteristicProperties` | The properties of the characteristic. | 6.0.0 | #### Descriptor | Prop | Type | Description | Since | | -------- | -------- | --------------------------- | ----- | | **`id`** | `string` | The UUID of the descriptor. | 6.0.0 | #### CharacteristicPermissions | Prop | Type | Description | Since | | ------------------------ | --------- | ---------------------------------------------------------------------------------------------------------------- | ----- | | **`read`** | `boolean` | Whether or not the characteristic can be read. | 7.2.0 | | **`readEncrypted`** | `boolean` | Whether or not the characteristic can be read with encryption. | 7.2.0 | | **`readEncryptedMitm`** | `boolean` | Whether or not the characteristic can be read with encryption and MITM protection. Only available on Android. | 7.2.0 | | **`write`** | `boolean` | Whether or not the characteristic can be written. | 7.2.0 | | **`writeEncrypted`** | `boolean` | Whether or not the characteristic can be written with encryption. | 7.2.0 | | **`writeEncryptedMitm`** | `boolean` | Whether or not the characteristic can be written with encryption and MITM protection. Only available on Android. | 7.2.0 | | **`writeSigned`** | `boolean` | Whether or not the characteristic can be written signed. Only available on Android. | 7.2.0 | | **`writeSignedMitm`** | `boolean` | Whether or not the characteristic can be written signed with encryption. Only available on Android. | 7.2.0 | #### CharacteristicProperties | Prop | Type | Description | Since | | -------------------------------- | --------- | ------------------------------------------------------------------ | ----- | | **`broadcast`** | `boolean` | Whether or not the characteristic can be broadcast. | 6.0.0 | | **`read`** | `boolean` | Whether or not the characteristic can be read. | 6.0.0 | | **`writeWithoutResponse`** | `boolean` | Whether or not the characteristic can be written without response. | 6.0.0 | | **`write`** | `boolean` | Whether or not the characteristic can be written. | 6.0.0 | | **`notify`** | `boolean` | Whether or not the characteristic supports notifications. | 6.0.0 | | **`indicate`** | `boolean` | Whether or not the characteristic supports indications. | 6.0.0 | | **`authenticatedSignedWrites`** | `boolean` | Whether or not the characteristic supports signed writes. | 6.0.0 | | **`extendedProperties`** | `boolean` | Whether or not the characteristic supports extended properties. | 6.0.0 | | **`notifyEncryptionRequired`** | `boolean` | Whether or not the characteristic supports reliable writes. | 6.0.0 | | **`indicateEncryptionRequired`** | `boolean` | Whether or not the characteristic supports writable auxiliaries. | 6.0.0 | #### GetServicesOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deviceId`** | `string` | The address of the device to get the services for. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the get services operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### InitializeOptions | Prop | Type | Description | Default | Since | | ---------- | ----------- | -------------- | ------------------------------------------------------------------- | ----------- | | **`mode`** | \`'central' | 'peripheral'\` | The mode of the Bluetooth Low Energy plugin. Only available on iOS. | `'central'` | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | --------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not Bluetooth Low Energy is available on the device. | 7.3.0 | #### IsBondedResult | Prop | Type | Description | Since | | ------------ | --------- | ------------------------------------ | ----- | | **`bonded`** | `boolean` | Whether or not the device is bonded. | 6.0.0 | #### IsBondedOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`deviceId`** | `string` | The address of the device to check if it is bonded. | 6.0.0 | | **`timeout`** | `number` | The timeout for the is bonded operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | 6.0.0 | #### IsEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------ | ----- | | **`enabled`** | `boolean` | Whether or not Bluetooth is enabled. | 6.0.0 | #### IsLocationEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | --------------------------------------------- | ----- | | **`enabled`** | `boolean` | Whether or not location services are enabled. | 7.7.0 | #### ReadCharacteristicResult | Prop | Type | Description | Since | | ----------- | ---------- | -------------------------------------- | ----- | | **`value`** | `number[]` | The value bytes of the characteristic. | 6.0.0 | #### ReadCharacteristicOptions | Prop | Type | Description | Default | Since | | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic to read. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device to read the characteristic from. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service to read the characteristic from. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the read operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### ReadDescriptorResult | Prop | Type | Description | Since | | ----------- | ---------- | ---------------------------------- | ----- | | **`value`** | `number[]` | The value bytes of the descriptor. | 6.0.0 | #### ReadDescriptorOptions | Prop | Type | Description | Default | Since | | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic that the descriptor belongs to. | | 6.0.0 | | **`descriptorId`** | `string` | The UUID of the descriptor to read. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device to read the descriptor from. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service that the descriptor belongs to. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the read operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### ReadRssiResult | Prop | Type | Description | Since | | ---------- | -------- | --------------- | ----- | | **`rssi`** | `number` | The RSSI value. | 6.0.0 | #### ReadRssiOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`deviceId`** | `string` | The address of the device to read the RSSI for. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the read RSSI operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### RequestConnectionPriorityOptions | Prop | Type | Description | Since | | ------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`deviceId`** | `string` | The address of the device to request the connection priority for. | 6.0.0 | | **`connectionPriority`** | `ConnectionPriority` | The connection priority to request. | 6.0.0 | | **`timeout`** | `number` | The timeout for the request connection priority operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | 6.0.0 | #### RequestMtuOptions | Prop | Type | Description | Since | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`deviceId`** | `string` | The address of the device to request the MTU size for. | 6.0.0 | | **`mtu`** | `number` | The mtu size to request. | 6.0.0 | | **`timeout`** | `number` | The timeout for the request MTU operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | 6.0.0 | #### SetCharacteristicValueOptions | Prop | Type | Description | Since | | ---------------------- | ---------- | ---------------------------------------------------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic to set the value for. | 7.2.0 | | **`serviceId`** | `string` | The UUID of the service to set the value for. | 7.2.0 | | **`value`** | `number[]` | The value bytes to set for the characteristic. | 7.2.0 | #### StartAdvertisingOptions | Prop | Type | Description | Default | Since | | ---------------------- | ------------------------------ | ----------------------------------------------------------------------- | ----------- | ----- | | **`manufacturerData`** | `{ [key: number]: number[]; }` | The manufacturer specific data to advertise. Only available on Android. | | 7.5.0 | | **`name`** | `string` | The name of the local device to advertise. Only available on iOS. | `"Unknown"` | 7.2.0 | | **`services`** | `Service[]` | The services to advertise. | | 7.2.0 | #### StartCharacteristicNotificationsOptions | Prop | Type | Description | Default | Since | | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic to start notifications for. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device to start notifications for. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service to start notifications for. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the start notifications operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### StartForegroundServiceOptions | Prop | Type | Description | Default | Since | | --------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | ----- | | **`body`** | `string` | The body of the notification, shown below the title. | `"App is running in the background to keep Bluetooth connections alive."` | 6.0.0 | | **`id`** | `number` | The notification identifier. | `105` | 6.0.0 | | **`smallIcon`** | `string` | The status bar icon for the notification. Icons should be placed in your app's `res/drawable` folder. The value for this option should be the drawable resource ID, which is the filename without an extension. | | 6.0.0 | | **`title`** | `string` | The title of the notification. | `"Bluetooth Low Energy"` | 6.0.0 | #### StartScanOptions | Prop | Type | Description | Since | | ---------------- | ---------- | --------------------------------------------------------------------------------------- | ----- | | **`serviceIds`** | `string[]` | Find devices with services that match any of the provided UUIDs. Only available on iOS. | 6.0.0 | #### StopCharacteristicNotificationsOptions | Prop | Type | Description | Default | Since | | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic to stop notifications for. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device to stop notifications for. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service to stop notifications for. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the stop notifications operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | #### WriteCharacteristicOptions | Prop | Type | Description | Default | Since | | ---------------------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | ---------------------------- | ----------- | | **`characteristicId`** | `string` | The UUID of the characteristic to write. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device to write the characteristic to. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service to write the characteristic to. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the write operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | | **`type`** | \`'default' | 'withoutResponse'\` | The type of write operation. | `'default'` | | **`value`** | `number[]` | The value bytes to write to the characteristic. | | 6.0.0 | #### WriteDescriptorOptions | Prop | Type | Description | Default | Since | | ---------------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic that the descriptor belongs to. | | 6.0.0 | | **`descriptorId`** | `string` | The UUID of the descriptor. | | 6.0.0 | | **`deviceId`** | `string` | The address of the device that the descriptor belongs to. | | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service that the descriptor belongs to. | | 6.0.0 | | **`timeout`** | `number` | The timeout for the write operation in milliseconds. If the operation takes longer than this value, the promise will be rejected. | `5000` | 6.0.0 | | **`value`** | `number[]` | The value bytes of the descriptor. | | 6.0.0 | #### PermissionStatus | Prop | Type | Description | Since | | ---------------------- | ----------------- | --------------------------------------------------------------------------- | ----- | | **`bluetooth`** | `PermissionState` | Permission state for using bluetooth. Only available on iOS. | 6.0.0 | | **`bluetoothConnect`** | `PermissionState` | Permission state for connecting to a BLE device. Only available on Android. | 6.0.0 | | **`bluetoothScan`** | `PermissionState` | Permission state for scanning for BLE devices. Only available on Android. | 6.0.0 | | **`location`** | `PermissionState` | Permission state for using location services. Only available on Android. | 6.0.0 | | **`notifications`** | `PermissionState` | Permission state for using notifications. Only available on Android. | 6.0.0 | #### BluetoothLowEnergyPluginPermission | Prop | Type | Description | Default | | ----------------- | ------------------------------------ | --------------------------- | --------------------------------------------------------------------------------------------------- | | **`permissions`** | `BluetoothLowEnergyPermissionType[]` | The permissions to request. | `['bluetooth', 'bluetoothAdmin', 'bluetoothConnect', 'bluetoothScan', 'location', 'notifications']` | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### CharacteristicChangedEvent | Prop | Type | Description | Since | | ---------------------- | ---------- | ---------------------------------------------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic. | 6.0.0 | | **`deviceId`** | `string` | The address of the device. | 6.0.0 | | **`serviceId`** | `string` | The UUID of the service. | 6.0.0 | | **`value`** | `number[]` | The changed value bytes of the characteristic. | 6.0.0 | #### CharacteristicWriteRequestEvent | Prop | Type | Description | Since | | ---------------------- | ---------- | ----------------------------------------------- | ----- | | **`characteristicId`** | `string` | The UUID of the characteristic. | 7.2.0 | | **`serviceId`** | `string` | The address of the device. | 7.2.0 | | **`value`** | `number[]` | The value bytes to write to the characteristic. | 7.2.0 | #### DeviceConnectedEvent | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------ | ----- | | **`deviceId`** | `string` | The address of the connected device. | 7.1.0 | | **`name`** | `string` | The name of the connected device. | 7.1.0 | #### DeviceDisconnectedEvent | Prop | Type | Description | Since | | -------------- | -------- | --------------------------------------- | ----- | | **`deviceId`** | `string` | The address of the disconnected device. | 6.0.0 | | **`name`** | `string` | The name of the disconnected device. | 6.0.0 | #### DeviceScannedEvent | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------ | ----- | | **`id`** | `string` | The address of the scanned device. | 6.0.0 | | **`name`** | `string` | The name of the scanned device. | 6.0.0 | | **`rssi`** | `number` | The RSSI value of the scanned device. Only available on iOS. | 6.0.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### BluetoothLowEnergyPermissionType `'bluetooth' | 'bluetoothAdmin' | 'bluetoothAdvertise' | 'bluetoothConnect' | 'bluetoothScan' | 'location' | 'notifications'` ### Enums #### ConnectionPriority | Members | Value | Description | Since | | ------------------ | ----- | ------------------------------------ | ----- | | **`BALANCED`** | `0` | Balanced connection priority. | 6.0.0 | | **`HIGH`** | `1` | High connection priority. | 6.0.0 | | **`LOW_POWER`** | `2` | Low power connection priority. | 6.0.0 | | **`PRIORITY_DCK`** | `3` | Digital Car Key connection priority. | 6.0.0 | ## Utils This plugin provides a utility class `BluetoothLowEnergyUtils` that can be used for various Bluetooth Low Energy related operations, for example, converting byte arrays to hexadecimal strings: ``` import { BluetoothLowEnergyUtils } from '@capacitor-community/bluetooth-low-energy'; const convertBytesToHex = (bytes: number[]) => { return BluetoothLowEnergyUtils.convertBytesToHex({ bytes }); }; ``` See [docs/utils/README.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/docs/utils/README.md) for more information. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/LICENSE). # @capawesome/capacitor-cloudinary Unofficial Capacitor plugin for [Cloudinary SDK](https://cloudinary.com/documentation/cloudinary_sdks).[1](#fn:1) ## Features Capacitor Cloudinary allows you to use the native Cloudinary SDKs to upload files directly from the filesystem without going through the WebView. - 🔋 Supports Android, iOS and the Web - 🍕 Chunk upload of large files - ❌ No more out-of-memory issues - 📁 Works with the [Capacitor Filesystem](https://capacitorjs.com/docs/apis/filesystem) and [Capacitor File Picker](https://github.com/capawesome-team/capacitor-file-picker) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-cloudinary` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-cloudinary npx cap sync ``` ### Android This API requires the following permission be added to your `AndroidManifest.xml` **before** the `application` tag: ``` ``` You also need to add the following receiver **inside** the `application` tag in your `AndroidManifest.xml`: ``` ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$cloudinaryAndroidVersion` version of `com.cloudinary:cloudinary-android` (default: `3.1.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { Cloudinary, ResourceType } from '@capawesome/capacitor-cloudinary'; const initialize = async () => { await Cloudinary.initialize({ cloudName: 'my_cloud_name' }); }; const uploadResource = async () => { await Cloudinary.uploadResource({ path: 'file:///var/mobile/Containers/Data/Application/22A433FD-D82D-4989-8BE6-9FC49DEA20BB/Images/test.png', publicId: 'my_public_id', resourceType: ResourceType.image, uploadPreset: 'my_preset', }); }; const downloadResource = async () => { const { path } = await Cloudinary.downloadResource({ url: 'https://res.cloudinary.com/myCloudName/image/upload/v123/123.png', }); return path; }; ``` ## API - [`initialize(...)`](#initialize) - [`uploadResource(...)`](#uploadresource) - [`downloadResource(...)`](#downloadresource) - [Interfaces](#interfaces) - [Enums](#enums) ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the plugin. This method must be called once before all other methods. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### uploadResource(...) ``` uploadResource(options: UploadResourceOptions) => Promise ``` Upload a file to Cloudinary. **Note**: Currently, only unsigned uploads are supported. | Param | Type | | ------------- | ----------------------- | | **`options`** | `UploadResourceOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### downloadResource(...) ``` downloadResource(options: DownloadResourceOptions) => Promise ``` Download a file from Cloudinary. On **Android**, the file will be downloaded to the `Downloads` directory. On **iOS**, the file will be downloaded to the temporary directory. It is recommended to copy the file to a permanent location for further processing after downloading. | Param | Type | | ------------- | ------------------------- | | **`options`** | `DownloadResourceOptions` | **Returns:** `Promise` **Since:** 0.0.3 ______________________________________________________________________ ### Interfaces #### InitializeOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------------------- | ----- | | **`cloudName`** | `string` | The cloud name of your app which you can find in the Cloudinary Management Console. | 0.0.1 | #### UploadResourceResult | Prop | Type | Description | Since | | ---------------------- | -------------- | ---------------------------------------------------------------------------------------- | ----- | | **`assetId`** | `string` | The unique asset identifier of the uploaded resource. Only available on Android and Web. | 0.0.1 | | **`bytes`** | `number` | The number of bytes of the uploaded resource. | 0.0.1 | | **`createdAt`** | `string` | The timestamp at which the resource was uploaded. | 0.0.1 | | **`duration`** | `number` | The duration of the uploaded resource in seconds. | 0.1.5 | | **`format`** | `string` | The format of the uploaded resource. | 0.0.1 | | **`height`** | `number` | The height of the uploaded resource. | 0.1.4 | | **`originalFilename`** | `string` | The original filename of the uploaded resource. Only available on Android and iOS. | 0.0.1 | | **`resourceType`** | `ResourceType` | The resource type of the uploaded resource. | 0.0.1 | | **`publicId`** | `string` | The unique public identifier of the uploaded resource. | 0.0.1 | | **`url`** | `string` | The url of the uploaded resource. | 0.0.1 | | **`secureUrl`** | `string` | The secure url of the uploaded resource. | 5.1.0 | | **`width`** | `number` | The width of the uploaded resource. | 0.1.4 | #### UploadResourceOptions | Prop | Type | Description | Since | | ------------------ | -------------- | ------------------------------------------------------------------ | ----- | | **`resourceType`** | `ResourceType` | The resource type to upload. | 0.0.1 | | **`blob`** | `Blob` | The file to upload. Only available on Web. | 0.0.1 | | **`uploadPreset`** | `string` | The selected upload preset. | 0.0.1 | | **`path`** | `string` | The path of the file to upload. Only available on Android and iOS. | 0.0.1 | | **`publicId`** | `string` | Assign a unique public identifier to the resource. | 0.0.1 | #### DownloadResourceResult | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path of the downloaded resource where it is stored on the device. Only available on Android and iOS. | 0.0.3 | | **`blob`** | `Blob` | The downloaded resource as a blob. Only available on Web. | 0.0.1 | #### DownloadResourceOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------ | ----- | | **`url`** | `string` | The url of the resource to download. | 0.0.3 | ### Enums #### ResourceType | Members | Value | Since | | ----------- | --------- | ----- | | **`Image`** | `'image'` | 0.0.1 | | **`Video`** | `'video'` | 0.0.1 | | **`Raw`** | `'raw'` | 0.0.1 | ## Utils See [docs/utils/README.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/cloudinary/docs/utils/README.md). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/cloudinary/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/cloudinary/LICENSE). ## Credits This plugin is based on the [Capacitor Cloudinary](https://github.com/capawesome-team/capacitor-cloudinary) plugin. Thanks to everyone who contributed to the project! ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Cloudinary Ltd. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome-team/capacitor-contacts Capacitor plugin to read, write, or select device contacts. Supports Android, iOS and Web with advanced features like contact groups, pagination, and native modals. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for contacts. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 📇 **Contacts**: Create, update, delete and retrieve device contacts. - 🔄 **Partial Updates**: Update only specific contact fields without affecting others - missing properties are preserved, null values delete fields. - 📌 **Groups**: Create, update, delete and retrieve contact groups on iOS. - 🎫 **Accounts**: Add contacts to specific accounts on Android. - 📖 **Pagination**: Paginate through contacts to avoid performance issues. - 🔍 **Filtering**: Filter contacts by ID, email, phone number, etc. (Coming soon!) - 📱 **Native Modals**: Create, update and display contacts in native modals. - 🎯 **Picking**: Let the user select a device contact. - 🖼️ **Photos**: Set, update and retrieve contact photos. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Guides - [Alternative to the Capacitor Community Contacts plugin](https://capawesome.io/blog/alternative-to-capacitor-community-contacts-plugin/) - [Announcing the Capacitor Contacts Plugin](https://capawesome.io/blog/announcing-the-capacitor-contacts-plugin/) - [Exploring the Capacitor Contacts API](https://capawesome.io/blog/exploring-the-capacitor-contacts-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-contacts` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-contacts npx cap sync ``` ### Android #### Permissions This API requires the following elements be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSContactsUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the user's contacts: ``` NSContactsUsageDescription We need access to your contacts to display them in the app. ``` #### Entitlements To access the `note` field of a contact, your app must have the `com.apple.developer.contacts.notes` entitlement. Check out the [Apple documentation](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.developer.contacts.notes) for more information. If you don't need access to the `note` field, you can skip this step. ## Configuration No configuration required for this plugin. ## Usage ``` import { Contacts, EmailAddressType, PhoneNumberType, PostalAddressType } from '@capawesome-team/capacitor-contacts'; const createContact = async () => { return Contacts.createContact({ contact: { birthday: { day: 1, month: 1, year: 1990 }, givenName: 'John', familyName: 'Doe', emailAddresses: [ { value: 'mail@example.com', type: EmailAddressType.Home, isPrimary: true } ], phoneNumbers: [ { value: '1234567890', type: PhoneNumberType.Mobile, isPrimary: true } ], postalAddresses: [ { street: '123 Main St', city: 'Springfield', state: 'IL', postalCode: '62701', country: 'USA', type: PostalAddressType.Home, isPrimary: true } ] } }); }; const updateContactById = async (id: string) => { await Contacts.updateContactById({ id, contact: { givenName: 'John', familyName: 'Doe', birthday: null, // This will remove the birthday field from the contact note: undefined // This will preserve the existing note field } }); }; const deleteContactById = async (id: string) => { await Contacts.deleteContactById({ id }); }; const pickContacts = async () => { const { contacts } = await Contacts.pickContacts({ fields: [ 'id', 'givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses' ], multiple: true }); return contacts; }; const getContactById = async (id: string) => { const { contact } = await Contacts.getContactById({ id }); return contact; }; const getContacts = async () => { const { contacts } = await Contacts.getContacts({ fields: [ 'id', 'givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses' ], limit: 10, offset: 0 }); return contacts; }; const displayContactById = async (id: string) => { await Contacts.displayContactById({ id }); }; const displayUpdateContactById = async (id: string) => { await Contacts.displayUpdateContactById({ id }); }; const countContacts = async () => { const { total } = await Contacts.countContacts(); return total; }; const createGroup = async () => { return Contacts.createGroup({ group: { name: 'My Group' } }); }; const deleteGroupById = async (id: string) => { await Contacts.deleteGroupById({ id }); }; const getAccounts = async () => { const { accounts } = await Contacts.getAccounts(); return accounts; }; const getGroupById = async (id: string) => { const { group } = await Contacts.getGroupById({ id }); return group; }; const getGroups = async () => { const { groups } = await Contacts.getGroups(); return groups; }; const isAvailable = async () => { const { isAvailable } = await Contacts.isAvailable(); return isAvailable; }; const isSupported = async () => { const { isSupported } = await Contacts.isSupported(); return isSupported; }; const checkPermissions = async () => { return Contacts.checkPermissions(); }; const requestPermissions = async () => { return Contacts.requestPermissions(); }; ``` ## API - [`countContacts()`](#countcontacts) - [`createContact(...)`](#createcontact) - [`createGroup(...)`](#creategroup) - [`deleteContactById(...)`](#deletecontactbyid) - [`deleteGroupById(...)`](#deletegroupbyid) - [`displayContactById(...)`](#displaycontactbyid) - [`displayCreateContact(...)`](#displaycreatecontact) - [`displayUpdateContactById(...)`](#displayupdatecontactbyid) - [`getAccounts()`](#getaccounts) - [`getContactById(...)`](#getcontactbyid) - [`getContacts(...)`](#getcontacts) - [`getGroupById(...)`](#getgroupbyid) - [`getGroups()`](#getgroups) - [`isAvailable()`](#isavailable) - [`isSupported()`](#issupported) - [`openSettings()`](#opensettings) - [`pickContact(...)`](#pickcontact) - [`pickContacts(...)`](#pickcontacts) - [`updateContactById(...)`](#updatecontactbyid) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions(...)`](#requestpermissions) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### countContacts() ``` countContacts() => Promise ``` Count the number of contacts on the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### createContact(...) ``` createContact(options: CreateContactOptions) => Promise ``` Create a new contact on the device. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `CreateContactOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### createGroup(...) ``` createGroup(options: CreateGroupOptions) => Promise ``` Create a new contact group on the device. Only available on iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `CreateGroupOptions` | **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### deleteContactById(...) ``` deleteContactById(options: DeleteContactByIdOptions) => Promise ``` Delete a contact from the device. Only available on Android and iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `DeleteContactByIdOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### deleteGroupById(...) ``` deleteGroupById(options: DeleteGroupByIdOptions) => Promise ``` Delete a contact group from the device. Only available on iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `DeleteGroupByIdOptions` | **Since:** 7.4.0 ______________________________________________________________________ ### displayContactById(...) ``` displayContactById(options: DisplayContactByIdOptions) => Promise ``` Display an existing contact by identifier. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `DisplayContactByIdOptions` | **Since:** 7.4.0 ______________________________________________________________________ ### displayCreateContact(...) ``` displayCreateContact(options?: DisplayCreateContactOptions | undefined) => Promise ``` Open a native modal to create a new device contact. This allows the user to update the contact information before saving it and does not require any permissions. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `DisplayCreateContactOptions` | **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### displayUpdateContactById(...) ``` displayUpdateContactById(options: DisplayUpdateContactByIdOptions) => Promise ``` Open a native modal to update a contact. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------------- | | **`options`** | `DisplayUpdateContactByIdOptions` | **Since:** 7.4.0 ______________________________________________________________________ ### getAccounts() ``` getAccounts() => Promise ``` List all accounts on the device. Only available on Android. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getContactById(...) ``` getContactById(options: GetContactByIdOptions) => Promise ``` Find a contact by identifier. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `GetContactByIdOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### getContacts(...) ``` getContacts(options?: GetContactsOptions | undefined) => Promise ``` List all contacts on the device. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `GetContactsOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### getGroupById(...) ``` getGroupById(options: GetGroupByIdOptions) => Promise ``` Find a contact group by identifier. Only available on iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `GetGroupByIdOptions` | **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getGroups() ``` getGroups() => Promise ``` List all contact groups on the device. Only available on iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether or not contacts is available on the device. **Returns:** `Promise` **Since:** 7.6.0 ______________________________________________________________________ ### isSupported() ``` isSupported() => Promise ``` Check if the contacts API is available on the device. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Opens the native app settings page to allow the user to grant the app contacts permissions. Only available on Android and iOS. **Since:** 7.7.0 ______________________________________________________________________ ### pickContact(...) ``` pickContact(options?: PickContactsOptions | undefined) => Promise ``` Open the contact picker to select a contact from the device. | Param | Type | | ------------- | --------------------- | | **`options`** | `PickContactsOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### pickContacts(...) ``` pickContacts(options?: PickContactsOptions | undefined) => Promise ``` Open the contact picker to select a contact from the device. | Param | Type | | ------------- | --------------------- | | **`options`** | `PickContactsOptions` | **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### updateContactById(...) ``` updateContactById(options: UpdateContactByIdOptions) => Promise ``` Update an existing contact on the device. Only available on Android and iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `UpdateContactByIdOptions` | **Since:** 7.4.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions to access contacts. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions to access contacts. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### Interfaces #### CountContactsResult | Prop | Type | Description | Since | | ----------- | -------- | ----------------------- | ----- | | **`total`** | `number` | The number of contacts. | 7.4.0 | #### CreateContactResult | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------- | ----- | | **`id`** | `string` | The identifier for the created contact. | 7.0.0 | #### CreateContactOptions | Prop | Type | Description | Since | | ------------- | --------------------- | ---------------------- | ----- | | **`contact`** | `Omit` | The contact to create. | 7.0.0 | #### Contact | Prop | Type | Description | Since | | ---------------------- | ----------------- | ------------------------------------------------------------------------------- | ----- | | **`account`** | `Account` | The account associated with the contact. Only available on Android. | 7.4.0 | | **`birthday`** | `Birthday` | The birthday of the contact. | 7.3.0 | | **`emailAddresses`** | `EmailAddress[]` | The list of email addresses for the contact. | 7.0.0 | | **`familyName`** | `string` | The family name of the contact. Only available on Android and iOS. | 7.0.0 | | **`givenName`** | `string` | The given name of the contact. Only available on Android and iOS. | 7.0.0 | | **`groupIds`** | `string[]` | The identifier of the groups the contact belongs to. Only available on iOS. | 7.4.0 | | **`id`** | `string` | The identifier for the contact. Only available on Android and iOS. | 7.0.0 | | **`jobTitle`** | `string` | The job title of the contact. Only available on Android and iOS. | 7.0.0 | | **`middleName`** | `string` | The middle name of the contact. Only available on Android and iOS. | 7.0.0 | | **`fullName`** | `string` | The full name of the contact. Only available on Web. | 7.0.0 | | **`namePrefix`** | `string` | The name prefix of the contact. Only available on Android and iOS. | 7.0.0 | | **`nameSuffix`** | `string` | The name suffix of the contact. Only available on Android and iOS. | 7.0.0 | | **`note`** | `string` | A note about the contact. Only available on Android and iOS. | 7.0.0 | | **`organizationName`** | `string` | The organization name of the contact. Only available on Android and iOS. | 7.0.0 | | **`phoneNumbers`** | `PhoneNumber[]` | The list of phone numbers for the contact. | 7.0.0 | | **`photo`** | `string` | The photo of the contact as a base64 string. Only available on Android and iOS. | 7.0.0 | | **`postalAddresses`** | `PostalAddress[]` | The list of postal addresses for the contact. | 7.0.0 | | **`urlAddresses`** | `UrlAddress[]` | The list of URL addresses for the contact. Only available on Android and iOS. | 7.0.0 | #### Account | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------- | ----- | | **`name`** | `string` | The account name. Only available on Android. | 7.4.0 | | **`type`** | `string` | The account type. Only available on Android. | 7.4.0 | #### Birthday | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`day`** | `number` | The day of the birthdate. | 7.3.0 | | **`month`** | `number` | The month of the birthdate. | 7.3.0 | | **`year`** | `number` | The year of the birthdate. On **Android**, this must be provided if the `day` and `month` are provided when using the `displayCreateContact(...)` method. | 7.3.0 | #### EmailAddress | Prop | Type | Description | Default | Since | | --------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- | | **`isPrimary`** | `boolean` | Whether this email address is the primary one for the contact. | `false` | 7.0.0 | | **`label`** | `string` | A custom label for the email address. On **iOS**, this label is only set if the type is [`EmailAddressType.Custom`](#emailaddresstype). | | 7.0.0 | | **`type`** | `EmailAddressType` | The type of email address. | `EmailAddressType.Other` | 7.0.0 | | **`value`** | `string` | The email address. | | 7.0.0 | #### PhoneNumber | Prop | Type | Description | Default | Since | | --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------ | ----------------------- | ----- | | **`isPrimary`** | `boolean` | Whether this email address is the primary one for the contact. | | 7.0.0 | | **`label`** | `string` | A custom label for the phone number. On **iOS**, this label is only set if the type is [`PhoneNumberType.Custom`](#phonenumbertype). | | 7.0.0 | | **`type`** | `PhoneNumberType` | The type of phone number. | `PhoneNumberType.Other` | 7.0.0 | | **`value`** | `string` | The phone number. | | | #### PostalAddress | Prop | Type | Description | Default | Since | | -------------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ----- | | **`city`** | `string` | The city for the postal address. | | 7.0.0 | | **`country`** | `string` | The country for the postal address. | | 7.0.0 | | **`formatted`** | `string` | The formatted postal address. | | 7.0.0 | | **`isoCountryCode`** | `string` | The ISO country code for the postal address. Only available on iOS. | | 7.0.0 | | **`isPrimary`** | `boolean` | Whether this postal address is the primary one for the contact. Only available on Android and iOS. | `false` | 7.0.0 | | **`label`** | `string` | A custom label for the postal address. On **iOS**, this label is only set if the type is [`PostalAddressType.Custom`](#postaladdresstype). Only available on Android and iOS. | | 7.0.0 | | **`neighborhood`** | `string` | The neighborhood for the postal address. Only available on Android and iOS. | | 7.0.0 | | **`postalCode`** | `string` | The postal code for the postal address. Only available on Android and iOS. | | 7.0.0 | | **`state`** | `string` | The state for the postal address. | | 7.0.0 | | **`street`** | `string` | The street for the postal address. Only available on Android and iOS. | | 7.0.0 | | **`type`** | `PostalAddressType` | The type of postal address. Only available on Android and iOS. | `PostalAddressType.Other` | 7.0.0 | #### UrlAddress | Prop | Type | Description | Default | Since | | ----------- | ---------------- | ----------------------------------- | ---------------------- | ----- | | **`label`** | `string` | A custom label for the URL address. | | 7.5.0 | | **`type`** | `UrlAddressType` | The type of URL address. | `UrlAddressType.Other` | 7.5.0 | | **`value`** | `string` | The URL address. | | | #### CreateGroupResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------- | ----- | | **`id`** | `string` | The identifier for the created group. | 7.4.0 | #### CreateGroupOptions | Prop | Type | Description | Since | | ----------- | ------------------- | -------------------- | ----- | | **`group`** | `Omit` | The group to create. | 7.4.0 | #### Group | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier for the group. | 7.4.0 | | **`name`** | `string` | The name of the group. | 7.4.0 | #### DeleteContactByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ------------------------------- | ----- | | **`id`** | `string` | The identifier for the contact. | 7.0.0 | #### DeleteGroupByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier for the group. | 7.4.0 | #### DisplayContactByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------------------- | ----- | | **`id`** | `string` | The identifier of the contact to display. | 7.4.0 | #### DisplayCreateContactResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------------------------------------------------------------------------------------ | ----- | | **`id`** | `string` | The identifier for the created contact. On **Android**, you need the `readContacts` permission to return the identifier. | 7.4.0 | #### DisplayCreateContactOptions | Prop | Type | Description | Since | | ------------- | --------------------- | --------------------------------------------------- | ----- | | **`contact`** | `Omit` | The contact to display in the create contact modal. | 7.2.0 | #### DisplayUpdateContactByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ---------------------------------------- | ----- | | **`id`** | `string` | The identifier of the contact to update. | 7.4.0 | #### GetAccountsResult | Prop | Type | Description | Since | | -------------- | ----------- | --------------------------------------------- | ----- | | **`accounts`** | `Account[]` | An array of available accounts on the device. | 7.4.0 | #### GetContactByIdResult | Prop | Type | | ------------- | --------- | | **`contact`** | \`Contact | #### GetContactByIdOptions | Prop | Type | Description | Default | Since | | ------------ | ------------------- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`fields`** | `(keyof Contact)[]` | The fields to return for the contact. | `['birthday', 'emailAddresses', 'familyName', 'givenName', 'id', 'jobTitle', 'middleName', 'namePrefix', 'nameSuffix', 'organizationName', 'phoneNumbers', 'postalAddresses', 'urlAddresses']` | 7.1.0 | | **`id`** | `string` | The identifier for the contact. | | 7.0.0 | #### GetContactsResult | Prop | Type | Description | Since | | -------------- | ----------- | ------------------------------------------------------------------------------------------------- | ----- | | **`contacts`** | `Contact[]` | The list of contacts on the device. **Note**: No photos are returned to avoid performance issues. | 7.0.0 | #### GetContactsOptions | Prop | Type | Description | Default | Since | | ------------ | ------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`fields`** | `(keyof Contact)[]` | The fields to return for the contact. | `['emailAddresses', 'familyName', 'givenName', 'id', 'jobTitle', 'middleName', 'namePrefix', 'nameSuffix', 'organizationName', 'phoneNumbers', 'postalAddresses', 'urlAddresses']` | 7.1.0 | | **`limit`** | `number` | Limit the number of contacts returned. | `20` | 7.4.0 | | **`offset`** | `number` | Offset the number of contacts returned. | `0` | 7.4.0 | #### GetGroupByIdResult | Prop | Type | Description | Since | | ----------- | ------- | ----------- | ---------------------------------------- | | **`group`** | \`Group | null\` | The group with the specified identifier. | #### GetGroupByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier for the group. | 7.4.0 | #### GetGroupsResult | Prop | Type | Description | Since | | ------------ | --------- | --------------------------------- | ----- | | **`groups`** | `Group[]` | The list of groups on the device. | 7.4.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | --------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not contacts is available on the device. | 7.6.0 | #### IsSupportedResult | Prop | Type | Description | Since | | ----------------- | --------- | ---------------------------------------------------------------------------------------------- | ----- | | **`isSupported`** | `boolean` | Whether the contacts API is available on the device. This is always `true` on Android and iOS. | 7.0.0 | #### PickContactsResult | Prop | Type | Description | Since | | -------------- | ----------- | --------------------------------------------------- | ----- | | **`contacts`** | `Contact[]` | The selected contacts. Empty if none were selected. | 7.0.0 | #### PickContactsOptions | Prop | Type | Description | Default | Since | | -------------- | ------------------- | ------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`fields`** | `(keyof Contact)[]` | The fields to return for the contact. Only available on Android and iOS. | `['birthday', 'emailAddresses', 'familyName', 'givenName', 'id', 'jobTitle', 'middleName', 'namePrefix', 'nameSuffix', 'organizationName', 'phoneNumbers', 'postalAddresses', 'urlAddresses']` | 7.4.0 | | **`multiple`** | `boolean` | Whether to allow selecting multiple contacts. Only available on Web. | `false` | 7.0.0 | #### UpdateContactByIdOptions | Prop | Type | Description | Since | | ------------- | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`contact`** | `Nullable>` | The updated contact information. Missing properties are ignored and keep their existing values. Properties explicitly set to `null` (or empty arrays `[]`) will be deleted. | 8.0.0 | | **`id`** | `string` | The identifier for the contact. | 8.0.0 | #### PermissionStatus | Prop | Type | Description | Since | | ------------------- | ------------------------- | ------------------------------------------------------------------------- | ----- | | **`readContacts`** | `ContactsPermissionState` | Permission state for reading contacts. Only available on Android and iOS. | 7.0.0 | | **`writeContacts`** | `ContactsPermissionState` | Permission state for writing contacts. Only available on Android and iOS. | 7.0.0 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | -------------------------- | --------------------------- | ----------------------------------- | ----- | | **`permissions`** | `ContactsPermissionType[]` | The permissions to request. | `['readContacts', 'writeContacts']` | 7.0.0 | ### Type Aliases #### ContactField `keyof Contact` #### PickContactOptions `PickContactsOptions` #### PickContactResult `PickContactsResult` #### Nullable Makes all properties of T nullable. `{ [K in keyof T]: T[K] | null }` #### ContactsPermissionState `PermissionState | 'limited'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### ContactsPermissionType `'readContacts' | 'writeContacts'` ### Enums #### EmailAddressType | Members | Value | Description | Since | | ------------ | ---------- | -------------------------- | ----- | | **`Custom`** | `'CUSTOM'` | | 7.0.0 | | **`Home`** | `'HOME'` | | 7.0.0 | | **`ICloud`** | `'ICLOUD'` | Only available on iOS. | 7.0.0 | | **`Mobile`** | `'MOBILE'` | Only available on Android. | 7.0.0 | | **`Other`** | `'OTHER'` | | 7.0.0 | | **`School`** | `'SCHOOL'` | Only available on iOS. | 7.0.0 | | **`Work`** | `'WORK'` | | 7.0.0 | #### PhoneNumberType | Members | Value | Description | Since | | ----------------- | ---------------- | -------------------------- | ----- | | **`Assistant`** | `'ASSISTANT'` | Only available on Android. | 7.0.0 | | **`Callback`** | `'CALLBACK'` | Only available on Android. | 7.0.0 | | **`Car`** | `'CAR'` | Only available on Android. | 7.0.0 | | **`CompanyMain`** | `'COMPANY_MAIN'` | Only available on Android. | 7.0.0 | | **`Custom`** | `'CUSTOM'` | | 7.0.0 | | **`FaxHome`** | `'FAX_HOME'` | | 7.0.0 | | **`FaxOther`** | `'FAX_OTHER'` | | 7.0.0 | | **`FaxWork`** | `'FAX_WORK'` | | 7.0.0 | | **`Home`** | `'HOME'` | | 7.0.0 | | **`IPhone`** | `'IPHONE'` | Only available on iOS. | 7.0.0 | | **`Isdn`** | `'ISDN'` | Only available on Android. | 7.0.0 | | **`Main`** | `'MAIN'` | | 7.0.0 | | **`Mms`** | `'MMS'` | Only available on Android. | 7.0.0 | | **`Mobile`** | `'MOBILE'` | | 7.0.0 | | **`Other`** | `'OTHER'` | | 7.0.0 | | **`Pager`** | `'PAGER'` | | 7.0.0 | | **`Radio`** | `'RADIO'` | Only available on Android. | 7.0.0 | | **`Telex`** | `'TELEX'` | Only available on Android. | 7.0.0 | | **`TtyTdd`** | `'TTY_TDD'` | Only available on Android. | 7.0.0 | | **`Work`** | `'WORK'` | | 7.0.0 | | **`WorkMobile`** | `'WORK_MOBILE'` | Only available on Android. | 7.0.0 | | **`WorkPager`** | `'WORK_PAGER'` | Only available on Android. | 7.0.0 | #### PostalAddressType | Members | Value | Since | | ------------ | ---------- | ----- | | **`Custom`** | `'CUSTOM'` | 7.0.0 | | **`Home`** | `'HOME'` | 7.0.0 | | **`Other`** | `'OTHER'` | 7.0.0 | | **`Work`** | `'WORK'` | 7.0.0 | #### UrlAddressType | Members | Value | Description | Since | | -------------- | ------------ | -------------------------- | ----- | | **`Blog`** | `'BLOG'` | Only available on Android. | 7.5.0 | | **`Custom`** | `'CUSTOM'` | | 7.5.0 | | **`Ftp`** | `'FTP'` | Only available on Android. | 7.5.0 | | **`Home`** | `'HOME'` | | 7.5.0 | | **`Homepage`** | `'HOMEPAGE'` | | 7.5.0 | | **`Other`** | `'OTHER'` | | 7.5.0 | | **`Profile`** | `'PROFILE'` | Only available on Android. | 7.5.0 | | **`School`** | `'SCHOOL'` | Only available on iOS. | 7.5.0 | | **`Work`** | `'WORK'` | | 7.5.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/contacts/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/contacts/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/contacts/LICENSE). # @capawesome-team/capacitor-datetime-picker Capacitor plugin for seamless date and time selection with advanced features like localization, theming, and more. Available for Android and iOS. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for date and time picking. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📅 **Multiple modes**: Date, time, and datetime picker modes. - 🌍 **Localization**: Support for BCP 47 language tags. - 🎨 **Theming**: Auto, light, and dark theme support. - ⚡ **Custom formats**: Define your own date/time format strings. - 🔒 **Min/Max constraints**: Set minimum and maximum selectable dates/times. - 📱 **Native UI**: Uses platform-specific picker components. - ⚙️ **Flexible configuration**: Customizable button texts and picker modes. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-datetime-picker` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome-team/capacitor-datetime-picker npx cap sync ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | iOS | | ------- | --- | | | | ## Usage ``` import { DatetimePicker } from '@capawesome-team/capacitor-datetime-picker'; const present = async () => { const date = new Date('1995-12-24T02:23:00'); const { value } = await DatetimePicker.present({ cancelButtonText: 'Cancel', doneButtonText: 'Ok', mode: 'time', value: date.toISOString(), theme: 'dark', locale: 'en-US', }); return value; }; ``` ## API - [`present(...)`](#present) - [`cancel()`](#cancel) - [Interfaces](#interfaces) ### present(...) ``` present(options?: PresentOptions | undefined) => Promise ``` Open the datetime picker. An error is thrown if the input is canceled or dismissed by the user. Only available on Android and iOS. | Param | Type | | ------------- | ---------------- | | **`options`** | `PresentOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### cancel() ``` cancel() => Promise ``` Cancel the currently active datetime picker. If there is no active picker, this method does nothing. Only available on Android and iOS. **Since:** 7.2.0 ______________________________________________________________________ ### Interfaces #### PresentResult | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------------------------------------------------------------------------- | ----- | | **`value`** | `string` | The value entered by the user. The format of this value matches the value of the `format` parameter. | 0.0.1 | #### PresentOptions | Prop | Type | Description | Default | Since | | --------------------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`cancelButtonText`** | `string` | The cancel button text. | `'Cancel'` | 0.0.1 | | **`doneButtonText`** | `string` | The done button text. | `'Ok'` | 0.0.1 | | **`format`** | `string` | The format in which values are received and returned. | `'yyyy-MM-dd'T'HHss.sss'Z''` | 0.0.1 | | **`locale`** | `string` | BCP 47 language tag to define the language of the UI. | | 0.0.2 | | **`max`** | `string` | The latest date and time to accept. The format of this value must match the value of the `format` parameter. This value must specify a date string later than or equal to the one specified by the `min` attribute. | | 0.0.1 | | **`min`** | `string` | The earliest date and time to accept. The format of this value must match the value of the `format` parameter. This value must specify a date string earlier than or equal to the one specified by the `max` attribute. | | 0.0.1 | | **`mode`** | \`'date' | 'time' | 'datetime'\` | Whether you want a date or time or datetime picker. | | **`theme`** | \`'auto' | 'light' | 'dark'\` | Choose the theme that the datetime picker should have. With `auto` the system theme is used. This value overwrites the `theme` configuration value. Only available on Android and iOS. Spinner options only available on Android | | **`value`** | `string` | The predefined value when opening the picker. The format of this value must match the value of the `format` parameter. | | 0.0.1 | | **`androidTimePickerMode`** | \`'clock' | 'spinner'\` | Whether to use the spinner or clock mode for the time picker on Android. This value overwrites the `androidTimePickerMode` configuration value. Only available on Android. | | | **`androidDatePickerMode`** | \`'spinner' | 'calendar'\` | Whether to use the calendar or spinner mode for the date picker on Android. This value overwrites the `androidDatePickerMode` configuration value. Only available on Android. | | | **`minuteInterval`** | `number` | The minute interval of the time picker. This controls the granularity of the minute selector (e.g., 15 for 0, 15, 30, 45). The value must be evenly divisible into 60. Only available on iOS when using time or datetime modes. On Android, this parameter is ignored. | `1` | 7.1.0 | ## Credits The iOS implementation of this plugin is based on [RPicker](https://github.com/rheyansh/RPicker) which is licensed under [MIT](https://github.com/rheyansh/RPicker/blob/master/LICENSE). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/datetime-picker/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/datetime-picker/LICENSE). # @capawesome-team/capacitor-file-compressor Capacitor plugin for efficient file compression with support for image formats like PNG, JPEG, and WebP. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for file compression. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🌅 **Compress Images**: Compress png, jpeg, and webp images. - 🤝 **Compatibility**: Compatible with the [Zip](https://capawesome.io/plugins/zip/) plugin. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-plugin-demo). | Android | | ------- | | | ## Guides - [Exploring the Capacitor File Compressor API](https://capawesome.io/blog/exploring-the-capacitor-file-compressor-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-file-compressor` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-file-compressor npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxDocumentFileVersion` version of `androidx.documentfile:documentfile` (default: `1.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Usage ``` import { FileCompressor } from '@capawesome-team/capacitor-file-compressor'; const compressImage = async () => { const { path } = await FileCompressor.compressImage({ height: 1000, mimeType: 'image/jpeg', path: 'content://com.android.providers.downloads.documents/document/msf%3A1000000485', quality: 0.7, width: 1000, }); return path; }; ``` ## API - [`compressImage(...)`](#compressimage) - [Interfaces](#interfaces) ### compressImage(...) ``` compressImage(options: CompressImageOptions) => Promise ``` Compress an image. Only png, jpeg, and webp images are supported. **Attention**: The exif data of the image is lost during compression. | Param | Type | | ------------- | ---------------------- | | **`options`** | `CompressImageOptions` | **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### Interfaces #### CompressImageResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path of the compressed file. Only available on Android and iOS. | 5.0.0 | | **`blob`** | `Blob` | The blob of the compressed file. Only available on Web. | 5.0.0 | #### CompressImageOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------- | ----- | | **`blob`** | `Blob` | The blob of the file to compress. Only available on Web. | | 5.0.0 | | **`height`** | `number` | The height of the resulting image. | | 7.1.0 | | **`mimeType`** | `string` | The mime type of the compressed file. On Android, only `image/jpeg` and `image/webp` are supported. On iOS, only `image/jpeg` is supported. On Web, only `image/jpeg` and `image/webp` are supported. | `'image/jpeg'` | 5.0.0 | | **`path`** | `string` | The path of the file to compress. Only available on Android and iOS. | | 5.0.0 | | **`quality`** | `number` | The quality of the resulting image, expressed as a value from `0.0` to `1.0`. The value `0.0` represents the maximum compression (or lowest quality) while the value `1.0` represents the least compression (or best quality). | `0.6` | 5.0.0 | | **`width`** | `number` | The width of the resulting image. | | 7.1.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-compressor/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-compressor/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-compressor/LICENSE). # @capawesome-team/capacitor-file-opener Capacitor plugin to open a file with the default application. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for opening files. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 📄 **Multiple file formats**: Open various file types with their default applications. - 🌐 **Web support**: Open files using Blob objects on Web platform. - 📱 **Native integration**: Uses system file associations on mobile platforms. - 📁 **Flexible paths**: Support for file paths and content URIs on Android. - 🔍 **MIME type detection**: Automatic MIME type detection or manual specification. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-file-opener` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-file-opener npx cap sync ``` ### Android You need to specify the directories that contain the files you want to open. To do this, create a new file named `file_paths.xml` in the `res/xml` directory of your Android project (e.g. `android/app/src/main/res/xml/file_paths.xml`). Here is an example of the content of the file: ``` ``` More information can be found in the [Android documentation](https://developer.android.com/training/secure-file-sharing/setup-sharing#DefineMetaData). #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxDocumentFileVersion` version of `androidx.documentfile:documentfile` (default: `1.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | iOS | | ------- | --- | | | | ## Usage ``` import { FileOpener } from '@capawesome-team/capacitor-file-opener'; const open = async () => { await FileOpener.openFile({ path: 'content://com.android.providers.downloads.documents/document/msf%3A1000000073', }); }; ``` ## API - [`openFile(...)`](#openfile) - [Interfaces](#interfaces) ### openFile(...) ``` openFile(options: OpenFileOptions) => Promise ``` Open a file with the default application. | Param | Type | | ------------- | ----------------- | | **`options`** | `OpenFileOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### OpenFileOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The blob instance of the file to open. Only available on Web. | 6.1.0 | | **`path`** | `string` | The path of the file. Only available on Android and iOS. | 0.0.1 | | **`mimeType`** | `string` | The mime type of the file. If not specified, the mime type will be determined. Only available on Android and iOS. | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-opener/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-opener/LICENSE). # @capawesome/capacitor-file-picker Capacitor plugin that allows the user to select a file, directory, image, or video from the device's file system or gallery. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for file picking. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 📂 **Directory picking**: Allows users to select a directory to retrieve all files. - 🖼️ **Image picking**: Lets users select one or more images from the gallery. - 🎥 **Video picking**: Lets users select one or more videos from the gallery. - 📄 **File picking**: Lets users select one or more miscellaneous files from the file system. - 📸 **HEIC to JPEG conversion**: Converts HEIC images to JPEG format on iOS. - 📜 **File metadata**: Retrieves metadata such as file size, name, mime type, and last modified timestamp. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-file-picker` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-file-picker npx cap sync ``` ### Android #### Permissions This API requires the following permissions be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` ### iOS #### Entitlements To use this plugin with Mac Catalyst, your app must have the `com.apple.security.files.user-selected.read-only` entitlement enabled. This allows the app to read files selected by the user. Check out the [Apple documentation](https://developer.apple.com/documentation/bundleresources/entitlements/com.apple.security.files.user-selected.read-only) for more information. ``` com.apple.security.files.user-selected.read-only ``` If you don't want to use the plugin with Mac Catalyst, you can skip this step. ## Configuration No configuration required for this plugin. ## Usage ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const appendFileToFormData = async () => { const result = await FilePicker.pickFiles(); const file = result.files[0]; const formData = new FormData(); if (file.blob) { const rawFile = new File(file.blob, file.name, { type: file.mimeType, }); formData.append('file', rawFile, file.name); } }; const checkPermissions = async () => { const result = await FilePicker.checkPermissions(); }; const copyFile = async () => { const result = await FilePicker.copyFile({ from: 'path/to/file', to: 'path/to/destination', }); }; const pickFiles = async () => { const result = await FilePicker.pickFiles({ types: ['image/png'], }); }; const pickDirectory = async () => { const result = await FilePicker.pickDirectory(); }; const pickImages = async () => { const result = await FilePicker.pickImages(); }; const pickMedia = async () => { const result = await FilePicker.pickMedia(); }; const pickVideos = async () => { const result = await FilePicker.pickVideos(); }; const requestPermissions = async () => { const result = await FilePicker.requestPermissions(); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`convertHeicToJpeg(...)`](#convertheictojpeg) - [`copyFile(...)`](#copyfile) - [`pickFiles(...)`](#pickfiles) - [`pickDirectory()`](#pickdirectory) - [`pickImages(...)`](#pickimages) - [`pickMedia(...)`](#pickmedia) - [`pickVideos(...)`](#pickvideos) - [`requestPermissions(...)`](#requestpermissions) - [`addListener('pickerDismissed', ...)`](#addlistenerpickerdismissed-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions to access files. Only available on Android. **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### convertHeicToJpeg(...) ``` convertHeicToJpeg(options: ConvertHeicToJpegOptions) => Promise ``` Convert a HEIC image to JPEG. Only available on iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `ConvertHeicToJpegOptions` | **Returns:** `Promise` **Since:** 0.6.0 ______________________________________________________________________ ### copyFile(...) ``` copyFile(options: CopyFileOptions) => Promise ``` Copy a file to a new location. | Param | Type | | ------------- | ----------------- | | **`options`** | `CopyFileOptions` | **Since:** 7.1.0 ______________________________________________________________________ ### pickFiles(...) ``` pickFiles(options?: PickFilesOptions | undefined) => Promise ``` Open the file picker that allows the user to select one or more files. | Param | Type | | ------------- | ------------------ | | **`options`** | `PickFilesOptions` | **Returns:** `Promise` ______________________________________________________________________ ### pickDirectory() ``` pickDirectory() => Promise ``` Open a picker dialog that allows the user to select a directory. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.2.0 ______________________________________________________________________ ### pickImages(...) ``` pickImages(options?: PickMediaOptions | undefined) => Promise ``` Pick one or more images from the gallery. On iOS 13 and older it only allows to pick one image. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `PickMediaOptions` | **Returns:** `Promise` **Since:** 0.5.3 ______________________________________________________________________ ### pickMedia(...) ``` pickMedia(options?: PickMediaOptions | undefined) => Promise ``` Pick one or more images or videos from the gallery. On iOS 13 and older it only allows to pick one image or video. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `PickMediaOptions` | **Returns:** `Promise` **Since:** 0.5.3 ______________________________________________________________________ ### pickVideos(...) ``` pickVideos(options?: PickMediaOptions | undefined) => Promise ``` Pick one or more videos from the gallery. On iOS 13 and older it only allows to pick one video. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `PickMediaOptions` | **Returns:** `Promise` **Since:** 0.5.3 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions to access files. Only available on Android. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### addListener('pickerDismissed', ...) ``` addListener(eventName: 'pickerDismissed', listenerFunc: () => void) => Promise ``` Called when the file picker is dismissed. Only available on iOS. | Param | Type | | ------------------ | ------------------- | | **`eventName`** | `'pickerDismissed'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.6.2 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.6.2 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Description | Since | | ------------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------- | ----- | | **`accessMediaLocation`** | `PermissionState` | Permission state for accessing media location. On Android, this requests/checks the `ACCESS_MEDIA_LOCATION` permission. | 6.1.0 | | **`readExternalStorage`** | `PermissionState` | Permission state for reading external storage. On Android, this requests/checks the `READ_EXTERNAL_STORAGE` permission. | 6.1.0 | #### ConvertHeicToJpegResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------- | ----- | | **`path`** | `string` | The path of the converted JPEG image. | 0.6.0 | #### ConvertHeicToJpegOptions | Prop | Type | Description | Since | | ---------- | -------- | --------------------------- | ----- | | **`path`** | `string` | The path of the HEIC image. | 0.6.0 | #### CopyFileOptions | Prop | Type | Description | Default | Since | | --------------- | --------- | --------------------------------------------------------------- | ------- | ----- | | **`from`** | `string` | The path of the file to copy. | | 7.1.0 | | **`overwrite`** | `boolean` | Whether to overwrite if the file at destination already exists. | `true` | 7.2.0 | | **`to`** | `string` | The path to copy the file to. | | 7.1.0 | #### PickFilesResult | Prop | Type | | ----------- | -------------- | | **`files`** | `PickedFile[]` | #### PickedFile | Prop | Type | Description | Since | | ---------------- | -------- | -------------------------------------------------------------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The Blob instance of the file. Only available on Web. | | | **`data`** | `string` | The Base64 string representation of the data contained in the file. Is only provided if `readData` is set to `true`. | | | **`duration`** | `number` | The duration of the video in seconds. Only available on Android and iOS. | 0.5.3 | | **`height`** | `number` | The height of the image or video in pixels. Only available on Android and iOS. | 0.5.3 | | **`mimeType`** | `string` | The mime type of the file. | | | **`modifiedAt`** | `number` | The last modified timestamp of the file in milliseconds. | 0.5.9 | | **`name`** | `string` | The name of the file. | | | **`path`** | `string` | The path of the file. Only available on Android and iOS. | | | **`size`** | `number` | The size of the file in bytes. | | | **`width`** | `number` | The width of the image or video in pixels. Only available on Android and iOS. | 0.5.3 | #### PickFilesOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`types`** | `string[]` | List of accepted file types. Look at [IANA Media Types](https://www.iana.org/assignments/media-types/media-types.xhtml) for a complete list of standard media types. This option is ignored if `limit` is set. | | | | **`limit`** | `number` | The maximum number of files that the user can select. Setting this to `0` sets the selection limit to unlimited. Currently, only `0` and `1` are supported. | `0` | 6.0.0 | | **`readData`** | `boolean` | Whether to read the file data. **Attention**: Reading large files can lead to app crashes. It's therefore not recommended to use this option. Instead, use the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) to load the file as a blob, see [this example](https://capawesome.io/blog/the-file-handling-guide-for-capacitor/#read-a-file). | `false` | | #### PickDirectoryResult | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------------- | ----- | | **`path`** | `string` | The path to the selected directory. | 6.2.0 | #### PickMediaOptions | Prop | Type | Description | Default | Since | | --------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`readData`** | `boolean` | Whether to read the file data. | `false` | | | **`skipTranscoding`** | `boolean` | Whether to avoid transcoding, if possible. On iOS, for example, HEIC images are automatically transcoded to JPEG. Only available on iOS. | `true` | | | **`limit`** | `number` | The maximum number of files that the user can select. Setting this to `0` sets the selection limit to unlimited. On Android and Web, only `0` and `1` are supported. | `0` | 5.2.0 | | **`ordered`** | `boolean` | Whether an ordered number is displayed instead of a check mark in the selection badge. Only available on iOS (15+). | `false` | 5.3.0 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | ------------------ | --------------------------- | ------------------------------------------------ | ----- | | **`permissions`** | `PermissionType[]` | The permissions to request. | `["accessMediaLocation", "readExternalStorage"]` | 6.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### PickImagesOptions `PickMediaOptions` #### PickImagesResult `PickMediaResult` #### PickMediaResult `PickFilesResult` #### PickVideosOptions `PickMediaOptions` #### PickVideosResult `PickMediaResult` #### PermissionType `'accessMediaLocation' | 'readExternalStorage'` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-picker/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-picker/LICENSE). ## Credits This plugin is based on the [Capacitor File Picker](https://github.com/capawesome-team/capacitor-file-picker) plugin. Thanks to everyone who contributed to the project! # @capawesome-team/capacitor-geocoder Capacitor plugin for handling geocoding and reverse geocoding. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for geocoding and reverse geocoding. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 📍 **Geocoding**: Convert addresses into geographic coordinates. - 🗺️ **Reverse Geocoding**: Convert geographic coordinates into human-readable addresses. - 🌐 **Multiple Providers**: Support for Google Maps and OpenStreetMap on Web. - 🌍 **Localization**: Customize the locale for geocoding requests. - 🔢 **Configurable Results**: Limit the number of addresses returned in reverse geocoding operations. - 🛠️ **Native APIs**: Uses platform-native geocoding services on Android and iOS for reliable and accurate results. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Demo | Android | iOS | Web | | ------- | --- | --- | | | | | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-geocoder` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-geocoder universal-geocoder npx cap sync ``` ## Usage ``` import { Geocoder } from '@capawesome-team/capacitor-geocoder'; const geocode = async () => { const result = await Geocoder.geocode({ address: '1600 Amphitheatre Parkway, Mountain View, CA', }); console.log('Geocode result:', result); }; const geodecode = async () => { const result = await Geocoder.geodecode({ latitude: 37.422, longitude: -122.084, }); console.log('Geodecode result:', result); }; ``` ## API - [`geocode(...)`](#geocode) - [`geodecode(...)`](#geodecode) - [Interfaces](#interfaces) ### geocode(...) ``` geocode(options: GeocodeOptions) => Promise ``` Translate an address into geographic coordinates. Only available on Android and iOS. | Param | Type | | ------------- | ---------------- | | **`options`** | `GeocodeOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### geodecode(...) ``` geodecode(options: GeodecodeOptions) => Promise ``` Translate geographic coordinates into a human-readable address. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `GeodecodeOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### GeocodeResult | Prop | Type | Description | Since | | --------------- | -------- | --------------------------------------- | ----- | | **`latitude`** | `number` | The latitude of the geocoded location. | 0.0.1 | | **`longitude`** | `number` | The longitude of the geocoded location. | 0.0.1 | #### GeocodeOptions | Prop | Type | Description | Default | Since | | ------------------ | -------------- | ----------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------ | | **`address`** | `string` | The address to geocode. | | 0.0.1 | | **`locale`** | `string` | The locale (BCP 47 language tag) to use for the geocoding request. By default, the device's locale is used. | | 0.0.1 | | **`webApiKey`** | `string` | The API key to use for the geocoding service. Only available on Web. | | 0.0.1 | | **`webProvider`** | \`'googlemaps' | 'openstreetmaps'\` | The provider to use for the geocoding service. Only available on Web. | `'openstreetmaps'` | | **`webUserAgent`** | `string` | The User-Agent identifying your application. Only available on the web. | | 0.0.1 | #### GeodecodeResult | Prop | Type | Description | Since | | --------------- | ----------- | -------------------------------------------------------------------------------------------------------------------------- | ----- | | **`addresses`** | `Address[]` | The list of addresses that match the given coordinates. The number of addresses returned is limited by the `limit` option. | 0.0.1 | #### Address | Prop | Type | Description | Since | | ------------------ | ---------- | ---------------------------------------------------------------- | ----- | | **`adminArea`** | `string` | The administrative area (e.g. state or province) of the address. | 0.0.1 | | **`addressLines`** | `string[]` | The lines of the address. | 0.0.1 | | **`countryCode`** | `string` | The country code of the address. | 0.0.1 | | **`countryName`** | `string` | The name of the country. | 0.0.1 | | **`phoneNumber`** | `string` | The phone number of the address. | 0.0.1 | | **`postalCode`** | `string` | The postal code of the address. | 0.0.1 | | **`subAdminArea`** | `string` | The sub-administrative area (e.g. county) of the address. | 0.0.1 | | **`url`** | `string` | The URL of the address. | 0.0.1 | #### GeodecodeOptions | Prop | Type | Description | Default | Since | | ------------------ | -------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | ------------------ | | **`latitude`** | `number` | The latitude of the location to reverse geocode. | | 0.0.1 | | **`limit`** | `number` | The maximum number of results to return. | `5` | 0.0.1 | | **`longitude`** | `number` | The longitude of the location to reverse geocode. | | 0.0.1 | | **`webApiKey`** | `string` | The API key to use for the geocoding service. Only available on Web. | | 0.0.1 | | **`webProvider`** | \`'googlemaps' | 'openstreetmaps'\` | The provider to use for the geocoding service. Only available on Web. | `'openstreetmaps'` | | **`webUserAgent`** | `string` | The User-Agent identifying your application. Only needed if `webProvider` is set to `openstreetmaps` which uses the Nominatim service (see https://operations.osmfoundation.org/policies/nominatim/). The goal is to be able to limit the number of requests per application. Only available on the web. | | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geocoder/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geocoder/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geocoder/LICENSE). # @capawesome/capacitor-google-sign-in Unofficial Capacitor plugin to sign-in with Google.[1](#fn:1) ## Features We are proud to offer a comprehensive Capacitor plugin for Google Sign-In. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 🔐 **Authentication**: Sign in users with their Google account and receive an ID token (JWT). - 🔑 **Authorization**: Optionally request OAuth scopes to get an access token and server auth code. - 👤 **User Profile**: Retrieve the user's email, display name, profile picture, and more. - 🛡️ **Nonce Support**: Prevent replay attacks with a custom nonce on Android and Web. - 🪶 **Lightweight**: Just a single dependency and zero unnecessary bloat. - 🚨 **Error Codes**: Provides detailed error codes for better error handling. - 🤝 **Compatibility**: Compatible with the [OAuth](https://capawesome.io/plugins/oauth) plugins. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.1.x | >=8.x.x | Active support | ## Guides - [How to Sign In with Google using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-google-using-capacitor/) ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-google-sign-in` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-google-sign-in npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$androidxCredentialsVersion` version of `androidx.credentials:credentials` (default: `1.5.0`) - `$googleIdVersion` version of `com.google.android.libraries.identity.googleid:googleid` (default: `1.1.1`) - `$playServicesAuthVersion` version of `com.google.android.gms:play-services-auth` (default: `21.5.0`) ### iOS Add the `GIDClientID` key to the `ios/App/App/Info.plist` file with your iOS client ID from the Google Cloud Console: ``` GIDClientID YOUR_IOS_CLIENT_ID ``` You also need to add the URL scheme for your iOS client ID to the `ios/App/App/Info.plist` file: ``` CFBundleURLTypes CFBundleURLSchemes com.googleusercontent.apps.YOUR_IOS_CLIENT_ID ``` Replace `YOUR_IOS_CLIENT_ID` with the reversed client ID from the Google Cloud Console (e.g. `com.googleusercontent.apps.123456789-abc`). ## Configuration No configuration required for this plugin. ## Usage ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; import { Capacitor } from '@capacitor/core'; const initialize = async () => { await GoogleSignIn.initialize({ clientId: '123456789-abc.apps.googleusercontent.com', scopes: ['https://www.googleapis.com/auth/userinfo.profile'], }); }; const signIn = async () => { const result = await GoogleSignIn.signIn(); console.log(result.idToken); console.log(result.userId); console.log(result.email); console.log(result.displayName); console.log(result.accessToken); console.log(result.serverAuthCode); }; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== 'web') { return; } const result = await GoogleSignIn.handleRedirectCallback(); console.log(result.idToken); console.log(result.userId); console.log(result.email); console.log(result.displayName); console.log(result.accessToken); console.log(result.serverAuthCode); }; const signOut = async () => { await GoogleSignIn.signOut(); }; ``` ## API - [`handleRedirectCallback()`](#handleredirectcallback) - [`initialize(...)`](#initialize) - [`signIn(...)`](#signin) - [`signOut()`](#signout) - [Interfaces](#interfaces) ### handleRedirectCallback() ``` handleRedirectCallback() => Promise ``` Handle the redirect callback from the OAuth provider. This method must be called when the app is redirected back from the OAuth provider. It exchanges the authorization code for tokens and returns the sign-in result. Only available on Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the Google Sign-In plugin. This method must be called once before all other methods. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### signIn(...) ``` signIn(options?: SignInOptions | undefined) => Promise ``` Start the Google Sign-In flow. On Web, this redirects to the Google OAuth authorization page. The promise will never resolve on Web. After the user signs in, the app will be redirected back to the `redirectUrl`. Use `handleRedirectCallback()` to complete the sign-in flow. | Param | Type | | ------------- | --------------- | | **`options`** | `SignInOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signOut() ``` signOut() => Promise ``` Sign out the current user. On Android, this clears the credential state. On iOS, this signs out from the Google Sign-In SDK. On Web, this is a no-op. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### SignInResult | Prop | Type | Description | Since | | -------------------- | -------- | ----------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`idToken`** | `string` | The ID token (JWT) returned by Google. This token can be sent to your backend for verification. | 0.1.0 | | **`userId`** | `string` | The unique identifier of the user's Google Account. | 0.1.0 | | **`email`** | \`string | null\` | The user's email address. | | **`displayName`** | \`string | null\` | The user's display name (full name). | | **`givenName`** | \`string | null\` | The user's given name (first name). | | **`familyName`** | \`string | null\` | The user's family name (last name). | | **`imageUrl`** | \`string | null\` | The URL of the user's profile picture. | | **`accessToken`** | \`string | null\` | The access token for accessing Google APIs. Only available when `scopes` are configured in `initialize()`. | | **`serverAuthCode`** | \`string | null\` | The server auth code that can be exchanged on your backend for access and refresh tokens. Only available on Android and iOS when `scopes` are configured in `initialize()`. | #### InitializeOptions | Prop | Type | Description | Since | | ----------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`clientId`** | `string` | The web client ID from Google Cloud Console. On Android, this is passed as the server client ID to the Credential Manager API and the AuthorizationClient API. On iOS, this is used as the server client ID for the Google Sign-In SDK. On Web, this is used to initialize the Google Sign-In JavaScript API. **Attention**: This must be a web client ID on all platforms, even on Android and iOS. | 0.1.0 | | **`redirectUrl`** | `string` | The URL to redirect to after the OAuth flow. Only available on Web. | 0.1.0 | | **`scopes`** | `string[]` | The OAuth scopes to request. If provided, the plugin will request authorization in addition to authentication. This enables `accessToken` and `serverAuthCode` in the sign-in result. | 0.1.0 | #### SignInOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------- | ----- | | **`nonce`** | `string` | A nonce to prevent replay attacks. Only available on Android and Web. | 0.1.0 | ## Security This plugin handles the OAuth flow and returns tokens to your app. To keep your integration secure, be aware of the following: - **Server-side token verification is required.** The `idToken` (JWT) is **not** verified client-side. Your backend **must** verify the JWT signature using [Google's public keys](https://www.googleapis.com/oauth2/v3/certs) before trusting any claims (e.g. `userId`, `email`). Never use client-side token data for authorization decisions without server-side verification. - **Exchange `serverAuthCode` on your backend.** If you use scopes, send the `serverAuthCode` to your backend and exchange it there for access and refresh tokens. Never exchange it client-side, as this would expose your client secret. ## FAQ ### What's the difference between this plugin and other Google Sign-In plugins? This plugin is purpose-built for Google Sign-In and focuses on providing a clean and modern API with the latest platform features. Here are some of the key differences: - **Cross-platform**: Supports Android, iOS, and Web. - **Lightweight**: No unnecessary dependencies. Just Google Sign-In, nothing else. - **No deprecated APIs**: Uses the latest platform APIs (Credential Manager on Android, Google Sign-In SDK on iOS). - **Authentication + Authorization**: Supports both authentication (ID tokens) and authorization (access tokens, server auth codes) in a single flow. - **Error codes**: Provides typed error codes for proper error handling. - **Redirect flow on Web**: Uses a redirect-based OAuth flow instead of popups, resulting in a more reliable and user-friendly experience. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/google-sign-in/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/google-sign-in/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-libsql Capacitor plugin for [libSQL](https://docs.turso.tech/libsql) databases.[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.2.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-libsql` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-libsql npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app's `variables.gradle` file to change the default version of the dependency: - `$libsqlVersion` version of `tech.turso.libsql:libsql` (default: `0.1.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Usage ``` import { Libsql } from '@capawesome/capacitor-libsql'; const connectToLocalDatabase = async () => { const { connectionId } = await Libsql.connect({ path: 'database.db', }); console.log('Connected to database with ID:', connectionId); }; const connectToRemoteDatabase = async () => { const { connectionId } = await Libsql.connect({ url: 'libsql://your-database-url.turso.io', authToken: 'your-auth-token', }); console.log('Connected to remote database with ID:', connectionId); }; const query = async () => { const result = await Libsql.query({ connectionId: 'my-connection-id', statement: 'SELECT * FROM my_table', }); console.log('Query result:', result.rows); }; const execute = async () => { await Libsql.execute({ connectionId: 'my-connection-id', statement: 'INSERT INTO my_table (column1, column2) VALUES (?, ?)', values: ['value1', 'value2'], }); console.log('Insert executed successfully'); }; const performTransaction = async () => { const { transactionId } = await Libsql.beginTransaction({ connectionId: 'my-connection-id', }); try { await Libsql.execute({ connectionId: 'my-connection-id', statement: 'UPDATE my_table SET column1 = ? WHERE column2 = ?', values: ['new_value', 'value2'], transactionId, }); await Libsql.commitTransaction({ connectionId: 'my-connection-id', transactionId, }); console.log('Transaction committed successfully'); } catch (error) { await Libsql.rollbackTransaction({ connectionId: 'my-connection-id', transactionId, }); console.error('Transaction rolled back due to error:', error); } }; const sync = async () => { await Libsql.sync({ connectionId: 'my-connection-id', }); console.log('Database synchronized successfully'); }; ``` ## API - [`beginTransaction(...)`](#begintransaction) - [`commitTransaction(...)`](#committransaction) - [`connect(...)`](#connect) - [`execute(...)`](#execute) - [`executeBatch(...)`](#executebatch) - [`query(...)`](#query) - [`rollbackTransaction(...)`](#rollbacktransaction) - [`sync(...)`](#sync) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### beginTransaction(...) ``` beginTransaction(options: BeginTransactionOptions) => Promise ``` Begin a transaction on the specified database connection. Only available on Android. | Param | Type | | ------------- | ------------------------- | | **`options`** | `BeginTransactionOptions` | **Returns:** `Promise` **Since:** 0.0.0 ______________________________________________________________________ ### commitTransaction(...) ``` commitTransaction(options: CommitTransactionOptions) => Promise ``` Commit the current transaction on the specified database connection. Only available on Android. | Param | Type | | ------------- | -------------------------- | | **`options`** | `CommitTransactionOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### connect(...) ``` connect(options: ConnectOptions) => Promise ``` Connect to a database. This method must be called before any other methods that interact with the database. | Param | Type | | ------------- | ---------------- | | **`options`** | `ConnectOptions` | **Returns:** `Promise` **Since:** 0.0.0 ______________________________________________________________________ ### execute(...) ``` execute(options: ExecuteOptions) => Promise ``` Execute a single SQL statement on the specified database connection. This method can be used to execute any SQL statement, including `INSERT`, `UPDATE`, `DELETE`, and `CREATE TABLE`. | Param | Type | | ------------- | ---------------- | | **`options`** | `ExecuteOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### executeBatch(...) ``` executeBatch(options: ExecuteBatchOptions) => Promise ``` Execute a batch of SQL statements on the specified database connection. This method can be used to execute multiple SQL statements in a single call. | Param | Type | | ------------- | --------------------- | | **`options`** | `ExecuteBatchOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### query(...) ``` query(options: QueryOptions) => Promise ``` Query the database and return the result set. This method can be used to execute `SELECT` statements and retrieve the result set. | Param | Type | | ------------- | -------------- | | **`options`** | `QueryOptions` | **Returns:** `Promise` **Since:** 0.0.0 ______________________________________________________________________ ### rollbackTransaction(...) ``` rollbackTransaction(options: RollbackTransactionOptions) => Promise ``` Rollback the current transaction on the specified database connection. This method will undo all changes made in the current transaction. Only available on Android. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `RollbackTransactionOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### sync(...) ``` sync(options: SyncOptions) => Promise ``` Synchronize the database with the remote server. Available on iOS and Android. | Param | Type | | ------------- | ------------- | | **`options`** | `SyncOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### Interfaces #### BeginTransactionResult | Prop | Type | Description | Since | | ------------------- | -------- | ------------------------------------------- | ----- | | **`transactionId`** | `string` | The ID of the transaction that was started. | 0.0.0 | #### BeginTransactionOptions | Prop | Type | Description | Since | | ------------------ | -------- | ----------------------------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to begin the transaction on. | 0.0.0 | #### CommitTransactionOptions | Prop | Type | Description | Since | | ------------------- | -------- | ------------------------------------------------------ | ----- | | **`connectionId`** | `string` | The ID of the connection to commit the transaction on. | 0.0.0 | | **`transactionId`** | `string` | The ID of the transaction to commit. | 0.0.0 | #### ConnectResult | Prop | Type | Description | Since | | ------------------ | -------- | ------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection. | 0.0.0 | #### ConnectOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`authToken`** | `string` | The authentication token for the database. This is required for connecting to a remote database. If the database is local (i.e., a file on the device), this can be omitted. | 0.0.0 | | **`path`** | `string` | The path to the database file. If no path or URL is provided, the plugin will create a new in-memory database. If no file exists at the specified path, a new file will be created. | 0.0.0 | | **`url`** | `string` | The URL of the database. This can be used to connect to a remote database. If the URL is provided, the `authToken` must also be provided. If no path or URL is provided, the plugin will create a new in-memory database. | 0.0.0 | #### ExecuteOptions | Prop | Type | Description | Since | | ------------------- | --------- | --------------------------------------------------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to execute the SQL statement on. | 0.0.0 | | **`statement`** | `string` | The SQL statement to execute. | 0.0.0 | | **`transactionId`** | `string` | The transaction ID to use for the SQL statement. Only available on Android. | 0.0.0 | | **`values`** | `Value[]` | The values to bind to the SQL statement. | 0.0.0 | #### ExecuteBatchOptions | Prop | Type | Description | Since | | ------------------ | ----------- | ------------------------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to execute the batch on. | 0.0.0 | | **`statement`** | `string[]` | The SQL statements to execute in the batch. | 0.0.0 | | **`values`** | `Value[][]` | The values to bind to the SQL statements. | 0.0.0 | #### QueryResult | Prop | Type | Description | Since | | ---------- | ----------- | --------------------------------- | ----- | | **`rows`** | `Value[][]` | The values returned by the query. | 0.0.0 | #### QueryOptions | Prop | Type | Description | Since | | ------------------- | --------- | ------------------------------------------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to query. | 0.0.0 | | **`statement`** | `string` | The SQL statement to execute. | 0.0.0 | | **`transactionId`** | `string` | The transaction ID to use for the query. Only available on Android. | 0.0.0 | | **`values`** | `Value[]` | The values to bind to the SQL statement. | 0.0.0 | #### RollbackTransactionOptions | Prop | Type | Description | Since | | ------------------- | -------- | -------------------------------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to rollback the transaction on. | 0.0.0 | | **`transactionId`** | `string` | The ID of the transaction to rollback. | 0.0.0 | #### SyncOptions | Prop | Type | Description | Since | | ------------------ | -------- | --------------------------------- | ----- | | **`connectionId`** | `string` | The ID of the connection to sync. | 0.0.0 | ### Type Aliases #### Value `string | number | null` ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by CHISELSTRIKE INC. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-live-update Capacitor plugin that allows you to update your app remotely in real-time without requiring users to download a new version from the app store, known as Over-the-Air (OTA) updates. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for Over-the-Air (OTA) updates. Here are some of the key features: - 🔋 Supports **Android and iOS** - ⚡️ **Capacitor 6/7/8** support - 📦 **Bundle Management**: Download, set, and delete bundles. - ☁️ **Cloud Support**: Use the [Capawesome Cloud](https://cloud.capawesome.io/) to manage your app updates. - 📺 **Channel Support**: Set a channel for the app to manage different versions. - 🔄 **Auto Update**: Automatically download and set the latest bundle for the app with configurable background update strategy. - 🛟 **Rollback**: Reset the app to the default bundle if an incompatible bundle has been set. - 🚀 **Rollout**: Gradually roll out new bundles to gather valuable feedback. - 🔁 **Delta Updates**: Make your updates faster by only downloading changed files. - ⚙️ **Runtime Configuration**: Update plugin configuration at runtime without rebuilding the app. - 📡 **Update Lifecycle Events**: Track download progress, react to bundle changes, and detect reloads with auto-blocking of rolled back bundles. - 🏠 **Self-Hosted Bundles**: Download bundles from any URL, no Capawesome Cloud dependency required. - 🏷️ **Custom Properties**: Associate custom key-value metadata with bundles via Capawesome Cloud. - 🔒 **Security**: Verify the authenticity and integrity of the bundle using a public key. - ⚔️ **Battle-Tested**: Used in more than 1,000 projects to update apps on more than 20,000,000 devices. - 🌐 **Open Source**: Licensed under the MIT License. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Maintenance | | 6.x.x | 6.x.x | Maintenance | | 5.x.x | 5.x.x | Deprecated | ## Guides - [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/cloud/live-updates/setup/) - [Migrating from Ionic Appflow to Capawesome Cloud](https://capawesome.io/blog/migrating-from-ionic-appflow-to-capawesome-cloud/) - [Migrating from App Center to Capawesome Cloud](https://capawesome.io/blog/migrating-from-app-center-to-capawesome-cloud/) ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-live-update` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-live-update npx cap sync ``` ### Android #### Channel If you are using [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels), you can set a default channel directly in your native project by adding a string resource. This allows you to tie the channel to the version code at build time. Add the following to your app's `build.gradle` file: ``` android { defaultConfig { resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode } } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$okhttp3Version` version of `com.squareup.okhttp3:okhttp` (default: `5.3.2`) - `$zip4jVersion` version of `net.lingala.zip4j:zip4j` (default: `2.11.5`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Channel If you are using [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels), you can set a default channel directly in your native project by adding a key to your `Info.plist` file. This allows you to tie the channel to the build version at build time. Add the following to your `Info.plist` file: ``` CapawesomeLiveUpdateDefaultChannel production-$(CURRENT_PROJECT_VERSION) ``` #### Privacy manifest Add the `NSPrivacyAccessedAPICategoryUserDefaults` dictionary key to your [Privacy Manifest](https://capacitorjs.com/docs/ios/privacy-manifest) (usually `ios/App/PrivacyInfo.xcprivacy`): ``` NSPrivacyAccessedAPITypes NSPrivacyAccessedAPIType NSPrivacyAccessedAPICategoryUserDefaults NSPrivacyAccessedAPITypeReasons CA92.1 ``` We recommend to declare [`CA92.1`](https://developer.apple.com/documentation/bundleresources/privacy_manifest_files/describing_use_of_required_reason_api#4278401) as the reason for accessing the [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) API. ## Configuration | Prop | Type | Description | Default | Since | | -------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- | | **`appId`** | `string` | The app ID is used to identify the app when using [Capawesome Cloud](https://capawesome.io/cloud/). This is **NOT** the same as the app identifier (e.g. `com.example.app`). This is a unique identifier generated by Capawesome Cloud (e.g. `6e351b4f-69a7-415e-a057-4567df7ffe94`). | | 5.0.0 | | **`autoBlockRolledBackBundles`** | `boolean` | Whether or not to automatically block bundles that have been rolled back. When enabled, the plugin will automatically block bundles that caused a rollback (up to 100 bundles). When the limit is reached, the oldest blocked bundle is unblocked. Blocked bundles will be skipped in future sync operations. **Attention**: This option has no effect if `readyTimeout` is set to `0`. Only available on Android and iOS. | `false` | 7.3.0 | | **`autoDeleteBundles`** | `boolean` | Whether or not to automatically delete unused bundles. When enabled, the plugin will automatically delete unused bundles after calling `ready()`. | `false` | 5.0.0 | | **`autoUpdateStrategy`** | \`'none' | 'background'\` | The auto-update strategy for live updates. - `none`: Live updates will not be applied automatically. - `background`: Live updates will be automatically downloaded and applied in the background at app startup and when the app resumes (if the last check was more than 15 minutes ago). Only available on Android and iOS. | `'none'` | | **`defaultChannel`** | `string` | The default channel of the app. This can be overridden by `setChannel()`, the `channel` parameter of `sync()`, or the native channel configuration (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or `capawesome_live_update_default_channel` in `strings.xml` on Android). | | 6.3.0 | | **`httpTimeout`** | `number` | The timeout in milliseconds for HTTP requests. | `60000` | 6.4.0 | | **`publicKey`** | `string` | The public key to verify the integrity of the bundle. The public key must be a PEM-encoded RSA public key. | | 6.1.0 | | **`readyTimeout`** | `number` | The timeout in milliseconds to wait for the app to be ready before resetting to the default bundle. It is strongly **recommended** to configure this option (e.g. `10000` ms) so that the plugin can roll back to the default bundle in case of problems. If configured, the plugin will wait for the app to call the `ready()` method before resetting to the default bundle. Set to `0` to disable the timeout. | `0` | 5.0.0 | | **`serverDomain`** | `string` | The API domain of the [Capawesome Cloud](https://cloud.capawesome.io) server **without** scheme or path. | `'api.cloud.capawesome.io'` | 7.0.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "LiveUpdate": { "appId": '6e351b4f-69a7-415e-a057-4567df7ffe94', "autoBlockRolledBackBundles": undefined, "autoDeleteBundles": undefined, "autoUpdateStrategy": 'background', "defaultChannel": 'production', "httpTimeout": undefined, "publicKey": '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----', "readyTimeout": 10000, "serverDomain": 'api.cloud.capawesome.eu' } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: '6e351b4f-69a7-415e-a057-4567df7ffe94', autoBlockRolledBackBundles: undefined, autoDeleteBundles: undefined, autoUpdateStrategy: 'background', defaultChannel: 'production', httpTimeout: undefined, publicKey: '-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----', readyTimeout: 10000, serverDomain: 'api.cloud.capawesome.eu', }, }, }; export default config; ``` ## Usage ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const deleteBundle = async () => { await LiveUpdate.deleteBundle({ bundleId: 'my-bundle' }); }; const downloadBundle = async () => { await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' }); }; const fetchChannels = async () => { const result = await LiveUpdate.fetchChannels(); return result.channels; }; const fetchLatestBundle = async () => { await LiveUpdate.fetchLatestBundle(); }; const getBundles = async () => { const result = await LiveUpdate.getBundles(); return result.bundleIds; }; const getChannel = async () => { const result = await LiveUpdate.getChannel(); return result.channel; }; const getCurrentBundle = async () => { const result = await LiveUpdate.getCurrentBundle(); return result.bundleId; }; const getCustomId = async () => { const result = await LiveUpdate.getCustomId(); return result.customId; }; const getDeviceId = async () => { const result = await LiveUpdate.getDeviceId(); return result.deviceId; }; const getNextBundle = async () => { const result = await LiveUpdate.getNextBundle(); return result.bundleId; }; const getVersionCode = async () => { const result = await LiveUpdate.getVersionCode(); return result.versionCode; }; const getVersionName = async () => { const result = await LiveUpdate.getVersionName(); return result.versionName; }; const ready = async () => { const result = await LiveUpdate.ready(); if (result.currentBundleId) { console.log(`The app is now using the bundle with the identifier ${result.currentBundleId}.`); } if (result.previousBundleId) { console.log(`The app was using the bundle with the identifier ${result.previousBundleId}.`); } if (result.rollback) { console.log('The app was reset to the default bundle.'); } }; const reload = async () => { await LiveUpdate.reload(); }; const reset = async () => { await LiveUpdate.reset(); }; const setChannel = async () => { await LiveUpdate.setChannel({ channel: 'production-5' }); }; const setCustomId = async () => { await LiveUpdate.setCustomId({ customId: 'my-custom-id' }); }; const setNextBundle = async () => { await LiveUpdate.setNextBundle({ bundleId: '7f0b9bf2-dff6-4be2-bcac-b068cc5ea756' }); }; const sync = async () => { const result = await LiveUpdate.sync({ channel: 'production-5', }); return result.nextBundleId; }; const isNewBundleAvailable = async () => { const { bundleId: latestBundleId } = await LiveUpdate.fetchLatestBundle({ channel: 'production-5', }); if (latestBundleId) { const { bundleId: currentBundleId } = await LiveUpdate.getCurrentBundle(); return latestBundleId !== currentBundleId; } else { return false; } }; ``` ## API - [`clearBlockedBundles()`](#clearblockedbundles) - [`deleteBundle(...)`](#deletebundle) - [`downloadBundle(...)`](#downloadbundle) - [`fetchChannels(...)`](#fetchchannels) - [`fetchLatestBundle(...)`](#fetchlatestbundle) - [`getBlockedBundles()`](#getblockedbundles) - [`getBundles()`](#getbundles) - [`getChannel()`](#getchannel) - [`getConfig()`](#getconfig) - [`getDownloadedBundles()`](#getdownloadedbundles) - [`getCurrentBundle()`](#getcurrentbundle) - [`getCustomId()`](#getcustomid) - [`getDeviceId()`](#getdeviceid) - [`isSyncing()`](#issyncing) - [`getNextBundle()`](#getnextbundle) - [`getVersionCode()`](#getversioncode) - [`getVersionName()`](#getversionname) - [`ready()`](#ready) - [`reload()`](#reload) - [`reset()`](#reset) - [`resetConfig()`](#resetconfig) - [`setChannel(...)`](#setchannel) - [`setConfig(...)`](#setconfig) - [`setCustomId(...)`](#setcustomid) - [`setNextBundle(...)`](#setnextbundle) - [`sync(...)`](#sync) - [`addListener('downloadBundleProgress', ...)`](#addlistenerdownloadbundleprogress-) - [`addListener('nextBundleSet', ...)`](#addlistenernextbundleset-) - [`addListener('reloaded', ...)`](#addlistenerreloaded-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### clearBlockedBundles() ``` clearBlockedBundles() => Promise ``` Clear all blocked bundles from the blocked list. This removes all bundle identifiers that were automatically blocked due to rollbacks when `autoBlockRolledBackBundles` is enabled. Only available on Android and iOS. **Since:** 7.4.0 ______________________________________________________________________ ### deleteBundle(...) ``` deleteBundle(options: DeleteBundleOptions) => Promise ``` Delete a bundle from the app. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `DeleteBundleOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### downloadBundle(...) ``` downloadBundle(options: DownloadBundleOptions) => Promise ``` Download a bundle. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `DownloadBundleOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### fetchChannels(...) ``` fetchChannels(options?: FetchChannelsOptions | undefined) => Promise ``` Fetch channels from [Capawesome Cloud](https://capawesome.io/cloud/). This is primarily intended for development and QA purposes. It allows you to retrieve a list of available channels so you can dynamically switch between them using `setChannel(...)`. **Attention**: Only works for apps with public channels enabled. If channels are private, they can still be set using `setChannel(...)` but won't be returned by this method. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `FetchChannelsOptions` | **Returns:** `Promise` **Since:** 8.2.0 ______________________________________________________________________ ### fetchLatestBundle(...) ``` fetchLatestBundle(options?: FetchLatestBundleOptions | undefined) => Promise ``` Fetch the latest bundle using the [Capawesome Cloud](https://capawesome.io/cloud/). Only available on Android and iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `FetchLatestBundleOptions` | **Returns:** `Promise` **Since:** 6.6.0 ______________________________________________________________________ ### getBlockedBundles() ``` getBlockedBundles() => Promise ``` Get all blocked bundle identifiers. Returns the list of bundle identifiers that were automatically blocked due to rollbacks when `autoBlockRolledBackBundles` is enabled. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getBundles() ``` getBundles() => Promise ``` Get all identifiers of bundles that have been downloaded. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### getChannel() ``` getChannel() => Promise ``` Get the channel that is used for the update. The channel is resolved in the following order (highest priority first): 1. `setChannel()` (SharedPreferences on Android / UserDefaults on iOS) 1. Native config (`CapawesomeLiveUpdateDefaultChannel` in `Info.plist` on iOS or `capawesome_live_update_default_channel` in `strings.xml` on Android) 1. Capacitor config `defaultChannel` **Note**: The `channel` parameter of `sync()` takes the highest priority but is not persisted and therefore not returned by this method. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### getConfig() ``` getConfig() => Promise ``` Get the runtime configuration. Returns the current plugin configuration including any runtime overrides set via `setConfig()`. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getDownloadedBundles() ``` getDownloadedBundles() => Promise ``` Get all identifiers of bundles that have been downloaded. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getCurrentBundle() ``` getCurrentBundle() => Promise ``` Get the bundle identifier of the current bundle. The current bundle is the bundle that is currently used by the app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.7.0 ______________________________________________________________________ ### getCustomId() ``` getCustomId() => Promise ``` Get the custom identifier of the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### getDeviceId() ``` getDeviceId() => Promise ``` Get the unique device identifier. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### isSyncing() ``` isSyncing() => Promise ``` Check whether a sync operation is currently in progress. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getNextBundle() ``` getNextBundle() => Promise ``` Get the bundle identifier of the next bundle. The next bundle is the bundle that will be used after calling `reload()` or restarting the app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.7.0 ______________________________________________________________________ ### getVersionCode() ``` getVersionCode() => Promise ``` Get the version code of the app. On **Android**, this is the `versionCode` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### getVersionName() ``` getVersionName() => Promise ``` Get the version name of the app. On **Android**, this is the `versionName` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### ready() ``` ready() => Promise ``` Notify the plugin that the app is ready to use and no rollback is needed. **Attention**: This method should be called as soon as the app is ready to use to prevent the app from being reset to the default bundle. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### reload() ``` reload() => Promise ``` Reload the app to apply the new bundle. Only available on Android and iOS. **Since:** 5.0.0 ______________________________________________________________________ ### reset() ``` reset() => Promise ``` Reset the app to the default bundle. Call `reload()` or restart the app to apply the changes. Only available on Android and iOS. **Since:** 5.0.0 ______________________________________________________________________ ### resetConfig() ``` resetConfig() => Promise ``` Reset the runtime configuration to the values from the Capacitor config file. This clears any runtime configuration set via `setConfig()`. The changes take effect immediately. Only available on Android and iOS. **Since:** 7.4.0 ______________________________________________________________________ ### setChannel(...) ``` setChannel(options: SetChannelOptions) => Promise ``` Set the channel to use for the update. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetChannelOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### setConfig(...) ``` setConfig(options: SetConfigOptions) => Promise ``` Set the runtime configuration. This allows updating plugin configuration options at runtime. The changes are persisted across app restarts and take effect immediately. **Important:** Runtime configuration is automatically reset to default values whenever the native app is updated to a new version. This ensures that configuration from previous versions doesn't persist after an app update. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetConfigOptions` | **Since:** 7.4.0 ______________________________________________________________________ ### setCustomId(...) ``` setCustomId(options: SetCustomIdOptions) => Promise ``` Set the custom identifier of the device. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetCustomIdOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### setNextBundle(...) ``` setNextBundle(options: SetNextBundleOptions) => Promise ``` Set the next bundle to use for the app. Call `reload()` or restart the app to apply the changes. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `SetNextBundleOptions` | **Since:** 6.7.0 ______________________________________________________________________ ### sync(...) ``` sync(options?: SyncOptions | undefined) => Promise ``` Automatically download and set the latest bundle for the app using the [Capawesome Cloud](https://capawesome.io/cloud/). Call `reload()` or restart the app to apply the changes. Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `SyncOptions` | **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### addListener('downloadBundleProgress', ...) ``` addListener(eventName: 'downloadBundleProgress', listenerFunc: DownloadBundleProgressListener) => Promise ``` Listen for the download progress of a bundle. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------- | | **`eventName`** | `'downloadBundleProgress'` | | **`listenerFunc`** | `DownloadBundleProgressListener` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### addListener('nextBundleSet', ...) ``` addListener(eventName: 'nextBundleSet', listenerFunc: NextBundleSetListener) => Promise ``` Listen for when a bundle is set as the next bundle. This event is triggered whenever a bundle is set to be used on the next app restart, either through automatic updates or manual calls to `setNextBundle()`. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------- | | **`eventName`** | `'nextBundleSet'` | | **`listenerFunc`** | `NextBundleSetListener` | **Returns:** `Promise` **Since:** 7.3.0 ______________________________________________________________________ ### addListener('reloaded', ...) ``` addListener(eventName: 'reloaded', listenerFunc: ReloadedListener) => Promise ``` Listen for when the app is reloaded. This event is triggered after the `reload()` method is called and the app has been reloaded. **Note:** To verify whether an update was successfully applied after a reload, use the `ready()` method instead. The `ready()` method provides detailed information about the current bundle, previous bundle, and whether a rollback occurred. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------ | | **`eventName`** | `'reloaded'` | | **`listenerFunc`** | `ReloadedListener` | **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 7.2.0 ______________________________________________________________________ ### Interfaces #### DeleteBundleOptions | Prop | Type | Description | Since | | -------------- | -------- | ---------------------------------------------- | ----- | | **`bundleId`** | `string` | The unique identifier of the bundle to delete. | 5.0.0 | #### DownloadBundleOptions | Prop | Type | Description | Default | Since | | ------------------ | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | ------- | | **`artifactType`** | \`'manifest' | 'zip'\` | The artifact type of the bundle. | `'zip'` | | **`bundleId`** | `string` | The unique identifier of the bundle. **Attention**: The value `public` is reserved and cannot be used as a bundle identifier. | | 5.0.0 | | **`checksum`** | `string` | The checksum of the self-hosted bundle as a SHA-256 hash in hex format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type. | | 7.1.0 | | **`signature`** | `string` | The signature of the self-hosted bundle as a signed SHA-256 hash in base64 format to verify the integrity of the bundle. **Attention**: Only supported for the `zip` artifact type. | | 7.1.0 | | **`url`** | `string` | The URL of the bundle to download. For the `zip` artifact type, the URL must point to a ZIP file. For the `manifest` artifact type, the URL serves as the base URL to download the individual files. For example, if the URL is `https://example.com/download`, the plugin will download the file with the href `index.html` from `https://example.com/download?href=index.html`. To **verify the integrity** of the file, the server should return a `X-Checksum` header with the SHA-256 hash in hex format. To **verify the signature** of the file, the server should return a `X-Signature` header with the signed SHA-256 hash in base64 format. | | 5.0.0 | #### FetchChannelsResult | Prop | Type | Description | Since | | -------------- | ----------- | --------------------- | ----- | | **`channels`** | `Channel[]` | The list of channels. | 8.2.0 | #### Channel | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------- | ----- | | **`id`** | `string` | The unique identifier of the channel. | 8.2.0 | | **`name`** | `string` | The name of the channel. | 8.2.0 | #### FetchChannelsOptions | Prop | Type | Description | Default | Since | | ------------ | -------- | ----------------------------------------- | ------- | ----- | | **`limit`** | `number` | The maximum number of channels to return. | `50` | 8.2.0 | | **`offset`** | `number` | The number of channels to skip. | `0` | 8.2.0 | | **`query`** | `string` | The query to filter channels by name. | | 8.2.0 | #### FetchLatestBundleResult | Prop | Type | Description | Since | | ---------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | **`artifactType`** | \`'manifest' | 'zip'\` | The artifact type of the bundle. | | **`bundleId`** | \`string | null\` | The unique identifier of the latest bundle. On Capawesome Cloud, this is the ID of the app build artifact. If `null`, no bundle is available. | | **`checksum`** | `string` | The checksum of the latest bundle if the bundle is self-hosted. If the bundle is hosted on Capawesome Cloud, the checksum will be returned as response header when downloading the bundle. | 7.1.0 | | **`customProperties`** | `{ [key: string]: string; }` | Custom properties that are associated with the latest bundle. | 7.0.0 | | **`downloadUrl`** | `string` | The URL of the latest bundle to download. Pass this URL to the `downloadBundle(...)` method to download the bundle. | 6.7.0 | | **`signature`** | `string` | The signature of the latest bundle if the bundle is self-hosted. If the bundle is hosted on Capawesome Cloud, the signature will be returned as response header when downloading the bundle. | 7.1.0 | #### FetchLatestBundleOptions | Prop | Type | Description | Since | | ------------- | -------- | ---------------------------------------------------------------- | ----- | | **`channel`** | `string` | The name of the channel where the latest bundle is fetched from. | 6.7.0 | #### GetBlockedBundlesResult | Prop | Type | Description | Since | | --------------- | ---------- | ------------------------------------------------------ | ----- | | **`bundleIds`** | `string[]` | An array of unique identifiers of all blocked bundles. | 7.4.0 | #### GetBundlesResult | Prop | Type | Description | Since | | --------------- | ---------- | -------------------------------------------------------- | ----- | | **`bundleIds`** | `string[]` | An array of unique identifiers of all available bundles. | 5.0.0 | #### GetChannelResult | Prop | Type | Description | Since | | ------------- | -------- | ----------- | ------------------------------------------------------------------ | | **`channel`** | \`string | null\` | The channel name. If `null`, the app is using the default channel. | #### GetConfigResult | Prop | Type | Description | Since | | ------------------------ | -------- | -------------- | ------------------------------------------------------------------------ | | **`appId`** | \`string | null\` | The app ID used to identify the app. If `null`, no app ID is configured. | | **`autoUpdateStrategy`** | \`'none' | 'background'\` | The auto-update strategy for live updates. | #### GetDownloadedBundlesResult | Prop | Type | Description | Since | | --------------- | ---------- | --------------------------------------------------------- | ----- | | **`bundleIds`** | `string[]` | An array of unique identifiers of all downloaded bundles. | 7.4.0 | #### GetCurrentBundleResult | Prop | Type | Description | Since | | -------------- | -------- | ----------- | ----------------------------------------------------------------------------------------- | | **`bundleId`** | \`string | null\` | The unique identifier of the current bundle. If `null`, the default bundle is being used. | #### GetCustomIdResult | Prop | Type | Description | Since | | -------------- | -------- | ----------- | ---------------------------------------------------------------------------- | | **`customId`** | \`string | null\` | The custom identifier of the device. If `null`, no custom identifier is set. | #### GetDeviceIdResult | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`deviceId`** | `string` | The unique identifier of the device. On iOS, [`identifierForVendor`](https://developer.apple.com/documentation/uikit/uidevice/1620059-identifierforvendor) is used. The value of this property is the same for apps that come from the same vendor running on the same device. | 5.0.0 | #### IsSyncingResult | Prop | Type | Description | Since | | ------------- | --------- | -------------------------------------------------- | ----- | | **`syncing`** | `boolean` | Whether a sync operation is currently in progress. | 7.4.0 | #### GetNextBundleResult | Prop | Type | Description | Since | | -------------- | -------- | ----------- | -------------------------------------------------------------------------------------- | | **`bundleId`** | \`string | null\` | The unique identifier of the next bundle. If `null`, the default bundle is being used. | #### GetVersionCodeResult | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`versionCode`** | `string` | The version code of the app. On **Android**, this is the `versionCode` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleVersion` from the `Info.plist` file. | 5.0.0 | #### GetVersionNameResult | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`versionName`** | `string` | The version name of the app. On **Android**, this is the `versionName` from the `android/app/build.gradle` file. On **iOS**, this is the `CFBundleShortVersionString` from the `Info.plist` file. | 5.0.0 | #### ReadyResult | Prop | Type | Description | Since | | ---------------------- | --------- | ------------------------------------------------------- | --------------------------------------------------------------------------------------- | | **`previousBundleId`** | \`string | null\` | The identifier of the previous bundle used. If `null`, the default bundle was used. | | **`currentBundleId`** | \`string | null\` | The identifier of the current bundle used. If `null`, the default bundle is being used. | | **`rollback`** | `boolean` | Whether or not the app was reset to the default bundle. | 7.0.0 | #### SetChannelOptions | Prop | Type | Description | Since | | ------------- | -------- | ----------- | --------------------------------------------------- | | **`channel`** | \`string | null\` | The channel name. Set `null` to remove the channel. | #### SetConfigOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------- | ----------------------------------------------------------------------------------------------------- | | **`appId`** | \`string | null\` | The app ID used to identify the app. Set `null` to reset to the value from the Capacitor config file. | #### SetCustomIdOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------- | -------------------------------------------------------------------------------- | | **`customId`** | \`string | null\` | The custom identifier of the device. Set `null` to remove the custom identifier. | #### SetNextBundleOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------- | | **`bundleId`** | \`string | null\` | The unique identifier of the bundle to use. Set `null` to use the default bundle (same as calling `reset()`). | #### SyncResult | Prop | Type | Description | Since | | ------------------ | -------- | ----------- | ---------------------------------------------------------------------------------------------------------- | | **`nextBundleId`** | \`string | null\` | The identifier of the next bundle to use. If `null`, the app is up-to-date and no new bundle is available. | #### SyncOptions | Prop | Type | Description | Since | | ------------- | -------- | ---------------------------------------------------------------- | ----- | | **`channel`** | `string` | The name of the channel where the latest bundle is fetched from. | 6.7.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### DownloadBundleProgressEvent Event that is triggered when the download progress of a bundle changes. | Prop | Type | Description | Since | | --------------------- | -------- | ----------------------------------------------------------------------- | ----- | | **`bundleId`** | `string` | The unique identifier of the bundle that is being downloaded. | 7.0.0 | | **`downloadedBytes`** | `number` | The number of bytes that have been downloaded. | 7.0.0 | | **`progress`** | `number` | The progress of the download in percent as a value between `0` and `1`. | 7.0.0 | | **`totalBytes`** | `number` | The total number of bytes to download. | 7.0.0 | #### NextBundleSetEvent Event that is triggered when a bundle is set as the next bundle. | Prop | Type | Description | Since | | -------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------- | | **`bundleId`** | \`string | null\` | The unique identifier of the bundle that is set as the next bundle. If `null`, the default bundle will be used. | ### Type Aliases #### DownloadBundleProgressListener Listener for the download progress of a bundle. `(event: DownloadBundleProgressEvent): void` #### NextBundleSetListener Listener for when a bundle is set as the next bundle. `(event: NextBundleSetEvent): void` #### ReloadedListener Listener for when the app is reloaded. `(): void` ## Testing When testing the plugin, you must make sure that you do not use the [Live Reload](https://ionicframework.com/docs/cli/livereload) option, as in this case a development server is used to load the bundle and not the local file system. Therefore, simply start your app without the live reload option, for example with the following command: ``` npx ionic cap run android --open ``` If you want to **not** receive live updates to test other parts of your app, you can simply set a non-existent channel, for example: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { await LiveUpdate.sync({ channel: 'non-existent-channel' }); }; ``` This way, the app will check for updates, but no updates will be found. ## Limitations Live updates are only supported for [binary-compatible changes](https://capawesome.io/cloud/live-updates/faq/#what-are-binary-compatible-changes) (e.g. HTML, CSS, JavaScript). If you change native code, such as adding a new plugin, you need to resubmit your app to the app stores. For this reason, you must be careful to [restrict live updates to compatible native versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/) of your app. ## FAQ ### What is a bundle? A bundle is a set of web assets (HTML, CSS, JavaScript, etc.) that make up your app's user interface. The plugin manages two types of bundles: the **built-in bundle** that ships with the native app binary, and **live update bundles** that are downloaded at runtime to update the app without a native release. Each live update bundle has a unique **bundle ID** returned by the [`fetchLatestBundle(...)`](#fetchlatestbundle) method. On Capawesome Cloud, the bundle ID corresponds to the ID of the app build artifact. ### How do I set a channel? There are four ways to set a channel, listed from lowest to highest priority: 1. **Capacitor config**: Set the `defaultChannel` property in the [plugin configuration](#configuration). This is the simplest way to set a static default channel. 1. **Native config**: Set `CapawesomeLiveUpdateDefaultChannel` in `Info.plist` (iOS) or `capawesome_live_update_default_channel` in `strings.xml` (Android). This is useful for [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels) where the channel is tied to the build version. 1. **[`setChannel(...)`](#setchannel)**: Set the channel at runtime. The value is persisted across app restarts. 1. **[`sync(...)`](#sync)**: Pass a `channel` option to override the channel for a single sync call. This does **not** persist the channel. Each method overrides the ones above it. You can check the currently resolved channel by calling [`getChannel()`](#getchannel). Additionally, Capawesome Cloud supports [forced channel assignments](https://capawesome.io/blog/capawesome-cloud-forced-channel-assignments/) which allow you to override the channel for a specific device without any app code changes. Please note that forced channel assignments have a higher priority than all of the above methods and should be used with caution. ### Why can't I see my changes during development? As soon as you have installed a live update, the app will use the live update bundle and no longer the default bundle. So if you make local changes to your app and execute `npx cap run`, for example, these changes will apply to the default bundle, which is not currently in use. You then have three options to get back to the default bundle: 1. **Reset**: Call the [`reset()`](#reset) method to reset the app to the default bundle. 1. **Reinstall**: Reinstall the app to remove the live update bundle. 1. **Update**: Increase the native version code of your app so that Capacitor automatically resets to the default bundle. However, this is only a problem during development. It is not a problem in production, as Capacitor automatically resets to the default bundle after a native update. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/LICENSE). # @capawesome/capacitor-managed-configurations Capacitor plugin to access managed configuration settings. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-managed-configurations` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-managed-configurations npx cap sync ``` ### Android See [Define managed configurations](https://developer.android.com/work/managed-configurations#define-configuration) and follow the instructions to declare the app's managed configurations correctly. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { ManagedConfigurations } from '@capawesome/capacitor-managed-configurations'; const getString = async () => { const result = await ManagedConfigurations.getString({ key: 'server_url' }); return result.value; }; const getNumber = async () => { const result = await ManagedConfigurations.getNumber({ key: 'server_port' }); return result.value; }; const getBoolean = async () => { const result = await ManagedConfigurations.getBoolean({ key: 'download_on_cellular', }); return result.value; }; ``` ## API - [`getString(...)`](#getstring) - [`getNumber(...)`](#getnumber) - [`getBoolean(...)`](#getboolean) - [Interfaces](#interfaces) ### getString(...) ``` getString(options: GetOptions) => Promise> ``` Fetches the value associated with the given key, or `null` if no mapping exists for the given key. Only available on Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise>` ______________________________________________________________________ ### getNumber(...) ``` getNumber(options: GetOptions) => Promise> ``` Fetches the value associated with the given key, or `null` if no mapping exists for the given key. Only available on Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise>` ______________________________________________________________________ ### getBoolean(...) ``` getBoolean(options: GetOptions) => Promise> ``` Fetches the value associated with the given key, or `null` if no mapping exists for the given key. Only available on Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise>` ______________________________________________________________________ ### Interfaces #### GetResult | Prop | Type | Description | | ----------- | ---- | ----------- | | **`value`** | \`T | null\` | #### GetOptions | Prop | Type | Description | | --------- | -------- | --------------------------------------- | | **`key`** | `string` | Unique key for the configuration entry. | ## Test your implementation On **Android**, see [Set up device owner for testing](https://source.android.com/devices/tech/admin/testing-setup#set_up_the_device_owner_for_testing) and follow the instructions to set up a device owner testing environment. On **iOS**, you need to install the app as a [managed app](https://support.apple.com/de-de/guide/deployment-reference-ios/iorf4d72eded/web) with a MDM solution. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/managed-configurations/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/managed-configurations/LICENSE). ## Credits This plugin is based on the [Capacitor Managed Configurations](https://github.com/capawesome-team/capacitor-managed-configurations) plugin. Thanks to everyone who contributed to the project! # @capawesome-team/capacitor-media-session Capacitor plugin to interact with media controllers, volume keys and media buttons. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for media sessions. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🎮 **Media Controls**: Handle hardware media keys, lock screen controls, and notification controls. - 🎵 **Rich Metadata**: Display song title, artist, album, and artwork on lock screen and notifications. - 🎨 **Customizable Icon**: Configure the notification icon on Android to match your app's branding. - ▶️ **Action Handlers**: Support for play, pause, seek, next/previous track, and more. - 📍 **Position State**: Track and display playback position, duration, and playback rate. - 🔧 **Native APIs**: Uses MediaSession API on Android and MPNowPlayingInfoCenter on iOS for the best possible integration. - 🤝 **Compatibility**: Compatible with the [Audio Player](https://capawesome.io/plugins/audio-player/) plugin. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Demo | Android | iOS | Web | | ------- | --- | --- | | | | | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-media-session` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-media-session npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidMediaVersion` version of `androidx.media:media` (default: `1.7.1`) ## Configuration These configuration options are available: | Prop | Type | Description | Default | Since | | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- | | **smallIcon** | `string` | The name of the drawable resource to use as the small icon in the media notification. Only available on Android. The resource name should not include the `R.drawable.` prefix or file extension. If the resource is not found, the default icon is used. | `"ic_media_play"` | 8.1.0 | ### Examples In `capacitor.config.ts`: ``` import type { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { MediaSession: { smallIcon: 'ic_notification', }, }, }; export default config; ``` In `capacitor.config.json`: ``` { "plugins": { "MediaSession": { "smallIcon": "ic_notification" } } } ``` ### Android Custom Icon Setup To use a custom notification icon on Android: 1. Add your icon to `android/app/src/main/res/drawable/` (e.g., `ic_notification.png`) 1. Icon should be single-color white with transparent background for best display 1. Can use density-specific folders (`drawable-mdpi`, `drawable-hdpi`, etc.) 1. Configure the plugin in `capacitor.config.ts`: ``` const config: CapacitorConfig = { plugins: { MediaSession: { smallIcon: 'ic_notification', // Matches ic_notification.png }, }, }; ``` 1. Run: `npx cap sync` ## Usage ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; import { MediaSession, MediaSessionAction, MediaSessionPlaybackState } from '@capawesome-team/capacitor-media-session'; const setMetadata = async () => { await MediaSession.setMetadata({ title: 'Test Song', artist: 'My Awesome Artist', album: 'My Awesome Album', artwork: [ { src: 'https://example.com/cover-96x96.png', sizes: '96x96', type: 'image/png', }, { src: 'https://example.com/cover-512x512.png', sizes: '512x512', type: 'image/png', }, ], }); }; const registerActions = async () => { await MediaSession.registerActionHandler({ action: MediaSessionAction.Play }); await MediaSession.registerActionHandler({ action: MediaSessionAction.Pause }); await MediaSession.registerActionHandler({ action: MediaSessionAction.SeekBackward }); await MediaSession.registerActionHandler({ action: MediaSessionAction.SeekForward }); await MediaSession.registerActionHandler({ action: MediaSessionAction.SeekTo }); await MediaSession.registerActionHandler({ action: MediaSessionAction.Stop }); }; const setPlaybackState = async () => { await MediaSession.setPlaybackState({ playbackState: MediaSessionPlaybackState.Playing, }); }; const setPositionState = async () => { await MediaSession.setPositionState({ duration: 180, playbackRate: 1.0, position: 30, }); }; const addActionListener = () => { MediaSession.addListener('action', async (event) => { switch (event.action) { case MediaSessionAction.Play: await AudioPlayer.resume(); await MediaSession.setPlaybackState({ playbackState: MediaSessionPlaybackState.Playing, }); break; case MediaSessionAction.Pause: await AudioPlayer.pause(); await MediaSession.setPlaybackState({ playbackState: MediaSessionPlaybackState.Paused, }); break; case MediaSessionAction.SeekBackward: const { position: currentPos } = await AudioPlayer.getCurrentPosition(); const offsetMs = event.seekOffset ? event.seekOffset * 1000 : 10000; await AudioPlayer.seekTo({ position: Math.max(0, currentPos - offsetMs) }); break; case MediaSessionAction.SeekForward: const { position: pos } = await AudioPlayer.getCurrentPosition(); const { duration } = await AudioPlayer.getDuration(); const offset = event.seekOffset ? event.seekOffset * 1000 : 10000; await AudioPlayer.seekTo({ position: Math.min(duration, pos + offset) }); break; case MediaSessionAction.SeekTo: if (event.seekTime !== undefined) { await AudioPlayer.seekTo({ position: event.seekTime * 1000 }); } break; case MediaSessionAction.Stop: await AudioPlayer.stop(); await MediaSession.setPlaybackState({ playbackState: MediaSessionPlaybackState.None, }); break; } }); }; const removeActionListener = async () => { await MediaSession.unregisterActionHandler({ action: MediaSessionAction.Play }); await MediaSession.unregisterActionHandler({ action: MediaSessionAction.Pause }); await MediaSession.removeAllListeners(); }; ``` ## API - [`registerActionHandler(...)`](#registeractionhandler) - [`setCameraActive(...)`](#setcameraactive) - [`setMetadata(...)`](#setmetadata) - [`setMicrophoneActive(...)`](#setmicrophoneactive) - [`setPlaybackState(...)`](#setplaybackstate) - [`setPositionState(...)`](#setpositionstate) - [`unregisterActionHandler(...)`](#unregisteractionhandler) - [`addListener('action', ...)`](#addlisteneraction-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### registerActionHandler(...) ``` registerActionHandler(options: RegisterActionHandlerOptions) => Promise ``` Register a handler for a media session action. If the action handler is registered, the media session will respond to the action by calling the `action` event listener. If the action handler is not registered, the media session will not respond to the action. Make sure to unregister the action handler when it is no longer needed. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `RegisterActionHandlerOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setCameraActive(...) ``` setCameraActive(options: SetCameraActiveOptions) => Promise ``` Set the camera active state. Only available on Web. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SetCameraActiveOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setMetadata(...) ``` setMetadata(options: SetMetadataOptions) => Promise ``` Set the metadata for the media session. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetMetadataOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setMicrophoneActive(...) ``` setMicrophoneActive(options: SetMicrophoneActive) => Promise ``` Set the microphone active state. Only available on Web. | Param | Type | | ------------- | --------------------- | | **`options`** | `SetMicrophoneActive` | **Since:** 0.0.1 ______________________________________________________________________ ### setPlaybackState(...) ``` setPlaybackState(options: SetPlaybackStateOptions) => Promise ``` Set the playback state. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SetPlaybackStateOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setPositionState(...) ``` setPositionState(options: SetPositionStateOptions) => Promise ``` Set the position state. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SetPositionStateOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### unregisterActionHandler(...) ``` unregisterActionHandler(options: UnregisterActionHandlerOptions) => Promise ``` Unregister a handler for a media session action. If the action handler is unregistered, the media session will no longer respond to the action and the `action` event listener will no longer be called for the specific action. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `UnregisterActionHandlerOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### addListener('action', ...) ``` addListener(eventName: 'action', listenerFunc: (event: ActionEvent) => void) => Promise ``` | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'action'` | | **`listenerFunc`** | `(event: ActionEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### RegisterActionHandlerOptions | Prop | Type | Description | Since | | ------------ | -------------------- | --------------------- | ----- | | **`action`** | `MediaSessionAction` | The action to handle. | 0.0.1 | #### SetCameraActiveOptions | Prop | Type | Description | Since | | ------------ | --------- | ------------------------------------ | ----- | | **`active`** | `boolean` | Whether or not the camera is active. | 0.0.1 | #### SetMetadataOptions | Prop | Type | Description | Since | | ------------- | ------------------------ | ------------------------------ | ----- | | **`album`** | `string` | | 0.0.1 | | **`artist`** | `string` | | 0.0.1 | | **`artwork`** | `MediaMetadataArtwork[]` | Only available on iOS and Web. | 0.0.1 | | **`title`** | `string` | | 0.0.1 | #### MediaMetadataArtwork | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------------------------- | ----- | | **`sizes`** | `string` | The size of the artwork. | 0.0.1 | | **`src`** | `string` | The URL from which the user agent fetches the image's data. | 0.0.1 | | **`type`** | `string` | The MIME type hint for the user agent. | 0.0.1 | #### SetMicrophoneActive | Prop | Type | Description | Since | | ------------ | --------- | ---------------------------------------- | ----- | | **`active`** | `boolean` | Whether or not the microphone is active. | 0.0.1 | #### SetPlaybackStateOptions | Prop | Type | Description | Since | | ------------------- | --------------------------- | -------------------------- | ----- | | **`playbackState`** | `MediaSessionPlaybackState` | The playback state to set. | 0.0.1 | #### SetPositionStateOptions | Prop | Type | Description | Since | | ------------------ | -------- | --------------------------------------------- | ----- | | **`duration`** | `number` | The duration of the media in seconds. | 0.0.1 | | **`playbackRate`** | `number` | The playback rate of the media. | 0.0.1 | | **`position`** | `number` | The current position of the media in seconds. | 0.0.1 | #### UnregisterActionHandlerOptions | Prop | Type | Description | Since | | ------------ | -------------------- | ------------------------- | ----- | | **`action`** | `MediaSessionAction` | The action to unregister. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ActionEvent | Prop | Type | Description | Since | | ---------------- | -------------------- | ------------------------------------------ | ----- | | **`action`** | `MediaSessionAction` | The action that was handled. | 0.0.1 | | **`fastSeek`** | `boolean` | Whether or not the action was a fast seek. | 0.0.1 | | **`seekOffset`** | `number` | The offset in seconds to seek to. | 0.0.1 | | **`seekTime`** | `number` | The time in seconds to seek to. | 0.0.1 | ### Enums #### MediaSessionAction | Members | Value | Description | Since | | --------------------------- | ---------------------------- | ---------------------- | ----- | | **`Play`** | `'PLAY'` | | 0.0.1 | | **`Pause`** | `'PAUSE'` | | 0.0.1 | | **`SeekBackward`** | `'SEEK_BACKWARD'` | | 0.0.1 | | **`SeekForward`** | `'SEEK_FORWARD'` | | 0.0.1 | | **`PreviousTrack`** | `'PREVIOUS_TRACK'` | | 0.0.1 | | **`NextTrack`** | `'NEXT_TRACK'` | | 0.0.1 | | **`SkipAd`** | `'SKIP_AD'` | Only available on Web. | 0.0.1 | | **`Stop`** | `'STOP'` | | 0.0.1 | | **`SeekTo`** | `'SEEK_TO'` | | 0.0.1 | | **`ToggleMicrophone`** | `'TOGGLE_MICROPHONE'` | Only available on Web. | 0.0.1 | | **`ToggleCamera`** | `'TOGGLE_CAMERA'` | Only available on Web. | 0.0.1 | | **`ToggleScreenShare`** | `'TOGGLE_SCREEN_SHARE'` | Only available on Web. | 0.0.1 | | **`HangUp`** | `'HANG_UP'` | Only available on Web. | 0.0.1 | | **`PreviousSlide`** | `'PREVIOUS_SLIDE'` | Only available on Web. | 0.0.1 | | **`NextSlide`** | `'NEXT_SLIDE'` | Only available on Web. | 0.0.1 | | **`EnterPictureInPicture`** | `'ENTER_PICTURE_IN_PICTURE'` | Only available on Web. | 0.0.1 | | **`VoiceActivity`** | `'VOICE_ACTIVITY'` | Only available on Web. | 0.0.1 | #### MediaSessionPlaybackState | Members | Value | Since | | ------------- | ----------- | ----- | | **`None`** | `'NONE'` | 0.0.1 | | **`Paused`** | `'PAUSED'` | 0.0.1 | | **`Playing`** | `'PLAYING'` | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/media-session/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/media-session/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/media-session/LICENSE). # @capawesome-team/capacitor-nfc Capacitor plugin for NFC tag reading, writing, and emulation. Supports Android, iOS, and Web with advanced features like HCE and raw command handling. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for NFC. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🔄 **NDEF**: Read and write NFC Data Exchange Format (NDEF) messages. - 📳 **HCE**: Emulate an NFC card that other devices can interact with. - ⌘ **Raw Commands**: Send raw commands to an NFC tag and receive the response. - 🛠️ **Utils**: Utility functions to make your life easier. - ⚔️ **Battle-Tested**: Used in more than 500 projects. - 📚 **Documentation**: Comprehensive documentation to help you get started. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-nfc-demo). | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor NFC Plugin](https://capawesome.io/blog/announcing-the-capacitor-nfc-plugin/) - [Exploring the Capacitor NFC API](https://capawesome.io/blog/exploring-the-capacitor-nfc-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-nfc` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-nfc npx cap sync ``` ### Android #### Features Add the following element to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` Set the `android:required` attribute to `true` if your app can't function, or isn't designed to function, when NFC is not available on the device. If your app can function without NFC, set the `android:required` attribute to `false`. This will allow your app to be installed on devices that do not support NFC. #### Intent Filter If you want to launch your app through an NFC tag, please take a look at the [Android documentation](https://developer.android.com/guide/topics/connectivity/nfc/nfc#dispatching). There you will find which changes you have to apply to your `AndroidManifest.xml` ([example](https://developer.android.com/guide/topics/connectivity/nfc/nfc#ndef-disc)). #### Services To be able to use Host Card Emulation (HCE), you also need to add the following service **inside** the `application` tag in your `AndroidManifest.xml` (usually `android/app/src/main/AndroidManifest.xml`): ``` ``` This meta-data tag points to an `apduservice.xml` file. The following is an example of such a file with a single AID group declaration containing two proprietary AIDs: ``` ``` You can find more information about this in the [Android documentation](https://developer.android.com/develop/connectivity/nfc/hce#manifest-declaration). #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Entitlements Ensure `Near Field Communication Tag Reading` and `NFC Scan` capabilities have been enabled in your application in Xcode. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. This will also create or update the `ios/App/App/App.entitlements` file. Make sure it contains the following key to allow reading NFC tags: ``` com.apple.developer.nfc.readersession.formats NDEF TAG ``` #### Privacy Descriptions Add the `NFCReaderUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why the app needs to use NFC: ``` NFCReaderUsageDescription The app enables the reading and writing of various NFC tags. ``` #### Universal Links If you want to launch your app through an NFC tag, please take a look at the [Core NFC documentation](https://developer.apple.com/documentation/corenfc/adding_support_for_background_tag_reading#3032598). The NFC tag requires a [URI record](https://w3c.github.io/web-nfc/#uri-record) (see [`createNdefUriRecord(...)`](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/nfc/docs/utils#createndefurirecord)) that must contain either a universal link (see [Deep Linking with Universal and App Links](https://capacitorjs.com/docs/guides/deep-links)) or a [supported URL scheme](https://developer.apple.com/documentation/corenfc/adding_support_for_background_tag_reading#3032600). ## Configuration No configuration required for this plugin. ## Usage ``` import { Nfc, NfcUtils, NfcTagTechType } from '@capawesome-team/capacitor-nfc'; import { Capacitor } from '@capacitor/core'; const createNdefTextRecord = () => { const utils = new NfcUtils(); const { record } = utils.createNdefTextRecord({ text: 'Capacitor NFC Plugin' }); return record; }; const write = async () => { return new Promise((resolve) => { const record = createNdefTextRecord(); Nfc.addListener('nfcTagScanned', async (event) => { await Nfc.write({ message: { records: [record] } }); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; const read = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { await Nfc.stopScanSession(); resolve(event.nfcTag); }); Nfc.startScanSession(); }); }; const makeReadOnly = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { await Nfc.makeReadOnly(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; const readSignature = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { if (Capacitor.getPlatform() === 'android') { // 1. Connect to the tag. await Nfc.connect({ techType: NfcTagTechType.NfcA }); // 2. Send one or more commands to the tag and receive the response. const result = await Nfc.transceive({ data: [60, 0] }); // 3. Close the connection to the tag. await Nfc.close(); await Nfc.stopScanSession(); resolve(result); } else { // 1. Send one or more commands to the tag and receive the response. const result = await Nfc.transceive({ techType: NfcTagTechType.NfcA, data: [60, 0] }); await Nfc.stopScanSession(); resolve(result); } }); Nfc.startScanSession(); }); }; const erase = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { await Nfc.erase(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; const format = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { await Nfc.format(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; const isSupported = async () => { const { nfc } = await Nfc.isSupported(); return nfc; }; const isEnabled = async () => { const { isEnabled } = await Nfc.isEnabled(); return isEnabled; }; const openSettings = async () => { await Nfc.openSettings(); }; const checkPermissions = async () => { const { nfc } = await Nfc.checkPermissions(); return nfc; }; const requestPermissions = async () => { const { nfc } = await Nfc.requestPermissions(); return nfc; }; const removeAllListeners = async () => { await Nfc.removeAllListeners(); }; ``` ### Advanced #### HCE Host Card Emulation (HCE) allows your device to emulate an NFC card that other devices can interact with. The first example below demonstrates how to send APDU commands from an NFC reader using the `transceive(...)` method: ``` import { Nfc, NfcTagTechType } from '@capawesome-team/capacitor-nfc'; import { Capacitor } from '@capacitor/core'; const sendApduCommands = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { // Define APDU commands const selectApdu = [0x00, 0xA4, 0x04, 0x00, 0x07, 0xF0, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x00]; const customApdu = [0x00, 0x01, 0x00, 0x00, 0x00]; // Connect to the tag (Android only) if (Capacitor.getPlatform() === 'android') { await Nfc.connect({ techType: NfcTagTechType.IsoDep }); } // Send SELECT APDU command const selectResponse = await Nfc.transceive({ techType: NfcTagTechType.Iso7816, data: selectApdu }); // Send custom APDU command const customResponse = await Nfc.transceive({ techType: NfcTagTechType.Iso7816, data: customApdu }); // Close the connection (Android only) if (Capacitor.getPlatform() === 'android') { await Nfc.close(); } // Stop the scan session await Nfc.stopScanSession(); resolve(); }); // Start the scan session Nfc.startScanSession(); }); }; ``` The second example below demonstrates how to respond to APDU commands sent by an NFC reader using the `commandReceived` event listener and the `respond(...)` method: ``` import { Nfc } from '@capawesome-team/capacitor-nfc'; const respondToApduCommands = async () => { // Listen for incoming APDU commands Nfc.addListener('commandReceived', async (event) => { // Parse the incoming command const command = event.data; // Check if it's a SELECT command if (command[0] === 0x00 && command[1] === 0xA4 && command[2] === 0x04 && command[3] === 0x00) { // Respond with success status (0x9000) const response = [0x90, 0x00]; await Nfc.respond({ data: response }); } // Check for custom command else if (command[0] === 0x00 && command[1] === 0x01) { // Respond with custom data const response = [0x48, 0x65, 0x6C, 0x6C, 0x6F, 0x90, 0x00]; // "Hello" + success status await Nfc.respond({ data: response }); } // Unknown command else { // Respond with error status (0x6D00 - Instruction not supported) const response = [0x6D, 0x00]; await Nfc.respond({ data: response }); } }); }; ``` ## API - [`startScanSession(...)`](#startscansession) - [`stopScanSession(...)`](#stopscansession) - [`write(...)`](#write) - [`respond(...)`](#respond) - [`makeReadOnly()`](#makereadonly) - [`erase()`](#erase) - [`format()`](#format) - [`transceive(...)`](#transceive) - [`connect(...)`](#connect) - [`close()`](#close) - [`isAvailable()`](#isavailable) - [`isSupported()`](#issupported) - [`isEnabled()`](#isenabled) - [`openSettings()`](#opensettings) - [`getAntennaInfo()`](#getantennainfo) - [`setAlertMessage(...)`](#setalertmessage) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('commandReceived', ...)`](#addlistenercommandreceived-) - [`addListener('nfcLinkDeactivated', ...)`](#addlistenernfclinkdeactivated-) - [`addListener('nfcTagScanned', ...)`](#addlistenernfctagscanned-) - [`addListener('scanSessionCanceled', ...)`](#addlistenerscansessioncanceled-) - [`addListener('scanSessionError', ...)`](#addlistenerscansessionerror-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### startScanSession(...) ``` startScanSession(options?: StartScanSessionOptions | undefined) => Promise ``` Start a scan session. Only one session can be active at a time. Stop the session as soon as you are done using `stopScanSession(...)`. On iOS, this will trigger the NFC Reader Session alert. | Param | Type | | ------------- | ------------------------- | | **`options`** | `StartScanSessionOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### stopScanSession(...) ``` stopScanSession(options?: StopScanSessionOptions | undefined) => Promise ``` Stop the active scan session. | Param | Type | | ------------- | ------------------------ | | **`options`** | `StopScanSessionOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### write(...) ``` write(options: WriteOptions) => Promise ``` Write to a NFC tag. This method must be called from within a `nfcTagScanned` handler. | Param | Type | | ------------- | -------------- | | **`options`** | `WriteOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### respond(...) ``` respond(options: RespondOptions) => Promise ``` Send a response APDU back to the remote device. Only available on Android. | Param | Type | | ------------- | ---------------- | | **`options`** | `RespondOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### makeReadOnly() ``` makeReadOnly() => Promise ``` Make a NFC tag readonly. This method must be called from within a `nfcTagScanned` handler. **Attention:** This is permanent and can not be undone. **Since:** 0.0.1 ______________________________________________________________________ ### erase() ``` erase() => Promise ``` Erase the NFC tag by writing an empty NDEF message. This method must be called from within a `nfcTagScanned` handler. **Since:** 0.3.0 ______________________________________________________________________ ### format() ``` format() => Promise ``` Format the NFC tag as NDEF and write an empty NDEF message. This method must be called from within a `nfcTagScanned` handler. Only available on Android. **Since:** 0.3.0 ______________________________________________________________________ ### transceive(...) ``` transceive(options: TransceiveOptions) => Promise ``` Send raw command to the tag and receive the response. This method must be called from within a `nfcTagScanned` handler. On **Android**, the tag must be connected with `connect()` first. ⚠️ **Experimental:** This method could not be tested extensively yet. Please let us know if you discover any issues! ⚠️ **Attention**: A bad command can damage the tag forever. Please read the Android and iOS documentation linked below first. More information on how to use this method on **Android**: https://bit.ly/3ywSkvT More information on how to use this method on **iOS** with... - ISO 15693-3: https://apple.co/3Lliaum - FeliCa: https://apple.co/3LpvRs6 Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `TransceiveOptions` | **Returns:** `Promise` **Since:** 0.3.0 ______________________________________________________________________ ### connect(...) ``` connect(options: ConnectOptions) => Promise ``` Connect to the tag and enable I/O operations. This method must be called from within a `nfcTagScanned` handler. Only available on Android. | Param | Type | | ------------- | ---------------- | | **`options`** | `ConnectOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### close() ``` close() => Promise ``` Close the connection to the tag. This method must be called from within a `nfcTagScanned` handler. Only available on Android. **Since:** 6.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Returns whether or not NFC is available. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### isSupported() ``` isSupported() => Promise ``` Returns whether or not NFC is supported. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Returns whether or not NFC is enabled. On **iOS** and **Web**, this method reflects whether NFC reading is available on the device. iOS does not expose a global NFC on/off setting. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Opens the NFC device settings so that the user can enable or disable NFC. Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### getAntennaInfo() ``` getAntennaInfo() => Promise ``` Returns information regarding Nfc antennas on the device such as their relative positioning on the device. Only available on Android. **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### setAlertMessage(...) ``` setAlertMessage(options: SetAlertMessageOptions) => Promise ``` Set a custom message, which is displayed in the NFC Reader Session alert. Only available on iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SetAlertMessageOptions` | **Since:** 6.2.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permission for reading and writing NFC tags. This method is only needed on Web. On Android and iOS, `granted` is always returned. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission for reading and writing NFC tags. This method is only needed on Web. On Android and iOS, `granted` is always returned. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('commandReceived', ...) ``` addListener(eventName: 'commandReceived', listenerFunc: (event: CommandReceivedEvent) => void) => Promise ``` Called whenever a NFC reader sends an Application Protocol Data Unit (APDU). Only available on Android. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'commandReceived'` | | **`listenerFunc`** | `(event: CommandReceivedEvent) => void` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### addListener('nfcLinkDeactivated', ...) ``` addListener(eventName: 'nfcLinkDeactivated', listenerFunc: (event: NfcLinkDeactivatedEvent) => void) => Promise ``` Called when the NFC link has been deactivated or lost. Only available on Android. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'nfcLinkDeactivated'` | | **`listenerFunc`** | `(event: NfcLinkDeactivatedEvent) => void` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### addListener('nfcTagScanned', ...) ``` addListener(eventName: 'nfcTagScanned', listenerFunc: (event: NfcTagScannedEvent) => void) => Promise ``` Called when a new NFC tag is scanned. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'nfcTagScanned'` | | **`listenerFunc`** | `(event: NfcTagScannedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('scanSessionCanceled', ...) ``` addListener(eventName: 'scanSessionCanceled', listenerFunc: () => void) => Promise ``` Called when the scan session was canceled. Only available on iOS. | Param | Type | | ------------------ | ----------------------- | | **`eventName`** | `'scanSessionCanceled'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('scanSessionError', ...) ``` addListener(eventName: 'scanSessionError', listenerFunc: (event: ScanSessionErrorEvent) => void) => Promise ``` Called when an error occurs during the scan session. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'scanSessionError'` | | **`listenerFunc`** | `(event: ScanSessionErrorEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### StartScanSessionOptions | Prop | Type | Description | Default | Since | | ----------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------- | ----- | | **`alertMessage`** | `string` | A custom message, which is displayed in the NFC Reader Session alert. Only available on iOS. | | 0.0.1 | | **`compatibilityMode`** | `boolean` | Whether or not to use the [`NFCNDEFReaderSession`](https://developer.apple.com/documentation/corenfc/nfcndefreadersession). **Attention:** This mode only supports reading NDEF tags. Only available on iOS. | `false` | 6.4.0 | | **`techTypes`** | `NfcTagTechType[]` | The NFC tag technologies to filter on in this session. Only available on Android. | | 0.0.1 | | **`mimeTypes`** | `string[]` | Mime types to filter on in this session. Only available on Android. | | 0.0.1 | | **`pollingOptions`** | `PollingOption[]` | Type of tags to detect during a polling sequence. Only available on iOS. | `[PollingOption.iso14443, PollingOption.iso15693]` | 0.2.0 | #### StopScanSessionOptions | Prop | Type | Description | Since | | ------------------ | -------- | -------------------------------------------------------------------------------------------------- | ----- | | **`errorMessage`** | `string` | A custom error message, which is displayed in the NFC Reader Session alert. Only available on iOS. | 0.0.1 | #### WriteOptions | Prop | Type | Description | Since | | ------------- | ------------- | -------------------------- | ----- | | **`message`** | `NdefMessage` | The NDEF message to write. | 0.0.1 | #### NdefMessage | Prop | Type | Description | Since | | ------------- | -------------- | -------------------------------- | ----- | | **`records`** | `NdefRecord[]` | The records of the NDEF message. | 0.0.1 | #### NdefRecord | Prop | Type | Description | Since | | ------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------ | ----- | | **`id`** | `number[]` | The record identifier as byte array. | 0.0.1 | | **`payload`** | `number[]` | The payload field data as byte array. | 0.0.1 | | **`tnf`** | `TypeNameFormat` | The record type name format which indicates the structure of the value of the `type` field. | 0.0.1 | | **`type`** | `number[]` | The type of the record payload. This should be used in conjunction with the `tnf` field to determine the payload format. | 0.0.1 | #### RespondOptions | Prop | Type | Description | Since | | ---------- | ---------- | -------------- | ----- | | **`data`** | `number[]` | Bytes to send. | 6.3.0 | #### TransceiveResult | Prop | Type | Description | Since | | -------------- | ---------- | --------------------------- | ----- | | **`response`** | `number[]` | Bytes received in response. | 0.3.0 | #### TransceiveOptions | Prop | Type | Description | Since | | -------------------------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`techType`** | `NfcTagTechType` | The NFC tag technology to connect with. Only available on iOS. | 0.3.0 | | **`data`** | `number[]` | Bytes to send. | 0.3.0 | | **`iso15693RequestFlags`** | `Iso15693RequestFlag[]` | The request flags for the NFC tag technology type `NfcV` (ISO 15693-3). Only available on iOS 14+ | 0.3.0 | | **`iso15693CommandCode`** | `number` | The custom command code defined by the IC manufacturer for the NFC tag technology type `NfcV` (ISO 15693-3). Valid range is 0xA0 to 0xDF inclusively, 0x23 and 0x24 are also supported. Only available on iOS 14+ | 0.3.0 | #### ConnectOptions | Prop | Type | Description | Since | | -------------- | ---------------- | ------------------------------------------------------------------ | ----- | | **`techType`** | `NfcTagTechType` | The NFC tag technology to connect with. Only available on Android. | 6.0.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------- | --------- | -------------------------------------------------------------------- | ----- | | **`hce`** | `boolean` | Whether or not Host Card Emulation (HCE) is available on the device. | 7.2.0 | | **`nfc`** | `boolean` | Whether or not NFC is available on the device. | 7.2.0 | #### IsSupportedResult | Prop | Type | Description | Since | | ----------------- | --------- | -------------------------------------------------------------------- | ----- | | **`isSupported`** | `boolean` | | 0.0.1 | | **`nfc`** | `boolean` | Whether or not NFC is supported on the device. | 6.3.0 | | **`hce`** | `boolean` | Whether or not Host Card Emulation (HCE) is supported on the device. | 6.3.0 | #### IsEnabledResult | Prop | Type | Since | | --------------- | --------- | ----- | | **`isEnabled`** | `boolean` | 0.0.1 | #### GetAntennaInfoResult | Prop | Type | Description | Since | | ----------------------- | ----------- | ----------------------------------------- | ----- | | **`availableAntennas`** | `Antenna[]` | The available NFC antennas on the device. | 6.1.0 | | **`deviceHeight`** | `number` | The height of the device in millimeters. | 6.1.0 | | **`deviceWidth`** | `number` | The width of the device in millimeters. | 6.1.0 | | **`isDeviceFoldable`** | `boolean` | Whether or not the device is foldable. | 6.1.0 | #### Antenna | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------------------- | ----- | | **`locationX`** | `number` | The location of the NFC antenna on the X axis in millimeters. | 6.1.0 | | **`locationY`** | `number` | The location of the NFC antenna on the Y axis in millimeters. | 6.1.0 | #### SetAlertMessageOptions | Prop | Type | Description | Since | | ------------- | -------- | ----------------------------------------------------------------------- | ----- | | **`message`** | `string` | The custom message, which is displayed in the NFC Reader Session alert. | 6.2.0 | #### PermissionResult | Prop | Type | Description | Since | | --------- | ----------------- | -------------------------------------------------- | ----- | | **`nfc`** | `PermissionState` | Permission state for reading and writing NFC tags. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### CommandReceivedEvent | Prop | Type | Description | Since | | ---------- | ---------- | -------------------------------------------- | ----- | | **`data`** | `number[]` | The command received from the remote device. | 6.3.0 | #### NfcLinkDeactivatedEvent | Prop | Type | Description | Since | | ------------ | -------------------- | ----------------------------------------- | ----- | | **`reason`** | `DeactivationReason` | The reason why the deactivation occurred. | 6.3.0 | #### NfcTagScannedEvent | Prop | Type | Description | Since | | ------------ | -------- | -------------------- | ----- | | **`nfcTag`** | `NfcTag` | The scanned NFC tag. | 0.0.1 | #### NfcTag | Prop | Type | Description | Since | | ---------------------- | ------------------ | -------------------------------------------------------------------------------------------------- | ----- | | **`atqa`** | `number[]` | The ATQA/SENS_RES bytes of an NFC A tag. Only available on Android. | 0.0.1 | | **`applicationData`** | `number[]` | The Application Data bytes from ATQB/SENSB_RES of an NFC B tag. Only available on Android and iOS. | 0.0.1 | | **`barcode`** | `number[]` | The barcode bytes of an NfcBarcode tag. Only available on Android. | 0.0.1 | | **`canMakeReadOnly`** | `boolean` | Whether the NDEF tag can be made read-only or not. Only available on Android. | 0.0.1 | | **`dsfId`** | `number[]` | The DSF ID bytes of an NFC V tag. Only available on Android. | 0.0.1 | | **`hiLayerResponse`** | `number[]` | The higher layer response bytes of an ISO-DEP tag. Only available on Android. | 0.0.1 | | **`historicalBytes`** | `number[]` | The historical bytes of an ISO-DEP tag. Only available on Android and iOS. | 0.0.1 | | **`id`** | `number[]` | The tag identifier (low level serial number) which is used for anti-collision and identification. | 0.0.1 | | **`isWritable`** | `boolean` | Whether the NDEF tag is writable or not. Only available on Android and iOS. | 0.0.1 | | **`manufacturer`** | `number[]` | The manufacturer bytes of an NFC F tag. Only available on Android and iOS. | 0.0.1 | | **`manufacturerCode`** | `number` | The Integrated Circuit (IC) manufacturer code of an NFC V tag. Only available on iOS. | 6.3.0 | | **`maxSize`** | `number` | The maximum NDEF message size in bytes. Only available on Android and iOS. | 0.0.1 | | **`message`** | `NdefMessage` | The NDEF-formatted message. | 0.0.1 | | **`protocolInfo`** | `number[]` | The Protocol Info bytes from ATQB/SENSB_RES of an NFC B tag. Only available on Android. | 0.0.1 | | **`responseFlags`** | `number[]` | The Response Flag bytes of an NFC V tag. Only available on Android. | 0.0.1 | | **`sak`** | `number[]` | The SAK/SEL_RES bytes of an NFC A tag. Only available on Android. | 0.0.1 | | **`systemCode`** | `number[]` | The System Code bytes of an NFC F tag. Only available on Android and iOS. | 0.0.1 | | **`techTypes`** | `NfcTagTechType[]` | The technologies available in this tag. Only available on Android and iOS. | 0.0.1 | | **`type`** | `NfcTagType` | The NDEF tag type. Only available on Android and iOS. | 0.0.1 | #### ScanSessionErrorEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 0.0.1 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### NfcTagTechType | Members | Value | Description | Since | | ---------------------- | --------------------- | ----------------------------------------------------------------------------- | ----- | | **`NfcA`** | `'NFC_A'` | The NFC-A (ISO 14443-3A) tag technology. Only available on Android. | 0.0.1 | | **`NfcB`** | `'NFC_B'` | The NFC-B (ISO 14443-3B) tag technology. Only available on Android. | 0.0.1 | | **`NfcF`** | `'NFC_F'` | The NFC-F (JIS 6319-4) tag technology. Only available on Android and iOS. | 0.0.1 | | **`NfcV`** | `'NFC_V'` | The NFC-V (ISO 15693) tag technology. Only available on Android and iOS. | 0.0.1 | | **`IsoDep`** | `'ISO_DEP'` | The ISO-DEP (ISO 14443-4) tag technology. Only available on Android. | 0.0.1 | | **`Iso7816`** | `'ISO_7816'` | The ISO 7816 tag technology. Only available on iOS. | 5.1.0 | | **`Ndef`** | `'NDEF'` | The NDEF tag technology. Only available on Android. | 0.0.1 | | **`MifareClassic`** | `'MIFARE_CLASSIC'` | The MIFARE Classic tag technology. Only available on Android. | 0.0.1 | | **`MifareDesfire`** | `'MIFARE_DESFIRE'` | The MIFARE Desfire tag technology. Only available on iOS. | 5.1.0 | | **`MifarePlus`** | `'MIFARE_PLUS'` | The MIFARE Plus tag technology. Only available on iOS. | 5.1.0 | | **`MifareUltralight`** | `'MIFARE_ULTRALIGHT'` | The MIFARE Ultralight tag technology. Only available on Android and iOS. | 0.0.1 | | **`NfcBarcode`** | `'NFC_BARCODE'` | The technology of a tag containing just a barcode. Only available on Android. | 0.0.1 | | **`NdefFormatable`** | `'NDEF_FORMATABLE'` | The NDEF formatable tag technology. Only available on Android. | 0.0.1 | #### PollingOption | Members | Value | Description | Since | | -------------- | ------------ | ------------------------------------------------------------- | ----- | | **`iso14443`** | `'iso14443'` | The option for detecting ISO 7816-compatible and MIFARE tags. | 0.2.0 | | **`iso15693`** | `'iso15693'` | The option for detecting ISO 15693 tags. | 0.2.0 | | **`iso18092`** | `'iso18092'` | The option for detecting FeliCa tags. | 0.2.0 | #### TypeNameFormat | Members | Value | Description | Since | | ----------------- | ----- | ------------------------------------------------------------------------------------------------------ | ----- | | **`Empty`** | `0` | An empty record with no type or payload. | 0.0.1 | | **`WellKnown`** | `1` | A predefined type defined in the RTD specification of the NFC Forum. | 0.0.1 | | **`MimeMedia`** | `2` | An Internet media type as defined in RFC 2046. | 0.0.1 | | **`AbsoluteUri`** | `3` | A URI as defined in RFC 3986. | 0.0.1 | | **`External`** | `4` | A user-defined value that is based on the rules of the NFC Forum Record Type Definition specification. | 0.0.1 | | **`Unknown`** | `5` | Type is unknown. | 0.0.1 | | **`Unchanged`** | `6` | Indicates the payload is an intermediate or final chunk of a chunked NDEF Record. | 0.0.1 | #### Iso15693RequestFlag | Members | Value | Since | | ------------------------- | ----------------------- | ----- | | **`address`** | `'address'` | 0.3.0 | | **`commandSpecificBit8`** | `'commandSpecificBit8'` | 0.3.0 | | **`dualSubCarriers`** | `'dualSubCarriers'` | 0.3.0 | | **`highDataRate`** | `'highDataRate'` | 0.3.0 | | **`option`** | `'option'` | 0.3.0 | | **`protocolExtension`** | `'protocolExtension'` | 0.3.0 | | **`select`** | `'select'` | 0.3.0 | #### DeactivationReason | Members | Value | Description | Since | | ---------------- | ----- | ----------------------------------------------------------------- | ----- | | **`LinkLoss`** | `0` | Indicates deactivation was due to the NFC link being lost. | 6.3.0 | | **`Deselected`** | `1` | Indicates deactivation was due to a different AID being selected. | 6.3.0 | #### NfcTagType | Members | Value | Description | Since | | ----------------------- | ----------------------- | ---------------------------------- | ----- | | **`NfcForumType1`** | `'NFC_FORUM_TYPE_1'` | Only available on Android. | 0.0.1 | | **`NfcForumType2`** | `'NFC_FORUM_TYPE_2'` | Only available on Android. | 0.0.1 | | **`NfcForumType3`** | `'NFC_FORUM_TYPE_3'` | Only available on Android and iOS. | 0.0.1 | | **`NfcForumType4`** | `'NFC_FORUM_TYPE_4'` | Only available on Android. | 0.0.1 | | **`MifareClassic`** | `'MIFARE_CLASSIC'` | Only available on Android. | 0.0.1 | | **`MifareDesfire`** | `'MIFARE_DESFIRE'` | | 0.0.1 | | **`MifarePlus`** | `'MIFARE_PLUS'` | Only available on Android. | 0.0.1 | | **`MifarePro`** | `'MIFARE_PRO'` | Only available on Android. | 0.0.1 | | **`MifareUltralight`** | `'MIFARE_ULTRALIGHT'` | Only available on Android. | 0.0.1 | | **`MifareUltralightC`** | `'MIFARE_ULTRALIGHT_C'` | Only available on Android. | 0.0.1 | ## Utils This plugin provides a utility class `NfcUtils` that can be used for various NFC-related operations, for example, creating NDEF records: ``` import { NfcUtils } from '@capawesome-team/capacitor-nfc'; const createNdefTextRecord = () => { const utils = new NfcUtils(); const { record } = utils.createNdefTextRecord({ text: 'Capacitor NFC Plugin' }); return record; }; ``` See [docs/utils/README.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/docs/utils/README.md) for more information. ## Troubleshooting ##### `The connection to service named com.apple.nfcd.service.corenfc was invalidated from this process` This error occurs on iOS when the required NFC capability is not added to the app. To fix this, add the `Near Field Communication Tag Reading` capability in Xcode under the `Signing & Capabilities` tab. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/LICENSE). # @capawesome-team/capacitor-oauth Capacitor plugin for communicating with OAuth 2.0 and OpenID Connect providers. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for OAuth. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🌐 **Providers**: Works with any OAuth 2.0 / OpenID Connect provider, including Auth0, Azure AD, Amazon Cognito, Okta and OneLogin. - 🔐 **PKCE**: Implements the Authorization Code flow with Proof Key for Code Exchange (PKCE). - 🔍 **Auto-discovery**: Automatically fetches endpoints via OpenID Connect discovery. - 🔄 **Token Refresh**: Refresh access tokens using a refresh token. - 🪪 **JWT Decoding**: Decode JWT ID tokens without verification. - 🪶 **Lightweight**: Just a single dependency and zero unnecessary bloat. - 🤝 **Compatibility**: Compatible with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/) plugin to securely store tokens. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.1.x | >=8.x.x | Active support | ## Demo | Android | iOS | Web | | ------- | --- | --- | | | | | ## Guides - [Announcing the Capacitor OAuth Plugin](https://capawesome.io/blog/announcing-the-capacitor-oauth-plugin/) - [How to Use Better Auth in Capacitor Apps](https://capawesome.io/blog/how-to-use-better-auth-in-capacitor-apps/) - [How to Sign in with Okta using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-okta-using-capacitor/) - [How to Sign in with Auth0 using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-auth0-using-capacitor/) - [How to Sign in with Azure Entra ID using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-azure-entra-id-using-capacitor/) - [Alternatives to Ionic Enterprise Plugins](https://capawesome.io/blog/alternatives-to-ionic-enterprise-plugins/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-oauth` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-oauth npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$appAuthVersion` version of `net.openid:appauth` (default: `0.11.1`) #### Redirect Scheme Add the following to your app's `build.gradle` file to configure the redirect scheme used by AppAuth: ``` android { defaultConfig { manifestPlaceholders = [appAuthRedirectScheme: "com.example.app"] } } ``` Replace `com.example.app` with the scheme of your redirect URI. #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ## Usage ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; import { Capacitor } from '@capacitor/core'; const login = async () => { // Sign in the user const result = await Oauth.login({ issuerUrl: 'https://accounts.google.com', clientId: 'YOUR_CLIENT_ID', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); console.log('Access token:', result.accessToken); console.log('ID token:', result.idToken); console.log('Refresh token:', result.refreshToken); // Store the tokens securely await SecurePreferences.set({ key: 'tokens', value: JSON.stringify(result), }); }; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== 'web') { return; } // Handle the redirect callback on web const result = await Oauth.handleRedirectCallback(); console.log('Access token:', result.accessToken); }; const refreshToken = async () => { const result = await Oauth.refreshToken({ issuerUrl: 'https://accounts.google.com', clientId: 'YOUR_CLIENT_ID', refreshToken: 'YOUR_REFRESH_TOKEN', }); console.log('New access token:', result.accessToken); }; const logout = async () => { await Oauth.logout({ issuerUrl: 'https://accounts.google.com', idToken: 'YOUR_ID_TOKEN', postLogoutRedirectUrl: 'com.example.app://oauth/logout', }); }; const decodeIdToken = async () => { const result = await Oauth.decodeIdToken({ token: 'YOUR_ID_TOKEN', }); console.log('Payload:', result.payload); }; ``` ## API - [`decodeIdToken(...)`](#decodeidtoken) - [`getAccessTokenExpirationDate(...)`](#getaccesstokenexpirationdate) - [`isAccessTokenAvailable(...)`](#isaccesstokenavailable) - [`isAccessTokenExpired(...)`](#isaccesstokenexpired) - [`isRefreshTokenAvailable(...)`](#isrefreshtokenavailable) - [`handleRedirectCallback()`](#handleredirectcallback) - [`login(...)`](#login) - [`logout(...)`](#logout) - [`refreshToken(...)`](#refreshtoken) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### decodeIdToken(...) ``` decodeIdToken(options: DecodeIdTokenOptions) => Promise ``` Decode a JWT ID token without verification. | Param | Type | | ------------- | ---------------------- | | **`options`** | `DecodeIdTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getAccessTokenExpirationDate(...) ``` getAccessTokenExpirationDate(options: GetAccessTokenExpirationDateOptions) => Promise ``` Get the access token expiration date as an ISO 8601 string. | Param | Type | | ------------- | ------------------------------------- | | **`options`** | `GetAccessTokenExpirationDateOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAccessTokenAvailable(...) ``` isAccessTokenAvailable(options: IsAccessTokenAvailableOptions) => Promise ``` Check if an access token is available (non-null and non-empty). | Param | Type | | ------------- | ------------------------------- | | **`options`** | `IsAccessTokenAvailableOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAccessTokenExpired(...) ``` isAccessTokenExpired(options: IsAccessTokenExpiredOptions) => Promise ``` Check if the access token has expired. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `IsAccessTokenExpiredOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isRefreshTokenAvailable(...) ``` isRefreshTokenAvailable(options: IsRefreshTokenAvailableOptions) => Promise ``` Check if a refresh token is available (non-null and non-empty). | Param | Type | | ------------- | -------------------------------- | | **`options`** | `IsRefreshTokenAvailableOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### handleRedirectCallback() ``` handleRedirectCallback() => Promise ``` Handle the redirect callback after a login or logout redirect on the web. Call this method on page load when the URL contains authorization response parameters. Only available on Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### login(...) ``` login(options: LoginOptions) => Promise ``` Start an OAuth 2.0 authorization code flow with PKCE. | Param | Type | | ------------- | -------------- | | **`options`** | `LoginOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### logout(...) ``` logout(options: LogoutOptions) => Promise ``` End the OAuth session by calling the end-session endpoint. Note that some providers (e.g. Microsoft Entra ID) may not redirect back to the app after logout and instead show a "You have signed out" page. In this case, the user has to close the browser manually which results in a `USER_CANCELED` error even though the logout was successful. | Param | Type | | ------------- | --------------- | | **`options`** | `LogoutOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### refreshToken(...) ``` refreshToken(options: RefreshTokenOptions) => Promise ``` Refresh the access token using a refresh token. | Param | Type | | ------------- | --------------------- | | **`options`** | `RefreshTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### DecodeIdTokenResult | Prop | Type | Description | Since | | ------------- | ------------------------- | ---------------------------------------------- | ----- | | **`header`** | `Record` | The decoded JWT header. | 0.1.0 | | **`payload`** | `Record` | The decoded JWT payload containing the claims. | 0.1.0 | #### DecodeIdTokenOptions | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------- | ----- | | **`token`** | `string` | The JWT ID token string to decode. | 0.1.0 | #### GetAccessTokenExpirationDateResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------- | ----- | | **`date`** | `string` | The access token expiration date as an ISO 8601 string. | 0.1.0 | #### GetAccessTokenExpirationDateOptions | Prop | Type | Description | Since | | ------------------------------- | -------- | ------------------------------------------------------- | ----- | | **`accessTokenExpirationDate`** | `number` | The access token expiration date in epoch milliseconds. | 0.1.0 | #### IsAccessTokenAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | --------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether the access token is non-null and non-empty. | 0.1.0 | #### IsAccessTokenAvailableOptions | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------- | ----- | | **`accessToken`** | `string` | The access token to check. | 0.1.0 | #### IsAccessTokenExpiredResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------- | ----- | | **`isExpired`** | `boolean` | Whether the access token has expired. | 0.1.0 | #### IsAccessTokenExpiredOptions | Prop | Type | Description | Since | | ------------------------------- | -------- | ------------------------------------------------------- | ----- | | **`accessTokenExpirationDate`** | `number` | The access token expiration date in epoch milliseconds. | 0.1.0 | #### IsRefreshTokenAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ---------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether the refresh token is non-null and non-empty. | 0.1.0 | #### IsRefreshTokenAvailableOptions | Prop | Type | Description | Since | | ------------------ | -------- | --------------------------- | ----- | | **`refreshToken`** | `string` | The refresh token to check. | 0.1.0 | #### LoginResult | Prop | Type | Description | Since | | ------------------------------- | -------- | ------------------------------------------------------- | ----- | | **`accessToken`** | `string` | The access token. | 0.1.0 | | **`accessTokenExpirationDate`** | `number` | The access token expiration date in epoch milliseconds. | 0.1.0 | | **`idToken`** | `string` | The JWT ID token (OpenID Connect). | 0.1.0 | | **`refreshToken`** | `string` | The refresh token. | 0.1.0 | | **`scope`** | `string` | The granted scopes as a space-delimited string. | 0.1.0 | | **`tokenType`** | `string` | The token type. | 0.1.0 | #### LoginOptions | Prop | Type | Description | Since | | --------------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`issuerUrl`** | `string` | The OpenID Connect issuer URL for auto-discovery. The plugin will fetch the OpenID Connect discovery document from `{issuerUrl}/.well-known/openid-configuration` to obtain the authorization and token endpoint URLs. Either `issuerUrl` or both `authorizationEndpoint` and `tokenEndpoint` must be provided. | 0.1.0 | | **`authorizationEndpoint`** | `string` | The authorization endpoint URL. Either `issuerUrl` or both `authorizationEndpoint` and `tokenEndpoint` must be provided. | 0.1.0 | | **`tokenEndpoint`** | `string` | The token endpoint URL. Either `issuerUrl` or both `authorizationEndpoint` and `tokenEndpoint` must be provided. | 0.1.0 | | **`clientId`** | `string` | The OAuth client ID. | 0.1.0 | | **`redirectUrl`** | `string` | The redirect URI to use after authentication. | 0.1.0 | | **`scopes`** | `string[]` | The OAuth scopes to request. | 0.1.0 | | **`loginHint`** | `string` | A hint to the authorization server about the user's identifier to pre-fill the login form. | 0.1.0 | | **`prompt`** | `string` | The prompt parameter to control the authorization server UI behavior. | 0.1.0 | | **`additionalParameters`** | `Record` | Additional parameters to include in the authorization request. | 0.1.0 | #### LogoutOptions | Prop | Type | Description | Since | | --------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`issuerUrl`** | `string` | The OpenID Connect issuer URL used to fetch the discovery document at `{issuerUrl}/.well-known/openid-configuration`. Either `issuerUrl` or `endSessionEndpoint` must be provided. | 0.1.0 | | **`endSessionEndpoint`** | `string` | The end-session endpoint URL. Either `issuerUrl` or `endSessionEndpoint` must be provided. | 0.1.0 | | **`idToken`** | `string` | The ID token hint for session identification. | 0.1.0 | | **`postLogoutRedirectUrl`** | `string` | The redirect URI to use after logout. | 0.1.0 | | **`additionalParameters`** | `Record` | Additional parameters to include in the end-session request. | 0.1.0 | #### RefreshTokenOptions | Prop | Type | Description | Since | | -------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`issuerUrl`** | `string` | The OpenID Connect issuer URL used to fetch the discovery document at `{issuerUrl}/.well-known/openid-configuration`. Either `issuerUrl` or `tokenEndpoint` must be provided. | 0.1.0 | | **`tokenEndpoint`** | `string` | The token endpoint URL. Either `issuerUrl` or `tokenEndpoint` must be provided. | 0.1.0 | | **`clientId`** | `string` | The OAuth client ID. | 0.1.0 | | **`refreshToken`** | `string` | The refresh token obtained from login. | 0.1.0 | | **`additionalParameters`** | `Record` | Additional parameters to include in the token refresh request. | 0.1.0 | ### Type Aliases #### HandleRedirectCallbackResult `LoginResult` #### RefreshTokenResult `LoginResult` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/oauth/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/oauth/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/oauth/LICENSE). # @capawesome-team/capacitor-pedometer Capacitor plugin to retrieve motion data, such as the number of steps and other information about the distance traveled. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for pedometer data. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🚶 **Motion Tracking**: Retrieve step count, distance, pace, cadence, and floor counting data. - 📊 **Real-time Updates**: Stream live pedometer data with event listeners for continuous monitoring. - 📅 **Historical Data**: Query pedometer measurements for specific time ranges and periods. - 🔍 **Feature Detection**: Check device capability for different pedometer features (steps, distance, floors, etc.). - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-pedometer` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-pedometer npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSMotionUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the user's contacts: ``` NSMotionUsageDescription The app needs to access the motion activity. ``` ## Usage ``` import { Pedometer } from '@capawesome-team/capacitor-pedometer'; const getMeasurement = async () => { const { measurement } = await Pedometer.getMeasurement(); console.log(measurement); }; const isAvailable = async () => { const { cadence, distance, floorCounting, pace, stepCounting } = await Pedometer.isAvailable(); console.log('Cadence available:', cadence); console.log('Distance available:', distance); console.log('Floor counting available:', floorCounting); console.log('Pace available:', pace); console.log('Step counting available:', stepCounting); }; const startMeasurementUpdates = async () => { Pedometer.addListener('measurement', (event) => { console.log('New measurement:', event); }); await Pedometer.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Pedometer.stopMeasurementUpdates(); }; const checkPermissions = async () => { const status = await Pedometer.checkPermissions(); console.log('Permission status:', status); }; const requestPermissions = async () => { const status = await Pedometer.requestPermissions(); console.log('Permission status after request:', status); }; ``` ## API - [`getMeasurement(...)`](#getmeasurement) - [`isAvailable()`](#isavailable) - [`startMeasurementUpdates()`](#startmeasurementupdates) - [`stopMeasurementUpdates()`](#stopmeasurementupdates) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('measurement', ...)`](#addlistenermeasurement-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getMeasurement(...) ``` getMeasurement(options?: GetMeasurementOptions | undefined) => Promise ``` Retrieve the pedometer data (for a specific time range). Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `GetMeasurementOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check which features are available on the device. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### startMeasurementUpdates() ``` startMeasurementUpdates() => Promise ``` Start receiving updates for the pedometer data. Only available on Android and iOS. **Since:** 7.0.0 ______________________________________________________________________ ### stopMeasurementUpdates() ``` stopMeasurementUpdates() => Promise ``` Stop receiving updates for the pedometer data. Only available on Android and iOS. **Since:** 7.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for the plugin. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permissions for the plugin. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### addListener('measurement', ...) ``` addListener(eventName: 'measurement', listenerFunc: (event: MeasurementEvent) => void) => Promise ``` Called when the pedometer data is updated. When the app is suspended, the delivery of updates stops temporarily. When the app is resumed, the updates will continue from where they left off. **Note:** The `startMeasurementUpdates` method must be called before this event can be received. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'measurement'` | | **`listenerFunc`** | `(event: Measurement) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 7.0.0 ______________________________________________________________________ ### Interfaces #### Measurement | Prop | Type | Description | Since | | ----------------------- | -------- | --------------------------------------------------------------------------------------- | ----- | | **`averageActivePace`** | `number` | The average pace of the user, measured in seconds per meter. Only available on iOS. | 7.0.0 | | **`currentCadence`** | `number` | The rate at which steps are taken, measured in steps per second. Only available on iOS. | 7.0.0 | | **`currentPace`** | `number` | The current pace of the user, measured in seconds per meter. Only available on iOS. | 7.0.0 | | **`distance`** | `number` | The estimated distance traveled by the user, measured in meters. Only available on iOS. | 7.0.0 | | **`end`** | `number` | The end date of the data in milliseconds since epoch. | 7.0.0 | | **`floorsAscended`** | `number` | The number of floors ascended by the user. Only available on iOS. | 7.0.0 | | **`floorsDescended`** | `number` | The number of floors descended by the user. Only available on iOS. | 7.0.0 | | **`numberOfSteps`** | `number` | The number of steps taken by the user. | 7.0.0 | | **`start`** | `number` | The start date of the data in milliseconds since epoch. | 7.0.0 | #### GetMeasurementOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`end`** | `number` | The end date for the query in milliseconds since epoch. On **iOS**, this option must be provided. Only available on iOS. | 7.0.0 | | **`start`** | `number` | The start date for the query in milliseconds since epoch. On **Android**, this is always set to the boot time of the device. On **iOS**, this option must be provided. Only available on iOS. | 7.0.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`cadence`** | `boolean` | Indicates whether cadence tracking is available on the device. On **Android**, this is always `false` because cadence tracking is not supported. | 7.0.0 | | **`distance`** | `boolean` | Indicates whether distance tracking is available on the device. On **Android**, this is always `false` because distance tracking is not supported. | 7.0.0 | | **`floorCounting`** | `boolean` | Indicates whether floor counting is available on the device. On **Android**, this is always `false` because floor counting is not supported. | 7.0.0 | | **`pace`** | `boolean` | Indicates whether pace tracking is available on the device. On **Android**, this is always `false` because pace tracking is not supported. | 7.0.0 | | **`stepCounting`** | `boolean` | Indicates whether step counting is available on the device. | 7.0.0 | #### PermissionStatus | Prop | Type | Description | Since | | ------------------------- | ----------------- | ---------------------------------------------------------- | ----- | | **`activityRecognition`** | `PermissionState` | The permission state for the activity recognition feature. | 7.0.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GetMeasurementResult `Measurement` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### MeasurementEvent `Measurement` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/pedometer/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/pedometer/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/pedometer/LICENSE). # @capawesome/capacitor-photo-editor Capacitor plugin that allows the user to edit a photo. ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-photo-editor` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-photo-editor npx cap sync ``` ### Android You need to specify the directories that contain the photos you want to edit. To specify the directories, start by creating the file `file_paths.xml` in the `res/xml/` subdirectory of your project (see [Android docs](https://developer.android.com/training/secure-file-sharing/setup-sharing#DefineMetaData)).\ This is an example: ``` ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | | ------- | | | ## Usage ``` import { PhotoEditor } from '@capawesome/capacitor-photo-editor'; const editPhoto = async () => { await PhotoEditor.editPhoto({ path: 'data/image.png' }); }; ``` ## API - [`editPhoto(...)`](#editphoto) - [Interfaces](#interfaces) ### editPhoto(...) ``` editPhoto(options: EditPhotoOptions) => Promise ``` Edit a photo at a given path. **Attention**: A suitable photo editing app must be installed (e.g. Google Photos) and the user should overwrite the image when saving so that the path to the image is not lost. Only available on Android. | Param | Type | | ------------- | ------------------ | | **`options`** | `EditPhotoOptions` | ______________________________________________________________________ ### Interfaces #### EditPhotoOptions | Prop | Type | Description | | ---------- | -------- | ----------------------------- | | **`path`** | `string` | The path of the file to edit. | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/photo-editor/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/photo-editor/LICENSE). ## Credits This plugin is based on the [Capacitor Photo Editor](https://github.com/capawesome-team/capacitor-photo-editor) plugin. Thanks to everyone who contributed to the project! # @capawesome/capacitor-posthog Unofficial Capacitor plugin for [PostHog](https://posthog.com/).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-posthog` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-posthog posthog-js npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxCoreKtxVersion` version of `androidx.core:core-ktx` (default: `1.13.1`) - `$posthogVersion` version of `com.posthog:posthog-android` (default: `3.27.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration | Prop | Type | Description | Default | Since | | --------------------------------------- | ---------------------- | ----------------------------------------------------------------------------------------- | ---------------------------- | ----- | | **`apiKey`** | `string` | The API key of your PostHog project. | | 7.1.0 | | **`apiHost`** | `string` | The API host of your PostHog instance or reverse proxy. | `'https://us.i.posthog.com'` | 8.3.0 | | **`host`** | `string` | The API host of your PostHog instance. Deprecated alias for `apiHost`. | `'https://us.i.posthog.com'` | 7.1.0 | | **`uiHost`** | `string` | The PostHog UI host used when `apiHost` points to a reverse proxy. Only available on Web. | | 8.3.0 | | **`enableSessionReplay`** | `boolean` | Whether to enable session recording automatically. | `false` | 7.3.0 | | **`sessionReplayConfig`** | `SessionReplayOptions` | Session recording configuration options. | | 7.3.0 | | **`captureApplicationLifecycleEvents`** | `boolean` | Whether to capture application lifecycle events. | `true` | 8.3.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "Posthog": { "apiKey": 'phc_g8wMenebiIQ1pYd5v9Vy7oakn6MczVKIsNG5ZHCspdy', "apiHost": 'https://eu.i.posthog.com', "host": 'https://eu.i.posthog.com', "uiHost": 'https://eu.posthog.com', "enableSessionReplay": undefined, "sessionReplayConfig": undefined, "captureApplicationLifecycleEvents": undefined } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { Posthog: { apiKey: 'phc_g8wMenebiIQ1pYd5v9Vy7oakn6MczVKIsNG5ZHCspdy', apiHost: 'https://eu.i.posthog.com', host: 'https://eu.i.posthog.com', uiHost: 'https://eu.posthog.com', enableSessionReplay: undefined, sessionReplayConfig: undefined, captureApplicationLifecycleEvents: undefined, }, }, }; export default config; ``` ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { Posthog } from '@capawesome/capacitor-posthog'; const alias = async () => { await Posthog.alias({ alias: 'new-distinct-id', }); }; const capture = async () => { await Posthog.capture({ event: 'event', properties: { key: 'value', }, }); }; const flush = async () => { await Posthog.flush(); }; const group = async () => { await Posthog.group({ type: 'group', key: 'key', groupProperties: { key: 'value', }, }); }; const identify = async () => { await Posthog.identify({ distinctId: 'distinct-id', userProperties: { key: 'value', }, }); }; const register = async () => { await Posthog.register({ key: 'super-property', value: 'super-value', }); }; const reset = async () => { await Posthog.reset(); }; const screen = async () => { await Posthog.screen({ screenTitle: 'screen', properties: { key: 'value', }, }); }; const setup = async () => { await Posthog.setup({ apiKey: 'YOUR_API_KEY', apiHost: 'https://eu.i.posthog.com', }); }; const unregister = async () => { await Posthog.unregister({ key: 'super-property', }); }; ``` ## API - [`alias(...)`](#alias) - [`capture(...)`](#capture) - [`flush()`](#flush) - [`getDistinctId()`](#getdistinctid) - [`getFeatureFlag(...)`](#getfeatureflag) - [`getFeatureFlagPayload(...)`](#getfeatureflagpayload) - [`group(...)`](#group) - [`identify(...)`](#identify) - [`isFeatureEnabled(...)`](#isfeatureenabled) - [`isOptOut()`](#isoptout) - [`optIn()`](#optin) - [`optOut()`](#optout) - [`register(...)`](#register) - [`reloadFeatureFlags()`](#reloadfeatureflags) - [`reset()`](#reset) - [`screen(...)`](#screen) - [`setup(...)`](#setup) - [`startSessionRecording()`](#startsessionrecording) - [`stopSessionRecording()`](#stopsessionrecording) - [`unregister(...)`](#unregister) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### alias(...) ``` alias(options: AliasOptions) => Promise ``` Assign another distinct ID to the current user. | Param | Type | | ------------- | -------------- | | **`options`** | `AliasOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### capture(...) ``` capture(options: CaptureOptions) => Promise ``` Capture an event. | Param | Type | | ------------- | ---------------- | | **`options`** | `CaptureOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### flush() ``` flush() => Promise ``` Flush all events in the queue. Only available on Android and iOS. **Since:** 6.0.0 ______________________________________________________________________ ### getDistinctId() ``` getDistinctId() => Promise ``` Get the current distinct ID. **Returns:** `Promise` **Since:** 8.2.0 ______________________________________________________________________ ### getFeatureFlag(...) ``` getFeatureFlag(options: GetFeatureFlagOptions) => Promise ``` Get the value of a feature flag. | Param | Type | | ------------- | ----------------------- | | **`options`** | `GetFeatureFlagOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### getFeatureFlagPayload(...) ``` getFeatureFlagPayload(options: GetFeatureFlagPayloadOptions) => Promise ``` Get the payload of a feature flag. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `GetFeatureFlagPayloadOptions` | **Returns:** `Promise` **Since:** 7.1.0 ______________________________________________________________________ ### group(...) ``` group(options: GroupOptions) => Promise ``` Associate the events for that user with a group. | Param | Type | | ------------- | -------------- | | **`options`** | `GroupOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### identify(...) ``` identify(options: IdentifyOptions) => Promise ``` Identify the current user. | Param | Type | | ------------- | ----------------- | | **`options`** | `IdentifyOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### isFeatureEnabled(...) ``` isFeatureEnabled(options: IsFeatureEnabledOptions) => Promise ``` Check if a feature flag is enabled. | Param | Type | | ------------- | ------------------------- | | **`options`** | `IsFeatureEnabledOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### isOptOut() ``` isOptOut() => Promise ``` Check if the user has opted out of capturing. **Returns:** `Promise` **Since:** 8.1.0 ______________________________________________________________________ ### optIn() ``` optIn() => Promise ``` Opt in to event capturing. **Since:** 8.1.0 ______________________________________________________________________ ### optOut() ``` optOut() => Promise ``` Opt out of event capturing. On Web with `cookielessMode: 'on_reject'`: switches to cookieless anonymous tracking. On iOS/Android: stops all event capturing entirely. **Since:** 8.1.0 ______________________________________________________________________ ### register(...) ``` register(options: RegisterOptions) => Promise ``` Register a new super property. This property will be sent with every event. | Param | Type | | ------------- | ----------------- | | **`options`** | `RegisterOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### reloadFeatureFlags() ``` reloadFeatureFlags() => Promise ``` Reload the feature flags. **Since:** 7.0.0 ______________________________________________________________________ ### reset() ``` reset() => Promise ``` Reset the current user's ID and anonymous ID. **Since:** 6.0.0 ______________________________________________________________________ ### screen(...) ``` screen(options: ScreenOptions) => Promise ``` Send a screen event. Only available on Android and iOS. | Param | Type | | ------------- | --------------- | | **`options`** | `ScreenOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### setup(...) ``` setup(options: SetupOptions) => Promise ``` Setup the PostHog SDK with the provided options. **Attention**: This method should be called before any other method. Alternatively, on Android and iOS, you can configure this plugin in your Capacitor Configuration file. In this case, you must not call this method. | Param | Type | | ------------- | -------------- | | **`options`** | `SetupOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### startSessionRecording() ``` startSessionRecording() => Promise ``` Start session recording. **Since:** 7.3.0 ______________________________________________________________________ ### stopSessionRecording() ``` stopSessionRecording() => Promise ``` Stop session recording. **Since:** 7.3.0 ______________________________________________________________________ ### unregister(...) ``` unregister(options: UnregisterOptions) => Promise ``` Remove a super property. | Param | Type | | ------------- | ------------------- | | **`options`** | `UnregisterOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### AliasOptions | Prop | Type | Description | Since | | ----------- | -------- | -------------------------------------------------- | ----- | | **`alias`** | `string` | The new distinct ID to assign to the current user. | 6.0.0 | #### CaptureOptions | Prop | Type | Description | Since | | ---------------- | --------------------- | -------------------------------------- | ----- | | **`event`** | `string` | The name of the event to capture. | 6.0.0 | | **`properties`** | `Record` | The properties to send with the event. | 6.0.0 | #### GetDistinctIdResult | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------ | ----- | | **`distinctId`** | `string` | The current distinct ID. | 8.2.0 | #### GetFeatureFlagResult | Prop | Type | Description | Since | | ----------- | -------- | ----------- | ------ | | **`value`** | \`string | boolean | null\` | #### GetFeatureFlagOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`key`** | `string` | The key of the feature flag. | 7.0.0 | #### GetFeatureFlagPayloadResult | Prop | Type | Description | Since | | ----------- | ---------- | -------------------------------------- | ----- | | **`value`** | `JsonType` | The value of the feature flag payload. | 7.1.0 | #### GetFeatureFlagPayloadOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`key`** | `string` | The key of the feature flag. | 7.1.0 | #### GroupOptions | Prop | Type | Description | Since | | --------------------- | --------------------- | -------------------------------------------- | ----- | | **`type`** | `string` | The group type. | 6.0.0 | | **`key`** | `string` | The group key. | 6.0.0 | | **`groupProperties`** | `Record` | The properties to send with the group event. | 6.0.0 | #### IdentifyOptions | Prop | Type | Description | Since | | -------------------- | --------------------- | ----------------------------- | ----- | | **`distinctId`** | `string` | The distinct ID of the user. | 6.0.0 | | **`userProperties`** | `Record` | The person properties to set. | 6.0.0 | #### IsFeatureEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | --------------------------------------------------------------------------------------------------- | ----- | | **`enabled`** | `boolean` | Whether the feature flag is enabled. If the feature flag does not exist, the value will be `false`. | 7.0.0 | #### IsFeatureEnabledOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`key`** | `string` | The key of the feature flag. | 7.0.0 | #### IsOptOutResult | Prop | Type | Description | Since | | -------------- | --------- | -------------------------------------------- | ----- | | **`optedOut`** | `boolean` | Whether the user has opted out of capturing. | 8.1.0 | #### RegisterOptions | Prop | Type | Description | Since | | ----------- | -------- | -------------------------------- | ----- | | **`key`** | `string` | The name of the super property. | 6.0.0 | | **`value`** | `any` | The value of the super property. | 6.0.0 | #### ScreenOptions | Prop | Type | Description | Since | | ----------------- | --------------------- | --------------------------------------------- | ----- | | **`screenTitle`** | `string` | The name of the screen. | 6.0.0 | | **`properties`** | `Record` | The properties to send with the screen event. | 6.0.0 | #### SetupOptions | Prop | Type | Description | Default | Since | | --------------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`apiKey`** | `string` | The API key of your PostHog project. | | 6.0.0 | | **`apiHost`** | `string` | The API host of your PostHog instance or reverse proxy. If both `apiHost` and `host` are provided, `apiHost` takes precedence. | `'https://us.i.posthog.com'` | 8.3.0 | | **`host`** | `string` | The API host of your PostHog instance. Deprecated alias for `apiHost`. | `'https://us.i.posthog.com'` | 6.0.0 | | **`uiHost`** | `string` | The PostHog UI host used when `apiHost` points to a reverse proxy. Only available on Web. | | 8.3.0 | | **`cookielessMode`** | \`'always' | 'on_reject'\` | Cookieless tracking mode. - `'always'`: Always use cookieless tracking with server-side anonymous hash. - `'on_reject'`: Normal tracking until `optOut()` is called, then switches to cookieless. Only available on Web. Requires cookieless mode to be enabled in PostHog project settings. | | | **`enableSessionReplay`** | `boolean` | Whether to enable session recording automatically. | `false` | 7.3.0 | | **`optOut`** | `boolean` | Whether to opt out of capturing by default. User must call `optIn()` to enable capturing. | `false` | 8.1.0 | | **`sessionReplayConfig`** | `SessionReplayOptions` | Session replay configuration options. | | 7.3.0 | | **`captureApplicationLifecycleEvents`** | `boolean` | Whether to capture application lifecycle events. Only available on iOS and Android. | `true` | 8.3.0 | #### SessionReplayOptions | Prop | Type | Description | Default | Since | | ----------------------------- | --------- | ----------------------------------------------------------------------------------------------- | ------- | ----- | | **`screenshotMode`** | `boolean` | Enable screenshot mode for session recordings. WARNING: This may capture sensitive information. | `false` | 7.3.0 | | **`maskAllTextInputs`** | `boolean` | Mask all text input fields in session recordings. | `true` | 7.3.0 | | **`maskAllImages`** | `boolean` | Mask all images in session recordings. | `true` | 7.3.0 | | **`maskAllSandboxedViews`** | `boolean` | Mask all sandboxed system views (iOS-specific). | `true` | 7.3.0 | | **`captureNetworkTelemetry`** | `boolean` | Capture network telemetry in session recordings. | `false` | 7.3.0 | | **`debouncerDelay`** | `number` | Debounce delay for session recording snapshots (in seconds). | `1.0` | 7.3.0 | #### UnregisterOptions | Prop | Type | Description | Since | | --------- | -------- | ----------------------------------------- | ----- | | **`key`** | `string` | The name of the super property to remove. | 6.0.0 | ### Type Aliases #### JsonType `string | number | boolean | null | { [key: string]: JsonType; } | JsonType[]` ## Advanced ### Reverse Proxy For PostHog managed reverse proxy, set `apiHost` to your proxy URL and `uiHost` to your PostHog app host (`https://us.posthog.com` or `https://eu.posthog.com`). `host` remains supported as a deprecated alias for `apiHost`. `uiHost` is only available on Web. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/posthog/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/posthog/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by PostHog, Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome-team/capacitor-printer Capacitor plugin for seamless printing on Android and iOS. Supports base64, files, HTML, PDFs, and web views. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for printing. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🖨️ **Base64 Printer**: Print base64 encoded files. - 🖨️ **File Printer**: Print files from the device. - 🖨️ **HTML Printer**: Print custom HTML content. - 🖨️ **PDF Printer**: Print PDF files. - 🖨️ **Web View Printer**: Print web view content. - ⚔️ **Battle-Tested**: Used in more than 100 projects. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/robingenz/capacitor-plugin-demo). | Android | iOS | | ------- | --- | | | | ## Guides - [Exploring the Capacitor Printer API](https://capawesome.io/blog/exploring-the-capacitor-printer-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-printer` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-printer npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxDocumentFileVersion` version of `androidx.documentfile:documentfile` (default: `1.1.0`) - `$androidxPrintVersion` version of `androidx.print:print` (default: `1.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Usage ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printBase64 = async () => { await Printer.printBase64({ name: 'My Document', data: 'JVBERi0...', }); } const printFile = async () => { await Printer.printFile({ mimeType: 'application/pdf', path: 'content://com.android.providers.downloads.documents/document/msf%3A1000000485', }); }; const printHtml = async () => { await Printer.printHtml({ name: 'My Document', html: '

Hello World

', }); }; const printPdf = async () => { await Printer.printPdf({ name: 'My Document', path: 'content://com.android.providers.downloads.documents/document/msf%3A1000000485', }); }; const printWebView = async () => { await Printer.printWebView({ name: 'My Document', }); }; ``` ## API - [`printBase64(...)`](#printbase64) - [`printFile(...)`](#printfile) - [`printHtml(...)`](#printhtml) - [`printPdf(...)`](#printpdf) - [`printWebView(...)`](#printwebview) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### printBase64(...) ``` printBase64(options: PrintBase64Options) => Promise ``` Present the printing user interface to print files encoded as base64 strings. **Attention**: Large files can lead to app crashes. It's therefore recommended to use the `printFile` method instead. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `PrintBase64Options` | **Since:** 7.1.0 ______________________________________________________________________ ### printFile(...) ``` printFile(options: PrintFileOptions) => Promise ``` Present the printing user interface to print files. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `PrintFileOptions` | **Since:** 7.1.0 ______________________________________________________________________ ### printHtml(...) ``` printHtml(options: PrintHtmlOptions) => Promise ``` Present the printing user interface to print a html document. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `PrintHtmlOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### printPdf(...) ``` printPdf(options: PrintPdfOptions) => Promise ``` Present the printing user interface to print a pdf document. Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `PrintPdfOptions` | **Since:** 5.1.0 ______________________________________________________________________ ### printWebView(...) ``` printWebView(options?: PrintOptions | undefined) => Promise ``` Present the printing user interface to print the web view content. You can use a print style sheet to customize the print output (see https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Printing). | Param | Type | | ------------- | -------------- | | **`options`** | `PrintOptions` | **Since:** 5.0.0 ______________________________________________________________________ ### Interfaces #### PrintBase64Options | Prop | Type | Description | Since | | -------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`data`** | `string` | A valid base 64 encoded string. | 7.1.0 | | **`mimeType`** | `string` | The mime type of the data. The following mime types are supported: `application/pdf`, `image/gif`, `image/heic`, `image/heif`, `image/jpeg`, `image/png`. | 7.1.0 | #### PrintFileOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------- | ----- | | **`mimeType`** | `string` | The mime type of the file. Only available on Android. | 7.1.0 | | **`path`** | `string` | The path to the file. | 7.1.0 | #### PrintHtmlOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------- | ----- | | **`html`** | `string` | The HTML content to print. | 5.0.0 | #### PrintPdfOptions | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------- | ----- | | **`path`** | `string` | The path to the pdf document on the device. | 5.1.0 | #### PrintOptions | Prop | Type | Description | Default | Since | | ---------- | -------- | -------------------------- | ------------ | ----- | | **`name`** | `string` | The name of the print job. | `'Document'` | 5.0.0 | ### Type Aliases #### PrintWebViewOptions `PrintOptions` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/printer/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/printer/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/printer/LICENSE). # @capawesome-team/capacitor-purchases Capacitor plugin to support in-app purchases. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for in-app purchases. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🛍️ **Product Types**: Supports subscriptions, consumables, and non-consumable in-app products. - 🗂️ **Product Retrieval**: Fetch multiple products in a single call for efficient loading. - 🎁 **Intro Offer Eligibility**: Check if introductory offers are available for subscription products. - 🔒 **Server Validation**: Provides verification tokens (iOS JWS, Android purchase tokens) for server-side validation. - 🔗 **Third-Party Validation**: Includes transaction properties for third-party validation. - 📋 **Transaction Management**: Track current, unfinished, and historical transactions. - 🔄 **Purchase Restoration**: Easily sync and restore purchases across devices. - 🚀 **Modern APIs**: Uses StoreKit 2 and Google Play Billing Library 8.0. - 🚨 **Error Codes**: Provides detailed error codes for better error handling. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll add it for you! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.3.x | >=8.x.x | Active support | ## Guides - [Tips for Setting Up In-App Purchases with Capacitor](https://capawesome.io/blog/tips-for-setting-up-in-app-purchases-with-capacitor/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-purchases` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-purchases npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$googlePlayBillingVersion` version of `com.android.billingclient:billing` (default: `8.2.0`) ### iOS #### Capabilities Ensure `In-App Purchase` capabilities have been enabled in your application in Xcode. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. ## Configuration No configuration required for this plugin. ## Usage ``` import { Purchases } from '@capawesome-team/capacitor-purchases'; const purchaseProduct = async (productId: string) => { const { transaction } = await Purchases.purchaseProduct({ productId }); // Deliver the purchased content or enable the service here // ... // Finish the transaction await Purchases.finishTransaction({ transactionId: transaction.id }); }; const restorePurchases = async () => { await Purchases.syncTransactions(); const { transactions } = await Purchases.getCurrentTransactions(); for (const transaction of transactions) { // Deliver the purchased content or enable the service here // ... } }; ``` ## API - [`finishTransaction(...)`](#finishtransaction) - [`getAllTransactions()`](#getalltransactions) - [`getCurrentTransactions()`](#getcurrenttransactions) - [`getProductById(...)`](#getproductbyid) - [`getProductsByIds(...)`](#getproductsbyids) - [`getUnfinishedTransactions()`](#getunfinishedtransactions) - [`isAvailable()`](#isavailable) - [`isIntroOfferAvailableForProduct(...)`](#isintroofferavailableforproduct) - [`purchaseProduct(...)`](#purchaseproduct) - [`syncTransactions()`](#synctransactions) - [Interfaces](#interfaces) - [Enums](#enums) ### finishTransaction(...) ``` finishTransaction(options: FinishTransactionOptions) => Promise ``` Finish a transaction. Indicates to the App Store that the app delivered the purchased content or enabled the service to finish the transaction. Only available on Android and iOS (15.0+). | Param | Type | | ------------- | -------------------------- | | **`options`** | `FinishTransactionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### getAllTransactions() ``` getAllTransactions() => Promise ``` Returns transaction details for all transactions made by the user within your app. Only available on iOS (15.0+). **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getCurrentTransactions() ``` getCurrentTransactions() => Promise ``` Returns transaction details for currently owned items bought within your app. Only active subscriptions and non-consumed one-time purchases are returned. Only available on Android and iOS (15.0+). **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getProductById(...) ``` getProductById(options: GetProductByIdOptions) => Promise ``` Get product details by product ID. Only available on Android and iOS (15.0+). | Param | Type | | ------------- | ----------------------- | | **`options`** | `GetProductByIdOptions` | **Returns:** `Promise` **Since:** 0.3.1 ______________________________________________________________________ ### getProductsByIds(...) ``` getProductsByIds(options: GetProductsByIdsOptions) => Promise ``` Get product details for multiple products by their IDs. Only available on Android and iOS (15.0+). | Param | Type | | ------------- | ------------------------- | | **`options`** | `GetProductsByIdsOptions` | **Returns:** `Promise` **Since:** 0.3.3 ______________________________________________________________________ ### getUnfinishedTransactions() ``` getUnfinishedTransactions() => Promise ``` Returns transaction details for all transactions that are not yet finished. Check for unfinished transactions at least once every app launch to ensure that all transactions are processed correctly. Only available on Android and iOS (15.0+). **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if in-app purchases are supported on the device. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isIntroOfferAvailableForProduct(...) ``` isIntroOfferAvailableForProduct(options: IsIntroOfferAvailableForProductOptions) => Promise ``` Check if an introductory offer is available for a product. On **iOS**, this uses StoreKit 2's `isEligibleForIntroOffer` to check if the customer has already used an introductory offer for any subscription within the same subscription group. On **Android**, this checks if the product has any introductory pricing phases available. Google Play automatically filters offers based on eligibility when querying products, so if an intro offer is present, the user can purchase it. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------------------- | | **`options`** | `IsIntroOfferAvailableForProductOptions` | **Returns:** `Promise` **Since:** 0.3.2 ______________________________________________________________________ ### purchaseProduct(...) ``` purchaseProduct(options: PurchaseProductOptions) => Promise ``` Purchase a product by its ID. Make sure to call `finishTransaction(...)` after the purchase is complete and the content has been delivered or the service has been enabled. Only available on Android and iOS (15.0+). | Param | Type | | ------------- | ------------------------ | | **`options`** | `PurchaseProductOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### syncTransactions() ``` syncTransactions() => Promise ``` Sync transactions with the App Store. This method is used to ensure that all transactions are up-to-date and methods like `getCurrentTransactions` return the latest transactions. On **iOS**, calling this method will display a system dialog to the user asking them to authenticate with their App Store credentials. Call this method only in response to an explicit user action. On **Android**, this method silently queries and refreshes purchases from Google Play without user interaction. Only available on Android and iOS (15.0+). **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### FinishTransactionOptions | Prop | Type | Description | Since | | ------------------- | -------- | ------------------------------------ | ----- | | **`transactionId`** | `string` | The ID of the transaction to finish. | 0.1.0 | #### GetAllTransactionsResult | Prop | Type | Description | Since | | ------------------ | --------------- | ------------------------------ | ----- | | **`transactions`** | `Transaction[]` | The transactions for the user. | 0.1.0 | #### Transaction | Prop | Type | Description | Since | | ------------------------ | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The unique identifier for the transaction. | 0.1.0 | | **`verificationResult`** | `string` | The JWS (JSON Web Signature) representation of the transaction verification result. Pass this to your server to validate the purchase. If the transaction could not be verified, this will not be present. Only available on iOS. | 0.1.0 | | **`token`** | `string` | A unique identifier that represents the user and the product ID for the in-app product they purchased. Pass this to your server to validate the purchase. Only available on Android. | 0.2.1 | | **`productId`** | `string` | The product identifier associated with the transaction. | 0.3.2 | | **`originalJson`** | `string` | The original JSON purchase data. Pass this to your server to validate the purchase. Only available on Android. | 0.3.2 | | **`signature`** | `string` | The RSA signature for purchase verification. Pass this to your server to validate the purchase. Only available on Android. | 0.3.2 | #### GetCurrentTransactionsResult | Prop | Type | Description | Since | | ------------------ | --------------- | -------------------------------------- | ----- | | **`transactions`** | `Transaction[]` | The current transactions for the user. | 0.1.0 | #### GetProductByIdResult | Prop | Type | Description | Since | | ------------- | --------- | -------------------- | ----- | | **`product`** | `Product` | The product details. | 0.3.1 | #### Product Represents an in-app product available for purchase. | Prop | Type | Description | Since | | ------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The unique product identifier. | 0.3.1 | | **`displayName`** | `string` | The localized display name of the product. On Android, this uses ProductDetails.getName(). On iOS, this uses [Product.displayName](#product). | 0.3.1 | | **`description`** | `string` | The localized description of the product. | 0.3.1 | | **`displayPrice`** | `string` | The localized price string, formatted for display. | 0.3.1 | | **`price`** | `number` | The price as a decimal number. On Android, this is calculated from priceAmountMicros / 1,000,000. On iOS, this uses [Product.price](#product) as a decimal. | 0.3.1 | | **`currencyCode`** | `string` | The ISO 4217 currency code. On Android, this uses priceCurrencyCode. On iOS, this uses priceFormatStyle.currencyCode. | 0.3.1 | | **`type`** | `ProductType` | The type of product. | 0.3.1 | #### GetProductByIdOptions | Prop | Type | Description | Since | | --------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`productCategory`** | `ProductCategory` | The category of product to fetch. If not specified, the plugin will query both categories. Only available on Android. | 0.3.3 | | **`productId`** | `string` | The product ID of the product to retrieve. On Android, this is the [Product](#product) ID configured in Google Play Console. On iOS, this is the [Product](#product) ID configured in App Store Connect. | 0.3.1 | #### GetProductsByIdsResult | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`products`** | `Product[]` | The product details for the requested products. Note: If some products are not found, they will not be included in the array. The array may contain fewer products than requested. | 0.3.3 | #### GetProductsByIdsOptions | Prop | Type | Description | Since | | --------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`productCategory`** | `ProductCategory` | The category of products to fetch. If not specified, the plugin will query both categories. Only available on Android. | 0.3.3 | | **`productIds`** | `string[]` | The product IDs of the products to retrieve. On Android, these are the [Product](#product) IDs configured in Google Play Console. On iOS, these are the [Product](#product) IDs configured in App Store Connect. | 0.3.3 | #### GetUnfinishedTransactionsResult | Prop | Type | Description | Since | | ------------------ | --------------- | ----------------------------------------- | ----- | | **`transactions`** | `Transaction[]` | The unfinished transactions for the user. | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | --------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Indicates whether in-app purchases are available on the device. | 0.1.0 | #### IsIntroOfferAvailableForProductResult | Prop | Type | Description | Since | | --------------------------- | --------- | ----------------------------------------------------------- | ----- | | **`isIntroOfferAvailable`** | `boolean` | Whether an introductory offer is available for the product. | 0.3.2 | #### IsIntroOfferAvailableForProductOptions | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------- | ----- | | **`productId`** | `string` | The product ID of the subscription to check. | 0.3.2 | #### PurchaseProductResult | Prop | Type | Description | Since | | ----------------- | ------------- | ------------------------------------------------------------- | ----- | | **`transaction`** | `Transaction` | The transaction that was created as a result of the purchase. | 0.1.0 | #### PurchaseProductOptions | Prop | Type | Description | Since | | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`basePlanId`** | `string` | The base plan ID of the subscription to purchase. Only available on Android. | 0.3.5 | | **`offerId`** | `string` | The offer ID of the subscription offer to purchase. Only available on Android. | 0.3.5 | | **`productId`** | `string` | The product ID of the product to purchase. On **Android**, this is the [Product](#product) ID configured in Google Play Console. On **iOS**, this is the [Product](#product) ID configured in App Store Connect. | 0.1.0 | ### Enums #### ProductType | Members | Value | Description | Since | | ------------------------------- | ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Consumable`** | `'CONSUMABLE'` | A consumable in-app product. On Android, this is [ProductType](#producttype).INAPP. On iOS, this is [Product](#product).[ProductType](#producttype).consumable. | 0.3.1 | | **`NonConsumable`** | `'NON_CONSUMABLE'` | A non-consumable in-app product. On iOS, this is [Product](#product).[ProductType](#producttype).nonConsumable. Only available on iOS. | 0.3.1 | | **`AutoRenewableSubscription`** | `'AUTO_RENEWABLE_SUBSCRIPTION'` | An auto-renewable subscription. On Android, this is [ProductType](#producttype).SUBS. On iOS, this is [Product](#product).[ProductType](#producttype).autoRenewable. | 0.3.1 | | **`NonRenewableSubscription`** | `'NON_RENEWABLE_SUBSCRIPTION'` | A non-renewing subscription. On iOS, this is [Product](#product).[ProductType](#producttype).nonRenewable. Only available on iOS. | 0.3.1 | #### ProductCategory | Members | Value | Description | Since | | ------------------ | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`InApp`** | `'IN_APP'` | In-app products (consumables and non-consumables). On Android, this maps to `BillingClient.ProductType.INAPP`. | 0.3.3 | | **`Subscription`** | `'SUBSCRIPTION'` | Subscription products. On Android, this maps to `BillingClient.ProductType.SUBS`. | 0.3.3 | ## Testing ### Android To test in-app purchases on Android, you need to: 1. Upload your app to the Google Play Console (internal testing track is sufficient) 1. Add test accounts in Google Play Console under **Settings** → **License Testing** 1. Install the app from Google Play (not via direct APK installation) 1. Use test product IDs or enable license testing for your account See [Test Google Play Billing](https://developer.android.com/google/play/billing/test) for more details. ### iOS To test in-app purchases on iOS, you can use: 1. **Sandbox Testing**: Create sandbox test accounts in App Store Connect and test on physical devices or simulators 1. **StoreKit Testing in Xcode**: Configure a StoreKit configuration file for local testing without server connectivity (requires iOS 14+) Sandbox accounts can be created in App Store Connect under **Users and Access** → **Sandbox**. See [Testing In-App Purchases](https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases) for more details. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/purchases/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/purchases/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/purchases/LICENSE). # @capawesome/capacitor-realtimekit Unofficial Capacitor plugin for using the [RealtimeKit SDK](https://docs.realtime.cloudflare.com/).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.2.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-realtimekit` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-realtimekit npx cap sync ``` ### Android This plugin is supported on Android SDK +24. #### Variables This plugin will use the following project variables (defined in your app’s `variables.gradle` file): - `$realtimekitUiVersion` version of `com.cloudflare.realtimekit:ui-android` (default: `0.3.1`) ### iOS #### Privacy Descriptions Add the following keys to the `ios/App/App/Info.plist` file: ``` NSBluetoothPeripheralUsageDescription We will use your Bluetooth to access your Bluetooth headphones. NSBluetoothAlwaysUsageDescription We will use your Bluetooth to access your Bluetooth headphones. NSCameraUsageDescription For people to see you during meetings, we need access to your camera. NSMicrophoneUsageDescription For people to hear you during meetings, we need access to your microphone. NSPhotoLibraryUsageDescription For people to share, we need access to your photos. UIBackgroundModes audio voip fetch remote-notification ``` ## Usage ``` import { RealtimeKit } from '@capawesome/capacitor-realtimekit'; const initialize = async () => { await RealtimeKit.initialize(); }; const startMeeting = async (options: StartMeetingOptions) => { await RealtimeKit.startMeeting(options); }; ``` ## API - [`initialize()`](#initialize) - [`startMeeting(...)`](#startmeeting) - [Interfaces](#interfaces) ### initialize() ``` initialize() => Promise ``` Initialize the RealtimeKit plugin. This method must be called before using any other methods in the plugin. **Since:** 0.0.0 ______________________________________________________________________ ### startMeeting(...) ``` startMeeting(options: StartMeetingOptions) => Promise ``` Start a meeting using the built-in UI. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `StartMeetingOptions` | **Since:** 0.0.0 ______________________________________________________________________ ### Interfaces #### StartMeetingOptions | Prop | Type | Description | Default | Since | | ----------------- | --------- | ----------------------------------------------- | ------- | ----- | | **`authToken`** | `string` | The authentication token for the participant. | | 0.0.0 | | **`enableAudio`** | `boolean` | Whether to join the meeting with audio enabled. | `true` | 0.0.0 | | **`enableVideo`** | `boolean` | Whether to join the meeting with video enabled. | `true` | 0.0.0 | ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Cloudflare, Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-screen-orientation Capacitor plugin to lock/unlock the screen orientation. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for screen orientation control. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 🔒 **Orientation locking**: Lock screen to specific orientations. - 🔓 **Orientation unlocking**: Unlock and restore automatic orientation. - 📱 **Multiple orientations**: Support for portrait, landscape, and specific orientations. - 🔄 **Orientation detection**: Get current screen orientation. - 📢 **Event listeners**: Listen to orientation change events. - 📐 **Fine-grained control**: Primary and secondary orientation modes. - 🍎 **iPad support**: Special configuration for iPad orientation locking. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-screen-orientation` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-screen-orientation npx cap sync ``` ### iOS #### General On iOS you must add the following to your app's `AppDelegate.swift`: ``` + import CapawesomeCapacitorScreenOrientation @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { + func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask { + return ScreenOrientation.getSupportedInterfaceOrientations() + } ``` #### iPad Orientation Lock On iPad, you must add the following to your app's `Info.plist`: ``` UIRequiresFullScreen ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { ScreenOrientation, OrientationType } from '@capawesome/capacitor-screen-orientation'; const lock = async () => { await ScreenOrientation.lock({ type: OrientationType.LANDSCAPE }); }; const unlock = async () => { await ScreenOrientation.unlock(); }; const getCurrentOrientation = async () => { const result = await ScreenOrientation.getCurrentOrientation(); return result.type; }; ``` ## API - [`lock(...)`](#lock) - [`unlock()`](#unlock) - [`getCurrentOrientation()`](#getcurrentorientation) - [`addListener('screenOrientationChange', ...)`](#addlistenerscreenorientationchange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### lock(...) ``` lock(options?: LockOptions | undefined) => Promise ``` Locks the device orientation. | Param | Type | | ------------- | ------------- | | **`options`** | `LockOptions` | ______________________________________________________________________ ### unlock() ``` unlock() => Promise ``` Unlocks the device orientation. ______________________________________________________________________ ### getCurrentOrientation() ``` getCurrentOrientation() => Promise ``` Gets the current device orientation type. **Returns:** `Promise` ______________________________________________________________________ ### addListener('screenOrientationChange', ...) ``` addListener(eventName: 'screenOrientationChange', listenerFunc: ScreenOrientationChangeListener) => Promise ``` Listen for screen orientation changes. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'screenOrientationChange'` | | **`listenerFunc`** | `ScreenOrientationChangeListener` | **Returns:** `Promise` ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. ______________________________________________________________________ ### Interfaces #### LockOptions | Prop | Type | Description | | ---------- | ----------------- | -------------------------- | | **`type`** | `OrientationType` | The orientation lock type. | #### GetCurrentOrientationResult | Prop | Type | Description | | ---------- | ----------------- | ----------------------------- | | **`type`** | `OrientationType` | The current orientation type. | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ScreenOrientationChange | Prop | Type | Description | | ---------- | ----------------- | ----------------------------- | | **`type`** | `OrientationType` | The current orientation type. | ### Type Aliases #### ScreenOrientationChangeListener Callback to receive the screen orientation change notifications. `(change: ScreenOrientationChange): void` ### Enums #### OrientationType | Members | Value | Description | | ------------------------- | ----------------------- | ------------------------------------------------------------------- | | **`LANDSCAPE`** | `'landscape'` | The orientation is either landscape-primary or landscape-secondary. | | **`LANDSCAPE_PRIMARY`** | `'landscape-primary'` | The orientation is in the primary landscape mode. | | **`LANDSCAPE_SECONDARY`** | `'landscape-secondary'` | The orientation is in the secondary landscape mode. | | **`PORTRAIT`** | `'portrait'` | The orientation is either portrait-primary or portrait-secondary. | | **`PORTRAIT_PRIMARY`** | `'portrait-primary'` | The orientation is in the primary portrait mode. | | **`PORTRAIT_SECONDARY`** | `'portrait-secondary'` | The orientation is in the secondary portrait mode. | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screen-orientation/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screen-orientation/LICENSE). ## Credits This plugin is based on the [Capacitor Screen Orientation](https://github.com/capawesome-team/capacitor-screen-orientation) plugin. Thanks to everyone who contributed to the project! # @capawesome/capacitor-screenshot Capacitor plugin for taking screenshots. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for capturing screenshots. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 📸 **Easy screenshots**: Simple one-method API for taking screenshots. - 🌐 **Web support**: Uses html2canvas for web platform screenshot capture. - 📱 **Native capture**: High-quality native screenshot capture on mobile. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-screenshot` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome/capacitor-screenshot npx cap sync ``` If you are using the Web platform, you must also install the `html2canvas` package: ``` npm i html2canvas ``` ## Usage ``` import { Screenshot } from '@capawesome/capacitor-screenshot'; const take = async () => { const { uri } = await Screenshot.take(); console.log('Screenshot saved at:', uri); }; ``` ## API - [`take()`](#take) - [Interfaces](#interfaces) ### take() ``` take() => Promise ``` Take a screenshot. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### TakeResult | Prop | Type | Description | Since | | --------- | -------- | -------------------------------------------------------------------- | ----- | | **`uri`** | `string` | The file path (Android and iOS) or data URI (Web) of the screenshot. | 6.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screenshot/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/screenshot/LICENSE). # @capawesome-team/capacitor-secure-preferences Capacitor plugin to securely store key/value pairs such as passwords, tokens or other sensitive information. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for secure storage. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🔒 **Secure**: Store sensitive information such as passwords securely using the [Android Keystore](https://developer.android.com/privacy-and-security/keystore) and [iOS Keychain](https://developer.apple.com/documentation/security/keychain-services). - 🔍 **Detailed Error Messages**: Get actionable error messages with specific failure reasons and error codes on iOS, making debugging keychain issues straightforward. - 🤝 **Compatibility**: Compatible with the [Biometrics](https://capawesome.io/plugins/biometrics/) and [SQLite](https://capawesome.io/plugins/sqlite/) plugins. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.2.x | >=8.x.x | Active support | ## Guides - [Alternative to the Ionic Secure Storage plugin](https://capawesome.io/blog/alternative-to-ionic-secure-storage-plugin/) - [Announcing the Capacitor Secure Preferences Plugin](https://capawesome.io/blog/announcing-the-capacitor-secure-preferences-plugin/) - [Exploring the Capacitor Secure Preferences API](https://capawesome.io/blog/exploring-the-capacitor-secure-preferences-api/) - [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-secure-preferences` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-secure-preferences npx cap sync ``` ### Android #### Backup rules To prevent the preferences file from being backed up to the cloud, you need to add backup rules to your Android project. You can read more about this in the [Android documentation](https://developer.android.com/identity/data/autobackup#IncludingFiles). ##### Android 11 and lower Add the `android:fullBackupContent` attribute to the `` tag in your `AndroidManifest.xml` file: ``` ``` Create a new file `res/xml/full_backup_content.xml` with the following content: ``` ``` ##### Android 12 and higher Add the `android:dataExtractionRules` attribute to the `` tag in your `AndroidManifest.xml` file: ``` ``` Create a new file `res/xml/data_extraction_rules.xml` with the following content: ``` ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const clear = async () => { await SecurePreferences.clear(); }; const get = async () => { const { value } = await SecurePreferences.get({ key: 'password', }); console.log(value); }; const keys = async () => { const { keys } = await SecurePreferences.keys(); console.log(keys); }; const remove = async () => { await SecurePreferences.remove({ key: 'password', }); }; const set = async () => { await SecurePreferences.set({ key: 'password', value: '123456', }); }; ``` ## API - [`clear()`](#clear) - [`get(...)`](#get) - [`keys()`](#keys) - [`remove(...)`](#remove) - [`set(...)`](#set) - [Interfaces](#interfaces) ### clear() ``` clear() => Promise ``` Clear all stored keys and values. **Since:** 7.0.0 ______________________________________________________________________ ### get(...) ``` get(options: GetOptions) => Promise ``` Get the value associated with a key. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### keys() ``` keys() => Promise ``` Get a list of all stored keys. **Returns:** `Promise` **Since:** 7.0.0 ______________________________________________________________________ ### remove(...) ``` remove(options: RemoveOptions) => Promise ``` Remove a value given its key. | Param | Type | | ------------- | --------------- | | **`options`** | `RemoveOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### set(...) ``` set(options: SetOptions) => Promise ``` Set a value given its key. On **Web**, the value is stored unencrypted in `localStorage`. This is for development purposes only and should not be used in production. | Param | Type | | ------------- | ------------ | | **`options`** | `SetOptions` | **Since:** 7.0.0 ______________________________________________________________________ ### Interfaces #### GetResult | Prop | Type | Description | Since | | ----------- | -------- | ----------- | -------------------- | | **`value`** | \`string | null\` | The retrieved value. | #### GetOptions | Prop | Type | Description | Since | | --------- | -------- | ----------------------------------------- | ----- | | **`key`** | `string` | The key associated with the stored value. | 7.0.0 | #### KeysResult | Prop | Type | Description | Since | | ---------- | ---------- | -------------------------- | ----- | | **`keys`** | `string[]` | The available stored keys. | 7.0.0 | #### RemoveOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------ | ----- | | **`key`** | `string` | The key to remove. | 7.0.0 | #### SetOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------- | ----- | | **`key`** | `string` | The key associated with the stored value. | 7.0.0 | | **`value`** | `string` | The value to store. | 7.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/secure-preferences/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/secure-preferences/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/secure-preferences/LICENSE). # @capawesome-team/capacitor-share-target Capacitor plugin to receive content such as text, links, and files from other apps. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for receiving content from other apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 📝 **Multi-content types**: Handle text, URLs, images, videos, and files. - 🌐 **Web Share Target API**: Leverage the native sharing capabilities of the web. - 📦 **Large File Support**: Efficient file caching without memory limitations. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.2.x | 7.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-share-target-demo). | Android | iOS | Web | | ------- | --- | --- | | | | | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-share-target` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-share-target npx cap sync ``` ### Android #### Intent Filters To enable your app to receive shared content from other apps, you need to add intent filters to your main activity in `AndroidManifest.xml` (usually `android/app/src/main/AndroidManifest.xml`). For example, to handle text, URLs, and other text-based content, add the following intent filter: ``` ``` To handle a single image, add the following intent filter: ``` ``` To handle multiple images, add the following intent filter: ``` ``` You can also add additional intent filters for other MIME types as needed, such as `application/pdf` for PDF files or `video/*` for videos. **Attention**: Make sure to add these intent filters inside the `` tag of your main activity, typically `MainActivity`. Also, make sure the activity has the `android:exported="true"` attribute set, which allows other apps to send intents to it. The `android:launchMode="singleTask"` attribute is recommended to prevent multiple instances of your app from being created when receiving shared content. Here's an example of how your `AndroidManifest.xml` might look like: ``` ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app's `variables.gradle` file to change the default version of the dependency: - `$androidxExifInterfaceVersion` version of `androidx.exifinterface:exifinterface` (default: `1.4.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS On iOS, it's not possible to receive shared content directly in the main app. Instead, you need to create a share extension that can handle the shared content and then communicate it back to your main app. This involves setting up a URL scheme and configuring the share extension to handle the shared content. The communication between the share extension and the main app is done using URL schemes, which allows the share extension to open the main app with the shared content as parameters in the URL. #### URL Scheme To enable your app to be opened by the share extension, you need to set up a URL scheme in your iOS app. This is done by adding a URL type in your app's `Info.plist` file. Add the following code to your `Info.plist` file: ``` CFBundleURLTypes CFBundleURLSchemes YOUR_URL_SCHEME ``` **Attention**: Replace `YOUR_URL_SCHEME` with your desired URL scheme (e.g. `myapp`). This will allow your app to be opened with URLs like `myapp://?text=Hello%20World`. #### Share Extension To enable your app to receive shared content from other apps on iOS, you need to create a share extension. First, you need to add a new target to your Xcode project: 1. Open your Xcode project. 1. Go to `File` > `New` > `Target...`. 1. Select `Share Extension` from the list of templates. 1. Name your extension `AppShare` and click `Finish`. This will create a new target in your Xcode project with the necessary files for a share extension. If you see a prompt to activate the new scheme, choose "Don't Activate". There is one file called `MainInterface.storyboard` that you should delete **via Xcode**, as we will not need it. After this, you need to configure the share extension to handle the shared content. Open the `Info.plist` file of your share extension **in a text editor of your choice** and replace its content with the following: ``` NSExtension NSExtensionAttributes NSExtensionActivationRule NSExtensionActivationDictionaryVersion 2 NSExtensionActivationSupportsWebURLWithMaxCount 1 NSExtensionActivationSupportsText NSExtensionActivationSupportsWebURLWithMaxCount 10 NSExtensionActivationSupportsFileWithMaxCount 10 NSExtensionActivationSupportsImageWithMaxCount 10 NSExtensionActivationSupportsMovieWithMaxCount 10 NSExtensionActivationSupportsWebPageWithMaxCount 1 NSExtensionPrincipalClass AppShare.ShareViewController NSExtensionPointIdentifier com.apple.share-services ``` Feel free to adjust the `NSExtensionActivationRule` keys to match the types of content you want to support. The example above supports text, web URLs, files, images, and movies. Next, you need to update the `ShareViewController.swift` file in your share extension target and replace its content with the following code: ``` import MobileCoreServices import Social import UIKit import UniformTypeIdentifiers struct SharedFileData { let uri: String let name: String? let mimeType: String? } class ShareViewController: UIViewController { private let appGroupIdentifier = "group." private let urlScheme = "" override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil) } override func viewDidLoad() { super.viewDidLoad() Task { processSharedContent() } } private func openURL(_ url: URL) { var responder: UIResponder? = self while responder != nil { if let application = responder as? UIApplication { application.open(url, options: [:], completionHandler: nil) return } responder = responder?.next } } private func sharedContainerURL(for fileName: String) -> URL? { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: appGroupIdentifier) else { return nil } return containerURL.appendingPathComponent(fileName) } private func removeFileIfExists(at url: URL) throws { if FileManager.default.fileExists(atPath: url.path) { try FileManager.default.removeItem(at: url) } } private func copyFileToSharedContainer(_ sourceURL: URL) -> String? { let fileName = sourceURL.lastPathComponent guard let destinationURL = sharedContainerURL(for: fileName) else { return nil } do { try removeFileIfExists(at: destinationURL) try FileManager.default.copyItem(at: sourceURL, to: destinationURL) return destinationURL.absoluteString } catch { print("Error copying file to shared container: \(error)") return nil } } private func copyImageToSharedContainerWithFixedOrientation(_ sourceURL: URL) -> String? { let fileName = sourceURL.lastPathComponent guard let destinationURL = sharedContainerURL(for: fileName) else { return nil } do { try removeFileIfExists(at: destinationURL) if let fixedImageData = loadImageDataWithFixedOrientation(from: sourceURL) { try fixedImageData.write(to: destinationURL) } else { try FileManager.default.copyItem(at: sourceURL, to: destinationURL) } return destinationURL.absoluteString } catch { print("Error copying image to shared container: \(error)") return nil } } private func loadImageDataWithFixedOrientation(from url: URL) -> Data? { guard let source = CGImageSourceCreateWithURL(url as CFURL, nil), let cgImage = CGImageSourceCreateImageAtIndex(source, 0, nil) else { return nil } let uiImage = UIImage(cgImage: cgImage) let fixedImage = normalizeImageOrientation(uiImage) let fileExtension = url.pathExtension.lowercased() if fileExtension == "jpg" || fileExtension == "jpeg" { return fixedImage.jpegData(compressionQuality: 0.9) } else { return fixedImage.pngData() } } private func normalizeImageOrientation(_ image: UIImage) -> UIImage { if let fixedImage = image.fixedOrientation() { return fixedImage } return image } private func extractFileMetadata(from url: URL) -> (name: String, mimeType: String?) { let fileName = url.lastPathComponent let mimeType = getMimeType(from: url) return (fileName, mimeType.isEmpty ? nil : mimeType) } private func getMimeType(from url: URL) -> String { let fileExtension = url.pathExtension as CFString guard let extUTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, fileExtension, nil)?.takeUnretainedValue() else { return "" } guard let mimeUTI = UTTypeCopyPreferredTagWithClass(extUTI, kUTTagClassMIMEType) else { return "" } return mimeUTI.takeRetainedValue() as String } private func buildAppURL(textValues: [String], fileValues: [SharedFileData], title: String) -> URL { var urlComponents = URLComponents(string: "\(urlScheme)://?")! var queryItems: [URLQueryItem] = [] if !title.isEmpty { queryItems.append(URLQueryItem(name: "title", value: title)) } queryItems.append(contentsOf: textValues.filter { !$0.isEmpty }.map { URLQueryItem(name: "text", value: $0) }) for (index, file) in fileValues.enumerated() { queryItems.append(URLQueryItem(name: "fileUri\(index)", value: file.uri)) if let name = file.name { queryItems.append(URLQueryItem(name: "fileName\(index)", value: name)) } if let mimeType = file.mimeType { queryItems.append(URLQueryItem(name: "fileMimeType\(index)", value: mimeType)) } } urlComponents.queryItems = queryItems.isEmpty ? nil : queryItems return urlComponents.url! } private func sendData(with textValues: [String], fileValues: [SharedFileData], title: String) { let url = buildAppURL(textValues: textValues, fileValues: fileValues, title: title) openURL(url) } private func processImageAttachment(_ attachment: NSItemProvider, dispatchGroup: DispatchGroup, completion: @escaping (SharedFileData?) -> Void) { dispatchGroup.enter() attachment.loadItem(forTypeIdentifier: UTType.image.identifier, options: nil) { [weak self] (item, _) in defer { dispatchGroup.leave() } guard let self = self else { completion(nil) return } if let url = item as? URL { if let sharedPath = self.copyImageToSharedContainerWithFixedOrientation(url) { let metadata = self.extractFileMetadata(from: url) completion(SharedFileData(uri: sharedPath, name: metadata.name, mimeType: metadata.mimeType)) return } } else if let image = item as? UIImage { let fixedImage = self.normalizeImageOrientation(image) if let data = fixedImage.pngData() { let base64String = data.base64EncodedString() completion(SharedFileData(uri: "data:image/png;base64,\(base64String)", name: nil, mimeType: "image/png")) return } } completion(nil) } } private func processMovieAttachment(_ attachment: NSItemProvider, dispatchGroup: DispatchGroup, completion: @escaping (SharedFileData?) -> Void) { dispatchGroup.enter() attachment.loadItem(forTypeIdentifier: UTType.movie.identifier, options: nil) { [weak self] (item, _) in defer { dispatchGroup.leave() } guard let self = self else { completion(nil) return } if let url = item as? URL { if let sharedPath = self.copyFileToSharedContainer(url) { let metadata = self.extractFileMetadata(from: url) completion(SharedFileData(uri: sharedPath, name: metadata.name, mimeType: metadata.mimeType)) return } } completion(nil) } } private func processPlainTextAttachment(_ attachment: NSItemProvider, dispatchGroup: DispatchGroup, completion: @escaping (String?) -> Void) { dispatchGroup.enter() attachment.loadItem(forTypeIdentifier: UTType.plainText.identifier, options: nil) { (item, _) in defer { dispatchGroup.leave() } if let text = item as? String { completion(text) } else { completion(nil) } } } private func processURLAttachment(_ attachment: NSItemProvider, dispatchGroup: DispatchGroup, completion: @escaping (String?) -> Void) { dispatchGroup.enter() attachment.loadItem(forTypeIdentifier: UTType.url.identifier, options: nil) { (item, _) in defer { dispatchGroup.leave() } if let url = item as? URL { completion(url.absoluteString) } else { completion(nil) } } } private func processSharedContent() { guard let extensionContext = extensionContext, let item = extensionContext.inputItems.first as? NSExtensionItem, let attachments = item.attachments else { self.extensionContext?.completeRequest(returningItems: [], completionHandler: nil) return } var textValues: [String] = [] var fileValues: [SharedFileData] = [] let title = item.attributedTitle?.string ?? item.attributedContentText?.string ?? "" let dispatchGroup = DispatchGroup() for attachment in attachments { if attachment.hasItemConformingToTypeIdentifier(UTType.image.identifier) { processImageAttachment(attachment, dispatchGroup: dispatchGroup) { fileData in if let fileData = fileData { fileValues.append(fileData) } } } if attachment.hasItemConformingToTypeIdentifier(UTType.movie.identifier) { processMovieAttachment(attachment, dispatchGroup: dispatchGroup) { fileData in if let fileData = fileData { fileValues.append(fileData) } } } if attachment.hasItemConformingToTypeIdentifier(UTType.plainText.identifier) { processPlainTextAttachment(attachment, dispatchGroup: dispatchGroup) { text in if let text = text { textValues.append(text) } } } if attachment.hasItemConformingToTypeIdentifier(UTType.url.identifier) { processURLAttachment(attachment, dispatchGroup: dispatchGroup) { urlString in if let urlString = urlString { textValues.append(urlString) } } } } dispatchGroup.notify(queue: .main) { [weak self] in self?.sendData(with: textValues, fileValues: fileValues, title: title) } } } extension UIImage { private func applyRotationTransform(for orientation: UIImage.Orientation, to transform: CGAffineTransform, size: CGSize) -> CGAffineTransform { var result = transform switch orientation { case .down, .downMirrored: result = result.translatedBy(x: size.width, y: size.height) result = result.rotated(by: .pi) case .left, .leftMirrored: result = result.translatedBy(x: size.width, y: 0) result = result.rotated(by: .pi / 2.0) case .right, .rightMirrored: result = result.translatedBy(x: 0, y: size.height) result = result.rotated(by: -.pi / 2.0) case .up, .upMirrored: break @unknown default: break } return result } private func applyMirrorTransform(for orientation: UIImage.Orientation, to transform: CGAffineTransform, size: CGSize) -> CGAffineTransform { var result = transform switch orientation { case .upMirrored, .downMirrored: result = result.translatedBy(x: size.width, y: 0) result = result.scaledBy(x: -1, y: 1) case .leftMirrored, .rightMirrored: result = result.translatedBy(x: size.height, y: 0) result = result.scaledBy(x: -1, y: 1) case .up, .down, .left, .right: break @unknown default: break } return result } private func drawRect(for orientation: UIImage.Orientation, size: CGSize) -> CGRect { switch orientation { case .left, .leftMirrored, .right, .rightMirrored: return CGRect(x: 0, y: 0, width: size.height, height: size.width) default: return CGRect(x: 0, y: 0, width: size.width, height: size.height) } } func fixedOrientation() -> UIImage? { guard imageOrientation != .up else { return self.copy() as? UIImage } guard let cgImage = self.cgImage, let colorSpace = cgImage.colorSpace, let context = CGContext(data: nil, width: Int(size.width), height: Int(size.height), bitsPerComponent: cgImage.bitsPerComponent, bytesPerRow: 0, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue) else { return nil } var transform = CGAffineTransform.identity transform = applyRotationTransform(for: imageOrientation, to: transform, size: size) transform = applyMirrorTransform(for: imageOrientation, to: transform, size: size) context.concatenate(transform) context.draw(cgImage, in: drawRect(for: imageOrientation, size: size)) guard let newCGImage = context.makeImage() else { return nil } return UIImage(cgImage: newCGImage, scale: 1, orientation: .up) } } ``` **Attention**: Replace `` with the URL scheme you defined in your main app's `Info.plist` file (e.g. `myapp`) and `` with your app identifier (e.g. `com.example.app`). Make sure to keep the `group.` prefix for the app group identifier. Finally, you need to modify the `AppDelegate.swift` file of your main app target to handle the URLs opened by the share extension. Add the missing import and the following code to the `application(_:open:options:)` method: ``` + import CapawesomeTeamCapacitorShareTarget func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + // Handle share target URLs + let _ = ShareTargetPlugin.handleOpenUrl(url) + // Called when the app was launched with a url. Feel free to add additional processing here, // but if you want the App API to support tracking app url opens, make sure to keep this call return ApplicationDelegateProxy.shared.application(app, open: url, options: options) } ``` #### Capabilities If you want to receive not only text but also files (e.g., images, videos) from other apps, you need to enable the "App Groups" capability for both your main app and the share extension. This allows both targets to share files in a common container. To do this, follow these steps: 1. Open your Xcode project. 1. Select your app target. 1. Go to the "Signing & Capabilities" tab. 1. Click the "+" button to add a new capability. 1. Select "App Groups" from the list. 1. Create a new app group using the format `group.` (e.g., `group.com.example.app`). 1. Repeat the same steps for your share extension target, ensuring that both targets use the same app group identifier. If you don't want to receive files, you can skip this step. ## Configuration No configuration required for this plugin. ## Web To use this plugin on the web, you need to set up a **Progressive Web App (PWA)** with a web manifest and service worker that handles share targets. ### Manifest To allow your PWA to act as a share target, you need to add a `share_target` configuration to your web manifest (`manifest.json`): ``` { "share_target": { "action": "/_share-target", "method": "POST", "enctype": "multipart/form-data", "params": { "title": "title", "text": "text", "url": "url", "files": [ { "name": "files", "accept": ["*/*"] } ] } } } ``` For more information on setting up the share target for your PWA, refer to the [Web Share Target documentation](https://developer.mozilla.org/en-US/docs/Web/Manifest/share_target). ### Service Worker Since service workers are the only way to intercept network requests in a PWA, you'll need to create one to handle the share target requests. Just create a file named `sw.js` in your project's root directory and add the following code: ``` self.addEventListener('fetch', event => { const url = new URL(event.request.url); if (event.request.method === 'POST' && url.pathname === '/_share-target') { event.respondWith(handleShareTarget(event.request)); } else if (url.pathname.startsWith('/_share-file/')) { event.respondWith(handleFileRequest(event.request)); } }); async function handleFileRequest(request) { try { const url = new URL(request.url); const fileId = url.pathname.substring(13); // Remove '/_share-file/' prefix const cache = await caches.open('share-target-files'); const cacheKey = `/${fileId}`; const response = await cache.match(cacheKey); if (response) { return response; } else { return new Response('File not found', { status: 404 }); } } catch (error) { console.error('Error serving file:', error); return new Response('Internal error', { status: 500 }); } } async function handleShareTarget(request) { try { const formData = await request.formData(); const title = formData.get('title') || ''; const text = formData.get('text') || ''; const url = formData.get('url') || ''; const files = formData.getAll('files'); const texts = []; if (text) texts.push(text); if (url) texts.push(url); const shareData = { title: title, texts: texts.length > 0 ? texts : undefined, files: undefined, }; if (files.length > 0) { const sharedFiles = []; const cache = await caches.open('share-target-files'); for (const file of files) { if (file instanceof File && file.size > 0) { const fileId = `share-file-${Date.now()}-${Math.random().toString(36).substring(2, 11)}`; const cacheKey = `/${fileId}`; const response = new Response(file, { headers: { 'Content-Type': file.type, 'Content-Length': file.size.toString(), 'X-File-Name': file.name || 'unknown', } }); await cache.put(cacheKey, response); sharedFiles.push({ uri: `/_share-file/${fileId}`, name: file.name || undefined, mimeType: file.type || undefined, }); } } if (sharedFiles.length > 0) { shareData.files = sharedFiles; } } const redirectUrl = new URL('/', self.location.origin); if (shareData.title) { redirectUrl.searchParams.set('title', shareData.title); } if (shareData.texts && shareData.texts.length > 0) { shareData.texts.forEach((text, index) => { redirectUrl.searchParams.set(`text${index}`, text); }); } if (shareData.files && shareData.files.length > 0) { shareData.files.forEach((file, index) => { redirectUrl.searchParams.set(`fileUri${index}`, file.uri); if (file.name) { redirectUrl.searchParams.set(`fileName${index}`, file.name); } if (file.mimeType) { redirectUrl.searchParams.set(`fileMimeType${index}`, file.mimeType); } }); } return Response.redirect(redirectUrl.href, 303); } catch (error) { console.error('Error handling share target:', error); return Response.redirect('/', 303); } } ``` Now, register your service worker in your main JavaScript file: ``` if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/sw.js'); } ``` ## Usage ``` import { Capacitor } from '@capacitor/core'; import { ShareTarget } from '@capawesome-team/capacitor-share-target'; const addListener = async () => { await ShareTarget.addListener('shareReceived', (event) => { console.log('Share received:', event); // Handle shared files if (event.files) { event.files.forEach(async (file) => { const webPath = Capacitor.convertFileSrc(file.uri); const response = await fetch(webPath); const blob = await response.blob(); // Process the file... }); } }); }; ``` ## API - [`addListener('shareReceived', ...)`](#addlistenersharereceived-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) ### addListener('shareReceived', ...) ``` addListener(eventName: 'shareReceived', listenerFunc: (event: ShareReceivedEvent) => void) => Promise ``` Called when a share is received. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'shareReceived'` | | **`listenerFunc`** | `(event: ShareReceivedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ShareReceivedEvent | Prop | Type | Description | Since | | ----------- | -------------- | --------------------------------- | ----- | | **`title`** | `string` | The title of the shared content. | 0.1.0 | | **`texts`** | `string[]` | The text content that was shared. | 0.1.0 | | **`files`** | `SharedFile[]` | The files that were shared. | 0.2.0 | #### SharedFile | Prop | Type | Description | Since | | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`mimeType`** | `string` | The mime type of the shared file. | 0.2.0 | | **`name`** | `string` | The name of the shared file with or without extension. | 0.2.0 | | **`uri`** | `string` | The URI of the shared file. On **Android** and **iOS**, this will contain the file paths or base64 encoded data URLs of the shared files. On **Web**, this will contain cached file URLs that can be fetched directly. | 0.2.0 | ## Troubleshooting ##### `Unable to find compatibility version string for object version XX` When creating a share extension, you might encounter the following error when building your app: ``` Unable to find compatibility version string for object version 70 ``` This error is typically caused by an updated `objectVersion` value in the `project.pbxproj` file of your Xcode project. To resolve this issue, you need to manually revert the `objectVersion` value to a previous version that is compatible with your Xcode version. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/share-target/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/share-target/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/share-target/LICENSE). # @capawesome-team/capacitor-speech-recognition Capacitor plugin to transcribe speech into text (also known as speech-to-text) with advanced features like silence detection, contextual strings, and more. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for speech recognition. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🌐 **Multiple Languages**: Supports many different languages. - 🔑 **Permissions**: Check and request permissions for recording audio. - 🔊 **Events**: Listen for events like `start`, `end`, `speechStart`, `speechEnd`, `error`, `partialResults`, and `results`. - 🔇 **Silence Detection**: Automatically detects silence to stop the recording. - 📊 **Silence Threshold**: Define what's considered "silence" for your recordings. - 💬 **Contextual Strings**: Provide an array of phrases that should be recognized, even if they are not in the system vocabulary. - 📱 **On-Device Recognition**: Force on-device-only speech recognition, query available on-device languages, and download language models. - 🧠 **SpeechTranscriber**: Opt-in support for Apple's modern `SpeechTranscriber` API (iOS 26+) with fully on-device processing and no "Speech data will be sent to Apple" permission dialog. - 🤝 **Compatibility**: Compatible with the [Audio Player](https://capawesome.io/plugins/audio-player/), [Audio Recorder](https://capawesome.io/plugins/audio-recorder/) and [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/) plugins. - ⚔️ **Battle-Tested**: Used in more than 250 projects. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Guides - [Exploring the Capacitor Speech Recognition API](https://capawesome.io/blog/exploring-the-capacitor-speech-recognition-api/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-speech-recognition` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-speech-recognition npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Privacy Descriptions Add the `NSSpeechRecognitionUsageDescription` and `NSMicrophoneUsageDescription` keys to the `ios/App/App/Info.plist` file, which tells the user why the app needs access to speech recognition and the microphone: ``` NSSpeechRecognitionUsageDescription Speech recognition is used to transcribe speech into text. NSMicrophoneUsageDescription Microphone is used to record audio for speech recognition. ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { SpeechRecognition } from '@capawesome-team/capacitor-speech-recognition'; const startListening = async () => { await SpeechRecognition.startListening({ language: 'en-US', silenceThreshold: 2000, }); }; const stopListening = async () => { await SpeechRecognition.stopListening(); }; const checkPermissions = async () => { const { audioRecording, speechRecognition } = await SpeechRecognition.checkPermissions(); }; const requestPermissions = async () => { const { audioRecording, speechRecognition } = await SpeechRecognition.requestPermissions({ permissions: ['audioRecording', 'speechRecognition'], }); }; const isAvailable = async () => { const { isAvailable } = await SpeechRecognition.isAvailable(); return isAvailable; }; const isListening = async () => { const { isListening } = await SpeechRecognition.isListening(); return isListening; }; const getLanguages = async () => { const { languages } = await SpeechRecognition.getLanguages(); return languages; }; const addListeners = () => { SpeechRecognition.addListener('start', () => { console.log('Speech recognition started'); }); SpeechRecognition.addListener('end', () => { console.log('Speech recognition ended'); }); SpeechRecognition.addListener('error', (event) => { console.error('Speech recognition error:', event.message); }); SpeechRecognition.addListener('partialResult', (event) => { console.log('Partial result:', event.result); }); SpeechRecognition.addListener('result', (event) => { console.log('Final result:', event.result); }); SpeechRecognition.addListener('soundLevel', (event) => { console.log('Sound level:', event.level); }); SpeechRecognition.addListener('speechStart', () => { console.log('User started speaking'); }); SpeechRecognition.addListener('speechEnd', () => { console.log('User stopped speaking'); }); }; const removeAllListeners = async () => { await SpeechRecognition.removeAllListeners(); }; ``` ## API - [`getLanguages()`](#getlanguages) - [`getOnDeviceLanguages()`](#getondevicelanguages) - [`downloadOnDeviceLanguage(...)`](#downloadondevicelanguage) - [`isAvailable()`](#isavailable) - [`isListening()`](#islistening) - [`startListening(...)`](#startlistening) - [`stopListening(...)`](#stoplistening) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions(...)`](#requestpermissions) - [`addListener('end', ...)`](#addlistenerend-) - [`addListener('error', ...)`](#addlistenererror-) - [`addListener('partialResult', ...)`](#addlistenerpartialresult-) - [`addListener('result', ...)`](#addlistenerresult-) - [`addListener('speechEnd', ...)`](#addlistenerspeechend-) - [`addListener('speechStart', ...)`](#addlistenerspeechstart-) - [`addListener('start', ...)`](#addlistenerstart-) - [`addListener('soundLevel', ...)`](#addlistenersoundlevel-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### getLanguages() ``` getLanguages() => Promise ``` Get the available languages for speech recognition. **Attention**: On Android, this method is unfortunately not supported by all devices. If the method is not supported, the promise will never resolve. It's recommended to set a timeout for the promise. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getOnDeviceLanguages() ``` getOnDeviceLanguages() => Promise ``` Get the available on-device languages for speech recognition. Only available on Android (SDK 33+) and iOS (26+). **Returns:** `Promise` **Since:** 8.1.0 ______________________________________________________________________ ### downloadOnDeviceLanguage(...) ``` downloadOnDeviceLanguage(options: DownloadOnDeviceLanguageOptions, callback: DownloadOnDeviceLanguageCallback) => Promise ``` Download an on-device language model for speech recognition. **Note**: On Android, this might trigger user interaction to approve the download. **Note**: On Android (SDK 33), the `callback` is never invoked because progress tracking is only available on SDK 34+. Use {@link getOnDeviceLanguages} to verify when the download completes. Only available on Android (SDK 33+) and iOS (26+). | Param | Type | | -------------- | ---------------------------------- | | **`options`** | `DownloadOnDeviceLanguageOptions` | | **`callback`** | `DownloadOnDeviceLanguageCallback` | **Returns:** `Promise` **Since:** 8.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the speech recognizer is available on the device. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isListening() ``` isListening() => Promise ``` Check if the speech recognizer is currently listening. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### startListening(...) ``` startListening(options?: StartListeningOptions | undefined) => Promise ``` Start listening for speech. | Param | Type | | ------------- | ----------------------- | | **`options`** | `StartListeningOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### stopListening(...) ``` stopListening(options?: StopListeningOptions | undefined) => Promise ``` Stop listening for speech. | Param | Type | | ------------- | ---------------------- | | **`options`** | `StopListeningOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for the plugin. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(permissions?: SpeechRecognitionPluginPermission | undefined) => Promise ``` Request permissions for the plugin. | Param | Type | | ----------------- | ----------------------------------- | | **`permissions`** | `SpeechRecognitionPluginPermission` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('end', ...) ``` addListener(eventName: 'end', listenerFunc: () => void) => Promise ``` Called when the speech recognizer has stopped listening. | Param | Type | | ------------------ | ------------ | | **`eventName`** | `'end'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('error', ...) ``` addListener(eventName: 'error', listenerFunc: (event: ErrorEvent) => void) => Promise ``` Called when an error occurs. | Param | Type | | ------------------ | ----------------------------- | | **`eventName`** | `'error'` | | **`listenerFunc`** | `(event: ErrorEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('partialResult', ...) ``` addListener(eventName: 'partialResult', listenerFunc: (event: PartialResultEvent) => void) => Promise ``` Called when a partial result is available. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'partialResult'` | | **`listenerFunc`** | `(event: PartialResultEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('result', ...) ``` addListener(eventName: 'result', listenerFunc: (event: ResultEvent) => void) => Promise ``` Called when the final results are available. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'result'` | | **`listenerFunc`** | `(event: ResultEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('speechEnd', ...) ``` addListener(eventName: 'speechEnd', listenerFunc: () => void) => Promise ``` Called when the user has stopped speaking. Only available on Android and Web. | Param | Type | | ------------------ | ------------- | | **`eventName`** | `'speechEnd'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('speechStart', ...) ``` addListener(eventName: 'speechStart', listenerFunc: () => void) => Promise ``` Called when the user has started to speak. Only available on Android and Web. | Param | Type | | ------------------ | --------------- | | **`eventName`** | `'speechStart'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('start', ...) ``` addListener(eventName: 'start', listenerFunc: () => void) => Promise ``` Called when the speech recognizer has started listening. | Param | Type | | ------------------ | ------------ | | **`eventName`** | `'start'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('soundLevel', ...) ``` addListener(eventName: 'soundLevel', listenerFunc: (event: SoundLevelEvent) => void) => Promise ``` Called when the sound level changes during speech recognition. **Attention**: There is no guarantee that this method will be called. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------- | | **`eventName`** | `'soundLevel'` | | **`listenerFunc`** | `(event: SoundLevelEvent) => void` | **Returns:** `Promise` **Since:** 7.5.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### GetLanguagesResult | Prop | Type | Description | Since | | --------------- | ---------- | ----------------------------------------------------------------------- | ----- | | **`languages`** | `string[]` | The supported languages for speech recognition as BCP-47 language tags. | 6.0.0 | #### GetOnDeviceLanguagesResult | Prop | Type | Description | Since | | ------------------------ | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`installedLanguages`** | `string[]` | The installed on-device languages as BCP-47 language tags. | 8.1.0 | | **`pendingLanguages`** | `string[]` | The on-device languages whose download is scheduled or in progress as BCP-47 language tags. Only available on Android. | 8.1.0 | | **`supportedLanguages`** | `string[]` | The supported on-device languages as BCP-47 language tags. These languages can be downloaded for on-device use using the {@link downloadOnDeviceLanguage} method. | 8.1.0 | #### DownloadOnDeviceLanguageOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------- | ----- | | **`language`** | `string` | The BCP-47 language tag for the language to download. | 8.1.0 | #### DownloadOnDeviceLanguageCallbackEvent | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------- | ----- | | **`progress`** | `number` | The download progress, as a percentage between 0 and 1. | 8.1.0 | | **`completed`** | `boolean` | Whether the download is completed or not. | 8.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ---------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not the speech recognizer is available on the device. | 6.0.0 | #### IsListeningResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------ | ----- | | **`isListening`** | `boolean` | Whether or not the speech recognizer is currently listening. | 6.0.0 | #### StartListeningOptions | Prop | Type | Description | Default | Since | | ---------------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ----- | | **`audioSessionCategory`** | `AudioSessionCategory` | The audio session category to use for speech recognition. Only available on iOS. | `AudioSessionCategory.Record` | 7.2.0 | | **`audioSessionMode`** | `AudioSessionMode` | The audio session mode for speech recognition. Only available on iOS. | `AudioSessionMode.Measurement` | 8.1.0 | | **`contextualStrings`** | `string[]` | An array of phrases that should be recognized, even if they are not in the system vocabulary. Only available on Android (SDK 33+) and iOS. | | 7.3.0 | | **`deactivateAudioSessionOnStop`** | `boolean` | Whether or not to deactivate your app's audio session on stop. Only available on iOS. | `true` | 7.2.0 | | **`enableFormatting`** | `boolean` | Whether to add punctuation to speech recognition results. **Note**: On Android, this option does not work reliably as it varies depending on the device and TTS engine. Only available on Android (SDK 33+) and iOS (+16). | `false` | 7.4.0 | | **`language`** | `string` | The BC-47 language tag for the language to use for speech recognition. | | 6.0.0 | | **`silenceThreshold`** | `number` | The number of milliseconds of silence before the speech recognition ends. **Attention**: This option may not work reliably on all devices and platforms as it depends on the underlying speech recognition service. This is a limitation of the platform and not the plugin itself. Continuous listening by setting an extremely high value will not work. Only available on Android (SDK 33+) and iOS. | `2000` | 6.0.0 | | **`taskHint`** | `TaskHint` | The type of task for which the speech recognition is being used for. Only available on iOS. | `TaskHint.Unspecified` | 7.5.0 | | **`requireOnDeviceRecognition`** | `boolean` | Whether to require on-device speech recognition. If `true`, speech recognition will only use on-device models. If the on-device model is not available, the call will fail with an error. Only available on Android (SDK 33+) and iOS. | `false` | 8.1.0 | | **`useSpeechTranscriber`** | `boolean` | Whether to use the `SpeechTranscriber` API for on-device speech recognition. This avoids the "Speech data from this app will be sent to Apple" permission dialog and only requires microphone permission. On-device recognition is used implicitly when this option is set to `true`, so `requireOnDeviceRecognition` does not need to be set separately. Only available on iOS (26+). | `false` | 8.1.0 | #### StopListeningOptions | Prop | Type | Description | Default | Since | | ---------------------------- | --------- | ----------------------------------------------------------------------------- | ------- | ----- | | **`deactivateAudioSession`** | `boolean` | Whether or not to deactivate your app's audio session. Only available on iOS. | `true` | 7.2.0 | #### PermissionStatus | Prop | Type | Description | Since | | ----------------------- | ----------------- | --------------------------------------------------------------- | ----- | | **`audioRecording`** | `PermissionState` | Permission state for recording audio. | 7.1.0 | | **`recordAudio`** | `PermissionState` | Permission state for speech recognition. | 6.0.0 | | **`speechRecognition`** | `PermissionState` | Permission state for speech recognition. Only available on iOS. | 7.1.0 | #### SpeechRecognitionPluginPermission | Prop | Type | | ----------------- | ----------------------------------- | | **`permissions`** | `SpeechRecognitionPermissionType[]` | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ErrorEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 6.0.0 | #### PartialResultEvent | Prop | Type | Description | Since | | ------------ | -------- | --------------------------------------------- | ----- | | **`result`** | `string` | The partial result of the speech recognition. | 6.0.0 | #### ResultEvent | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------- | ----- | | **`result`** | `string` | The final result of the speech recognition. | 6.0.0 | #### SoundLevelEvent | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`level`** | `number` | The sound level of the audio signal. The value is normalized across platforms to a range of 0-100, where 0 represents minimum detected sound and 100 represents maximum sound level. | 7.5.0 | ### Type Aliases #### DownloadOnDeviceLanguageCallback `(event: DownloadOnDeviceLanguageCallbackEvent | null, error: any): void` #### CallbackId `string` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### SpeechRecognitionPermissionType `'audioRecording' | 'speechRecognition'` ### Enums #### AudioSessionCategory | Members | Value | Description | Since | | ------------------- | ------------------- | --------------------------------------------------------------------- | ----- | | **`Record`** | `'RECORD'` | The category for recording audio while also silencing playback audio. | 7.2.0 | | **`PlayAndRecord`** | `'PLAY_AND_RECORD'` | The category for recording (input) and playback (output) of audio. | 7.2.0 | #### AudioSessionMode | Members | Value | Description | Since | | -------------------- | ------------------- | ----------------------------------------------------------------------------- | ----- | | **`Default`** | `'DEFAULT'` | Default mode that doesn't enable additional audio session features. | 8.1.0 | | **`GameChat`** | `'GAME_CHAT'` | Mode for chat communication over VoIP or internet, optimized for low latency. | 8.1.0 | | **`Measurement`** | `'MEASUREMENT'` | Mode for high-quality measurement recordings with maximum dynamic range. | 8.1.0 | | **`SpokenAudio`** | `'SPOKEN_AUDIO'` | Mode for speech recording and transcription with optimized voice processing. | 8.1.0 | | **`VideoChat`** | `'VIDEO_CHAT'` | Mode for two-way video chat communications. | 8.1.0 | | **`VideoRecording`** | `'VIDEO_RECORDING'` | Mode for recording video content with high-quality audio. | 8.1.0 | | **`VoiceChat`** | `'VOICE_CHAT'` | Mode for voice chat communications. | 8.1.0 | #### TaskHint | Members | Value | Since | | ------------------ | ---------------- | ----- | | **`Confirmation`** | `'CONFIRMATION'` | 7.5.0 | | **`Dictation`** | `'DICTATION'` | 7.5.0 | | **`Search`** | `'SEARCH'` | 7.5.0 | | **`Unspecified`** | `'UNSPECIFIED'` | 7.5.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-recognition/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-recognition/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-recognition/LICENSE). # @capawesome-team/capacitor-speech-synthesis Capacitor plugin for synthesizing speech from text (also known as text-to-speech) with advanced features like voice selection, pitch, and rate control. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for speech synthesis. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 🌐 **Multiple Languages**: Supports many different languages. - 🗣️ **Multiple Voices**: Supports multiple voices for each language. - 🎚️ **Customization**: Customize the pitch, rate, volume and voice of the speech. - 🎧 **Background Audio**: Synthesize speech from text while your application runs in the background. - 📜 **Queue Strategy**: Add or flush the utterance to the queue. - 🔊 **Events**: Listen for events like `boundary`, `end`, `error` and `start`. - ⏸️ **Pause/Resume**: Pause and resume speech synthesis. - 🤝 **Compatibility**: Compatible with the [Audio Player](https://capawesome.io/plugins/audio-player/), [Audio Recorder](https://capawesome.io/plugins/audio-recorder/) and [Speech Recognition](https://capawesome.io/plugins/speech-recognition/) plugins. - ⚔️ **Battle-Tested**: Used in more than 100 projects. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-speech-synthesis` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-speech-synthesis npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { SpeechSynthesis, AudioSessionCategory, QueueStrategy } from '@capawesome-team/capacitor-speech-synthesis'; const speak = async () => { // Add an utterance to the utterance queue to be spoken const { utteranceId } = await SpeechSynthesis.speak({ language: 'en-US', pitch: 1.0, queueStrategy: QueueStrategy.Add, rate: 1.0, text: 'Hello, World!', voiceId: 'com.apple.ttsbundle.Samantha-compact', volume: 1.0, }); // Wait for the utterance to finish await new Promise(resolve => { void SpeechSynthesis.addListener('end', event => { if (event.utteranceId === utteranceId) { resolve(); } }); }); }; const synthesizeToFile = async () => { // Add an utterance to the utterance queue to be synthesized to a file const { path, utteranceId } = await SpeechSynthesis.synthesizeToFile({ language: 'en-US', pitch: 1.0, queueStrategy: QueueStrategy.Add, rate: 1.0, text: 'Hello, World!', voiceId: 'com.apple.ttsbundle.Samantha-compact', volume: 1.0, }); // Wait for the utterance to finish await new Promise(resolve => { void SpeechSynthesis.addListener('end', event => { if (event.utteranceId === utteranceId) { resolve(); } }); }); // Return the path to the synthesized audio file return path; }; const cancel = async () => { await SpeechSynthesis.cancel(); }; const pause = async () => { await SpeechSynthesis.pause(); }; const resume = async () => { await SpeechSynthesis.resume(); }; const isAvailable = async () => { const result = await SpeechSynthesis.isAvailable(); return result.isAvailable; }; const isLanguageAvailable = async () => { const result = await SpeechSynthesis.isLanguageAvailable({ language: 'en-US' }); return result.isAvailable; }; const isVoiceAvailable = async () => { const result = await SpeechSynthesis.isVoiceAvailable({ voiceId: 'com.apple.ttsbundle.Samantha-compact' }); return result.isAvailable; }; const getLanguages = async () => { const result = await SpeechSynthesis.getLanguages(); return result.languages; }; const getVoices = async () => { const result = await SpeechSynthesis.getVoices(); return result.voices; }; const addListeners = () => { SpeechSynthesis.addListener('boundary', (event) => { console.log('boundary', event); }); SpeechSynthesis.addListener('end', (event) => { console.log('end', event); }); SpeechSynthesis.addListener('error', (event) => { console.log('error', event); }); SpeechSynthesis.addListener('start', (event) => { console.log('start', event); }); }; const removeAllListeners = async () => { await SpeechSynthesis.removeAllListeners(); }; ``` ## API - [`activateAudioSession(...)`](#activateaudiosession) - [`cancel()`](#cancel) - [`deactivateAudioSession()`](#deactivateaudiosession) - [`getLanguages()`](#getlanguages) - [`getVoices()`](#getvoices) - [`initialize()`](#initialize) - [`isAvailable()`](#isavailable) - [`isSpeaking()`](#isspeaking) - [`isLanguageAvailable(...)`](#islanguageavailable) - [`isVoiceAvailable(...)`](#isvoiceavailable) - [`pause()`](#pause) - [`resume()`](#resume) - [`speak(...)`](#speak) - [`synthesizeToFile(...)`](#synthesizetofile) - [`addListener('boundary', ...)`](#addlistenerboundary-) - [`addListener('end', ...)`](#addlistenerend-) - [`addListener('error', ...)`](#addlistenererror-) - [`addListener('start', ...)`](#addlistenerstart-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### activateAudioSession(...) ``` activateAudioSession(options: ActivateAudioSessionOptions) => Promise ``` Activate the audio session. This method is not mandatory. It can be used to set the audio session category before speaking. Only available on iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `ActivateAudioSessionOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### cancel() ``` cancel() => Promise ``` Remove all utterances from the utterance queue. **Since:** 6.0.0 ______________________________________________________________________ ### deactivateAudioSession() ``` deactivateAudioSession() => Promise ``` Deactivate the audio session. Only available on iOS. **Since:** 6.0.0 ______________________________________________________________________ ### getLanguages() ``` getLanguages() => Promise ``` Get the available languages for speech synthesis. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getVoices() ``` getVoices() => Promise ``` Get the available voices for speech synthesis. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### initialize() ``` initialize() => Promise ``` Initialize the plugin before any other method is called. Use this method to warm up the speech synthesis engine. If this method is not called, the plugin will be automatically initialized on the first call to any other method. Only available on Android and iOS. **Since:** 6.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if speech synthesis is available on the current device. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isSpeaking() ``` isSpeaking() => Promise ``` Check if speech synthesis is currently speaking. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isLanguageAvailable(...) ``` isLanguageAvailable(options: IsLanguageAvailableOption) => Promise ``` Check if a language is available for speech synthesis. | Param | Type | | ------------- | --------------------------- | | **`options`** | `IsLanguageAvailableOption` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isVoiceAvailable(...) ``` isVoiceAvailable(options: IsVoiceAvailableOption) => Promise ``` Check if a voice is available for speech synthesis. | Param | Type | | ------------- | ------------------------ | | **`options`** | `IsVoiceAvailableOption` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### pause() ``` pause() => Promise ``` Pause speech immediately. **Since:** 7.2.0 ______________________________________________________________________ ### resume() ``` resume() => Promise ``` Resume speech. **Since:** 7.2.0 ______________________________________________________________________ ### speak(...) ``` speak(options: SpeakOptions) => Promise ``` Add an utterance to the utterance queue to be spoken. The `end` event will be emitted when the utterance has finished. | Param | Type | | ------------- | -------------- | | **`options`** | `SpeakOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### synthesizeToFile(...) ``` synthesizeToFile(options: SynthesizeToFileOptions) => Promise ``` Add an utterance to the utterance queue to be synthesized to a file. The `end` event will be emitted when the utterance has finished. Only available on Android and iOS. | Param | Type | | ------------- | -------------- | | **`options`** | `SpeakOptions` | **Returns:** `Promise` **Since:** 7.1.0 ______________________________________________________________________ ### addListener('boundary', ...) ``` addListener(eventName: 'boundary', listenerFunc: (event: BoundaryEvent) => void) => Promise ``` Called hen the spoken utterance reaches a word boundary. | Param | Type | | ------------------ | -------------------------------- | | **`eventName`** | `'boundary'` | | **`listenerFunc`** | `(event: BoundaryEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('end', ...) ``` addListener(eventName: 'end', listenerFunc: (event: EndEvent) => void) => Promise ``` Called when the spoken utterance has finished. | Param | Type | | ------------------ | --------------------------- | | **`eventName`** | `'end'` | | **`listenerFunc`** | `(event: EndEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('error', ...) ``` addListener(eventName: 'error', listenerFunc: (event: ErrorEvent) => void) => Promise ``` Called when an error occurs during speech synthesis. | Param | Type | | ------------------ | ----------------------------- | | **`eventName`** | `'error'` | | **`listenerFunc`** | `(event: ErrorEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('start', ...) ``` addListener(eventName: 'start', listenerFunc: (event: StartEvent) => void) => Promise ``` Called when the spoken utterance has started. | Param | Type | | ------------------ | ----------------------------- | | **`eventName`** | `'start'` | | **`listenerFunc`** | `(event: StartEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for the plugin. **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### ActivateAudioSessionOptions | Prop | Type | Description | Since | | -------------- | ---------------------- | ---------------------------------- | ----- | | **`category`** | `AudioSessionCategory` | The audio session category to set. | 6.0.0 | #### GetLanguagesResult | Prop | Type | Description | Since | | --------------- | ---------- | ----------------------------------------------- | ----- | | **`languages`** | `string[]` | The available languages as BC-47 language tags. | 6.0.0 | #### GetVoicesResult | Prop | Type | Description | Since | | ------------ | --------- | --------------------- | ----- | | **`voices`** | `Voice[]` | The available voices. | 6.0.0 | #### Voice | Prop | Type | Description | Since | | --------------------------------- | ---------- | --------------------------------------------------------------------- | ----------------------------------------------- | | **`default`** | `boolean` | Whether or not the voice is the default voice. Only available on Web. | 6.0.0 | | **`gender`** | \`'female' | 'male'\` | The gender of the voice. Only available on iOS. | | **`id`** | `string` | The identifier of the voice. | 6.0.0 | | **`isNetworkConnectionRequired`** | `boolean` | Whether or not the voice is available via a local or remote service. | 6.0.0 | | **`language`** | `string` | The BC-47 language tag for the language of the voice. | 6.0.0 | | **`name`** | `string` | The name of the voice. | 6.0.0 | #### IsAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not speech synthesis is available on the current device. | 6.0.0 | #### IsSpeakingResult | Prop | Type | Description | Since | | ---------------- | --------- | ------------------------------------------------------ | ----- | | **`isSpeaking`** | `boolean` | Whether or not an utterance is currently being spoken. | 6.0.0 | #### IsLanguageAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | -------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not the language is available for speech synthesis. | 6.0.0 | #### IsLanguageAvailableOption | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------- | ----- | | **`language`** | `string` | The BC-47 language tag for the language to check. | 6.0.0 | #### IsVoiceAvailableResult | Prop | Type | Description | Since | | ----------------- | --------- | ----------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether or not the voice is available for speech synthesis. | 6.0.0 | #### IsVoiceAvailableOption | Prop | Type | Description | Since | | ------------- | -------- | ------------------------------------- | ----- | | **`voiceId`** | `string` | The identifier of the voice to check. | 6.0.0 | #### SpeakResult | Prop | Type | Description | Since | | ----------------- | -------- | ----------------------------------------------------- | ----- | | **`utteranceId`** | `string` | The identifier of the utterance that is being spoken. | 6.0.0 | #### SpeakOptions | Prop | Type | Description | Default | Since | | -------------------------- | ---------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------- | ----- | | **`audioSessionCategory`** | `AudioSessionCategory` | The audio session category to use for speech synthesis. The audio session will be automatically activated with this category when speech starts and deactivated when speech ends, restoring the previous audio session state. Only available on iOS. | `AudioSessionCategory.SoloAmbient` | 8.0.0 | | **`language`** | `string` | The BC-47 language tag for the language to use for speech synthesis. On **iOS**, this option is only used when the `voiceId` option is not provided. | | 6.0.0 | | **`pitch`** | `number` | The pitch that the utterance will be spoken at. | `1.0` | 6.0.0 | | **`queueStrategy`** | `QueueStrategy` | The queue strategy to use for the utterance. | `QueueStrategy.Add` | 6.0.0 | | **`rate`** | `number` | The speed at which the utterance will be spoken at. | `1.0` | 6.0.0 | | **`text`** | `string` | The text that will be synthesized when the utterance is spoken. | | 6.0.0 | | **`voiceId`** | `string` | The identifier of the voice to use for speech synthesis. | | 6.0.0 | | **`volume`** | `number` | The volume that the utterance will be spoken at. | `1.0` | 6.0.0 | #### SynthesizeToFileResult | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path to which the synthesized audio file will be saved. The file is available as soon as the `end` event is emitted. Only available on Android and iOS. | 7.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### BoundaryEvent | Prop | Type | Description | Since | | ----------------- | -------- | ----------------------------------------------------- | ----- | | **`endIndex`** | `number` | The index of the last character in the word. | 6.0.0 | | **`startIndex`** | `number` | The index of the first character in the word. | 6.0.0 | | **`utteranceId`** | `string` | The identifier of the utterance that is being spoken. | 6.0.0 | | **`word`** | `string` | The word that was spoken. | 6.0.0 | #### EndEvent | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------- | ----- | | **`utteranceId`** | `string` | The identifier of the utterance that has finished. | 6.0.0 | #### ErrorEvent | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------------ | ----- | | **`message`** | `string` | The error message. | 6.0.0 | | **`utteranceId`** | `string` | The identifier of the utterance that caused the error. | 6.0.0 | #### StartEvent | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------- | ----- | | **`utteranceId`** | `string` | The identifier of the utterance that has started. | 6.0.0 | ### Type Aliases #### SynthesizeToFileOptions `SpeakOptions` ### Enums #### AudioSessionCategory | Members | Value | Description | Since | | ------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Ambient`** | `'AMBIENT'` | The audio session category for ambient sound. Audio from other apps mixes with your audio. Screen locking and the Silent switch silence your audio. | 6.0.0 | | **`MultiRoute`** | `'MULTI_ROUTE'` | The audio session category for routing distinct streams to different outputs. | 8.0.0 | | **`PlayAndRecord`** | `'PLAY_AND_RECORD'` | The audio session category for recording or playback. | 8.0.0 | | **`Playback`** | `'PLAYBACK'` | The audio session category for playback. App audio continues with the Silent switch set to silent or when the screen locks. | 6.0.0 | | **`Record`** | `'RECORD'` | The audio session category for recording. | 8.0.0 | | **`SoloAmbient`** | `'SOLO_AMBIENT'` | The default audio session category. Audio from other apps is silenced. Screen locking and the Silent switch silence your audio. | 8.0.0 | #### QueueStrategy | Members | Value | Description | Since | | ----------- | ----- | -------------------------------------------------------------------- | ----- | | **`Add`** | `0` | Add the utterance to the end of the queue. | 6.0.0 | | **`Flush`** | `1` | Flush the queue and add the utterance to the beginning of the queue. | 6.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-synthesis/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-synthesis/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-synthesis/LICENSE). # @capawesome-team/capacitor-sqlite Capacitor plugin to access SQLite databases with support for encryption, transactions, and schema migrations. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins to access SQLite databases. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, Web and Electron. - 🔒 **Encryption**: Supports 256 bit AES encryption with custom keys. - 📦 **Bundled SQLite**: Opt-in bundling of the latest SQLite version. - 📖 **Read-only mode**: Open databases in read-only mode to prevent modifications. - 📂 **File-based**: Open existing databases or create new ones with a file path. - 💾 **In-memory databases**: Create temporary in-memory databases for quick operations or testing. - 📈 **Schema migrations**: Automatically apply schema migrations when opening a database. - 🔄 **Transactions**: Supports transactions with `beginTransaction(...)`, `commitTransaction(...)`, and `rollbackTransaction(...)`. - 🔍 **Querying**: Execute SQL queries with `query(...)` and `execute(...)`. - 🔢 **Data Types**: Supports all SQLite data types: `NULL`, `INTEGER`, `REAL`, `TEXT`, and `BLOB`. - 🛡️ **Prepared Statements**: Uses prepared statements to prevent SQL injection attacks. - 🕸️ **SQLite WASM**: Uses SQLite WebAssembly for web platform support. - ⚛️ **Electron Native**: Uses native SQLite for Electron via the `node:sqlite` module. - 📝 **Full Text Search**: Supports full text search with [FTS5](https://www.sqlite.org/fts5.html). - 🗄️ **Key-Value Store**: Built-in [key-value store](#key-value-store) for simple data persistence without SQL. - 🗃️ **ORM Support**: Works with popular ORMs like [Drizzle](#drizzle), [Kysely](#kysely) and [TypeORM](#typeorm). - 🤝 **Compatibility**: Compatible with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/) plugin. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll add it for you! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.3.x | >=8.x.x | Active support | | 0.2.x | >=8.x.x | Deprecated | | 0.1.x | 7.x.x | Deprecated | ## Guides - [Alternative to the Capacitor Community SQLite plugin](https://capawesome.io/blog/alternative-to-capacitor-community-sqlite-plugin/) - [Alternative to the Ionic Secure Storage plugin](https://capawesome.io/blog/alternative-to-ionic-secure-storage-plugin/) - [Announcing the SQLite Plugin for Capacitor](https://capawesome.io/blog/announcing-the-capacitor-sqlite-plugin/) - [Encrypting SQLite databases in Capacitor](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/) - [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/) - [How to Use Drizzle ORM with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/) - [How to Use Kysely with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/) - [How to Use TypeORM with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-and-sqlite/) ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-sqlite` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-sqlite @sqlite.org/sqlite-wasm npx cap sync ``` ### Android #### Encryption If you want to use encryption, you must include the SQLCipher dependency in your app's `variables.gradle` file by setting the `capawesomeCapacitorSqliteIncludeSqlcipher` variable to `true`: ``` ext { + capawesomeCapacitorSqliteIncludeSqlcipher = true // Default: false } ``` **Attention**: When using SQLCipher you are responsible for compliance with all export, re-export and import restrictions and regulations in all applicable countries. You can find more information about this in this [blog post](https://discuss.zetetic.net/t/export-requirements-for-applications-using-sqlcipher/47). #### Bundled SQLite By default, this plugin uses the system SQLite version provided by the Android device. If you want to use a newer, consistent SQLite version across all devices, you can opt in to bundling [requery/sqlite-android](https://github.com/requery/sqlite-android) by setting the `capawesomeCapacitorSqliteIncludeRequery` variable to `true` in your app's `variables.gradle` file: ``` ext { + capawesomeCapacitorSqliteIncludeRequery = true // Default: false } ``` **Attention**: This option cannot be combined with `capawesomeCapacitorSqliteIncludeSqlcipher`. SQLCipher already bundles its own SQLite version. You also need to add the JitPack repository to your app's `build.gradle` file: ``` repositories { google() mavenCentral() + maven { url 'https://jitpack.io' } } ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxSqliteVersion` version of `androidx.sqlite:sqlite` (default: `2.6.2`) - `$androidxSqliteFrameworkAndroidVersion` version of `androidx.sqlite:sqlite-framework-android` (default: `2.6.2`) - `$netZeteticSqlcipherVersion` version of `net.zetetic:sqlcipher-android` (default: `4.12.0`) - `$requeryVersion` version of `com.github.requery:sqlite-android` (default: `3.49.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### CocoaPods Add the `Plain` pod to your app's `Podfile`: ``` target 'App' do capacitor_pods # Add your Pods here + pod 'CapawesomeTeamCapacitorSqlite/Plain', :path => '../../node_modules/@capawesome-team/capacitor-sqlite' end ``` If you want to use encryption, add the `SQLCipher` pod instead: ``` target 'App' do capacitor_pods # Add your Pods here - pod 'CapawesomeTeamCapacitorSqlite/Plain', :path => '../../node_modules/@capawesome-team/capacitor-sqlite' + pod 'CapawesomeTeamCapacitorSqlite/SQLCipher', :path => '../../node_modules/@capawesome-team/capacitor-sqlite' end ``` **Attention**: Do not add the pod in the section `def capacitor_pods`, but under the comment `# Add your Pods here`. **Attention**: When using SQLCipher you are responsible for compliance with all export, re-export and import restrictions and regulations in all applicable countries. You can find more information about this in this [blog post](https://discuss.zetetic.net/t/export-requirements-for-applications-using-sqlcipher/47). #### Swift Package Manager No additional setup is required for SPM. If you want to use encryption, you must enable the `SQLCipher` package trait. Add the following to your `capacitor.config.json` (or `capacitor.config.ts`): ``` { "experimental": { "ios": { "spm": { "swiftToolsVersion": "6.1", "packageTraits": { "@capawesome-team/capacitor-sqlite": ["SQLCipher"] } } } } } ``` **Attention**: SPM trait support requires Capacitor CLI 8.3.0+ and Xcode 16.3+ (Swift 6.1+). **Attention**: When using SQLCipher you are responsible for compliance with all export, re-export and import restrictions and regulations in all applicable countries. You can find more information about this in this [blog post](https://discuss.zetetic.net/t/export-requirements-for-applications-using-sqlcipher/47). ### Web This plugin uses the [@sqlite.org/sqlite-wasm](https://www.npmjs.com/package/@sqlite.org/sqlite-wasm) package to provide SQLite support on the web platform. It will automatically load the SQLite WASM module when needed. #### Usage with Angular If you are using Angular, you need to add the following configuration to your `angular.json` file to ensure the SQLite WASM module is copied to the assets folder during the build process and to set the necessary headers for the web worker: ``` { "projects": { "your-app-name": { "architect": { "build": { "options": { "assets": [ + { + "glob": "**/*", + "input": "node_modules/@sqlite.org/sqlite-wasm/dist/", + "output": "/assets/sqlite-wasm/" + } ] } }, "serve": { + "options": { + "headers": { + "Cross-Origin-Embedder-Policy": "require-corp", + "Cross-Origin-Opener-Policy": "same-origin" + } + } } } } } } ``` Finally, you need to initialize the SQLite WASM module before using the plugin. You can do this in your `main.ts` file or in a service that is loaded at the start of your application: ``` import { Capacitor } from '@capacitor/core'; import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const initialize = async () => { const isWeb = Capacitor.getPlatform() === 'web'; if (isWeb) { // Initialize the SQLite WASM module await Sqlite.initialize({ worker: new Worker('/assets/sqlite-wasm/sqlite3-worker1.mjs', { type: 'module' }) }); } }; ``` #### Usage with Vite If you are using Vite, you need to add the following configuration to your `vite.config.ts` file to ensure the SQLite WASM module is loaded correctly: ``` import { defineConfig } from 'vite'; export default defineConfig({ optimizeDeps: { exclude: ['@sqlite.org/sqlite-wasm'], }, server: { headers: { 'Cross-Origin-Embedder-Policy': 'require-corp', 'Cross-Origin-Opener-Policy': 'same-origin', }, }, }); ``` ### Electron This plugin uses the Node.js `node:sqlite` module to provide native SQLite support on Electron. The `node:sqlite` module is available starting from Node.js 22.5.0 (Electron 33+). #### Database Storage By default, databases are stored directly in Electron's `userData` directory: - **Windows**: `%APPDATA%\YourAppName\` - **macOS**: `~/Library/Application Support/YourAppName/` - **Linux**: `~/.config/YourAppName/` You can organize databases into subfolders by including the subfolder path in the `path` parameter: ``` // Open database directly in userData directory const { databaseId } = await Sqlite.open({ path: 'mydb.db' }); // Open database in a subfolder const { databaseId } = await Sqlite.open({ path: 'app/mydb.db' }); // Open database from absolute path const { databaseId } = await Sqlite.open({ path: '/Users/username/Databases/MyApp/mydb.db' }); // Open in-memory database const { databaseId } = await Sqlite.open(); ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { Capacitor } from '@capacitor/core'; import { Directory, Filesystem } from '@capacitor/filesystem'; import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const open = async () => { const { databaseId } = await Sqlite.open({ encryptionKey: 'secret', // Tip: Use the Secure Preferences plugin to store the key securely readOnly: false, path: 'mydb.sqlite3', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)', ], }, { version: 2, statements: ['ALTER TABLE users ADD COLUMN email TEXT'], }, ], version: 2, }); }; const execute = async () => { const { databaseId } = await Sqlite.open(); await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, age) VALUES (?, ?)', values: ['Alice', 30], }); }; const query = async () => { const { databaseId } = await Sqlite.open(); const result = await Sqlite.query({ databaseId, statement: 'SELECT * FROM users WHERE age > ?', values: [25], }); console.log(result.columns); // The column names in the result set console.log(result.rows); // The rows returned by the query }; const performTransaction = async () => { const { databaseId } = await Sqlite.open(); await Sqlite.beginTransaction({ databaseId }); await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, age) VALUES (?, ?)', values: ['Alice', 30], }); await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, age) VALUES (?, ?)', values: ['Bob', 25], }); await Sqlite.commitTransaction({ databaseId }); }; const close = async () => { const { databaseId } = await Sqlite.open(); await Sqlite.close({ databaseId }); }; const changeEncryptionKey = async () => { // Open the database with the old encryption key const { databaseId } = await Sqlite.open({ encryptionKey: 'old-secret', }); // Change the encryption key to a new one await Sqlite.changeEncryptionKey({ databaseId, encryptionKey: 'new-secret', }); }; const getVersion = async () => { const result = await Sqlite.getVersion(); console.log(result.version); // The version of the SQLite library used by the plugin }; const vacuum = async () => { const { databaseId } = await Sqlite.open(); await Sqlite.vacuum({ databaseId }); }; ``` ## API - [`beginTransaction(...)`](#begintransaction) - [`changeEncryptionKey(...)`](#changeencryptionkey) - [`close(...)`](#close) - [`commitTransaction(...)`](#committransaction) - [`execute(...)`](#execute) - [`getVersion()`](#getversion) - [`initialize(...)`](#initialize) - [`open(...)`](#open) - [`query(...)`](#query) - [`rollbackTransaction(...)`](#rollbacktransaction) - [`vacuum(...)`](#vacuum) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### beginTransaction(...) ``` beginTransaction(options: BeginTransactionOptions) => Promise ``` Begin a transaction on the specified database. | Param | Type | | ------------- | ------------------------- | | **`options`** | `BeginTransactionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### changeEncryptionKey(...) ``` changeEncryptionKey(options: ChangeEncryptionKeyOptions) => Promise ``` Change the encryption key of the database. **Attention**: This must be called after opening the database with the current encryption key. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `ChangeEncryptionKeyOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### close(...) ``` close(options: CloseOptions) => Promise ``` Close the specified database. | Param | Type | | ------------- | -------------- | | **`options`** | `CloseOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### commitTransaction(...) ``` commitTransaction(options: CommitTransactionOptions) => Promise ``` Commit the current transaction on the specified database. | Param | Type | | ------------- | -------------------------- | | **`options`** | `CommitTransactionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### execute(...) ``` execute(options: ExecuteOptions) => Promise ``` Execute a single SQL statement on the specified database. This method can be used to execute any SQL statement, including `INSERT`, `UPDATE`, `DELETE`, and `CREATE TABLE`. | Param | Type | | ------------- | ---------------- | | **`options`** | `ExecuteOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getVersion() ``` getVersion() => Promise ``` Get the version of the SQLite library used by the plugin. To get the version of the database schema, simply run the `PRAGMA user_version;` command. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Initialize the plugin before any other method is called. Use this method to customize the plugin's behavior. If this method is not called, the plugin will be automatically initialized with default settings on the first call to any other method. On **Android** and **iOS**, this method is a no-op. On **Web**, this method allows you to pass a `Worker` instance that will be used for the SQLite WebAssembly initialization. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.3 ______________________________________________________________________ ### open(...) ``` open(options?: OpenOptions | undefined) => Promise ``` Open a database with the specified options. This method can be used to open an existing database or create a new one. | Param | Type | | ------------- | ------------- | | **`options`** | `OpenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### query(...) ``` query(options: QueryOptions) => Promise ``` Query the database and return the result set. This method can be used to execute `SELECT` statements and retrieve the result set. | Param | Type | | ------------- | -------------- | | **`options`** | `QueryOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### rollbackTransaction(...) ``` rollbackTransaction(options: RollbackTransactionOptions) => Promise ``` Rollback the current transaction on the specified database. This method will undo all changes made in the current transaction. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `RollbackTransactionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### vacuum(...) ``` vacuum(options: VacuumOptions) => Promise ``` Runs the [VACUUM](https://www.sqlite.org/lang_vacuum.html) command to rebuild the database file. This command can be used to reclaim unused space and optimize the database file. | Param | Type | | ------------- | --------------- | | **`options`** | `VacuumOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### BeginTransactionOptions | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------------------------------------------------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to begin a transaction on. | 0.1.0 | #### ChangeEncryptionKeyOptions | Prop | Type | Description | Since | | ------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to change the encryption key for. | 0.1.0 | | **`encryptionKey`** | `string` | The new encryption key to set for the database. **Attention:** It's recommended to use a strong encryption key to protect sensitive data. This key should be kept secret and not hard-coded in your application. If you lose the encryption key, you will not be able to access the data in the database. **Tip:** Use the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/) plugin to securely store the encryption key. | 0.1.0 | #### CloseOptions | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------------------ | ----- | | **`databaseId`** | `string` | The unique identifier for the database to close. | 0.1.0 | #### CommitTransactionOptions | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------------------------------------ | ----- | | **`databaseId`** | `string` | The unique identifier for the database to commit a transaction on. | 0.1.0 | #### ExecuteResult | Prop | Type | Description | Since | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ----- | | **`changes`** | `number` | The number of rows modified by the statement. This property is set for `INSERT`, `UPDATE`, and `DELETE` statements. | 0.1.1 | | **`rowId`** | `number` | The row ID of the last inserted row. This property is only set when executing an `INSERT` statement. | 0.1.1 | #### ExecuteOptions | Prop | Type | Description | Default | Since | | ------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to execute the statement on. | | 0.1.0 | | **`returnChanges`** | `boolean` | Whether to return the number of rows modified by the statement. Disabling this option can improve performance for statements that do not require the number of modified rows. | `true` | 0.1.2 | | **`returnRowId`** | `boolean` | Whether to return the row ID of the last inserted row. Disabling this option can improve performance for statements that do not require the row ID of the last inserted row. | `true` | 0.1.2 | | **`statement`** | `string` | The SQL statement to execute. This can be any valid SQL statement, such as `INSERT`, `UPDATE`, `DELETE`, or `CREATE TABLE`. | | 0.1.0 | | **`values`** | `Value[]` | Only available on Android. | | | #### GetVersionResult | Prop | Type | Description | Since | | ------------- | -------- | ----------------------------------------------------- | ----- | | **`version`** | `string` | The version of the SQLite library used by the plugin. | 0.1.0 | #### InitializeOptions | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`worker`** | `Worker` | The Worker to use for the SQLite WebAssembly initialization. If provided, this worker will be passed to the sqlite3Worker1Promiser method for initializing the SQLite WebAssembly module in the web implementation. Only available on Web. | 0.1.3 | #### OpenResult | Prop | Type | Description | Since | | ---------------- | -------- | -------------------------------------------- | ----- | | **`databaseId`** | `string` | A unique identifier for the opened database. | 0.1.0 | #### OpenOptions | Prop | Type | Description | Default | Since | | ----------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`encryptionKey`** | `string` | The encryption key to use for the database. If provided, the database will be opened as an encrypted database using the specified key. If not provided, the database will be opened as an unencrypted database. **Attention:** It's recommended to use a strong encryption key to protect sensitive data. This key should be kept secret and not hard-coded in your application. If you lose the encryption key, you will not be able to access the data in the database. **Tip:** Use the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/) plugin to securely store the encryption key. Only available on Android and iOS. | | 0.1.0 | | **`readOnly`** | `boolean` | Whether the database should be opened in read-only mode. Only available on Android and iOS. | `false` | 0.1.0 | | **`path`** | `string` | The path to the database file. If no file exists at the specified path, a new file will be created. If no path or URL is provided, the plugin will create a new in-memory database. On **Android**, the path can either be a simple filename or a file URI. If a simple filename is provided, the plugin will create the database in the default database directory (see [getDatabasePath]()). On **iOS**, the path can either be a simple filename or a file URL. If a simple filename is provided, the plugin will create the database in the default documents directory (see [documentsDirectory](https://developer.apple.com/documentation/foundation/url/documentsdirectory)). On **Web**, the path should be a simple filename without a directory (e.g., `mydb.sqlite3`). | | 0.1.0 | | **`upgradeStatements`** | `UpgradeStatement[]` | An array of upgrade statements to apply when opening the database. Each statement should specify the version of the database schema it applies to and the SQL statements to execute for the upgrade. The current version of the database schema can be checked using the `PRAGMA user_version;` command. | | 0.1.0 | | **`version`** | `number` | The version of the database schema. If provided, the plugin will check the schema version and apply migrations if necessary. If not provided, the latest version of the upgrade statements will be used, if any. If neither `version` nor `upgradeStatements` are provided, the database version will not be managed by the plugin. **Attention:** The version must be 1 or higher. | | 0.1.0 | #### UpgradeStatement | Prop | Type | Description | Since | | ---------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`version`** | `number` | The version of the database schema that this statement applies to. | 0.1.0 | | **`statements`** | `string[]` | The SQL statement to execute for the upgrade. This can be any valid SQL statement, such as `ALTER TABLE`, `CREATE TABLE`, or `INSERT`. | 0.1.0 | #### QueryResult | Prop | Type | Description | Default | Since | | ------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`columns`** | `string[]` | The column names in the result set. | `[]` | 0.1.0 | | **`rows`** | `Value[][]` | The rows returned by the query. Each row is represented as an object where the keys are column names and the values are the corresponding values in that row. | `[]` | 0.1.0 | #### QueryOptions | Prop | Type | Description | Default | Since | | ---------------- | --------- | -------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to query. | | 0.1.0 | | **`statement`** | `string` | The SQL statement to execute for the query. This should be a `SELECT` statement. | | 0.1.0 | | **`values`** | `Value[]` | An array of values to bind to the SQL statement. Each value corresponds to a placeholder in the SQL statement. | `[]` | 0.1.0 | #### RollbackTransactionOptions | Prop | Type | Description | Since | | ---------------- | -------- | -------------------------------------------------------------------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to rollback a transaction on. | 0.1.0 | #### VacuumOptions | Prop | Type | Description | Since | | ---------------- | -------- | -------------------------------------------------------------------- | ----- | | **`databaseId`** | `string` | The unique identifier for the database to run the VACUUM command on. | 0.1.0 | ### Type Aliases #### Value Represents a value that can be used in SQL statements. This can include strings, numbers, arrays of numbers (for BLOBs), or `null`. **Attention:** On Web, arrays of numbers (BLOBs) are not supported. `string | number | number[] | null` ## Key-Value Store This plugin includes a built-in key-value store (`SqliteKeyValueStore`) that provides a simple API for storing and retrieving key-value pairs without writing SQL. The database is automatically created and managed under the hood. ``` import { Sqlite, SqliteKeyValueStore } from '@capawesome-team/capacitor-sqlite'; const store = new SqliteKeyValueStore(Sqlite); // Set a value await store.set({ key: 'settings', value: JSON.stringify({ theme: 'dark', notifications: true }) }); // Get a value const result = await store.get({ key: 'settings' }); if (result.value) { const settings = JSON.parse(result.value); console.log(settings.theme); // 'dark' console.log(settings.notifications); // true } // Remove a value await store.remove({ key: 'settings' }); // Clear all values await store.clear(); // Get all keys const keysResult = await store.keys(); console.log(keysResult.keys); // ['settings', 'user', 'preferences', ...] ``` Perfect for storing small amounts of data such as user preferences, app settings, or session data without risking data loss due to web view data clearing. ## ORMs ### Drizzle This plugin is compatible with [Drizzle ORM](https://orm.drizzle.team/) via the [`@capawesome/capacitor-sqlite-drizzle`](https://github.com/capawesome-team/capacitor-sqlite-drivers/tree/main/packages/drizzle) adapter. ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; import { drizzle } from '@capawesome/capacitor-sqlite-drizzle'; import * as schema from './schema'; const { databaseId } = await Sqlite.open({ path: 'my.db' }); const db = drizzle(Sqlite, { databaseId, schema }); const users = await db.select().from(schema.users); ``` Check out the [How to use Drizzle with Capacitor SQLite](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-sqlite/) blog post for a step-by-step guide on how to set up and use Drizzle ORM with this plugin. ### Kysely This plugin is compatible with [Kysely](https://kysely.dev/) via the [`@capawesome/capacitor-sqlite-kysely`](https://github.com/capawesome-team/capacitor-sqlite-drivers/tree/main/packages/kysely) dialect. ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; import { Kysely } from 'kysely'; import { CapacitorSqliteDialect } from '@capawesome/capacitor-sqlite-kysely'; const { databaseId } = await Sqlite.open({ path: 'my.db' }); const db = new Kysely({ dialect: new CapacitorSqliteDialect(Sqlite, { databaseId }), }); const users = await db.selectFrom('users').selectAll().execute(); ``` Check out the [How to use Kysely with Capacitor SQLite](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-sqlite/) blog post for a step-by-step guide on how to set up and use Kysely with this plugin. ### TypeORM This plugin is compatible with [TypeORM](https://typeorm.io/), a popular ORM for TypeScript and JavaScript. ``` import { Sqlite, SQLiteConnection } from '@capawesome-team/capacitor-sqlite'; import { DataSource, DataSourceOptions } from 'typeorm'; const createDataSource = async () => { return new DataSource({ database: 'db', driver: new SQLiteConnection(Sqlite), entities: [ // Your TypeORM entities here ], migrationsRun: false, // Required with capacitor type type: 'capacitor' }); }; ``` Check out the [How to use TypeORM with Capacitor SQLite](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-sqlite/) blog post for a step-by-step guide on how to set up and use TypeORM with this plugin. ## Limitations This plugin has some limitations on certain platforms. ### Web The web implementation of this plugin has the following limitations: - **BLOBs**: Arrays of numbers (BLOBs) are not supported. You can only use strings, numbers, and `null` as values in SQL statements. ### Electron The Electron implementation of this plugin has the following limitations: - **Encryption**: Database encryption is not supported. - **Node.js version**: Requires Node.js 22.5.0 or later (Electron 33+) to use the native `node:sqlite` module. ## Troubleshooting ##### `SQLITE_ERROR: sqlite3 result code 1: no such vfs: opfs` This error occurs when OPFS (Origin Private File System) cannot be instantiated. This is likely due to the server not sending the required headers for the web worker to be able to access the OPFS. To fix this, you need to add the following headers to your server configuration: ``` 'Cross-Origin-Embedder-Policy': 'require-corp' 'Cross-Origin-Opener-Policy': 'same-origin' ``` ##### `Sqlite.open()` never resolves in production If `open()` hangs or never resolves while working in dev mode, this is typically caused by missing COOP/COEP headers in your production deployment. You need to configure your production web server (Netlify, Vercel, Nginx, Apache, etc.) to send these headers: ``` 'Cross-Origin-Embedder-Policy': 'require-corp' 'Cross-Origin-Opener-Policy': 'same-origin' ``` To verify this is the issue, open Chrome DevTools → Network tab and check if these headers are present in the response for your HTML document. ##### `No such module 'SQLite'` This error occurs when the `SQLite` module is not found in your iOS project. To fix this, make sure you have added the `CapawesomeTeamCapacitorSqlite/Plain` or `CapawesomeTeamCapacitorSqlite/SQLCipher` pod to your `Podfile` as described in the [Installation](#ios) section: ``` def capacitor_pods pod 'CapawesomeTeamCapacitorSqlite', :path => '../../node_modules/@capawesome-team/capacitor-sqlite' end target 'App' do capacitor_pods # Add your Pods here + pod 'CapawesomeTeamCapacitorSqlite/Plain', :path => '../../node_modules/@capawesome-team/capacitor-sqlite' end ``` **Attention**: Both `CapawesomeTeamCapacitorSqlite` and `CapawesomeTeamCapacitorSqlite/Plain` or `CapawesomeTeamCapacitorSqlite/SQLCipher` must be included in the `Podfile`. The first one is required for the plugin to work, while the second one is required for the specific implementation (Plain or SQLCipher). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/sqlite/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/sqlite/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/sqlite/LICENSE). # @capawesome/capacitor-square-mobile-payments Unofficial Capacitor plugin for [Square Mobile Payments SDK](https://developer.squareup.com/docs/mobile-payments-sdk).[1](#fn:1) ## Features This plugin provides a comprehensive integration with Square's Mobile Payments SDK for in-person payment processing. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 💳 **Payment Processing**: Accept in-person payments with Square card readers. - 📱 **Reader Management**: Pair, manage, and monitor Square card readers. - 🔐 **Secure Authorization**: OAuth-based authorization with Square access tokens. - 🎨 **Flexible UI**: Choose between Square's default payment UI or build your own custom interface. - 📊 **Real-time Updates**: Listen to reader status changes, pairing events, and payment completion. - 💰 **Multiple Payment Methods**: Support for contactless (tap), chip (dip), swipe, and manual entry. - 🌐 **Online & Offline**: Process payments online or offline with automatic sync. - 🧾 **Receipt Data**: Access card details, authorization codes, and EMV data for compliant receipts. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.1.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-square-mobile-payments` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-square-mobile-payments npx cap sync ``` ### Android #### SDK Initialization The Square Mobile Payments SDK must be initialized in your `Application` class before using the plugin. Create a custom `Application` class (if you don't already have one) and add the following code: 1. Create a file `MainApplication.java` in your app's `android/app/src/main/java//` directory: ``` package com.example.app; import android.app.Application; import com.squareup.sdk.mobilepayments.MobilePaymentsSdk; public class MainApplication extends Application { @Override public void onCreate() { super.onCreate(); MobilePaymentsSdk.initialize("YOUR_SQUARE_APPLICATION_ID", this); } } ``` **Note**: Replace `YOUR_SQUARE_APPLICATION_ID` with your actual Square Application ID. 1. Register the custom `Application` class in your `AndroidManifest.xml`: ``` ``` #### Variables If needed, you can define the following project variable in your app's `variables.gradle` file to change the default version of the dependency: - `$squareMobilePaymentsSdkVersion` version of `com.squareup.sdk:mobile-payments-sdk` (default: `2.3.4`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### SDK Initialization The Square Mobile Payments SDK must be initialized in your `AppDelegate.swift` file before using the plugin. Add the following code to your `AppDelegate.swift`: 1. Import the `SquareMobilePaymentsSDK` at the top of the file: ``` import SquareMobilePaymentsSDK ``` 1. Add the Square Application ID to your `Info.plist` file: ``` SquareApplicationID YOUR_SQUARE_APPLICATION_ID_HERE ``` **Note**: Replace `YOUR_SQUARE_APPLICATION_ID_HERE` with your actual Square Application ID. Do not use the placeholder value in production. 1. Initialize the SDK in the `application(_:didFinishLaunchingWithOptions:)` method: ``` func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Initialize Square Mobile Payments SDK // Get the Square Application ID from info.plist if let squareAppID = Bundle.main.object(forInfoDictionaryKey: "SquareApplicationID") as? String, !squareAppID.isEmpty && squareAppID != "YOUR_SQUARE_APPLICATION_ID_HERE" { MobilePaymentsSDK.initialize( applicationLaunchOptions: launchOptions, squareApplicationID: squareAppID ) } // Override point for customization after application launch. return true } ``` #### Build Phases Add a new "Run Script Phase" in Xcode to run the Square SDK setup script by following these steps: 1. On the **Build Phases** tab for your application target in Xcode, choose the + button (at the top left of the pane). 1. Choose **New Run Script Phase**. The new run script phase should be the last build phase. 1. Expand the new Run Script phase and enter the following script: ``` FRAMEWORKS="${BUILT_PRODUCTS_DIR}/${FRAMEWORKS_FOLDER_PATH}" "${FRAMEWORKS}/SquareMobilePaymentsSDK.framework/setup" ``` More details can be found in the [Square Mobile Payments SDK iOS setup guide](https://developer.squareup.com/docs/mobile-payments-sdk/ios). #### Privacy Descriptions Add the `NSLocationWhenInUseUsageDescription`, `NSBluetoothAlwaysUsageDescription`, and `NSMicrophoneUsageDescription` keys to the `ios/App/App/Info.plist` file, which tells the user why your app is requesting location, Bluetooth, and microphone permissions: ``` NSBluetoothAlwaysUsageDescription We need Bluetooth access to connect to Square card readers. NSLocationWhenInUseUsageDescription We need your location to confirm payments are occurring in a supported Square location. NSMicrophoneUsageDescription We need microphone access to receive data from magstripe card readers. ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { SquareMobilePayments, CardInputMethod } from '@capawesome/capacitor-square-mobile-payments'; const initializeSDK = async () => { await SquareMobilePayments.initialize({ locationId: 'YOUR_LOCATION_ID', }); await SquareMobilePayments.authorize({ accessToken: 'YOUR_ACCESS_TOKEN', }); }; const checkAuthorization = async () => { const { authorized } = await SquareMobilePayments.isAuthorized(); console.log('Authorized:', authorized); }; const pairReader = async () => { await SquareMobilePayments.startPairing(); }; const getReaders = async () => { const { readers } = await SquareMobilePayments.getReaders(); for (const reader of readers) { console.log('Reader:', reader.serialNumber, reader.model, reader.status); } }; const processPayment = async () => { await SquareMobilePayments.startPayment({ paymentParameters: { amountMoney: { amount: 100, currency: 'USD', }, paymentAttemptId: crypto.randomUUID(), }, promptParameters: { mode: 'DEFAULT', additionalMethods: ['KEYED'], }, }); }; const listenToPaymentEvents = () => { SquareMobilePayments.addListener('paymentDidFinish', (event) => { console.log('Payment completed:', event.payment.id); console.log('Amount:', event.payment.amountMoney.amount); console.log('Card:', event.payment.cardDetails?.card.lastFourDigits); }); SquareMobilePayments.addListener('paymentDidFail', (event) => { console.error('Payment failed:', event.message); }); SquareMobilePayments.addListener('paymentDidCancel', (event) => { console.log('Payment cancelled'); }); }; const listenToReaderEvents = () => { SquareMobilePayments.addListener('readerWasAdded', (event) => { console.log('Reader added:', event.reader.serialNumber); }); SquareMobilePayments.addListener('readerDidChange', (event) => { console.log('Reader changed:', event.reader.serialNumber, event.change); }); SquareMobilePayments.addListener('availableCardInputMethodsDidChange', (event) => { console.log('Available methods:', event.cardInputMethods); }); }; const getAvailableMethods = async () => { const { cardInputMethods } = await SquareMobilePayments.getAvailableCardInputMethods(); console.log('Available card input methods:', cardInputMethods); }; ``` ## API - [`initialize(...)`](#initialize) - [`authorize(...)`](#authorize) - [`isAuthorized()`](#isauthorized) - [`deauthorize()`](#deauthorize) - [`showSettings()`](#showsettings) - [`showMockReader()`](#showmockreader) - [`hideMockReader()`](#hidemockreader) - [`getSettings()`](#getsettings) - [`startPairing()`](#startpairing) - [`stopPairing()`](#stoppairing) - [`isPairingInProgress()`](#ispairinginprogress) - [`getReaders()`](#getreaders) - [`forgetReader(...)`](#forgetreader) - [`retryConnection(...)`](#retryconnection) - [`startPayment(...)`](#startpayment) - [`cancelPayment()`](#cancelpayment) - [`getAvailableCardInputMethods()`](#getavailablecardinputmethods) - [`linkAppleAccount()`](#linkappleaccount) - [`relinkAppleAccount()`](#relinkappleaccount) - [`isAppleAccountLinked()`](#isappleaccountlinked) - [`isDeviceCapable()`](#isdevicecapable) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('readerPairingDidBegin', ...)`](#addlistenerreaderpairingdidbegin-) - [`addListener('readerPairingDidSucceed', ...)`](#addlistenerreaderpairingdidsucceed-) - [`addListener('readerPairingDidFail', ...)`](#addlistenerreaderpairingdidfail-) - [`addListener('readerWasAdded', ...)`](#addlistenerreaderwasadded-) - [`addListener('readerWasRemoved', ...)`](#addlistenerreaderwasremoved-) - [`addListener('readerDidChange', ...)`](#addlistenerreaderdidchange-) - [`addListener('availableCardInputMethodsDidChange', ...)`](#addlisteneravailablecardinputmethodsdidchange-) - [`addListener('paymentDidFinish', ...)`](#addlistenerpaymentdidfinish-) - [`addListener('paymentDidFail', ...)`](#addlistenerpaymentdidfail-) - [`addListener('paymentDidCancel', ...)`](#addlistenerpaymentdidcancel-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the Square Mobile Payments SDK. This method must be called before any other method. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### authorize(...) ``` authorize(options: AuthorizeOptions) => Promise ``` Authorize the SDK with a Square access token. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `AuthorizeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### isAuthorized() ``` isAuthorized() => Promise ``` Check if the SDK is currently authorized. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### deauthorize() ``` deauthorize() => Promise ``` Deauthorize the SDK and clear the current authorization. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### showSettings() ``` showSettings() => Promise ``` Show the Square settings screen. This displays a pre-built settings UI where merchants can view and manage their paired readers. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### showMockReader() ``` showMockReader() => Promise ``` Show the Mock Reader UI for testing. This displays a mock reader interface for testing payment flows without physical hardware. This is only for development and testing purposes and is therefore only available in debug builds. Only available on Android and iOS. **Since:** 0.1.1 ______________________________________________________________________ ### hideMockReader() ``` hideMockReader() => Promise ``` Hide the Mock Reader UI. Only available on Android and iOS. **Since:** 0.1.1 ______________________________________________________________________ ### getSettings() ``` getSettings() => Promise ``` Get the current SDK settings. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### startPairing() ``` startPairing() => Promise ``` Start pairing with a Square reader. This initiates the reader pairing process. The SDK will search for nearby readers and pair with the first one found. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### stopPairing() ``` stopPairing() => Promise ``` Stop the reader pairing process. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### isPairingInProgress() ``` isPairingInProgress() => Promise ``` Check if a pairing process is currently in progress. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getReaders() ``` getReaders() => Promise ``` Get a list of all paired readers. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### forgetReader(...) ``` forgetReader(options: ForgetReaderOptions) => Promise ``` Forget a paired reader. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ForgetReaderOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### retryConnection(...) ``` retryConnection(options: RetryConnectionOptions) => Promise ``` Retry connecting to a reader. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `RetryConnectionOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### startPayment(...) ``` startPayment(options: StartPaymentOptions) => Promise ``` Start a payment flow. This presents the payment UI and processes the payment using the specified parameters. Only one payment can be active at a time. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `StartPaymentOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### cancelPayment() ``` cancelPayment() => Promise ``` Cancel an ongoing payment. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### getAvailableCardInputMethods() ``` getAvailableCardInputMethods() => Promise ``` Get the currently available card input methods. This returns the card entry methods that are available based on the connected readers (e.g., TAP, DIP, SWIPE, KEYED). Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### linkAppleAccount() ``` linkAppleAccount() => Promise ``` Link a Square seller account with an Apple ID for Tap to Pay on iPhone. Presents an Apple sheet prompting the user to accept the Tap to Pay on iPhone terms and conditions. Only available on iOS. **Since:** 0.1.3 ______________________________________________________________________ ### relinkAppleAccount() ``` relinkAppleAccount() => Promise ``` Re-link a Square seller account with a different Apple ID. Presents an Apple sheet to change the associated Apple ID by showing the Tap to Pay terms and conditions again. Only available on iOS. **Since:** 0.1.3 ______________________________________________________________________ ### isAppleAccountLinked() ``` isAppleAccountLinked() => Promise ``` Check if an Apple ID is already linked to a Square seller account. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.3 ______________________________________________________________________ ### isDeviceCapable() ``` isDeviceCapable() => Promise ``` Check if the device is capable of Tap to Pay on iPhone. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.3 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check the current permission status. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request the required permissions. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerPairingDidBegin', ...) ``` addListener(eventName: 'readerPairingDidBegin', listenerFunc: ReaderPairingDidBeginListener) => Promise ``` Listen for reader pairing events. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------- | | **`eventName`** | `'readerPairingDidBegin'` | | **`listenerFunc`** | `ReaderPairingDidBeginListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerPairingDidSucceed', ...) ``` addListener(eventName: 'readerPairingDidSucceed', listenerFunc: ReaderPairingDidSucceedListener) => Promise ``` Listen for successful reader pairing events. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'readerPairingDidSucceed'` | | **`listenerFunc`** | `ReaderPairingDidSucceedListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerPairingDidFail', ...) ``` addListener(eventName: 'readerPairingDidFail', listenerFunc: ReaderPairingDidFailListener) => Promise ``` Listen for failed reader pairing events. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'readerPairingDidFail'` | | **`listenerFunc`** | `ReaderPairingDidFailListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerWasAdded', ...) ``` addListener(eventName: 'readerWasAdded', listenerFunc: ReaderWasAddedListener) => Promise ``` Listen for reader added events. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------ | | **`eventName`** | `'readerWasAdded'` | | **`listenerFunc`** | `ReaderWasAddedListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerWasRemoved', ...) ``` addListener(eventName: 'readerWasRemoved', listenerFunc: ReaderWasRemovedListener) => Promise ``` Listen for reader removed events. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------- | | **`eventName`** | `'readerWasRemoved'` | | **`listenerFunc`** | `ReaderWasRemovedListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('readerDidChange', ...) ``` addListener(eventName: 'readerDidChange', listenerFunc: ReaderDidChangeListener) => Promise ``` Listen for reader status and property changes. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------- | | **`eventName`** | `'readerDidChange'` | | **`listenerFunc`** | `ReaderDidChangeListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('availableCardInputMethodsDidChange', ...) ``` addListener(eventName: 'availableCardInputMethodsDidChange', listenerFunc: AvailableCardInputMethodsDidChangeListener) => Promise ``` Listen for available card input method changes. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------------- | | **`eventName`** | `'availableCardInputMethodsDidChange'` | | **`listenerFunc`** | `AvailableCardInputMethodsDidChangeListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paymentDidFinish', ...) ``` addListener(eventName: 'paymentDidFinish', listenerFunc: PaymentDidFinishListener) => Promise ``` Listen for successful payment completion events. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------- | | **`eventName`** | `'paymentDidFinish'` | | **`listenerFunc`** | `PaymentDidFinishListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paymentDidFail', ...) ``` addListener(eventName: 'paymentDidFail', listenerFunc: PaymentDidFailListener) => Promise ``` Listen for failed payment events. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------ | | **`eventName`** | `'paymentDidFail'` | | **`listenerFunc`** | `PaymentDidFailListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paymentDidCancel', ...) ``` addListener(eventName: 'paymentDidCancel', listenerFunc: PaymentDidCancelListener) => Promise ``` Listen for cancelled payment events. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------- | | **`eventName`** | `'paymentDidCancel'` | | **`listenerFunc`** | `PaymentDidCancelListener` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### InitializeOptions | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------- | ----- | | **`locationId`** | `string` | The Square location ID. | 0.0.1 | #### AuthorizeOptions | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------ | ----- | | **`accessToken`** | `string` | The Square access token. | 0.0.1 | #### IsAuthorizedResult | Prop | Type | Description | Since | | ---------------- | --------- | ---------------------------------------- | ----- | | **`authorized`** | `boolean` | Whether the SDK is currently authorized. | 0.0.1 | #### GetSettingsResult | Prop | Type | Description | Since | | ----------------- | ------------- | ------------------------ | ----- | | **`version`** | `string` | The SDK version. | 0.0.1 | | **`environment`** | `Environment` | The current environment. | 0.0.1 | #### IsPairingInProgressResult | Prop | Type | Description | Since | | ---------------- | --------- | --------------------------------------------------- | ----- | | **`inProgress`** | `boolean` | Whether a pairing process is currently in progress. | 0.0.1 | #### GetReadersResult | Prop | Type | Description | Since | | ------------- | -------------- | --------------------------- | ----- | | **`readers`** | `ReaderInfo[]` | The list of paired readers. | 0.0.1 | #### ReaderInfo | Prop | Type | Description | Since | | ------------------------------- | ----------------------- | ---------------------------------------------------------------------------------------------------------- | ----- | | **`serialNumber`** | `string` | The reader's serial number. | 0.0.1 | | **`model`** | `ReaderModel` | The reader's model. | 0.0.1 | | **`status`** | `ReaderStatus` | The reader's current status. | 0.0.1 | | **`firmwareVersion`** | `string` | The reader's firmware version. | 0.0.1 | | **`batteryLevel`** | `number` | The reader's battery level (0-100). | 0.0.1 | | **`isCharging`** | `boolean` | Whether the reader is currently charging. | 0.0.1 | | **`supportedCardInputMethods`** | `CardInputMethod[]` | The card input methods supported by this reader. | 0.0.1 | | **`unavailableReasonInfo`** | `UnavailableReasonInfo` | Additional information about why the reader is unavailable. Only present when status is ReaderUnavailable. | 0.0.1 | #### UnavailableReasonInfo | Prop | Type | Description | Since | | ------------- | ------------------- | ------------------------------------------------------------------ | ----- | | **`reason`** | `UnavailableReason` | The reason code why the reader is unavailable. | 0.0.1 | | **`message`** | `string` | A human-readable message describing why the reader is unavailable. | 0.0.1 | #### ForgetReaderOptions | Prop | Type | Description | Since | | ------------------ | -------- | ------------------------------------------ | ----- | | **`serialNumber`** | `string` | The serial number of the reader to forget. | 0.0.1 | #### RetryConnectionOptions | Prop | Type | Description | Since | | ------------------ | -------- | ---------------------------------------------------- | ----- | | **`serialNumber`** | `string` | The serial number of the reader to retry connection. | 0.0.1 | #### StartPaymentOptions | Prop | Type | Description | Since | | ----------------------- | ------------------- | ----------------------- | ----- | | **`paymentParameters`** | `PaymentParameters` | The payment parameters. | 0.0.1 | | **`promptParameters`** | `PromptParameters` | The prompt parameters. | 0.0.1 | #### PaymentParameters | Prop | Type | Description | Default | Since | | ---------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----- | | **`amountMoney`** | `Money` | The amount of money to charge. | | 0.0.1 | | **`paymentAttemptId`** | `string` | A unique identifier for this payment attempt. This is used for idempotent payment requests. | | 0.0.1 | | **`processingMode`** | `ProcessingMode` | The processing mode for this payment. | `ProcessingMode.AutoDetect` | 0.0.1 | | **`referenceId`** | `string` | A user-defined reference ID to associate with the payment. | | 0.0.1 | | **`note`** | `string` | An optional note to add to the payment. | | 0.0.1 | | **`orderId`** | `string` | The Square order ID to associate with this payment. | | 0.0.1 | | **`tipMoney`** | `Money` | The tip amount. | | 0.0.1 | | **`applicationFee`** | `Money` | The application fee. | | 0.0.1 | | **`autocomplete`** | `boolean` | Whether to automatically complete the payment. If false, the payment will be authorized but not captured, requiring manual completion via the Payments API. | `true` | 0.0.1 | | **`delayDuration`** | `string` | The duration to delay before automatically completing or canceling the payment. Only applicable when autocomplete is false. | | 0.0.1 | | **`delayAction`** | `DelayAction` | The action to take when the delay duration expires. Only applicable when autocomplete is false. | | 0.0.1 | #### Money | Prop | Type | Description | Since | | -------------- | -------------- | --------------------------------------------------------------- | ----- | | **`amount`** | `number` | The amount in the smallest currency unit (e.g., cents for USD). | 0.0.1 | | **`currency`** | `CurrencyCode` | The ISO 4217 currency code. | 0.0.1 | #### PromptParameters | Prop | Type | Description | Default | Since | | ----------------------- | --------------------------- | ------------------------------------ | -------------------- | ----- | | **`mode`** | `PromptMode` | The prompt mode. | `PromptMode.Default` | 0.0.1 | | **`additionalMethods`** | `AdditionalPaymentMethod[]` | Additional payment methods to allow. | `[]` | 0.0.1 | #### GetAvailableCardInputMethodsResult | Prop | Type | Description | Since | | ---------------------- | ------------------- | --------------------------------- | ----- | | **`cardInputMethods`** | `CardInputMethod[]` | The available card input methods. | 0.0.1 | #### IsAppleAccountLinkedResult | Prop | Type | Description | Since | | ------------ | --------- | ----------------------------------------------------------- | ----- | | **`linked`** | `boolean` | Whether an Apple ID is linked to the Square seller account. | 0.1.3 | #### IsDeviceCapableResult | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------------------------ | ----- | | **`capable`** | `boolean` | Whether the device is capable of Tap to Pay on iPhone. | 0.1.3 | #### PermissionStatus | Prop | Type | Description | Since | | ---------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`location`** | `PermissionState` | Permission state for accessing location. Required to confirm that payments are occurring in a supported Square location. | 0.0.1 | | **`recordAudio`** | `PermissionState` | Permission state for recording audio. Required to receive data from magstripe readers. | 0.0.1 | | **`bluetoothConnect`** | `PermissionState` | Permission state for Bluetooth connect. Required to receive data from contactless and chip readers. Only available on Android. | 0.0.1 | | **`bluetoothScan`** | `PermissionState` | Permission state for Bluetooth scan. Required to store information during checkout. Only available on Android. | 0.0.1 | | **`readPhoneState`** | `PermissionState` | Permission state for reading phone state. Required to identify the device sending information to Square servers. Only available on Android. | 0.0.1 | | **`bluetooth`** | `PermissionState` | Permission state for using Bluetooth. Only available on iOS. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ReaderPairingDidFailEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`code`** | `string` | The error code. | 0.0.1 | | **`message`** | `string` | The error message. | 0.0.1 | #### ReaderWasAddedEvent | Prop | Type | Description | Since | | ------------ | ------------ | -------------------------- | ----- | | **`reader`** | `ReaderInfo` | The reader that was added. | 0.0.1 | #### ReaderWasRemovedEvent | Prop | Type | Description | Since | | ------------ | ------------ | ---------------------------- | ----- | | **`reader`** | `ReaderInfo` | The reader that was removed. | 0.0.1 | #### ReaderDidChangeEvent | Prop | Type | Description | Since | | ------------ | -------------- | --------------------------------- | ----- | | **`reader`** | `ReaderInfo` | The reader that changed. | 0.0.1 | | **`change`** | `ReaderChange` | The type of change that occurred. | 0.0.1 | #### AvailableCardInputMethodsDidChangeEvent | Prop | Type | Description | Since | | ---------------------- | ------------------- | --------------------------------- | ----- | | **`cardInputMethods`** | `CardInputMethod[]` | The available card input methods. | 0.0.1 | #### PaymentDidFinishEvent | Prop | Type | Description | Since | | ------------- | --------- | ---------------------- | ----- | | **`payment`** | `Payment` | The completed payment. | 0.0.1 | #### Payment | Prop | Type | Description | Since | | -------------------- | -------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------- | | **`id`** | \`string | null\` | The unique identifier for this payment. For offline payments, this may be null until synced. | | **`type`** | `PaymentType` | The payment type. | 0.0.1 | | **`status`** | `PaymentStatus` | The payment status. | 0.0.1 | | **`amountMoney`** | `Money` | The amount paid. | 0.0.1 | | **`tipMoney`** | `Money` | The tip amount. | 0.0.1 | | **`applicationFee`** | `Money` | The application fee. | 0.0.1 | | **`referenceId`** | `string` | The reference ID provided in the payment parameters. | 0.0.1 | | **`orderId`** | `string` | The order ID associated with this payment. | 0.0.1 | | **`cardDetails`** | `CardPaymentDetails` | [Card](#card) payment details. Only present for card payments. | 0.0.1 | | **`createdAt`** | `string` | The time the payment was created (ISO 8601 format). | 0.0.1 | | **`updatedAt`** | `string` | The time the payment was updated (ISO 8601 format). | 0.0.1 | #### CardPaymentDetails | Prop | Type | Description | Since | | ----------------------- | ----------------- | ------------------------------- | ----- | | **`card`** | `Card` | The card information. | 0.0.1 | | **`entryMethod`** | `CardInputMethod` | The card entry method used. | 0.0.1 | | **`authorizationCode`** | `string` | The authorization code. | 0.0.1 | | **`applicationName`** | `string` | The EMV application name. | 0.0.1 | | **`applicationId`** | `string` | The EMV application identifier. | 0.0.1 | #### Card | Prop | Type | Description | Since | | --------------------- | ----------- | ---------------------------------------- | ----- | | **`brand`** | `CardBrand` | The card brand. | 0.0.1 | | **`lastFourDigits`** | `string` | The last four digits of the card number. | 0.0.1 | | **`cardholderName`** | `string` | The cardholder name. | 0.0.1 | | **`expirationMonth`** | `number` | The card expiration month (1-12). | 0.0.1 | | **`expirationYear`** | `number` | The card expiration year. | 0.0.1 | #### PaymentDidFailEvent | Prop | Type | Description | Since | | ------------- | --------- | ------------------- | ----- | | **`payment`** | `Payment` | The failed payment. | 0.0.1 | | **`code`** | `string` | The error code. | 0.0.1 | | **`message`** | `string` | The error message. | 0.0.1 | #### PaymentDidCancelEvent | Prop | Type | Description | Since | | ------------- | --------- | ---------------------- | ----- | | **`payment`** | `Payment` | The cancelled payment. | 0.0.1 | ### Type Aliases #### CurrencyCode `string` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### ReaderPairingDidBeginListener Callback to receive reader pairing start notifications. `(): void` #### ReaderPairingDidSucceedListener Callback to receive reader pairing success notifications. `(): void` #### ReaderPairingDidFailListener Callback to receive reader pairing failure notifications. `(event: ReaderPairingDidFailEvent): void` #### ReaderWasAddedListener Callback to receive reader added notifications. `(event: ReaderWasAddedEvent): void` #### ReaderWasRemovedListener Callback to receive reader removed notifications. `(event: ReaderWasRemovedEvent): void` #### ReaderDidChangeListener Callback to receive reader change notifications. `(event: ReaderDidChangeEvent): void` #### AvailableCardInputMethodsDidChangeListener Callback to receive available card input method change notifications. `(event: AvailableCardInputMethodsDidChangeEvent): void` #### PaymentDidFinishListener Callback to receive payment completion notifications. `(event: PaymentDidFinishEvent): void` #### PaymentDidFailListener Callback to receive payment failure notifications. `(event: PaymentDidFailEvent): void` #### PaymentDidCancelListener Callback to receive payment cancellation notifications. `(event: PaymentDidCancelEvent): void` ### Enums #### Environment | Members | Value | Description | Since | | ---------------- | -------------- | -------------------------------- | ----- | | **`Production`** | `'production'` | Production environment. | 0.0.1 | | **`Sandbox`** | `'sandbox'` | Sandbox environment for testing. | 0.0.1 | #### ReaderModel | Members | Value | Description | Since | | ------------------------ | ------------------------ | --------------------------------------- | ----- | | **`ContactlessAndChip`** | `'CONTACTLESS_AND_CHIP'` | Square Reader for contactless and chip. | 0.0.1 | | **`Magstripe`** | `'MAGSTRIPE'` | Square Reader for magstripe. | 0.0.1 | | **`Stand`** | `'STAND'` | Square Stand. | 0.0.1 | | **`TapToPay`** | `'TAP_TO_PAY'` | Tap to Pay on iPhone. | 0.1.3 | | **`Unknown`** | `'UNKNOWN'` | Unknown reader model. | 0.0.1 | #### ReaderStatus | Members | Value | Description | Since | | ------------------------ | ------------------------ | -------------------------------------------------------------------- | ----- | | **`Ready`** | `'READY'` | Reader is paired, connected, and ready to accept payments. | 0.0.1 | | **`ConnectingToSquare`** | `'CONNECTING_TO_SQUARE'` | Reader is connecting to Square servers. | 0.0.1 | | **`ConnectingToDevice`** | `'CONNECTING_TO_DEVICE'` | Reader is connecting to the mobile device. | 0.0.1 | | **`Faulty`** | `'FAULTY'` | Reader has a hardware or connection error in an unrecoverable state. | 0.0.1 | | **`ReaderUnavailable`** | `'READER_UNAVAILABLE'` | Reader is connected but unable to process payments. | 0.0.1 | #### CardInputMethod | Members | Value | Description | Since | | ----------- | --------- | --------------------------- | ----- | | **`Tap`** | `'TAP'` | Contactless card tap (NFC). | 0.0.1 | | **`Dip`** | `'DIP'` | EMV chip card insertion. | 0.0.1 | | **`Swipe`** | `'SWIPE'` | Magnetic stripe swipe. | 0.0.1 | | **`Keyed`** | `'KEYED'` | Manually keyed card entry. | 0.0.1 | #### UnavailableReason | Members | Value | Description | Since | | ------------------------------ | ------------------------------ | --------------------------------------------------------- | ----- | | **`BluetoothError`** | `'BLUETOOTH_ERROR'` | Bluetooth connection issue. Only available on iOS. | 0.0.1 | | **`BluetoothFailure`** | `'BLUETOOTH_FAILURE'` | Bluetooth failure. Only available on Android. | 0.0.1 | | **`BluetoothDisabled`** | `'BLUETOOTH_DISABLED'` | Bluetooth is disabled. Only available on Android. | 0.0.1 | | **`FirmwareUpdate`** | `'FIRMWARE_UPDATE'` | Reader firmware is updating. Only available on iOS. | 0.0.1 | | **`BlockingUpdate`** | `'BLOCKING_UPDATE'` | Blocking firmware update. Only available on Android. | 0.0.1 | | **`ReaderUnavailableOffline`** | `'READER_UNAVAILABLE_OFFLINE'` | Reader is unavailable offline. Only available on Android. | 0.0.1 | | **`DeviceDeveloperMode`** | `'DEVICE_DEVELOPER_MODE'` | Device is in developer mode. Only available on Android. | 0.0.1 | | **`Unknown`** | `'UNKNOWN'` | Unknown reason. | 0.0.1 | #### ProcessingMode | Members | Value | Description | Since | | ----------------- | ---------------- | ------------------------------------------------------------------ | ----- | | **`AutoDetect`** | `'AUTO_DETECT'` | Automatically detect the best processing mode (online or offline). | 0.0.1 | | **`OnlineOnly`** | `'ONLINE_ONLY'` | Process the payment online only. | 0.0.1 | | **`OfflineOnly`** | `'OFFLINE_ONLY'` | Allow offline payment processing. | 0.0.1 | #### DelayAction | Members | Value | Description | Since | | -------------- | ------------ | ---------------------------------------------------------- | ----- | | **`Complete`** | `'COMPLETE'` | Automatically complete the payment when the delay expires. | 0.0.1 | | **`Cancel`** | `'CANCEL'` | Automatically cancel the payment when the delay expires. | 0.0.1 | #### PromptMode | Members | Value | Description | Since | | ------------- | ----------- | ---------------------------------- | ----- | | **`Default`** | `'DEFAULT'` | Use the default Square payment UI. | 0.0.1 | | **`Custom`** | `'CUSTOM'` | Use a custom payment UI. | 0.0.1 | #### AdditionalPaymentMethod | Members | Value | Description | Since | | ----------- | --------- | -------------------------------- | ----- | | **`Keyed`** | `'KEYED'` | Allow manually keyed card entry. | 0.0.1 | | **`Cash`** | `'CASH'` | Allow cash payments. | 0.0.1 | #### ReaderChange | Members | Value | Description | Since | | ----------------------------- | ------------------------------ | ------------------------------------------------------------------ | ----- | | **`BatteryDidBeginCharging`** | `'BATTERY_DID_BEGIN_CHARGING'` | Reader battery started charging. Only available on iOS. | 0.0.1 | | **`BatteryDidEndCharging`** | `'BATTERY_DID_END_CHARGING'` | Reader battery stopped charging. Only available on iOS. | 0.0.1 | | **`BatteryLevelDidChange`** | `'BATTERY_LEVEL_DID_CHANGE'` | Reader battery level changed. Only available on iOS. | 0.0.1 | | **`BatteryCharging`** | `'BATTERY_CHARGING'` | Reader battery charging status changed. Only available on Android. | 0.0.1 | | **`Added`** | `'ADDED'` | Reader was added. Only available on Android. | 0.0.1 | | **`Removed`** | `'REMOVED'` | Reader was removed. Only available on Android. | 0.0.1 | | **`StatusDidChange`** | `'STATUS_DID_CHANGE'` | Reader status changed. | 0.0.1 | #### PaymentType | Members | Value | Description | Since | | ------------- | ----------- | -------------------------------------- | ----- | | **`Online`** | `'ONLINE'` | [Payment](#payment) processed online. | 0.0.1 | | **`Offline`** | `'OFFLINE'` | [Payment](#payment) processed offline. | 0.0.1 | #### PaymentStatus | Members | Value | Description | Since | | --------------- | ------------- | ------------------------------------------------------- | ----- | | **`Completed`** | `'COMPLETED'` | [Payment](#payment) was completed successfully. | 0.0.1 | | **`Approved`** | `'APPROVED'` | [Payment](#payment) was approved but not yet completed. | 0.0.1 | | **`Canceled`** | `'CANCELED'` | [Payment](#payment) was canceled. | 0.0.1 | | **`Failed`** | `'FAILED'` | [Payment](#payment) failed. | 0.0.1 | | **`Pending`** | `'PENDING'` | [Payment](#payment) is pending. | 0.0.1 | #### CardBrand | Members | Value | Description | Since | | --------------------- | -------------------- | ---------------------------- | ----- | | **`Visa`** | `'VISA'` | Visa card. | 0.0.1 | | **`Mastercard`** | `'MASTERCARD'` | Mastercard. | 0.0.1 | | **`AmericanExpress`** | `'AMERICAN_EXPRESS'` | American Express card. | 0.0.1 | | **`Discover`** | `'DISCOVER'` | Discover card. | 0.0.1 | | **`DiscoverDiners`** | `'DISCOVER_DINERS'` | Discover Diners card. | 0.0.1 | | **`Jcb`** | `'JCB'` | JCB card. | 0.0.1 | | **`UnionPay`** | `'UNION_PAY'` | UnionPay card. | 0.0.1 | | **`Interac`** | `'INTERAC'` | Interac card. | 0.0.1 | | **`Eftpos`** | `'EFTPOS'` | Eftpos card. | 0.0.1 | | **`Other`** | `'OTHER'` | Other or unknown card brand. | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/square-mobile-payments/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/square-mobile-payments/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Square, Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-superwall Unofficial Capacitor plugin for [Superwall SDK](https://superwall.com/).[1](#fn:1) ## Features We are proud to offer a comprehensive Capacitor plugin for Superwall SDK integration. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 💰 **Paywall Presentation**: Show beautiful, remotely-configured paywalls to drive subscriptions. - 🎯 **Feature Gating**: Control feature access based on subscription status. - 📊 **Analytics Events**: Forward Superwall events to your analytics platform. - 👤 **User Management**: Identify users, set custom attributes, and manage subscription status. - 🧪 **A/B Testing**: Built-in support for paywall experiments and holdout groups. - 🔗 **Deep Linking**: Handle deep links for paywall campaigns. - 🎨 **Customization**: Configure paywall behavior, logging, and appearance. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.1.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-superwall` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-superwall npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$superwallAndroidVersion` version of `com.superwall.sdk:superwall-android` (default: `2.6.6`) #### Manifest Add the required activities to your `AndroidManifest.xml` file: ``` ``` Set your app's theme in the `android:theme` section of the `SuperwallPaywallActivity`. ## Configuration No configuration required for this plugin. ## Usage ``` import { Superwall } from '@capawesome/capacitor-superwall'; const configureSuperwall = async () => { await Superwall.configure({ apiKey: 'pk_your_api_key_here', options: { paywalls: { shouldPreload: true, automaticallyDismiss: true, }, logging: { level: 'WARN', scopes: ['ALL'], }, }, }); }; const showPaywall = async () => { const result = await Superwall.register({ placement: 'premium_feature', params: { user_level: 5, source: 'settings', }, }); console.log('Paywall result:', result.result); // 'PURCHASED', 'CANCELLED', or 'RESTORED' }; const checkPaywall = async () => { const result = await Superwall.getPresentationResult({ placement: 'premium_feature', }); if (result.type === 'PAYWALL') { console.log('Paywall would be shown'); } else if (result.type === 'NO_AUDIENCE_MATCH') { console.log('User does not match audience, no paywall'); } }; const identifyUser = async (userId: string) => { await Superwall.identify({ userId: userId, options: { restorePaywallAssignments: true, }, }); }; const setAttributes = async () => { await Superwall.setUserAttributes({ attributes: { username: 'john_doe', subscription_tier: 'free', total_purchases: 5, }, }); }; const checkSubscription = async () => { const result = await Superwall.getSubscriptionStatus(); console.log('Subscription status:', result.status); // 'ACTIVE', 'INACTIVE', or 'UNKNOWN' }; const addListeners = async () => { // Forward analytics events to your platform await Superwall.addListener('superwallEvent', (event) => { console.log('Superwall event:', event.type, event.data); // Forward to your analytics: Analytics.track(event.type, event.data); }); // Track subscription status changes await Superwall.addListener('subscriptionStatusDidChange', (event) => { console.log('Subscription status changed to:', event.status); }); // Handle paywall lifecycle events await Superwall.addListener('paywallPresented', (event) => { console.log('Paywall presented:', event.paywallInfo.placement); }); await Superwall.addListener('paywallDismissed', (event) => { console.log('Paywall dismissed:', event.paywallInfo.placement); }); // Handle custom paywall actions await Superwall.addListener('customPaywallAction', (event) => { console.log('Custom action:', event.name); // Handle custom actions like 'help', 'contact', etc. }); }; const handleCampaignDeepLink = async (url: string) => { await Superwall.handleDeepLink({ url }); }; const logout = async () => { await Superwall.reset(); }; ``` ## API - [`configure(...)`](#configure) - [`register(...)`](#register) - [`getPresentationResult(...)`](#getpresentationresult) - [`identify(...)`](#identify) - [`reset()`](#reset) - [`getUserId()`](#getuserid) - [`getIsLoggedIn()`](#getisloggedin) - [`setUserAttributes(...)`](#setuserattributes) - [`handleDeepLink(...)`](#handledeeplink) - [`getSubscriptionStatus()`](#getsubscriptionstatus) - [`addListener('superwallEvent', ...)`](#addlistenersuperwallevent-) - [`addListener('subscriptionStatusDidChange', ...)`](#addlistenersubscriptionstatusdidchange-) - [`addListener('paywallPresented', ...)`](#addlistenerpaywallpresented-) - [`addListener('paywallWillDismiss', ...)`](#addlistenerpaywallwilldismiss-) - [`addListener('paywallDismissed', ...)`](#addlistenerpaywalldismissed-) - [`addListener('customPaywallAction', ...)`](#addlistenercustompaywallaction-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### configure(...) ``` configure(options: ConfigureOptions) => Promise ``` Configure the Superwall SDK. This method must be called once before all other methods. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `ConfigureOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### register(...) ``` register(options: RegisterOptions) => Promise ``` Register a placement and present a paywall if the user doesn't have an active subscription. This is the primary method for feature gating and paywall presentation. The feature closure will execute based on the gating mode: - Non-gated: Executes immediately - Gated: Executes after subscription or if already subscribed Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `RegisterOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getPresentationResult(...) ``` getPresentationResult(options: GetPresentationResultOptions) => Promise ``` Check if a paywall would be presented for a placement without actually presenting it. Useful for determining whether to show a feature or paywall before the user interacts. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `GetPresentationResultOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### identify(...) ``` identify(options: IdentifyOptions) => Promise ``` Identify the current user with a unique ID. This links the user ID to their anonymous alias for analytics and paywall assignments. Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `IdentifyOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### reset() ``` reset() => Promise ``` Reset the user identity. This rotates the anonymous user ID, clears local paywall assignments, and requires the SDK to re-download configuration. Should only be called on explicit logout. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### getUserId() ``` getUserId() => Promise ``` Get the current user ID. Returns the identified user ID if set, otherwise returns the anonymous ID. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getIsLoggedIn() ``` getIsLoggedIn() => Promise ``` Check if the user is logged in (identified). Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### setUserAttributes(...) ``` setUserAttributes(options: SetUserAttributesOptions) => Promise ``` Set custom user attributes for personalization and audience filtering. Attributes can be used in audience filters on the Superwall dashboard. Keys starting with `$` are reserved for Superwall use. Arrays and nested structures are not supported. Set values to null to remove attributes. Only available on Android and iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `SetUserAttributesOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### handleDeepLink(...) ``` handleDeepLink(options: HandleDeepLinkOptions) => Promise ``` Handle a deep link URL for paywall campaigns. This processes deep links associated with Superwall campaigns configured on the dashboard. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `HandleDeepLinkOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### getSubscriptionStatus() ``` getSubscriptionStatus() => Promise ``` Get the current subscription status. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('superwallEvent', ...) ``` addListener(eventName: 'superwallEvent', listenerFunc: (event: SuperwallEventInfo) => void) => Promise ``` Add a listener for Superwall analytics events. These events can be forwarded to your analytics platform. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'superwallEvent'` | | **`listenerFunc`** | `(event: SuperwallEventInfo) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('subscriptionStatusDidChange', ...) ``` addListener(eventName: 'subscriptionStatusDidChange', listenerFunc: (event: SubscriptionStatusDidChangeEvent) => void) => Promise ``` Add a listener for subscription status changes. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------------------------- | | **`eventName`** | `'subscriptionStatusDidChange'` | | **`listenerFunc`** | `(event: SubscriptionStatusDidChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paywallPresented', ...) ``` addListener(eventName: 'paywallPresented', listenerFunc: (event: PaywallPresentedEvent) => void) => Promise ``` Add a listener for when a paywall is presented. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'paywallPresented'` | | **`listenerFunc`** | `(event: PaywallPresentedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paywallWillDismiss', ...) ``` addListener(eventName: 'paywallWillDismiss', listenerFunc: (event: PaywallWillDismissEvent) => void) => Promise ``` Add a listener for when a paywall will dismiss. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'paywallWillDismiss'` | | **`listenerFunc`** | `(event: PaywallWillDismissEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('paywallDismissed', ...) ``` addListener(eventName: 'paywallDismissed', listenerFunc: (event: PaywallDismissedEvent) => void) => Promise ``` Add a listener for when a paywall is dismissed. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'paywallDismissed'` | | **`listenerFunc`** | `(event: PaywallDismissedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('customPaywallAction', ...) ``` addListener(eventName: 'customPaywallAction', listenerFunc: (event: CustomPaywallActionEvent) => void) => Promise ``` Add a listener for custom paywall actions. Triggered when a user taps an element with the `data-pw-custom` attribute. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------- | | **`eventName`** | `'customPaywallAction'` | | **`listenerFunc`** | `(event: CustomPaywallActionEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### ConfigureOptions | Prop | Type | Description | Since | | ------------- | ------------------ | ------------------------------------------- | ----- | | **`apiKey`** | `string` | The Superwall API key from your dashboard. | 0.0.1 | | **`options`** | `SuperwallOptions` | Optional configuration options for the SDK. | 0.0.1 | #### SuperwallOptions | Prop | Type | Description | Default | Since | | ------------------------------------- | -------------------- | ------------------------------------------------------------------------------------ | ---------------------------- | ----- | | **`paywalls`** | `PaywallOptions` | Paywall presentation and behavior options. | | 0.0.1 | | **`logging`** | `LoggingOptions` | Logging configuration. | | 0.0.1 | | **`networkEnvironment`** | `NetworkEnvironment` | Network environment for API requests. | `NetworkEnvironment.Release` | 0.0.1 | | **`localeIdentifier`** | `string` | Override the locale identifier for localization. | | 0.0.1 | | **`shouldObservePurchases`** | `boolean` | Observe external Google Play purchases. Only available on Android. | `true` | 0.0.1 | | **`isExternalDataCollectionEnabled`** | `boolean` | Enable or disable external data collection for analytics. Only available on Android. | `true` | 0.0.1 | #### PaywallOptions | Prop | Type | Description | Default | Since | | ------------------------------------ | --------- | ----------------------------------------------------- | ------- | ----- | | **`isHapticFeedbackEnabled`** | `boolean` | Enable haptic feedback on paywall interactions. | `true` | 0.0.1 | | **`shouldShowPurchaseFailureAlert`** | `boolean` | Show an alert when purchase restoration fails. | `true` | 0.0.1 | | **`shouldPreload`** | `boolean` | Preload paywalls during SDK initialization. | `true` | 0.0.1 | | **`automaticallyDismiss`** | `boolean` | Automatically dismiss paywall on purchase or restore. | `true` | 0.0.1 | #### LoggingOptions | Prop | Type | Description | Default | Since | | ------------ | ------------ | --------------------------- | ---------------- | ----- | | **`level`** | `LogLevel` | The log level for SDK logs. | `LogLevel.Warn` | 0.0.1 | | **`scopes`** | `LogScope[]` | The log scopes to enable. | `[LogScope.All]` | 0.0.1 | #### RegisterResult | Prop | Type | Description | Since | | ------------ | --------------- | --------------------------------------- | ----- | | **`result`** | `PaywallResult` | The result of the paywall presentation. | 0.0.1 | #### RegisterOptions | Prop | Type | Description | Since | | --------------- | --------------------- | -------------------------------------------------------------------------------------------------- | ----- | | **`placement`** | `string` | The placement identifier configured in the Superwall dashboard. | 0.0.1 | | **`params`** | `Record` | Optional parameters for audience filtering. Keys starting with `$` are reserved for Superwall use. | 0.0.1 | #### GetPresentationResultResult | Prop | Type | Description | Since | | ---------------- | ------------------------ | ---------------------------------------------------------------------------- | ----- | | **`type`** | `PresentationResultType` | The type of presentation result. | 0.0.1 | | **`experiment`** | `Experiment` | [Experiment](#experiment) information if the result is a paywall or holdout. | 0.0.1 | #### Experiment | Prop | Type | Description | Since | | ------------- | -------- | ----------------------------------------- | ----- | | **`id`** | `string` | The unique identifier for the experiment. | 0.0.1 | | **`variant`** | `string` | The variant assigned to the user. | 0.0.1 | #### GetPresentationResultOptions | Prop | Type | Description | Since | | --------------- | --------------------- | ------------------------------------------- | ----- | | **`placement`** | `string` | The placement identifier to check. | 0.0.1 | | **`params`** | `Record` | Optional parameters for audience filtering. | 0.0.1 | #### IdentifyOptions | Prop | Type | Description | Since | | ------------- | ----------------------- | ---------------------------------------- | ----- | | **`userId`** | `string` | The unique user ID to identify the user. | 0.0.1 | | **`options`** | `IdentifyOptionsConfig` | Additional options for identification. | 0.0.1 | #### IdentifyOptionsConfig | Prop | Type | Description | Default | Since | | ------------------------------- | --------- | -------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`restorePaywallAssignments`** | `boolean` | Restore paywall assignments from the server. Set to true when switching accounts to restore assignments. | `false` | 0.0.1 | #### GetUserIdResult | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------- | ----- | | **`userId`** | `string` | The current user ID (identified or anonymous). | 0.0.1 | #### GetIsLoggedInResult | Prop | Type | Description | Since | | ---------------- | --------- | ------------------------------------------- | ----- | | **`isLoggedIn`** | `boolean` | Whether the user is logged in (identified). | 0.0.1 | #### SetUserAttributesOptions | Prop | Type | Description | Since | | ---------------- | --------------------- | ----------------------------------------------------------------------------------------------------------------- | ----- | | **`attributes`** | `Record` | User attributes as key-value pairs. Keys starting with `$` are reserved. Set values to null to remove attributes. | 0.0.1 | #### HandleDeepLinkOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`url`** | `string` | The deep link URL to handle. | 0.0.1 | #### GetSubscriptionStatusResult | Prop | Type | Description | Since | | ------------ | -------------------- | -------------------------------- | ----- | | **`status`** | `SubscriptionStatus` | The current subscription status. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### SuperwallEventInfo | Prop | Type | Description | Since | | ---------- | --------------------- | ----------------------------------------- | ----- | | **`type`** | `SuperwallEventType` | The type of Superwall event. | 0.0.1 | | **`data`** | `Record` | Additional event data as key-value pairs. | 0.0.1 | #### SubscriptionStatusDidChangeEvent | Prop | Type | Description | Since | | ------------ | -------------------- | ---------------------------- | ----- | | **`status`** | `SubscriptionStatus` | The new subscription status. | 0.0.1 | #### PaywallPresentedEvent | Prop | Type | Description | Since | | ----------------- | ------------- | ---------------------------------------- | ----- | | **`paywallInfo`** | `PaywallInfo` | Information about the presented paywall. | 0.0.1 | #### PaywallInfo | Prop | Type | Description | Since | | ---------------- | --------------------- | ---------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The unique identifier for the paywall. | 0.0.1 | | **`placement`** | `string` | The placement identifier. | 0.0.1 | | **`experiment`** | `Experiment` | [Experiment](#experiment) information if the paywall is part of an A/B test. | 0.0.1 | | **`data`** | `Record` | Additional paywall metadata. | 0.0.1 | #### PaywallWillDismissEvent | Prop | Type | Description | Since | | ----------------- | ------------- | ---------------------------------------------- | ----- | | **`paywallInfo`** | `PaywallInfo` | Information about the paywall being dismissed. | 0.0.1 | #### PaywallDismissedEvent | Prop | Type | Description | Since | | ----------------- | ------------- | ---------------------------------------- | ----- | | **`paywallInfo`** | `PaywallInfo` | Information about the dismissed paywall. | 0.0.1 | #### CustomPaywallActionEvent | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------ | ----- | | **`name`** | `string` | The name of the custom action. | 0.0.1 | ### Enums #### LogLevel | Members | Value | Description | Since | | ----------- | --------- | ---------------------- | ----- | | **`Debug`** | `'DEBUG'` | Debug level logging. | 0.0.1 | | **`Info`** | `'INFO'` | Info level logging. | 0.0.1 | | **`Warn`** | `'WARN'` | Warning level logging. | 0.0.1 | | **`Error`** | `'ERROR'` | Error level logging. | 0.0.1 | #### LogScope | Members | Value | Description | Since | | --------------- | ------------- | --------------------------- | ----- | | **`All`** | `'ALL'` | All log scopes. | 0.0.1 | | **`Analytics`** | `'ANALYTICS'` | Analytics related logs. | 0.0.1 | | **`Config`** | `'CONFIG'` | Configuration related logs. | 0.0.1 | | **`Events`** | `'EVENTS'` | Event tracking logs. | 0.0.1 | | **`Paywalls`** | `'PAYWALLS'` | Paywall related logs. | 0.0.1 | | **`Purchases`** | `'PURCHASES'` | Purchase related logs. | 0.0.1 | #### NetworkEnvironment | Members | Value | Description | Since | | --------------- | ------------- | ------------------------------------ | ----- | | **`Release`** | `'RELEASE'` | Production environment. | 0.0.1 | | **`Developer`** | `'DEVELOPER'` | Development environment for testing. | 0.0.1 | #### PaywallResult | Members | Value | Description | Since | | --------------- | ------------- | --------------------------- | ----- | | **`Purchased`** | `'PURCHASED'` | User completed a purchase. | 0.0.1 | | **`Cancelled`** | `'CANCELLED'` | User cancelled the paywall. | 0.0.1 | | **`Restored`** | `'RESTORED'` | User restored purchases. | 0.0.1 | #### PresentationResultType | Members | Value | Description | Since | | ------------------------- | ------------------------- | --------------------------------------- | ----- | | **`PlacementNotFound`** | `'PLACEMENT_NOT_FOUND'` | The placement was not found. | 0.0.1 | | **`NoAudienceMatch`** | `'NO_AUDIENCE_MATCH'` | No audience matched for this placement. | 0.0.1 | | **`Paywall`** | `'PAYWALL'` | A paywall would be presented. | 0.0.1 | | **`Holdout`** | `'HOLDOUT'` | User is in a holdout group. | 0.0.1 | | **`PaywallNotAvailable`** | `'PAYWALL_NOT_AVAILABLE'` | Paywall is not available. | 0.0.1 | #### SubscriptionStatus | Members | Value | Description | Since | | -------------- | ------------ | ------------------------------------------ | ----- | | **`Unknown`** | `'UNKNOWN'` | Subscription status is unknown. | 0.0.1 | | **`Active`** | `'ACTIVE'` | User has an active subscription. | 0.0.1 | | **`Inactive`** | `'INACTIVE'` | User does not have an active subscription. | 0.0.1 | #### SuperwallEventType | Members | Value | Description | Since | | --------------------------------- | ---------------------------------- | ---------------------------- | ----- | | **`FirstSeen`** | `'FIRST_SEEN'` | First time user is seen. | 0.0.1 | | **`AppOpen`** | `'APP_OPEN'` | App was opened. | 0.0.1 | | **`AppLaunch`** | `'APP_LAUNCH'` | App was launched. | 0.0.1 | | **`AppClose`** | `'APP_CLOSE'` | App was closed. | 0.0.1 | | **`SessionStart`** | `'SESSION_START'` | Session started. | 0.0.1 | | **`DeepLink`** | `'DEEP_LINK'` | Deep link was opened. | 0.0.1 | | **`TriggerFire`** | `'TRIGGER_FIRE'` | Trigger was fired. | 0.0.1 | | **`PaywallOpen`** | `'PAYWALL_OPEN'` | Paywall was opened. | 0.0.1 | | **`PaywallClose`** | `'PAYWALL_CLOSE'` | Paywall was closed. | 0.0.1 | | **`PaywallDecline`** | `'PAYWALL_DECLINE'` | Paywall was declined. | 0.0.1 | | **`TransactionStart`** | `'TRANSACTION_START'` | Transaction started. | 0.0.1 | | **`TransactionComplete`** | `'TRANSACTION_COMPLETE'` | Transaction completed. | 0.0.1 | | **`TransactionFail`** | `'TRANSACTION_FAIL'` | Transaction failed. | 0.0.1 | | **`TransactionAbandon`** | `'TRANSACTION_ABANDON'` | Transaction was abandoned. | 0.0.1 | | **`TransactionRestore`** | `'TRANSACTION_RESTORE'` | Transaction restore. | 0.0.1 | | **`TransactionTimeout`** | `'TRANSACTION_TIMEOUT'` | Transaction timeout. | 0.0.1 | | **`SubscriptionStart`** | `'SUBSCRIPTION_START'` | Subscription started. | 0.0.1 | | **`FreeTrialStart`** | `'FREE_TRIAL_START'` | Free trial started. | 0.0.1 | | **`SubscriptionStatusDidChange`** | `'SUBSCRIPTION_STATUS_DID_CHANGE'` | Subscription status changed. | 0.0.1 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/superwall/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/superwall/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Nest 22, Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capawesome/capacitor-torch Capacitor plugin for switching the flashlight on and off. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for torch/flashlight control. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 🔦 **Torch control**: Enable, disable, and toggle torch/flashlight. - ✅ **Availability check**: Check if torch is available on the device. - 🌐 **Web support**: Uses MediaStream API for web torch control. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome/capacitor-torch` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome/capacitor-torch npx cap sync ``` ### Android #### Permissions This API requires the following permissions be added to your `AndroidManifest.xml` before the `application` tag: ``` ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxCameraCoreVersion` version of `androidx.camera:camera-core` (default: `1.5.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## Usage ``` import { Torch } from '@capawesome/capacitor-torch'; const enable = async () => { await Torch.enable(); }; const disable = async () => { await Torch.disable(); }; const isAvailable = async () => { const result = await Torch.isAvailable(); return result.available; }; const isEnabled = async () => { const result = await Torch.isEnabled(); return result.enabled; }; const toggle = async () => { await Torch.toggle(); }; ``` ## API - [`enable(...)`](#enable) - [`disable(...)`](#disable) - [`isAvailable()`](#isavailable) - [`isEnabled(...)`](#isenabled) - [`toggle(...)`](#toggle) - [Interfaces](#interfaces) ### enable(...) ``` enable(options?: EnableOptions | undefined) => Promise ``` Enable the torch. Only available on Android (SDK 23+), iOS and Web. | Param | Type | | ------------- | --------------- | | **`options`** | `EnableOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### disable(...) ``` disable(options?: DisableOptions | undefined) => Promise ``` Disable the torch. Only available on Android (SDK 23+), iOS and Web. | Param | Type | | ------------- | ---------------- | | **`options`** | `DisableOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the torch is available. Only available on Android, iOS and Web. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isEnabled(...) ``` isEnabled(options?: IsEnabledOptions | undefined) => Promise ``` Check if the torch is enabled. Only available on Android, iOS and Web. | Param | Type | | ------------- | ------------------ | | **`options`** | `IsEnabledOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### toggle(...) ``` toggle(options?: ToggleOptions | undefined) => Promise ``` Toggle the torch. Only available on Android (SDK 23+), iOS and Web. | Param | Type | | ------------- | --------------- | | **`options`** | `ToggleOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### EnableOptions | Prop | Type | Description | Since | | ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`stream`** | `MediaStream` | The stream of media to enable the torch on. **Attention**: The stream must have a video track. The facing mode of the video track must be the one that corresponds to the torch. Only available on Web. | 6.2.0 | #### DisableOptions | Prop | Type | Description | Since | | ------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`stream`** | `MediaStream` | The stream of media to disable the torch on. **Attention**: The stream must have a video track. The facing mode of the video track must be the one that corresponds to the torch. Only available on Web. | 6.2.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------- | ----- | | **`available`** | `boolean` | Whether the torch is available or not. | 6.0.0 | #### IsEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------ | ----- | | **`enabled`** | `boolean` | Whether the torch is enabled or not. | 6.0.0 | #### IsEnabledOptions | Prop | Type | Description | Since | | ------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`stream`** | `MediaStream` | The stream of media to check if the torch is enabled on. **Attention**: The stream must have a video track. The facing mode of the video track must be the one that corresponds to the torch. Only available on Web. | 6.2.0 | #### ToggleOptions | Prop | Type | Description | Since | | ------------ | ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`stream`** | `MediaStream` | The stream of media to toggle the torch on. **Attention**: The stream must have a video track. The facing mode of the video track must be the one that corresponds to the torch. Only available on Web. | 6.2.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/torch/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/torch/LICENSE). # @capawesome-team/capacitor-wifi Capacitor plugin to manage Wi-Fi connectivity, including adding, connecting, and disconnecting networks. Supports both Android and iOS. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for Wi-Fi connectivity. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🌐 **Network Management**: Add, connect and disconnect networks. - 🔍 **Network Scan**: Perform scans for available networks. - 📟 **Device Info**: Retrieve essential device information like IP address. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Demo | Android | iOS | | ------- | --- | | | | ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-wifi` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-wifi npx cap sync ``` ### Android #### Permissions This API requires the following elements be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` ### iOS #### Entitlements Ensure `Access Wi-Fi Information` and `Hotspot` capabilities have been enabled in your application in Xcode. See [Add a capability to a target](https://help.apple.com/xcode/mac/current/#/dev88ff319e7) for more information. #### Privacy Descriptions Add the `NSLocationWhenInUseUsageDescription` and `NSLocationAlwaysAndWhenInUseUsageDescription` keys to the `ios/App/App/Info.plist` file, which tells the user why your app is requesting location information: ``` NSLocationWhenInUseUsageDescription We need your location to request Wi-Fi information. NSLocationAlwaysAndWhenInUseUsageDescription We need your location to request Wi-Fi information. ``` ## Configuration | Prop | Type | Description | Default | Since | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`useWifiManager`** | `boolean` | Whether or not to use the **deprecated** `WifiManager` API for connecting to Wi-Fi networks using the `connect(...)` method. Only available on Android. | `false` | 6.3.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "Wifi": { "useWifiManager": undefined } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { Wifi: { useWifiManager: undefined, }, }, }; export default config; ``` ## Usage ``` import { Wifi } from '@capawesome-team/capacitor-wifi'; const connect = async () => { await Wifi.connect({ ssid: 'MyNetwork', password: 'MyPassword', isHiddenSsid: false }); } const disconnect = async () => { await Wifi.disconnect(); } const getAvailableNetworks = async () => { const result = await Wifi.getAvailableNetworks(); return result.networks; } const getIpAddress = async () => { const result = await Wifi.getIpAddress(); return result.address; } const getRssi = async () => { const result = await Wifi.getRssi(); return result.rssi; } const getSsid = async () => { const result = await Wifi.getSsid(); return result.ssid; } const isEnabled = async () => { const result = await Wifi.isEnabled(); return result.enabled; } const startScan = async () => { await Wifi.startScan(); } ``` ## API - [`addNetwork(...)`](#addnetwork) - [`connect(...)`](#connect) - [`disconnect(...)`](#disconnect) - [`getAvailableNetworks()`](#getavailablenetworks) - [`getIpAddress()`](#getipaddress) - [`getRssi()`](#getrssi) - [`getSsid()`](#getssid) - [`isEnabled()`](#isenabled) - [`startScan()`](#startscan) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions(...)`](#requestpermissions) - [`addListener('networksScanned', ...)`](#addlistenernetworksscanned-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### addNetwork(...) ``` addNetwork(options: AddNetworkOptions) => Promise ``` Show a system dialog to add a Wi-Fi network to the device. If the user accepts, the network will be added to the device's list of known networks and the traffic will be routed through it. Only available on Android (SDK 30+) and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `AddNetworkOptions` | **Since:** 7.1.0 ______________________________________________________________________ ### connect(...) ``` connect(options: ConnectOptions) => Promise ``` Connect to a Wi-Fi network. On **Android**, the network will NOT be added to the device's list of known networks and NO traffic will be routed through it. If you want to route traffic through the network, use `addNetwork(...)` instead. On **iOS**, this is the same as `addNetwork()`. Only available on Android and iOS. | Param | Type | | ------------- | ---------------- | | **`options`** | `ConnectOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### disconnect(...) ``` disconnect(options?: DisconnectOptions | undefined) => Promise ``` Disconnect from a Wi-Fi network. On **iOS**, you can only disconnect from networks that you connected to using the plugin. This also removes the Wi-Fi network from the list of known networks. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `DisconnectOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### getAvailableNetworks() ``` getAvailableNetworks() => Promise ``` Get a list of Wi-Fi networks found during the last scan. The returned networks are the most recently updated results, which may be from a previous scan if your current scan has not completed or succeeded. Only available on Android. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getIpAddress() ``` getIpAddress() => Promise ``` Get the current IP address of the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getRssi() ``` getRssi() => Promise ``` Get the received signal strength indicator (RSSI) of the current network in dBm. Only available on Android. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getSsid() ``` getSsid() => Promise ``` Get the service set identifier (SSID) of the current network. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Check if Wi-Fi is enabled. Only available on Android. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### startScan() ``` startScan() => Promise ``` Start a scan for Wi-Fi networks. This call may fail for any of the following reasons: - Scan requests may be throttled because of too many scans in a short time. - The device is idle and scanning is disabled. - Wi-Fi hardware reports a scan failure. Only available on Android. **Since:** 6.0.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for the plugin. Only available on Android and iOS. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions for the plugin. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### addListener('networksScanned', ...) ``` addListener(eventName: 'networksScanned', listenerFunc: (event: NetworksScannedEvent) => void) => Promise ``` Called when the scan results are available. Only available on Android. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'networksScanned'` | | **`listenerFunc`** | `(event: NetworksScannedEvent) => void` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### AddNetworkOptions | Prop | Type | Description | Default | Since | | ------------------ | ------------------------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | | **`ssid`** | `string` | The SSID of the network to add. | | 7.1.0 | | **`isHiddenSsid`** | `boolean` | Whether or not the SSID is hidden. Only available on Android. | `false` | 6.0.0 | | **`password`** | `string` | The password of the network to add. | | 7.1.0 | | **`securityType`** | \`NetworkSecurityType.PSK | NetworkSecurityType.SAE\` | The security type of the network to add. Use [`NetworkSecurityType.PSK`](#networksecuritytype) for WPA/WPA2 networks. Use [`NetworkSecurityType.SAE`](#networksecuritytype) for WPA3 networks. Only available on Android. | `NetworkSecurityType.PSK` | #### ConnectOptions | Prop | Type | Description | Default | Since | | ------------------ | --------- | ------------------------------------------------------------- | ------- | ----- | | **`ssid`** | `string` | The SSID of the network to connect to. | | 6.0.0 | | **`isHiddenSsid`** | `boolean` | Whether or not the SSID is hidden. Only available on Android. | `false` | 6.0.0 | | **`password`** | `string` | The password of the network to connect to. | | 6.0.0 | #### DisconnectOptions | Prop | Type | Description | Since | | ---------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ssid`** | `string` | The SSID of the network to disconnect from. If not provided, the device will disconnect from the current network. Only available on iOS. | 6.0.0 | #### GetAvailableNetworksResult | Prop | Type | Description | Since | | -------------- | ----------- | ------------------------------------------------------ | ----- | | **`networks`** | `Network[]` | The list of Wi-Fi networks found during the last scan. | 6.0.0 | #### Network | Prop | Type | Description | Since | | ------------------- | ----------------------- | -------------------------------------------------------------------------------------- | ----- | | **`rssi`** | `number` | The received signal strength indicator (RSSI) of the network in dBm. | 6.1.0 | | **`securityTypes`** | `NetworkSecurityType[]` | The service set identifier (SSID) of the network. Only available on Android (SDK 33+). | 6.1.0 | | **`ssid`** | `string` | The service set identifier (SSID) of the network. | 6.0.0 | #### GetIpAddressResult | Prop | Type | Description | Since | | ------------- | -------- | ----------------------------- | ----- | | **`address`** | `string` | The IP address of the device. | 6.0.0 | #### GetRssiResult | Prop | Type | Description | Since | | ---------- | -------- | ---------------------------------------------------------------------------- | ----- | | **`rssi`** | `number` | The received signal strength indicator (RSSI) of the current network in dBm. | 6.0.0 | #### GetSsidResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`ssid`** | `string` | The service set identifier (SSID) of the current network. On **iOS 14+**, the SSID can only be retrieved if the network was connected to using the plugin or if the app has permission to access precise location. | 6.0.0 | #### IsEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | -------------------------------- | ----- | | **`enabled`** | `boolean` | Whether or not Wi-Fi is enabled. | 6.0.0 | #### PermissionStatus | Prop | Type | Since | | -------------- | ----------------- | ----- | | **`location`** | `PermissionState` | 6.0.0 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | -------------- | --------------------------- | -------------- | ----- | | **`permissions`** | `'location'[]` | The permissions to request. | `["location"]` | 6.0.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### NetworksScannedEvent | Prop | Type | Description | Since | | -------------- | ----------- | ------------------------------------------------- | ----- | | **`networks`** | `Network[]` | The list of Wi-Fi networks found during the scan. | 6.0.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### PermissionType `'location'` ### Enums #### NetworkSecurityType | Members | Value | Description | Since | | --------------------------------- | ----- | ------------------------------------------------------------------------------------------ | ----- | | **`UNKNOWN`** | `-1` | Unknown security type. | 6.1.0 | | **`OPEN`** | `0` | Open network. | 6.1.0 | | **`WEP`** | `1` | WEP network. | 6.1.0 | | **`PSK`** | `2` | PSK (Pre-Shared Key) network. This includes WPA/WPA2/WPA3-Personal networks. | 6.1.0 | | **`EAP`** | `3` | EAP (Extensible Authentication Protocol) network. | 6.1.0 | | **`SAE`** | `4` | SAE (Simultaneous Authentication of Equals) network. | 6.1.0 | | **`EAP_WPA3_ENTERPRISE_192_BIT`** | `5` | WPA3-Enterprise in 192-bit security network. | 6.1.0 | | **`OWE`** | `6` | OWE network. | 6.1.0 | | **`WAPI_PSK`** | `7` | WAPI PSK network. | 6.1.0 | | **`WAPI_CERT`** | `8` | WAPI Certificate network. | 6.1.0 | | **`WPA3_ENTERPRISE`** | `9` | WPA3-Enterprise network. | 6.1.0 | | **`OSEN`** | `10` | OSEN network. | 6.1.0 | | **`PASSPOINT_R1_R2`** | `11` | Passpoint R1/R2 network, where TKIP and WEP are not allowed. | 6.1.0 | | **`PASSPOINT_R3`** | `12` | Passpoint R3 network, where TKIP and WEP are not allowed, and PMF must be set to Required. | 6.1.0 | | **`DPP`** | `13` | Easy Connect (DPP) network. | 6.1.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/wifi/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/wifi/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/wifi/LICENSE). # @capawesome-team/capacitor-zip Capacitor plugin to zip and unzip files and directories with support for encryption. ## Features We are proud to offer one of the most complete and feature-rich Capacitor plugins for zipping and unzipping files. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📁 **File Compression**: Zip and unzip single or multiple files. - 🔑 **Encryption**: Encrypt and decrypt files. - 🤝 **Compatibility**: Compatible with the [File Compressor](https://capawesome.io/plugins/file-compressor/) plugin. - 📦 **CocoaPods & SPM**: Supports CocoaPods and Swift Package Manager for iOS. - 🔁 **Up-to-date**: Always supports the latest Capacitor version. - ⭐️ **Support**: Priority support from the Capawesome Team. - ✨ **Handcrafted**: Built from the ground up with care and expertise, not forked or AI-generated. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll add it for you! ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Demo A working example can be found [here](https://github.com/robingenz/capacitor-plugin-demo). ## Installation This plugin is only available to [Capawesome Insiders](https://capawesome.io/insiders/). First, make sure you have the Capawesome npm registry set up. You can do this by running the following commands: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received from Polar. If you don't have a license key yet, you can get one by becoming a [Capawesome Insider](https://capawesome.io/insiders/). Next, you can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capawesome-team/capacitor-zip` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capawesome-team/capacitor-zip npx cap sync ``` ### Android #### Proguard If you are using Proguard, you need to add the following rules to your `proguard-rules.pro` file: ``` -keep class io.capawesome.capacitorjs.plugins.** { *; } ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$zip4jVersion` version of `net.lingala.zip4j:zip4j` (default: `2.11.5`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Minimum Deployment Target If you are using **Swift Package Manager**, make sure that your iOS deployment target is set to at least `16.0` in your Xcode project settings (usually in `ios/App/App.xcodeproj`): ``` -IPHONEOS_DEPLOYMENT_TARGET = 15.0 +IPHONEOS_DEPLOYMENT_TARGET = 16.0 ``` If you are using **CocoaPods**, make sure that your iOS deployment target is set to at least `16.0` in your `Podfile`: ``` platform :ios, '16.0' ``` ## Configuration No configuration required for this plugin. ## Usage ``` import { Zip } from '@capawesome-team/capacitor-zip'; const unzip = async () => { await Zip.unzip({ source: 'file:///data/user/0/dev.robingenz.capacitor.plugindemo/cache/1714900095398.zip', destination: 'file:///data/user/0/dev.robingenz.capacitor.plugindemo/cache/1714900095398', password: 'secret', }); }; const zip = async () => { await Zip.zip({ source: 'file:///data/user/0/dev.robingenz.capacitor.plugindemo/cache/1714900095398', destination: 'file:///data/user/0/dev.robingenz.capacitor.plugindemo/cache/1714900095398.zip', password: 'secret', }); }; ``` ## API - [`unzip(...)`](#unzip) - [`zip(...)`](#zip) - [Interfaces](#interfaces) ### unzip(...) ``` unzip(options: UnzipOptions) => Promise ``` Unzip a file. Only available on Android and iOS. | Param | Type | | ------------- | -------------- | | **`options`** | `UnzipOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### zip(...) ``` zip(options: ZipOptions) => Promise ``` Zip a file or directory. Only available on Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `ZipOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### Interfaces #### UnzipOptions | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------- | ----- | | **`destination`** | `string` | The destination directory. | 6.0.0 | | **`password`** | `string` | The password to decrypt the zip file. | 6.1.0 | | **`source`** | `string` | The source file to unzip. | 6.0.0 | #### ZipOptions | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------- | ----- | | **`destination`** | `string` | The destination file. | 6.0.0 | | **`password`** | `string` | The password to encrypt the zip file. | 6.1.0 | | **`source`** | `string` | The source file or directory to zip. | 6.0.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/zip/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/zip/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/zip/LICENSE). # Firebase Plugins This is a list of our Capacitor Firebase plugins: - [Analytics](https://capawesome.io/plugins/firebase/analytics/index.md) - [App](https://capawesome.io/plugins/firebase/app/index.md) - [App Check](https://capawesome.io/plugins/firebase/app-check/index.md) - [Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) - [Crashlytics](https://capawesome.io/plugins/firebase/crashlytics/index.md) - [Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) - [Cloud Functions](https://capawesome.io/plugins/firebase/cloud-functions/index.md) - [Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) - [Cloud Storage](https://capawesome.io/plugins/firebase/cloud-storage/index.md) - [Performance Monitoring](https://capawesome.io/plugins/firebase/performance-monitoring/index.md) - [Remote Config](https://capawesome.io/plugins/firebase/remote-config/index.md) # @capacitor-firebase/analytics Unofficial Capacitor plugin for [Firebase Analytics](https://firebase.google.com/docs/analytics).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/analytics` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/analytics firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android #### Disable Analytics data collection See [Disable Analytics data collection](https://firebase.google.com/docs/analytics/configure-data-collection?platform=android#disable_data_collection) if you want to disable Analytics data collection. #### Disable Advertising ID collection See [Disable Advertising ID collection](https://firebase.google.com/docs/analytics/configure-data-collection?platform=android#disable_advertising_id_collection) if you want to disable Advertising ID collection. #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseAnalyticsVersion` version of `com.google.firebase:firebase-analytics` (default: `23.0.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS If you are using **CocoaPods** for your iOS project, you need to add the `CapacitorFirebaseAnalytics/Analytics` pod to your `Podfile` (usually `ios/App/Podfile`): ``` target 'App' do capacitor_pods # Add your Pods here + pod 'CapacitorFirebaseAnalytics/Analytics', :path => '../../node_modules/@capacitor-firebase/analytics' end ``` **Attention**: Do not add the pod in the section `def capacitor_pods`, but under the comment `# Add your Pods here` ([example](https://github.com/robingenz/capacitor-firebase-plugin-demo/blob/e1684a0af6871442ed0a87dceeeba6fd9ce0185d/ios/App/Podfile#L30)). #### Disable Analytics data collection See [Disable Analytics data collection](https://firebase.google.com/docs/analytics/configure-data-collection?platform=ios#disable_data_collection) if you want to disable Analytics data collection. #### Disable IDFA collection If you are using **CocoaPods** for your iOS project and you want to disable IDFA collection, you can use the `CapacitorFirebaseAnalytics/AnalyticsWithoutAdIdSupport` pod instead of the `CapacitorFirebaseAnalytics/Analytics` pod: ``` target 'App' do capacitor_pods # Add your Pods here - pod 'CapacitorFirebaseAnalytics/Analytics', :path => '../../node_modules/@capacitor-firebase/analytics' + pod 'CapacitorFirebaseAnalytics/AnalyticsWithoutAdIdSupport', :path => '../../node_modules/@capacitor-firebase/analytics' end ``` If you are using **Swift Package Manager** for your iOS project and you want to disable IDFA collection, you can enable the `AnalyticsWithoutAdIdSupport` trait in your `capacitor.config.json` (or `capacitor.config.ts`): ``` { "experimental": { "ios": { "spm": { "swiftToolsVersion": "6.1", "packageTraits": { "@capacitor-firebase/analytics": ["AnalyticsWithoutAdIdSupport"] } } } } } ``` **Note**: SPM trait support requires Capacitor CLI **8.3.0+** and Xcode **16.3+** (Swift 6.1+). ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Usage ``` import { FirebaseAnalytics } from '@capacitor-firebase/analytics'; const setUserId = async () => { await FirebaseAnalytics.setUserId({ userId: '123', }); }; const setUserProperty = async () => { await FirebaseAnalytics.setUserProperty({ key: 'language', value: 'en', }); }; const setCurrentScreen = async () => { await FirebaseAnalytics.setCurrentScreen({ screenName: 'Login', screenClassOverride: 'LoginPage', }); }; const logEvent = async () => { await FirebaseAnalytics.logEvent({ name: 'sign_up', params: { method: 'password' }, }); }; const setSessionTimeoutDuration = async () => { await FirebaseAnalytics.setSessionTimeoutDuration({ duration: '120', }); }; const setEnabled = async () => { await FirebaseAnalytics.setEnabled({ enabled: true, }); }; const isEnabled = async () => { const { enabled } = await FirebaseAnalytics.isEnabled(); return enabled; }; const resetAnalyticsData = async () => { await FirebaseAnalytics.resetAnalyticsData(); }; const initiateOnDeviceConversionMeasurementWithEmailAddress = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithEmailAddress({ emailAddress: 'mail@example.com', }); }; const initiateOnDeviceConversionMeasurementWithPhoneNumber = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithPhoneNumber({ phoneNumber: '+49123456789', }); }; const initiateOnDeviceConversionMeasurementWithHashedEmailAddress = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithHashedEmailAddress({ emailAddressToHash: 'mail@example.com', }); }; const initiateOnDeviceConversionMeasurementWithHashedPhoneNumber = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber({ phoneNumberToHash: '+49123456789', }); }; ``` ## API - [`getAppInstanceId()`](#getappinstanceid) - [`setConsent(...)`](#setconsent) - [`setUserId(...)`](#setuserid) - [`setUserProperty(...)`](#setuserproperty) - [`setCurrentScreen(...)`](#setcurrentscreen) - [`logEvent(...)`](#logevent) - [`setSessionTimeoutDuration(...)`](#setsessiontimeoutduration) - [`setEnabled(...)`](#setenabled) - [`isEnabled()`](#isenabled) - [`resetAnalyticsData()`](#resetanalyticsdata) - [`logTransaction(...)`](#logtransaction) - [`initiateOnDeviceConversionMeasurementWithEmailAddress(...)`](#initiateondeviceconversionmeasurementwithemailaddress) - [`initiateOnDeviceConversionMeasurementWithPhoneNumber(...)`](#initiateondeviceconversionmeasurementwithphonenumber) - [`initiateOnDeviceConversionMeasurementWithHashedEmailAddress(...)`](#initiateondeviceconversionmeasurementwithhashedemailaddress) - [`initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(...)`](#initiateondeviceconversionmeasurementwithhashedphonenumber) - [Interfaces](#interfaces) - [Enums](#enums) ### getAppInstanceId() ``` getAppInstanceId() => Promise ``` Retrieves the app instance id. Only available for Android and iOS. **Returns:** `Promise` **Since:** 1.4.0 ______________________________________________________________________ ### setConsent(...) ``` setConsent(options: SetConsentOptions) => Promise ``` Sets the user's consent mode. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetConsentOptions` | **Since:** 6.0.0 ______________________________________________________________________ ### setUserId(...) ``` setUserId(options: SetUserIdOptions) => Promise ``` Sets the user ID property. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetUserIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUserProperty(...) ``` setUserProperty(options: SetUserPropertyOptions) => Promise ``` Sets a custom user property to a given value. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SetUserPropertyOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setCurrentScreen(...) ``` setCurrentScreen(options: SetCurrentScreenOptions) => Promise ``` Sets the current screen name. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SetCurrentScreenOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### logEvent(...) ``` logEvent(options: LogEventOptions) => Promise ``` Logs an app event. | Param | Type | | ------------- | ----------------- | | **`options`** | `LogEventOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setSessionTimeoutDuration(...) ``` setSessionTimeoutDuration(options: SetSessionTimeoutDurationOptions) => Promise ``` Sets the duration of inactivity that terminates the current session. Only available for Android and iOS. | Param | Type | | ------------- | ---------------------------------- | | **`options`** | `SetSessionTimeoutDurationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setEnabled(...) ``` setEnabled(options: SetEnabledOptions) => Promise ``` Enables/disables automatic data collection. The value does not apply until the next run of the app. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetEnabledOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Returns whether or not automatic data collection is enabled. Only available for Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### resetAnalyticsData() ``` resetAnalyticsData() => Promise ``` Clears all analytics data for this app from the device. Resets the app instance id. Only available for Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### logTransaction(...) ``` logTransaction(options: LogTransactionOptions) => Promise ``` Logs a StoreKit 2 transaction. Only available for iOS (15.0+). | Param | Type | | ------------- | ----------------------- | | **`options`** | `LogTransactionOptions` | **Since:** 8.2.0 ______________________________________________________________________ ### initiateOnDeviceConversionMeasurementWithEmailAddress(...) ``` initiateOnDeviceConversionMeasurementWithEmailAddress(options: InitiateOnDeviceConversionMeasurementWithEmailAddressOptions) => Promise ``` Initiates on-device conversion measurement with an email address. Only available for iOS. | Param | Type | | ------------- | -------------------------------------------------------------- | | **`options`** | `InitiateOnDeviceConversionMeasurementWithEmailAddressOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### initiateOnDeviceConversionMeasurementWithPhoneNumber(...) ``` initiateOnDeviceConversionMeasurementWithPhoneNumber(options: InitiateOnDeviceConversionMeasurementWithPhoneNumberOptions) => Promise ``` Initiates on-device conversion measurement with a phone number. Only available for iOS. | Param | Type | | ------------- | ------------------------------------------------------------- | | **`options`** | `InitiateOnDeviceConversionMeasurementWithPhoneNumberOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### initiateOnDeviceConversionMeasurementWithHashedEmailAddress(...) ``` initiateOnDeviceConversionMeasurementWithHashedEmailAddress(options: InitiateOnDeviceConversionMeasurementWithHashedEmailAddressOptions) => Promise ``` Initiates on-device conversion measurement with a hashed email address. Only available for iOS. | Param | Type | | ------------- | -------------------------------------------------------------------- | | **`options`** | `InitiateOnDeviceConversionMeasurementWithHashedEmailAddressOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(...) ``` initiateOnDeviceConversionMeasurementWithHashedPhoneNumber(options: InitiateOnDeviceConversionMeasurementWithHashedPhoneNumberOptions) => Promise ``` Initiates on-device conversion measurement with a hashed phone number. Only available for iOS. | Param | Type | | ------------- | ------------------------------------------------------------------- | | **`options`** | `InitiateOnDeviceConversionMeasurementWithHashedPhoneNumberOptions` | **Since:** 7.2.0 ______________________________________________________________________ ### Interfaces #### GetAppInstanceIdResult | Prop | Type | Description | Since | | ------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`appInstanceId`** | `string` | The app instance id. Not defined if `FirebaseAnalytics.ConsentType.ANALYTICS_STORAGE` has been set to `FirebaseAnalytics.ConsentStatus.DENIED`. | 1.4.0 | #### SetConsentOptions | Prop | Type | Description | Since | | ------------ | --------------- | ------------------- | ----- | | **`type`** | `ConsentType` | The consent type. | 6.0.0 | | **`status`** | `ConsentStatus` | The consent status. | 6.0.0 | #### SetUserIdOptions | Prop | Type | Since | | ------------ | -------- | ------ | | **`userId`** | \`string | null\` | #### SetUserPropertyOptions | Prop | Type | Since | | ----------- | -------- | ------ | | **`key`** | `string` | 0.1.0 | | **`value`** | \`string | null\` | #### SetCurrentScreenOptions | Prop | Type | Description | Default | Since | | ------------------------- | -------- | ----------- | ----------------------------------- | ------ | | **`screenName`** | \`string | null\` | | | | **`screenClassOverride`** | \`string | null\` | Only available for Android and iOS. | `null` | #### LogEventOptions | Prop | Type | Description | Since | | ------------ | ------------------------- | -------------------------- | ----- | | **`name`** | `string` | The event name. | 0.1.0 | | **`params`** | `{ [key: string]: any; }` | The optional event params. | 0.1.0 | #### SetSessionTimeoutDurationOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | -------------------- | ------- | ----- | | **`duration`** | `number` | Duration in seconds. | `1800` | 0.1.0 | #### SetEnabledOptions | Prop | Type | Since | | ------------- | --------- | ----- | | **`enabled`** | `boolean` | 0.1.0 | #### IsEnabledResult | Prop | Type | Since | | ------------- | --------- | ----- | | **`enabled`** | `boolean` | 0.1.0 | #### LogTransactionOptions | Prop | Type | Description | Since | | ------------------- | -------- | ---------------------------------------------------------- | ----- | | **`transactionId`** | `string` | The StoreKit 2 `Transaction.id` value as a numeric string. | 8.2.0 | #### InitiateOnDeviceConversionMeasurementWithEmailAddressOptions | Prop | Type | Description | Since | | ------------------ | -------- | -------------------------------------------------------------------- | ----- | | **`emailAddress`** | `string` | The email address to initiate on-device conversion measurement with. | 7.2.0 | #### InitiateOnDeviceConversionMeasurementWithPhoneNumberOptions | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------------------------- | ----- | | **`phoneNumber`** | `string` | The phone number to initiate on-device conversion measurement with. | 7.2.0 | #### InitiateOnDeviceConversionMeasurementWithHashedEmailAddressOptions | Prop | Type | Description | Since | | ------------------------ | -------- | -------------------------------------------------------------------- | ----- | | **`emailAddressToHash`** | `string` | The email address to initiate on-device conversion measurement with. | 7.2.0 | #### InitiateOnDeviceConversionMeasurementWithHashedPhoneNumberOptions | Prop | Type | Description | Since | | ----------------------- | -------- | ------------------------------------------------------------------- | ----- | | **`phoneNumberToHash`** | `string` | The phone number to initiate on-device conversion measurement with. | 7.2.0 | ### Enums #### ConsentType | Members | Value | Since | | ---------------------------- | --------------------------- | ----- | | **`AdPersonalization`** | `'AD_PERSONALIZATION'` | 6.0.0 | | **`AdStorage`** | `'AD_STORAGE'` | 6.0.0 | | **`AdUserData`** | `'AD_USER_DATA'` | 6.0.0 | | **`AnalyticsStorage`** | `'ANALYTICS_STORAGE'` | 6.0.0 | | **`FunctionalityStorage`** | `'FUNCTIONALITY_STORAGE'` | 6.0.0 | | **`PersonalizationStorage`** | `'PERSONALIZATION_STORAGE'` | 6.0.0 | #### ConsentStatus | Members | Value | Since | | ------------- | ----------- | ----- | | **`Granted`** | `'GRANTED'` | 6.0.0 | | **`Denied`** | `'DENIED'` | 6.0.0 | ## Test your implementation [Here](https://firebase.google.com/docs/analytics/debugview) you can find more information on how to test the Firebase Analytics implementation using the **DebugView**. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/analytics/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/analytics/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/app-check Unofficial Capacitor plugin for [Firebase App Check](https://firebase.google.com/docs/app-check).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/app-check` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/app-check firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android See [Set up your Firebase project](https://firebase.google.com/docs/app-check/android/play-integrity-provider#project-setup) and follow the instructions to set up your app correctly. #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseAppCheckPlayIntegrityVersion` version of `com.google.firebase:firebase-appcheck-playintegrity` (default: `19.0.1`) - `$firebaseAppCheckDebugVersion` version of `com.google.firebase:firebase-appcheck-debug` (default: `19.0.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS On **iOS 14 and later**, see [Set up your Firebase project](https://firebase.google.com/docs/app-check/ios/app-attest-provider#project-setup) and follow the instructions to set up your app correctly. On **iOS 13**, see [Set up your Firebase project](https://firebase.google.com/docs/app-check/ios/devicecheck-provider#project-setup) and follow the instructions to set up your app correctly. Make sure that the private key (\*.p8) you upload to Firebase has `DeviceCheck` selected as a service. ### Web See [Set up your Firebase project](https://firebase.google.com/docs/app-check/web/recaptcha-provider#project-setup) and follow the instructions to set up your app correctly. ## Configuration No configuration required for this plugin. ## Firebase JavaScript SDK [Here](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app-check/docs/firebase-js-sdk.md) you can find information on how to use the plugin with the Firebase JS SDK. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Usage ``` import { FirebaseAppCheck } from '@capacitor-firebase/app-check'; import { ReCaptchaV3Provider } from '@capacitor-firebase/app-check'; import { Capacitor } from '@capacitor/core'; const initialize = async () => { await FirebaseAppCheck.initialize({ provider: Capacitor.getPlatform() === 'web' ? new ReCaptchaV3Provider('myKey') : undefined, }); }; const getToken = async () => { const { token } = FirebaseAppCheck.getToken({ forceRefresh: false, }); return token; }; const setTokenAutoRefreshEnabled = async () => { await FirebaseAppCheck.setTokenAutoRefreshEnabled({ enabled: true }); }; const addTokenChangedListener = async () => { await FirebaseAppCheck.addListener('tokenChanged', event => { console.log('tokenChanged', { event }); }); }; const removeAllListeners = async () => { await FirebaseAppCheck.removeAllListeners(); }; ``` ## API - [`getToken(...)`](#gettoken) - [`initialize(...)`](#initialize) - [`setTokenAutoRefreshEnabled(...)`](#settokenautorefreshenabled) - [`addListener('tokenChanged', ...)`](#addlistenertokenchanged-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getToken(...) ``` getToken(options?: GetTokenOptions | undefined) => Promise ``` Get the current App Check token. | Param | Type | | ------------- | ----------------- | | **`options`** | `GetTokenOptions` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Activate App Check for the given app. Can be called only once per app. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 1.3.0 ______________________________________________________________________ ### setTokenAutoRefreshEnabled(...) ``` setTokenAutoRefreshEnabled(options: SetTokenAutoRefreshEnabledOptions) => Promise ``` Set whether the App Check token should be refreshed automatically or not. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `SetTokenAutoRefreshEnabledOptions` | **Since:** 1.3.0 ______________________________________________________________________ ### addListener('tokenChanged', ...) ``` addListener(eventName: 'tokenChanged', listenerFunc: TokenChangedListener) => Promise ``` Called when the App Check token changed. | Param | Type | | ------------------ | ---------------------- | | **`eventName`** | `'tokenChanged'` | | **`listenerFunc`** | `TokenChangedListener` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available for Web. **Since:** 1.3.0 ______________________________________________________________________ ### Interfaces #### GetTokenResult | Prop | Type | Description | Since | | ---------------------- | -------- | ---------------------------------------------------------------------------------------------------------------- | ----- | | **`token`** | `string` | The App Check token in JWT format. | 1.3.0 | | **`expireTimeMillis`** | `number` | The timestamp after which the token will expire in milliseconds since epoch. Only available for Android and iOS. | 1.3.0 | #### GetTokenOptions | Prop | Type | Description | Default | Since | | ------------------ | --------- | ----------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`forceRefresh`** | `boolean` | If `true`, will always try to fetch a fresh token. If `false`, will use a cached token if found in storage. | `false` | 1.3.0 | #### InitializeOptions | Prop | Type | Description | Default | Since | | ------------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | | **`debug`** | `boolean` | If `true`, the debug provider is used. ⚠️ **Attention**: The debug provider allows access to your Firebase resources from unverified devices. Don't use the debug provider in production builds of your app, and don't share your debug builds with untrusted parties. ⚠️ **Deprecated**: Use `debugToken` instead. This option will be removed in the next major version. Read more: https://firebase.google.com/docs/app-check/web/debug-provider | `false` | 1.3.0 | | **`debugToken`** | \`string | boolean\` | If `true`, the debug provider is used. On **Web**, you can also set a predefined debug token string instead of `true`. On Android and iOS, you have to use environment variables for this. ⚠️ **Attention**: The debug provider allows access to your Firebase resources from unverified devices. Don't use the debug provider in production builds of your app, and don't share your debug builds with untrusted parties. | `false` | | **`isTokenAutoRefreshEnabled`** | `boolean` | If `true`, the SDK automatically refreshes App Check tokens as needed. | `false` | 1.3.0 | | **`provider`** | `any` | The provider to use for App Check. Must be an instance of `ReCaptchaV3Provider`, `ReCaptchaEnterpriseProvider`, or `CustomProvider`. Only available for Web. | `ReCaptchaV3Provider` | 7.1.0 | | **`siteKey`** | `string` | The reCAPTCHA v3 site key (public key). This option is ignored when `provider` is set. Only available for Web. | | 1.3.0 | #### SetTokenAutoRefreshEnabledOptions | Prop | Type | Description | Since | | ------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`enabled`** | `boolean` | If `true`, the SDK automatically refreshes App Check tokens as needed. This overrides any value set during initializeAppCheck(). | 1.3.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### TokenChangedEvent | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------- | ----- | | **`token`** | `string` | The App Check token in JWT format. | 1.3.0 | ### Type Aliases #### TokenChangedListener Callback to receive the token change event. `(event: TokenChangedEvent): void` ## Testing ### Android Follow these steps to test your implementation on a real device: 1. Start your app on the Android device. 1. Run the following command to grab your temporary secret from the android logs: ``` adb logcat | grep DebugAppCheckProvider ``` The output should look like this: ``` D DebugAppCheckProvider: Enter this debug secret into the allow list in the Firebase Console for your project: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX ``` 1. Next, open the [App Check project](https://console.firebase.google.com/u/0/project/_/appcheck/apps) in the Firebase Console and select Manage debug tokens from the overflow menu of your app. Then, register the debug secret from the output. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app-check/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app-check/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/app Unofficial Capacitor plugin for [Firebase App](https://firebase.google.com/docs).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/app` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/app firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseCommonVersion` version of `com.google.firebase:firebase-common` (default: `22.0.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Usage ``` import { FirebaseApp } from '@capacitor-firebase/app'; const getName = async () => { const result = await FirebaseApp.getName(); }; const getOptions = async () => { const result = await FirebaseApp.getOptions(); }; ``` ## API - [`getName()`](#getname) - [`getOptions()`](#getoptions) - [Interfaces](#interfaces) ### getName() ``` getName() => Promise ``` Get the name for this app. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getOptions() ``` getOptions() => Promise ``` Get the configuration options for this app. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetNameResult | Prop | Type | Description | Since | | ---------- | -------- | ---------------------------- | ----- | | **`name`** | `string` | The unique name of this app. | 0.1.0 | #### GetOptionsResult | Prop | Type | Description | Since | | ------------------- | -------- | -------------------------------------------------------------- | ----- | | **`apiKey`** | `string` | API key used for authenticating requests from your app. | 0.1.0 | | **`applicationId`** | `string` | Google App ID used to uniquely identify an instance of an app. | 0.1.0 | | **`databaseUrl`** | `string` | The database root URL. | 0.1.0 | | **`gcmSenderId`** | `string` | The Project Number. | 0.1.0 | | **`projectId`** | `string` | The Google Cloud project ID. | 0.1.0 | | **`storageBucket`** | `string` | The Google Cloud Storage bucket name. | 0.1.0 | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/authentication Unofficial Capacitor plugin for [Firebase Authentication](https://firebase.google.com/docs/auth).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/authentication` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/authentication firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). On **iOS**, verify that this function is included in your app's `AppDelegate.swift`: ``` func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { return ApplicationDelegateProxy.shared.application(app, open: url, options: options) } ``` **Attention**: If you use this plugin on **iOS** in combination with `@capacitor-firebase/messaging`, then add the following to your app's `AppDelegate.swift`: ``` + import FirebaseAuth func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if Auth.auth().canHandle(url) { + return true + } return ApplicationDelegateProxy.shared.application(app, open: url, options: options) } ``` The further installation steps depend on the selected authentication method: - [Apple Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-apple.md) - [Facebook Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-facebook.md) - [Game Center Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-game-center.md) - [GitHub Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-github.md) - [Google Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-google.md) - [Microsoft Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-microsoft.md) - [OpenID Connect Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-oidc.md) - [Play Games Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-play-games.md) - [Twitter Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-twitter.md) - [Yahoo Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-yahoo.md) - [Anonymous Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-anonymous.md) - [Email Link Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-email-link.md) - [Phone Number Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-phone.md) - [Custom Token Sign-In](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/setup-custom-token.md) **Attention**: Please note that this plugin uses third-party SDKs to offer native sign-in. These SDKs can initialize on their own and collect various data. For more information, see [Third-Party SDKs](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/third-party-sdks.md). ## Configuration These configuration values are available: | Prop | Type | Description | Default | Since | | -------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`authDomain`** | `string` | Configure the custom auth domain you want to use. Only available for Android and iOS. | | 7.3.0 | | **`skipNativeAuth`** | `boolean` | Configure whether the plugin should skip the native authentication. Only needed if you want to use the Firebase JavaScript SDK. This configuration option has no effect on Firebase account linking. **Note that the plugin may behave differently across the platforms.** Only available for Android and iOS. | `false` | 0.1.0 | | **`providers`** | `string[]` | Configure the providers that should be loaded by the plugin. Possible values: `["apple.com", "facebook.com", "gc.apple.com", "github.com", "google.com", "microsoft.com", "playgames.google.com", "twitter.com", "yahoo.com", "phone"]` Only available for Android and iOS. | `[]` | 0.1.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "FirebaseAuthentication": { "authDomain": undefined, "skipNativeAuth": false, "providers": ["apple.com", "facebook.com"] } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { FirebaseAuthentication: { authDomain: undefined, skipNativeAuth: false, providers: ["apple.com", "facebook.com"], }, }, }; export default config; ``` ## FAQ 1. **What does this plugin do?**\ This plugin enables the use of [Firebase Authentication](https://firebase.google.com/docs/auth) in a Capacitor app. It uses the Firebase SDK for [Java](https://firebase.google.com/docs/reference/android) (Android), [Swift](https://firebase.google.com/docs/reference/swift) (iOS) and [JavaScript](https://firebase.google.com/docs/reference/js). 1. **What is the difference between the web implementation of this plugin and the Firebase JS SDK?**\ The web implementation of this plugin encapsulates the Firebase JS SDK and enables a consistent interface across all platforms. You can decide if you prefer to use the web implementation or the Firebase JS SDK. 1. **What is the difference between the native and web authentication?**\ For web authentication, the Firebase JS SDK is used. This only works to a limited extent on Android and iOS in the WebViews (see [here](https://developers.googleblog.com/2016/08/modernizing-oauth-interactions-in-native-apps.html)). For native authentication, the native SDKs from Firebase, Google, etc. are used. These offer all the functionalities that the Firebase JS SDK also offers on the web. However, after a login with the native SDK, the user is only logged in on the native layer of the app. If the user should also be logged in on the web layer (for example to access Cloud Firestore via Firebase JS SDK), additional steps are required (see [here](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/firebase-js-sdk.md)). 1. **How can I use this plugin with the Firebase JavaScript SDK?**\ See [here](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/firebase-js-sdk.md). ## Firebase JavaScript SDK [Here](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/docs/firebase-js-sdk.md) you can find information on how to use the plugin with the Firebase JS SDK. ## Demo A working example can be found here: [robingenz/capacitor-firebase-authentication-demo](https://github.com/robingenz/capacitor-firebase-authentication-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const applyActionCode = async () => { await FirebaseAuthentication.applyActionCode({ oobCode: '1234' }); }; const createUserWithEmailAndPassword = async () => { const result = await FirebaseAuthentication.createUserWithEmailAndPassword({ email: 'mail@exmaple.com', password: '1234', }); return result.user; }; const confirmPasswordReset = async () => { await FirebaseAuthentication.confirmPasswordReset({ oobCode: '1234', newPassword: '4321', }); }; const deleteUser = async () => { await FirebaseAuthentication.deleteUser(); }; const fetchSignInMethodsForEmail = async () => { const result = await FirebaseAuthentication.fetchSignInMethodsForEmail({ email: 'mail@example.tld', }); return result.signInMethods; }; const getCurrentUser = async () => { const result = await FirebaseAuthentication.getCurrentUser(); return result.user; }; const getPendingAuthResult = async () => { const result = await FirebaseAuthentication.getPendingAuthResult(); return result.user; }; const getIdToken = async () => { const currentUser = await getCurrentUser(); if (!currentUser) { return; } const result = await FirebaseAuthentication.getIdToken(); return result.token; }; const getPendingAuthResult = async () => { const result = await FirebaseAuthentication.getPendingAuthResult(); return result.user; }; const sendEmailVerification = async () => { const currentUser = await getCurrentUser(); if (!currentUser) { return; } await FirebaseAuthentication.sendEmailVerification(); }; const sendPasswordResetEmail = async () => { await FirebaseAuthentication.sendPasswordResetEmail({ email: 'mail@example.com', }); }; const sendSignInLinkToEmail = async () => { const email = 'mail@example.com'; await FirebaseAuthentication.sendSignInLinkToEmail({ email, actionCodeSettings: { // URL you want to redirect back to. The domain (www.example.com) for this // URL must be in the authorized domains list in the Firebase Console. url: 'https://www.example.com/finishSignUp?cartId=1234', // This must be true. handleCodeInApp: true, iOS: { bundleId: 'com.example.ios', }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12', }, dynamicLinkDomain: 'example.page.link', }, }); // The link was successfully sent. Inform the user. // Save the email locally so you don't need to ask the user for it again // if they open the link on the same device. window.localStorage.setItem('emailForSignIn', email); }; const setLanguageCode = async () => { await FirebaseAuthentication.setLanguageCode({ languageCode: 'en-US' }); }; const signInAnonymously = async () => { const result = await FirebaseAuthentication.signInAnonymously(); return result.user; }; const signInWithApple = async () => { const result = await FirebaseAuthentication.signInWithApple(); return result.user; }; const signInWithCustomToken = async () => { const result = await FirebaseAuthentication.signInWithCustomToken({ token: '1234', }); return result.user; }; const signInWithEmailAndPassword = async () => { const result = await FirebaseAuthentication.signInWithEmailAndPassword({ email: 'mail@example.com', password: '1234', }); return result.user; }; const signInWithEmailLink = async () => { // Get the email if available. This should be available if the user completes // the flow on the same device where they started it. const emailLink = window.location.href; // Confirm the link is a sign-in with email link. const { isSignInWithEmailLink } = await FirebaseAuthentication.isSignInWithEmailLink({ emailLink, }); if (!isSignInWithEmailLink) { return; } let email = window.localStorage.getItem('emailForSignIn'); if (!email) { // User opened the link on a different device. To prevent session fixation // attacks, ask the user to provide the associated email again. email = window.prompt('Please provide your email for confirmation.'); } // The client SDK will parse the code from the link for you. const result = await FirebaseAuthentication.signInWithEmailLink({ email, emailLink, }); // Clear email from storage. window.localStorage.removeItem('emailForSignIn'); return result.user; }; const signInWithFacebook = async () => { const result = await FirebaseAuthentication.signInWithFacebook(); return result.user; }; const signInWithGameCenter = async () => { const result = await FirebaseAuthentication.signInWithGameCenter(); return result.user; }; const signInWithGithub = async () => { const result = await FirebaseAuthentication.signInWithGithub(); return result.user; }; const signInWithGoogle = async () => { const result = await FirebaseAuthentication.signInWithGoogle(); return result.user; }; const signInWithMicrosoft = async () => { const result = await FirebaseAuthentication.signInWithMicrosoft(); return result.user; }; const signInWithOpenIdConnect = async () => { const result = await FirebaseAuthentication.signInWithOpenIdConnect({ providerId: 'oidc.example.com', }); return result.user; }; const signInWithPlayGames = async () => { const result = await FirebaseAuthentication.signInWithPlayGames(); return result.user; }; const signInWithPhoneNumber = async () => { return new Promise(async resolve => { // Attach `phoneCodeSent` listener to be notified as soon as the SMS is sent await FirebaseAuthentication.addListener('phoneCodeSent', async event => { // Ask the user for the SMS code const verificationCode = window.prompt( 'Please enter the verification code that was sent to your mobile device.', ); // Confirm the verification code const result = await FirebaseAuthentication.confirmVerificationCode({ verificationId: event.verificationId, verificationCode, }); resolve(result.user); }); // Attach `phoneVerificationCompleted` listener to be notified if phone verification could be finished automatically await FirebaseAuthentication.addListener( 'phoneVerificationCompleted', async event => { resolve(event.result.user); }, ); // Start sign in with phone number and send the SMS await FirebaseAuthentication.signInWithPhoneNumber({ phoneNumber: '123456789', }); }); }; const signInWithTwitter = async () => { const result = await FirebaseAuthentication.signInWithTwitter(); return result.user; }; const signInWithYahoo = async () => { const result = await FirebaseAuthentication.signInWithYahoo(); return result.user; }; const signOut = async () => { await FirebaseAuthentication.signOut(); }; const updateEmail = async () => { const currentUser = await getCurrentUser(); if (!currentUser) { return; } await FirebaseAuthentication.updateEmail({ newEmail: 'new.mail@example.com', }); }; const updatePassword = async () => { const currentUser = await getCurrentUser(); if (!currentUser) { return; } await FirebaseAuthentication.updatePassword({ newPassword: '4321', }); }; const useAppLanguage = async () => { await FirebaseAuthentication.useAppLanguage(); }; const useEmulator = async () => { await FirebaseAuthentication.useEmulator({ host: '10.0.2.2', port: 9099, }); }; const verifyBeforeUpdateEmail = async () => { const currentUser = await getCurrentUser(); if (!currentUser) { return; } await FirebaseAuthentication.verifyBeforeUpdateEmail({ newEmail: 'new.mail@example.com', actionCodeSettings: { url: 'https://www.example.com/cart?email=user@example.com&cartId=123', iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, handleCodeInApp: true } }); }; ``` ## API - [`applyActionCode(...)`](#applyactioncode) - [`confirmPasswordReset(...)`](#confirmpasswordreset) - [`confirmVerificationCode(...)`](#confirmverificationcode) - [`createUserWithEmailAndPassword(...)`](#createuserwithemailandpassword) - [`deleteUser()`](#deleteuser) - [`fetchSignInMethodsForEmail(...)`](#fetchsigninmethodsforemail) - [`getCurrentUser()`](#getcurrentuser) - [`getPendingAuthResult()`](#getpendingauthresult) - [`getIdToken(...)`](#getidtoken) - [`getIdTokenResult(...)`](#getidtokenresult) - [`getRedirectResult()`](#getredirectresult) - [`getTenantId()`](#gettenantid) - [`isSignInWithEmailLink(...)`](#issigninwithemaillink) - [`linkWithApple(...)`](#linkwithapple) - [`linkWithEmailAndPassword(...)`](#linkwithemailandpassword) - [`linkWithEmailLink(...)`](#linkwithemaillink) - [`linkWithFacebook(...)`](#linkwithfacebook) - [`linkWithGameCenter(...)`](#linkwithgamecenter) - [`linkWithGithub(...)`](#linkwithgithub) - [`linkWithGoogle(...)`](#linkwithgoogle) - [`linkWithMicrosoft(...)`](#linkwithmicrosoft) - [`linkWithOpenIdConnect(...)`](#linkwithopenidconnect) - [`linkWithPhoneNumber(...)`](#linkwithphonenumber) - [`linkWithPlayGames(...)`](#linkwithplaygames) - [`linkWithTwitter(...)`](#linkwithtwitter) - [`linkWithYahoo(...)`](#linkwithyahoo) - [`reload()`](#reload) - [`revokeAccessToken(...)`](#revokeaccesstoken) - [`sendEmailVerification(...)`](#sendemailverification) - [`sendPasswordResetEmail(...)`](#sendpasswordresetemail) - [`sendSignInLinkToEmail(...)`](#sendsigninlinktoemail) - [`setLanguageCode(...)`](#setlanguagecode) - [`setPersistence(...)`](#setpersistence) - [`setTenantId(...)`](#settenantid) - [`signInAnonymously()`](#signinanonymously) - [`signInWithApple(...)`](#signinwithapple) - [`signInWithCustomToken(...)`](#signinwithcustomtoken) - [`signInWithEmailAndPassword(...)`](#signinwithemailandpassword) - [`signInWithEmailLink(...)`](#signinwithemaillink) - [`signInWithFacebook(...)`](#signinwithfacebook) - [`signInWithGameCenter(...)`](#signinwithgamecenter) - [`signInWithGithub(...)`](#signinwithgithub) - [`signInWithGoogle(...)`](#signinwithgoogle) - [`signInWithMicrosoft(...)`](#signinwithmicrosoft) - [`signInWithOpenIdConnect(...)`](#signinwithopenidconnect) - [`signInWithPhoneNumber(...)`](#signinwithphonenumber) - [`signInWithPlayGames(...)`](#signinwithplaygames) - [`signInWithTwitter(...)`](#signinwithtwitter) - [`signInWithYahoo(...)`](#signinwithyahoo) - [`signOut()`](#signout) - [`unlink(...)`](#unlink) - [`updateEmail(...)`](#updateemail) - [`updatePassword(...)`](#updatepassword) - [`updateProfile(...)`](#updateprofile) - [`useAppLanguage()`](#useapplanguage) - [`useEmulator(...)`](#useemulator) - [`verifyBeforeUpdateEmail(...)`](#verifybeforeupdateemail) - [`checkAppTrackingTransparencyPermission()`](#checkapptrackingtransparencypermission) - [`requestAppTrackingTransparencyPermission()`](#requestapptrackingtransparencypermission) - [`addListener('authStateChange', ...)`](#addlistenerauthstatechange-) - [`addListener('idTokenChange', ...)`](#addlisteneridtokenchange-) - [`addListener('phoneVerificationCompleted', ...)`](#addlistenerphoneverificationcompleted-) - [`addListener('phoneVerificationFailed', ...)`](#addlistenerphoneverificationfailed-) - [`addListener('phoneCodeSent', ...)`](#addlistenerphonecodesent-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### applyActionCode(...) ``` applyActionCode(options: ApplyActionCodeOptions) => Promise ``` Applies a verification code sent to the user by email. | Param | Type | | ------------- | ------------------------ | | **`options`** | `ApplyActionCodeOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### confirmPasswordReset(...) ``` confirmPasswordReset(options: ConfirmPasswordResetOptions) => Promise ``` Completes the password reset process. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `ConfirmPasswordResetOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### confirmVerificationCode(...) ``` confirmVerificationCode(options: ConfirmVerificationCodeOptions) => Promise ``` Finishes the phone number verification process. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `ConfirmVerificationCodeOptions` | **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### createUserWithEmailAndPassword(...) ``` createUserWithEmailAndPassword(options: CreateUserWithEmailAndPasswordOptions) => Promise ``` Creates a new user account with email and password. If the new account was created, the user is signed in automatically. | Param | Type | | ------------- | --------------------------------------- | | **`options`** | `CreateUserWithEmailAndPasswordOptions` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### deleteUser() ``` deleteUser() => Promise ``` Deletes and signs out the user. **Since:** 1.3.0 ______________________________________________________________________ ### fetchSignInMethodsForEmail(...) ``` fetchSignInMethodsForEmail(options: FetchSignInMethodsForEmailOptions) => Promise ``` Fetches the sign-in methods for an email address. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `FetchSignInMethodsForEmailOptions` | **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getCurrentUser() ``` getCurrentUser() => Promise ``` Fetches the currently signed-in user. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getPendingAuthResult() ``` getPendingAuthResult() => Promise ``` Returns the [`SignInResult`](#signinresult) if your app launched a web sign-in flow and the OS cleans up the app while in the background. Only available for Android. **Returns:** `Promise` **Since:** 6.0.0 ______________________________________________________________________ ### getIdToken(...) ``` getIdToken(options?: GetIdTokenOptions | undefined) => Promise ``` Fetches the Firebase Auth ID Token for the currently signed-in user. | Param | Type | | ------------- | ------------------- | | **`options`** | `GetIdTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getIdTokenResult(...) ``` getIdTokenResult(options?: GetIdTokenResultOptions | undefined) => Promise ``` Returns a deserialized JSON Web Token (JWT) used to identify the user to a Firebase service. | Param | Type | | ------------- | ------------------------- | | **`options`** | `GetIdTokenResultOptions` | **Returns:** `Promise` **Since:** 7.4.0 ______________________________________________________________________ ### getRedirectResult() ``` getRedirectResult() => Promise ``` Returns the [`SignInResult`](#signinresult) from the redirect-based sign-in flow. If sign-in was unsuccessful, fails with an error. If no redirect operation was called, returns a [`SignInResult`](#signinresult) with a null user. Only available for Web. **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### getTenantId() ``` getTenantId() => Promise ``` Get the tenant id. **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### isSignInWithEmailLink(...) ``` isSignInWithEmailLink(options: IsSignInWithEmailLinkOptions) => Promise ``` Checks if an incoming link is a sign-in with email link suitable for `signInWithEmailLink`. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `IsSignInWithEmailLinkOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithApple(...) ``` linkWithApple(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Apple authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithEmailAndPassword(...) ``` linkWithEmailAndPassword(options: LinkWithEmailAndPasswordOptions) => Promise ``` Links the user account with Email authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | --------------------------------- | | **`options`** | `LinkWithEmailAndPasswordOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithEmailLink(...) ``` linkWithEmailLink(options: LinkWithEmailLinkOptions) => Promise ``` Links the user account with Email authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | -------------------------- | | **`options`** | `LinkWithEmailLinkOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithFacebook(...) ``` linkWithFacebook(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Facebook authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithGameCenter(...) ``` linkWithGameCenter(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Game Center authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. Only available for iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### linkWithGithub(...) ``` linkWithGithub(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with GitHub authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithGoogle(...) ``` linkWithGoogle(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Google authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithMicrosoft(...) ``` linkWithMicrosoft(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Microsoft authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithOpenIdConnect(...) ``` linkWithOpenIdConnect(options: LinkWithOpenIdConnectOptions) => Promise ``` Links the user account with an OpenID Connect provider. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SignInWithOpenIdConnectOptions` | **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### linkWithPhoneNumber(...) ``` linkWithPhoneNumber(options: LinkWithPhoneNumberOptions) => Promise ``` Links the user account with Phone Number authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. Use the `phoneVerificationCompleted` listener to be notified when the verification is completed. Use the `phoneVerificationFailed` listener to be notified when the verification is failed. Use the `phoneCodeSent` listener to get the verification id. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SignInWithPhoneNumberOptions` | **Since:** 1.1.0 ______________________________________________________________________ ### linkWithPlayGames(...) ``` linkWithPlayGames(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Play Games authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. Only available for Android. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithTwitter(...) ``` linkWithTwitter(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Twitter authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### linkWithYahoo(...) ``` linkWithYahoo(options?: SignInWithOAuthOptions | undefined) => Promise ``` Links the user account with Yahoo authentication provider. The user must be logged in on the native layer. The `skipNativeAuth` configuration option has no effect here. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### reload() ``` reload() => Promise ``` Reloads user account data, if signed in. **Since:** 1.3.0 ______________________________________________________________________ ### revokeAccessToken(...) ``` revokeAccessToken(options: RevokeAccessTokenOptions) => Promise ``` Revokes the given access token. Currently only supports Apple OAuth access tokens. | Param | Type | | ------------- | -------------------------- | | **`options`** | `RevokeAccessTokenOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### sendEmailVerification(...) ``` sendEmailVerification(options?: SendEmailVerificationOptions | undefined) => Promise ``` Sends a verification email to the currently signed in user. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SendEmailVerificationOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### sendPasswordResetEmail(...) ``` sendPasswordResetEmail(options: SendPasswordResetEmailOptions) => Promise ``` Sends a password reset email. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `SendPasswordResetEmailOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### sendSignInLinkToEmail(...) ``` sendSignInLinkToEmail(options: SendSignInLinkToEmailOptions) => Promise ``` Sends a sign-in email link to the user with the specified email. To complete sign in with the email link, call `signInWithEmailLink` with the email address and the email link supplied in the email sent to the user. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SendSignInLinkToEmailOptions` | **Since:** 1.1.0 ______________________________________________________________________ ### setLanguageCode(...) ``` setLanguageCode(options: SetLanguageCodeOptions) => Promise ``` Sets the user-facing language code for auth operations. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SetLanguageCodeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setPersistence(...) ``` setPersistence(options: SetPersistenceOptions) => Promise ``` Sets the type of persistence for the currently saved auth session. Only available for Web. | Param | Type | | ------------- | ----------------------- | | **`options`** | `SetPersistenceOptions` | **Since:** 5.2.0 ______________________________________________________________________ ### setTenantId(...) ``` setTenantId(options: SetTenantIdOptions) => Promise ``` Sets the tenant id. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetTenantIdOptions` | **Since:** 1.1.0 ______________________________________________________________________ ### signInAnonymously() ``` signInAnonymously() => Promise ``` Signs in as an anonymous user. **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### signInWithApple(...) ``` signInWithApple(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the Apple sign-in flow. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithCustomToken(...) ``` signInWithCustomToken(options: SignInWithCustomTokenOptions) => Promise ``` Starts the Custom Token sign-in flow. This method cannot be used in combination with `skipNativeAuth` on Android and iOS. In this case you have to use the `signInWithCustomToken` interface of the Firebase JS SDK directly. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SignInWithCustomTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithEmailAndPassword(...) ``` signInWithEmailAndPassword(options: SignInWithEmailAndPasswordOptions) => Promise ``` Starts the sign-in flow using an email and password. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `SignInWithEmailAndPasswordOptions` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### signInWithEmailLink(...) ``` signInWithEmailLink(options: SignInWithEmailLinkOptions) => Promise ``` Signs in using an email and sign-in email link. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `SignInWithEmailLinkOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### signInWithFacebook(...) ``` signInWithFacebook(options?: SignInWithFacebookOptions | undefined) => Promise ``` Starts the Facebook sign-in flow. | Param | Type | | ------------- | --------------------------- | | **`options`** | `SignInWithFacebookOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithGameCenter(...) ``` signInWithGameCenter(options?: SignInWithOAuthOptions | SignInOptions | undefined) => Promise ``` Starts the Game Center sign-in flow. Only available for iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | \`SignInWithOAuthOptions | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### signInWithGithub(...) ``` signInWithGithub(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the GitHub sign-in flow. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithGoogle(...) ``` signInWithGoogle(options?: SignInWithGoogleOptions | undefined) => Promise ``` Starts the Google sign-in flow. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SignInWithGoogleOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithMicrosoft(...) ``` signInWithMicrosoft(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the Microsoft sign-in flow. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithOpenIdConnect(...) ``` signInWithOpenIdConnect(options: SignInWithOpenIdConnectOptions) => Promise ``` Starts the OpenID Connect sign-in flow. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SignInWithOpenIdConnectOptions` | **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### signInWithPhoneNumber(...) ``` signInWithPhoneNumber(options: SignInWithPhoneNumberOptions) => Promise ``` Starts the sign-in flow using a phone number. Use the `phoneVerificationCompleted` listener to be notified when the verification is completed. Use the `phoneVerificationFailed` listener to be notified when the verification is failed. Use the `phoneCodeSent` listener to get the verification id. Only available for Android and iOS. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `SignInWithPhoneNumberOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### signInWithPlayGames(...) ``` signInWithPlayGames(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the Play Games sign-in flow. Only available for Android. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithTwitter(...) ``` signInWithTwitter(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the Twitter sign-in flow. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signInWithYahoo(...) ``` signInWithYahoo(options?: SignInWithOAuthOptions | undefined) => Promise ``` Starts the Yahoo sign-in flow. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SignInWithOAuthOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signOut() ``` signOut() => Promise ``` Starts the sign-out flow. **Since:** 0.1.0 ______________________________________________________________________ ### unlink(...) ``` unlink(options: UnlinkOptions) => Promise ``` Unlinks a provider from a user account. | Param | Type | | ------------- | --------------- | | **`options`** | `UnlinkOptions` | **Returns:** `Promise` **Since:** 1.1.0 ______________________________________________________________________ ### updateEmail(...) ``` updateEmail(options: UpdateEmailOptions) => Promise ``` Updates the email address of the currently signed in user. | Param | Type | | ------------- | -------------------- | | **`options`** | `UpdateEmailOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updatePassword(...) ``` updatePassword(options: UpdatePasswordOptions) => Promise ``` Updates the password of the currently signed in user. | Param | Type | | ------------- | ----------------------- | | **`options`** | `UpdatePasswordOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updateProfile(...) ``` updateProfile(options: UpdateProfileOptions) => Promise ``` Updates a user's profile data. | Param | Type | | ------------- | ---------------------- | | **`options`** | `UpdateProfileOptions` | **Since:** 1.3.0 ______________________________________________________________________ ### useAppLanguage() ``` useAppLanguage() => Promise ``` Sets the user-facing language code to be the default app language. **Since:** 0.1.0 ______________________________________________________________________ ### useEmulator(...) ``` useEmulator(options: UseEmulatorOptions) => Promise ``` Instrument your app to talk to the Authentication emulator. | Param | Type | | ------------- | -------------------- | | **`options`** | `UseEmulatorOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### verifyBeforeUpdateEmail(...) ``` verifyBeforeUpdateEmail(options: VerifyBeforeUpdateEmailOptions) => Promise ``` Verifies the new email address before updating the email address of the currently signed in user. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `VerifyBeforeUpdateEmailOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### checkAppTrackingTransparencyPermission() ``` checkAppTrackingTransparencyPermission() => Promise ``` Checks the current status of app tracking transparency. Only available on iOS. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### requestAppTrackingTransparencyPermission() ``` requestAppTrackingTransparencyPermission() => Promise ``` Opens the system dialog to authorize app tracking transparency. **Attention:** The user may have disabled the tracking request in the device settings, see [Apple's documentation](https://support.apple.com/guide/iphone/iph4f4cbd242/ios). Only available on iOS. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### addListener('authStateChange', ...) ``` addListener(eventName: 'authStateChange', listenerFunc: AuthStateChangeListener) => Promise ``` Listen for the user's sign-in state changes. **Attention:** This listener is not triggered when the `skipNativeAuth` is used. Use the Firebase JavaScript SDK instead. | Param | Type | | ------------------ | ------------------------- | | **`eventName`** | `'authStateChange'` | | **`listenerFunc`** | `AuthStateChangeListener` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('idTokenChange', ...) ``` addListener(eventName: 'idTokenChange', listenerFunc: IdTokenChangeListener) => Promise ``` Listen to ID token changes for the currently signed-in user. **Attention:** This listener is not triggered when the `skipNativeAuth` is used. Use the Firebase JavaScript SDK instead. | Param | Type | | ------------------ | ----------------------- | | **`eventName`** | `'idTokenChange'` | | **`listenerFunc`** | `IdTokenChangeListener` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### addListener('phoneVerificationCompleted', ...) ``` addListener(eventName: 'phoneVerificationCompleted', listenerFunc: PhoneVerificationCompletedListener) => Promise ``` Listen for a completed phone verification. This listener only fires in two situations: 1. **Instant verification**: In some cases the phone number can be instantly verified without needing to send or enter a verification code. 1. **Auto-retrieval**: On some devices Google Play services can automatically detect the incoming verification SMS and perform verification without user action. Only available for Android. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'phoneVerificationCompleted'` | | **`listenerFunc`** | `PhoneVerificationCompletedListener` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### addListener('phoneVerificationFailed', ...) ``` addListener(eventName: 'phoneVerificationFailed', listenerFunc: PhoneVerificationFailedListener) => Promise ``` Listen for a failed phone verification. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'phoneVerificationFailed'` | | **`listenerFunc`** | `PhoneVerificationFailedListener` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### addListener('phoneCodeSent', ...) ``` addListener(eventName: 'phoneCodeSent', listenerFunc: PhoneCodeSentListener) => Promise ``` Listen for a phone verification code. | Param | Type | | ------------------ | ----------------------- | | **`eventName`** | `'phoneCodeSent'` | | **`listenerFunc`** | `PhoneCodeSentListener` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ApplyActionCodeOptions | Prop | Type | Description | Since | | ------------- | -------- | ------------------------------------- | ----- | | **`oobCode`** | `string` | A verification code sent to the user. | 0.2.2 | #### ConfirmPasswordResetOptions | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------- | ----- | | **`oobCode`** | `string` | A verification code sent to the user. | 0.2.2 | | **`newPassword`** | `string` | The new password. | 0.2.2 | #### SignInResult | Prop | Type | Description | Since | | ------------------------ | -------------------- | ----------- | --------------------------------------------------------------- | | **`user`** | \`User | null\` | The currently signed-in user, or null if there isn't any. | | **`credential`** | \`AuthCredential | null\` | Credentials returned by an auth provider. | | **`additionalUserInfo`** | \`AdditionalUserInfo | null\` | Additional user information from a federated identity provider. | #### User | Prop | Type | Description | Since | | ------------------- | -------------- | -------------------------------------------------------------------- | ----- | | **`displayName`** | \`string | null\` | | | **`email`** | \`string | null\` | | | **`emailVerified`** | `boolean` | | 0.1.0 | | **`isAnonymous`** | `boolean` | | 0.1.0 | | **`metadata`** | `UserMetadata` | The user's metadata. | 5.2.0 | | **`phoneNumber`** | \`string | null\` | | | **`photoUrl`** | \`string | null\` | | | **`providerData`** | `UserInfo[]` | Additional per provider such as displayName and profile information. | 5.2.0 | | **`providerId`** | `string` | | 0.1.0 | | **`tenantId`** | \`string | null\` | | | **`uid`** | `string` | | 0.1.0 | #### UserMetadata | Prop | Type | Description | Since | | -------------------- | -------- | ------------------------------------------------------------- | ----- | | **`creationTime`** | `number` | Time the user was created in milliseconds since the epoch. | 5.2.0 | | **`lastSignInTime`** | `number` | Time the user last signed in in milliseconds since the epoch. | 5.2.0 | #### UserInfo | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------- | ----------------------------------------------------------------------------------------- | | **`displayName`** | \`string | null\` | The display name of the user. | | **`email`** | \`string | null\` | The email of the user. | | **`phoneNumber`** | \`string | null\` | The phone number normalized based on the E.164 standard (e.g. +16505550101) for the user. | | **`photoUrl`** | \`string | null\` | The profile photo URL of the user. | | **`providerId`** | `string` | The provider used to authenticate the user. | 5.2.0 | | **`uid`** | `string` | The user's unique ID. | 5.2.0 | #### AuthCredential | Prop | Type | Description | Since | | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`accessToken`** | `string` | The OAuth access token associated with the credential if it belongs to an OAuth provider. | 0.1.0 | | **`authorizationCode`** | `string` | A token that the app uses to interact with the server. Only available for Apple Sign-in on iOS. | 1.2.0 | | **`idToken`** | `string` | The OAuth ID token associated with the credential if it belongs to an OIDC provider. | 0.1.0 | | **`nonce`** | `string` | The random string used to make sure that the ID token you get was granted specifically in response to your app's authentication request. | 0.1.0 | | **`providerId`** | `string` | The authentication provider ID for the credential. | 0.1.0 | | **`secret`** | `string` | The OAuth access token secret associated with the credential if it belongs to an OAuth 1.0 provider. | 0.1.0 | | **`serverAuthCode`** | `string` | The server auth code. Only available for Google Sign-in and Play Games Sign-In on Android and iOS. | 5.2.0 | #### AdditionalUserInfo | Prop | Type | Description | Since | | ---------------- | ----------------------------- | ----------------------------------------------------------- | ----- | | **`isNewUser`** | `boolean` | Whether the user is new (sign-up) or existing (sign-in). | 0.5.1 | | **`profile`** | `{ [key: string]: unknown; }` | Map containing IDP-specific user data. | 0.5.1 | | **`providerId`** | `string` | Identifier for the provider used to authenticate this user. | 0.5.1 | | **`username`** | `string` | The username if the provider is GitHub or Twitter. | 0.5.1 | #### ConfirmVerificationCodeOptions | Prop | Type | Description | Since | | ---------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`verificationId`** | `string` | The verification ID received from the `phoneCodeSent` listener. The `verificationCode` option must also be provided. | 5.0.0 | | **`verificationCode`** | `string` | The verification code either received from the `phoneCodeSent` listener or entered by the user. The `verificationId` option must also be provided. | 5.0.0 | #### CreateUserWithEmailAndPasswordOptions | Prop | Type | Since | | -------------- | -------- | ----- | | **`email`** | `string` | 0.2.2 | | **`password`** | `string` | 0.2.2 | #### FetchSignInMethodsForEmailResult | Prop | Type | Description | Since | | ------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`signInMethods`** | `string[]` | The sign-in methods for the specified email address. This list is empty when [Email Enumeration Protection](https://cloud.google.com/identity-platform/docs/admin/email-enumeration-protection) is enabled, irrespective of the number of authentication methods available for the given email. | 6.0.0 | #### FetchSignInMethodsForEmailOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------- | ----- | | **`email`** | `string` | The user's email address. | 6.0.0 | #### GetCurrentUserResult | Prop | Type | Description | Since | | ---------- | ------ | ----------- | --------------------------------------------------------- | | **`user`** | \`User | null\` | The currently signed-in user, or null if there isn't any. | #### GetIdTokenResult | Prop | Type | Description | Since | | ----------- | -------- | -------------------------------------- | ----- | | **`token`** | `string` | The Firebase Auth ID token JWT string. | 0.1.0 | #### GetIdTokenOptions | Prop | Type | Description | Since | | ------------------ | --------- | --------------------------------------------- | ----- | | **`forceRefresh`** | `boolean` | Force refresh regardless of token expiration. | 0.1.0 | #### GetIdTokenResultResult | Prop | Type | Description | Since | | ------------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | | **`authTime`** | `number` | The authentication time in milliseconds since the epoch. This is the time the user authenticated (signed in) and not the time the token was refreshed. | 7.4.0 | | **`expirationTime`** | `number` | The ID token expiration time in milliseconds since the epoch. | 7.4.0 | | **`issuedAtTime`** | `number` | The ID token issuance time in milliseconds since the epoch. | 7.4.0 | | **`signInProvider`** | \`string | null\` | The sign-in provider through which the ID token was obtained. | | **`signInSecondFactor`** | \`string | null\` | The type of second factor associated with this session, provided the user was multi-factor authenticated (eg. phone, etc). | | **`claims`** | `Record` | The entire payload claims of the ID token including the standard reserved claims as well as the custom claims. | 7.4.0 | #### GetIdTokenResultOptions | Prop | Type | Description | Since | | ------------------ | --------- | --------------------------------------------- | ----- | | **`forceRefresh`** | `boolean` | Force refresh regardless of token expiration. | 7.4.0 | #### GetTenantIdResult | Prop | Type | Description | Since | | -------------- | -------- | ----------- | ----------------------------------------------- | | **`tenantId`** | \`string | null\` | The tenant id. `null` if it has never been set. | #### IsSignInWithEmailLinkResult | Prop | Type | Description | | --------------------------- | --------- | --------------------------------------------------------------------------------------------- | | **`isSignInWithEmailLink`** | `boolean` | Whether an incoming link is a signup with email link suitable for `signInWithEmailLink(...)`. | #### IsSignInWithEmailLinkOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------ | ----- | | **`emailLink`** | `string` | The link sent to the user's email address. | 1.1.0 | #### SignInWithOAuthOptions | Prop | Type | Description | Default | Since | | ---------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------- | | **`customParameters`** | `SignInCustomParameter[]` | Configures custom parameters to be passed to the identity provider during the OAuth sign-in flow. Supports Apple, Facebook, GitHub, Google, Microsoft, Twitter and Yahoo on Web. Supports Apple, GitHub, Microsoft, Twitter and Yahoo on Android. Supports GitHub, Microsoft, Twitter and Yahoo on iOS. | | 1.1.0 | | **`mode`** | \`'popup' | 'redirect'\` | Whether to use the popup-based OAuth authentication flow or the full-page redirect flow. If you choose `redirect`, you will get the result of the call via the `authStateChange` listener after the redirect. Only available for Web. | `'popup'` | | **`scopes`** | `string[]` | Scopes to request from provider. Supports Apple, Facebook, GitHub, Google, Microsoft, Twitter and Yahoo on Web. Supports Apple, GitHub, Google, Microsoft, Twitter, Yahoo and Play Games on Android. Supports Facebook, GitHub, Google, Microsoft, Twitter and Yahoo on iOS. | | 1.1.0 | #### SignInCustomParameter | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------ | ----- | | **`key`** | `string` | The custom parameter key (e.g. `login_hint`). | 0.1.0 | | **`value`** | `string` | The custom parameter value (e.g. `user@firstadd.onmicrosoft.com`). | 0.1.0 | #### LinkWithEmailAndPasswordOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------- | ----- | | **`email`** | `string` | The user's email address. | 1.1.0 | | **`password`** | `string` | The user's password. | 1.1.0 | #### LinkWithEmailLinkOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------ | ----- | | **`email`** | `string` | The user's email address. | 1.1.0 | | **`emailLink`** | `string` | The link sent to the user's email address. | 1.1.0 | #### SignInWithOpenIdConnectOptions | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------- | ----- | | **`providerId`** | `string` | The OpenID Connect provider ID. | 6.1.0 | #### SignInWithPhoneNumberOptions | Prop | Type | Description | Default | Since | | ----------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`phoneNumber`** | `string` | The phone number to be verified in E.164 format. | | 0.1.0 | | **`recaptchaVerifier`** | `unknown` | The reCAPTCHA verifier. Must be an instance of `firebase.auth.RecaptchaVerifier`. Only available for Web. | | 5.2.0 | | **`resendCode`** | `boolean` | Resend the verification code to the specified phone number. `signInWithPhoneNumber` must be called once before using this option. Only available for Android. | `false` | 1.3.0 | | **`timeout`** | `number` | The maximum amount of time in seconds to wait for the SMS auto-retrieval. Use 0 to disable SMS-auto-retrieval. Only available for Android. | `60` | 5.4.0 | #### RevokeAccessTokenOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------- | ----- | | **`token`** | `string` | The access token to revoke. | 6.1.0 | #### SendEmailVerificationOptions | Prop | Type | Description | Since | | ------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------- | ----- | | **`actionCodeSettings`** | `ActionCodeSettings` | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 6.1.0 | #### ActionCodeSettings An interface that defines the required continue/state URL with optional Android and iOS bundle identifiers. | Prop | Type | Description | Since | | --------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`android`** | `{ installApp?: boolean; minimumVersion?: string; packageName: string; }` | Sets the Android package name. | | | **`handleCodeInApp`** | `boolean` | When set to true, the action code link will be be sent as a Universal Link or Android App Link and will be opened by the app if installed. | | | **`iOS`** | `{ bundleId: string; }` | Sets the iOS bundle ID. | | | **`url`** | `string` | Sets the link continue/state URL. | | | **`linkDomain`** | `string` | When multiple custom Firebase Hosting link domains are defined for a project, specify which one to use when the link is to be opened via a specified mobile app (for example, `example.page.link`). The domain must be configured in Firebase Hosting and owned by the project. | 7.1.0 | #### SendPasswordResetEmailOptions | Prop | Type | Description | Since | | ------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------- | ----- | | **`email`** | `string` | | 0.2.2 | | **`actionCodeSettings`** | `ActionCodeSettings` | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 6.1.0 | #### SendSignInLinkToEmailOptions | Prop | Type | Description | Since | | ------------------------ | -------------------- | --------------------------------------------------------------------------------------------------------- | ----- | | **`email`** | `string` | The user's email address. | 1.1.0 | | **`actionCodeSettings`** | `ActionCodeSettings` | Structure that contains the required continue/state URL with optional Android and iOS bundle identifiers. | 1.1.0 | #### SetLanguageCodeOptions | Prop | Type | Description | Since | | ------------------ | -------- | --------------------- | ----- | | **`languageCode`** | `string` | BCP 47 language code. | 0.1.0 | #### SetPersistenceOptions | Prop | Type | Description | Since | | ----------------- | ------------- | ---------------------- | ----- | | **`persistence`** | `Persistence` | The persistence types. | 5.2.0 | #### SetTenantIdOptions | Prop | Type | Description | Since | | -------------- | -------- | -------------- | ----- | | **`tenantId`** | `string` | The tenant id. | 1.1.0 | #### SignInWithCustomTokenOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`token`** | `string` | The custom token to sign in with. | 0.1.0 | #### SignInWithEmailAndPasswordOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------- | ----- | | **`email`** | `string` | The user's email address. | 0.2.2 | | **`password`** | `string` | The user's password. | 0.2.2 | #### SignInWithEmailLinkOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------ | ----- | | **`email`** | `string` | The user's email address. | 1.1.0 | | **`emailLink`** | `string` | The link sent to the user's email address. | 1.1.0 | #### SignInWithFacebookOptions | Prop | Type | Description | Default | Since | | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`useLimitedLogin`** | `boolean` | Whether to use the Facebook Limited Login mode. If set to `true`, no access token will be returned but the user does not have to grant App Tracking Transparency permission. If set to `false`, the user has to grant App Tracking Transparency permission. You can request the permission with `requestAppTrackingTransparencyPermission()`. Only available for iOS. | `false` | 7.2.0 | #### SignInOptions | Prop | Type | Description | Since | | -------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`skipNativeAuth`** | `boolean` | Whether the plugin should skip the native authentication or not. Only needed if you want to use the Firebase JavaScript SDK. This value overwrites the configrations value of the `skipNativeAuth` option. If no value is set, the configuration value is used. **Note that the plugin may behave differently across the platforms.** `skipNativeAuth` cannot be used in combination with `signInWithCustomToken`, `createUserWithEmailAndPassword` or `signInWithEmailAndPassword`. Only available for Android and iOS. | 1.1.0 | #### SignInWithGoogleOptions | Prop | Type | Description | Default | Since | | -------------------------- | --------- | --------------------------------------------------------------------------------- | ------- | ----- | | **`useCredentialManager`** | `boolean` | Whether to use the Credential Manager API to sign in. Only available for Android. | `true` | 7.2.0 | #### UnlinkResult | Prop | Type | Description | Since | | ---------- | ------ | ----------- | --------------------------------------------------------- | | **`user`** | \`User | null\` | The currently signed-in user, or null if there isn't any. | #### UnlinkOptions | Prop | Type | Description | Since | | ---------------- | ------------ | ----------------------- | ----- | | **`providerId`** | `ProviderId` | The provider to unlink. | 1.1.0 | #### UpdateEmailOptions | Prop | Type | Description | Since | | -------------- | -------- | ---------------------- | ----- | | **`newEmail`** | `string` | The new email address. | 0.2.2 | #### UpdatePasswordOptions | Prop | Type | Description | Since | | ----------------- | -------- | ----------------- | ----- | | **`newPassword`** | `string` | The new password. | 0.2.2 | #### UpdateProfileOptions | Prop | Type | Description | Since | | ----------------- | -------- | ----------- | ------------------------ | | **`displayName`** | \`string | null\` | The user's display name. | | **`photoUrl`** | \`string | null\` | The user's photo URL. | #### UseEmulatorOptions | Prop | Type | Description | Default | Since | | ------------ | -------- | --------------------------------------------- | -------- | ----- | | **`host`** | `string` | The emulator host without any port or scheme. | | 0.2.0 | | **`port`** | `number` | The emulator port. | `9099` | 0.2.0 | | **`scheme`** | `string` | The emulator scheme. Only available for Web. | `"http"` | 5.2.0 | #### VerifyBeforeUpdateEmailOptions | Prop | Type | Description | Since | | ------------------------ | -------------------- | --------------------------------------------------- | ----- | | **`newEmail`** | `string` | The new email address to be verified before update. | 6.3.0 | | **`actionCodeSettings`** | `ActionCodeSettings` | The action code settings | 6.3.0 | #### CheckAppTrackingTransparencyPermissionResult | Prop | Type | Description | Since | | ------------ | ---------------------------------------- | --------------------------------------------------- | ----- | | **`status`** | `AppTrackingTransparencyPermissionState` | The permission status of App Tracking Transparency. | 7.2.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### AuthStateChange | Prop | Type | Description | Since | | ---------- | ------ | ----------- | --------------------------------------------------------- | | **`user`** | \`User | null\` | The currently signed-in user, or null if there isn't any. | #### PhoneVerificationCompletedEvent | Prop | Type | Description | Since | | ---------------------- | -------- | ----------------------------------------------------------------------------------------------------------------- | ----- | | **`verificationCode`** | `string` | The verification code sent to the user's phone number. If instant verification is used, this property is not set. | 5.0.0 | #### PhoneVerificationFailedEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 1.3.0 | #### PhoneCodeSentEvent | Prop | Type | Description | Since | | -------------------- | -------- | ----------------------------------------------------------------------- | ----- | | **`verificationId`** | `string` | The verification ID, which is needed to identify the verification code. | 1.3.0 | ### Type Aliases #### LinkWithOAuthOptions `SignInWithOAuthOptions` #### LinkResult `SignInResult` #### LinkWithOpenIdConnectOptions `SignInWithOpenIdConnectOptions` #### LinkWithPhoneNumberOptions `SignInWithPhoneNumberOptions` #### AppTrackingTransparencyPermissionState `PermissionState | 'restricted'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### RequestAppTrackingTransparencyPermissionResult `CheckAppTrackingTransparencyPermissionResult` #### AuthStateChangeListener Callback to receive the user's sign-in state change notifications. `(change: AuthStateChange): void` #### IdTokenChangeListener Callback to receive the ID token change notifications. `(change: GetIdTokenResult): void` #### PhoneVerificationCompletedListener Callback to receive the verification code sent to the user's phone number. `(event: PhoneVerificationCompletedEvent): void` #### PhoneVerificationFailedListener Callback to receive notifications of failed phone verification. `(event: PhoneVerificationFailedEvent): void` #### PhoneCodeSentListener Callback to receive the verification ID. `(event: PhoneCodeSentEvent): void` ### Enums #### Persistence | Members | Value | Description | Since | | -------------------- | -------------------- | -------------------------------------------- | ----- | | **`IndexedDbLocal`** | `'INDEXED_DB_LOCAL'` | Long term persistence using IndexedDB. | 5.2.0 | | **`InMemory`** | `'IN_MEMORY'` | No persistence. | 5.2.0 | | **`BrowserLocal`** | `'BROWSER_LOCAL'` | Long term persistence using local storage. | 5.2.0 | | **`BrowserSession`** | `'BROWSER_SESSION'` | Temporary persistence using session storage. | 5.2.0 | #### ProviderId | Members | Value | | ----------------- | ------------------------ | | **`APPLE`** | `'apple.com'` | | **`FACEBOOK`** | `'facebook.com'` | | **`GAME_CENTER`** | `'gc.apple.com'` | | **`GITHUB`** | `'github.com'` | | **`GOOGLE`** | `'google.com'` | | **`MICROSOFT`** | `'microsoft.com'` | | **`PLAY_GAMES`** | `'playgames.google.com'` | | **`TWITTER`** | `'twitter.com'` | | **`YAHOO`** | `'yahoo.com'` | | **`PASSWORD`** | `'password'` | | **`PHONE`** | `'phone'` | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/LICENSE). ## Credits This plugin is based on the [Capacitor Firebase Authentication](https://github.com/robingenz/capacitor-firebase-authentication) plugin. Thanks to everyone who contributed to the project! ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/firestore Unofficial Capacitor plugin for [Firebase Cloud Firestore](https://firebase.google.com/docs/firestore/).[1](#fn:1) ## Guides - [Announcing the Capacitor Firebase Cloud Firestore Plugin](https://capawesome.io/blog/announcing-the-capacitor-firebase-cloud-firestore-plugin/) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/firestore` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/firestore npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseFirestoreVersion` version of `com.google.firebase:firebase-firestore` (default: `26.0.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration These configuration values are available: | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------------------------------------------------------- | ----- | | **`databaseId`** | `string` | The database ID of the Firestore database to use. Only available for Android and iOS. | 8.2.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "FirebaseFirestore": { "databaseId": undefined } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { FirebaseFirestore: { databaseId: undefined, }, }, }; export default config; ``` ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const addDocument = async () => { await FirebaseFirestore.addDocument({ reference: 'users', data: { first: 'Alan', last: 'Turing', born: 1912 }, }); }; const setDocument = async () => { await FirebaseFirestore.setDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, merge: true, }); }; const getDocument = async () => { const { snapshot } = await FirebaseFirestore.getDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', }); return snapshot; }; const updateDocument = async () => { await FirebaseFirestore.updateDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, }); }; const deleteDocument = async () => { await FirebaseFirestore.deleteDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', }); }; const writeBatch = async () => { await FirebaseFirestore.writeBatch({ operations: [ { type: 'set', reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, options: { merge: true }, }, { type: 'update', reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, }, { type: 'delete', reference: 'users/Aorq09lkt1ynbR7xhTUx', }, ], }); }; const getCollection = async () => { const { snapshots } = await FirebaseFirestore.getCollection({ reference: 'users', compositeFilter: { type: 'and', queryConstraints: [ { type: 'where', fieldPath: 'born', opStr: '==', value: 1912, }, ], }, queryConstraints: [ { type: 'orderBy', fieldPath: 'born', directionStr: 'desc', }, { type: 'limit', limit: 10, }, ], }); return snapshots; }; const getCollectionGroup = async () => { const { snapshots } = await FirebaseFirestore.getCollectionGroup({ reference: 'users', compositeFilter: { type: 'and', queryConstraints: [ { type: 'where', fieldPath: 'born', opStr: '==', value: 1912, }, ], }, queryConstraints: [ { type: 'orderBy', fieldPath: 'born', directionStr: 'desc', }, { type: 'limit', limit: 10, }, ], }); return snapshots; }; const enableNetwork = async () => { await FirebaseFirestore.enableNetwork(); }; const disableNetwork = async () => { await FirebaseFirestore.disableNetwork(); }; const useEmulator = async () => { await FirebaseFirestore.useEmulator({ host: '10.0.2.2', port: 9001, }); }; const addDocumentSnapshotListener = async () => { const callbackId = await FirebaseFirestore.addDocumentSnapshotListener( { reference: 'users/Aorq09lkt1ynbR7xhTUx', }, (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; const addCollectionSnapshotListener = async () => { const callbackId = await FirebaseFirestore.addCollectionSnapshotListener( { reference: 'users', compositeFilter: { type: 'and', queryConstraints: [ { type: 'where', fieldPath: 'born', opStr: '==', value: 1912, }, ], }, queryConstraints: [ { type: 'orderBy', fieldPath: 'born', directionStr: 'desc', }, { type: 'limit', limit: 10, }, ], }, (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; const addCollectionGroupSnapshotListener = async () => { const callbackId = await FirebaseFirestore.addCollectionGroupSnapshotListener( { reference: 'users', compositeFilter: { type: 'and', queryConstraints: [ { type: 'where', fieldPath: 'born', opStr: '==', value: 1912, }, ], }, queryConstraints: [ { type: 'orderBy', fieldPath: 'born', directionStr: 'desc', }, { type: 'limit', limit: 10, }, ], }, (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; const removeSnapshotListener = async (callbackId: string) => { await FirebaseFirestore.removeSnapshotListener({ callbackId, }); }; const removeAllListeners = async () => { await FirebaseFirestore.removeAllListeners(); }; ``` ## API - [`addDocument(...)`](#adddocument) - [`addCollectionGroupSnapshotListener(...)`](#addcollectiongroupsnapshotlistener) - [`addCollectionSnapshotListener(...)`](#addcollectionsnapshotlistener) - [`addDocumentSnapshotListener(...)`](#adddocumentsnapshotlistener) - [`clearPersistence()`](#clearpersistence) - [`deleteDocument(...)`](#deletedocument) - [`disableNetwork()`](#disablenetwork) - [`disablePersistence()`](#disablepersistence) - [`enablePersistence(...)`](#enablepersistence) - [`enableNetwork()`](#enablenetwork) - [`getCollection(...)`](#getcollection) - [`getCollectionGroup(...)`](#getcollectiongroup) - [`getCountFromServer(...)`](#getcountfromserver) - [`getDocument(...)`](#getdocument) - [`removeAllListeners()`](#removealllisteners) - [`removeSnapshotListener(...)`](#removesnapshotlistener) - [`setDocument(...)`](#setdocument) - [`updateDocument(...)`](#updatedocument) - [`useEmulator(...)`](#useemulator) - [`writeBatch(...)`](#writebatch) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### addDocument(...) ``` addDocument(options: AddDocumentOptions) => Promise ``` Adds a new document to a collection with the given data. | Param | Type | | ------------- | -------------------- | | **`options`** | `AddDocumentOptions` | **Returns:** `Promise` **Since:** 5.2.0 ______________________________________________________________________ ### addCollectionGroupSnapshotListener(...) ``` addCollectionGroupSnapshotListener(options: AddCollectionGroupSnapshotListenerOptions, callback: AddCollectionGroupSnapshotListenerCallback) => Promise ``` Adds a listener for collection group snapshot events. | Param | Type | | -------------- | ----------------------------------------------- | | **`options`** | `AddCollectionGroupSnapshotListenerOptions` | | **`callback`** | `AddCollectionGroupSnapshotListenerCallback` | **Returns:** `Promise` **Since:** 6.1.0 ______________________________________________________________________ ### addCollectionSnapshotListener(...) ``` addCollectionSnapshotListener(options: AddCollectionSnapshotListenerOptions, callback: AddCollectionSnapshotListenerCallback) => Promise ``` Adds a listener for collection snapshot events. | Param | Type | | -------------- | ------------------------------------------ | | **`options`** | `AddCollectionSnapshotListenerOptions` | | **`callback`** | `AddCollectionSnapshotListenerCallback` | **Returns:** `Promise` **Since:** 5.2.0 ______________________________________________________________________ ### addDocumentSnapshotListener(...) ``` addDocumentSnapshotListener(options: AddDocumentSnapshotListenerOptions, callback: AddDocumentSnapshotListenerCallback) => Promise ``` Adds a listener for document snapshot events. | Param | Type | | -------------- | ---------------------------------------- | | **`options`** | `AddDocumentSnapshotListenerOptions` | | **`callback`** | `AddDocumentSnapshotListenerCallback` | **Returns:** `Promise` **Since:** 5.2.0 ______________________________________________________________________ ### clearPersistence() ``` clearPersistence() => Promise ``` Clears the persistent storage. This includes pending writes and cached documents. **Attention**: Must be called after the app is shutdown or when the app is first initialized. **Since:** 5.2.0 ______________________________________________________________________ ### deleteDocument(...) ``` deleteDocument(options: DeleteDocumentOptions) => Promise ``` Deletes the document referred to by the specified reference. | Param | Type | | ------------- | ----------------------- | | **`options`** | `DeleteDocumentOptions` | **Since:** 5.2.0 ______________________________________________________________________ ### disableNetwork() ``` disableNetwork() => Promise ``` Disables use of the network. **Since:** 5.2.0 ______________________________________________________________________ ### disablePersistence() ``` disablePersistence() => Promise ``` Disables offline persistence. **Attention**: Must be called before any other Firestore method. **Since:** 8.2.0 ______________________________________________________________________ ### enablePersistence(...) ``` enablePersistence(options?: EnablePersistenceOptions | undefined) => Promise ``` Enables offline persistence. **Attention**: Must be called before any other Firestore method. | Param | Type | | ------------- | -------------------------- | | **`options`** | `EnablePersistenceOptions` | **Since:** 8.2.0 ______________________________________________________________________ ### enableNetwork() ``` enableNetwork() => Promise ``` Re-enables use of the network. **Since:** 5.2.0 ______________________________________________________________________ ### getCollection(...) ``` getCollection(options: GetCollectionOptions) => Promise> ``` Reads the collection referenced by the specified reference. | Param | Type | | ------------- | ---------------------- | | **`options`** | `GetCollectionOptions` | **Returns:** `Promise>` **Since:** 5.2.0 ______________________________________________________________________ ### getCollectionGroup(...) ``` getCollectionGroup(options: GetCollectionGroupOptions) => Promise> ``` Reads the collection group referenced by the specified reference. | Param | Type | | ------------- | --------------------------- | | **`options`** | `GetCollectionGroupOptions` | **Returns:** `Promise>` ______________________________________________________________________ ### getCountFromServer(...) ``` getCountFromServer(options: GetCountFromServerOptions) => Promise ``` Fetches the number of documents in a collection. | Param | Type | | ------------- | --------------------------- | | **`options`** | `GetCountFromServerOptions` | **Returns:** `Promise` **Since:** 6.4.0 ______________________________________________________________________ ### getDocument(...) ``` getDocument(options: GetDocumentOptions) => Promise> ``` Reads the document referred to by the specified reference. | Param | Type | | ------------- | -------------------- | | **`options`** | `GetDocumentOptions` | **Returns:** `Promise>` **Since:** 5.2.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 5.2.0 ______________________________________________________________________ ### removeSnapshotListener(...) ``` removeSnapshotListener(options: RemoveSnapshotListenerOptions) => Promise ``` Remove a listener for document or collection snapshot events. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `RemoveSnapshotListenerOptions` | **Since:** 5.2.0 ______________________________________________________________________ ### setDocument(...) ``` setDocument(options: SetDocumentOptions) => Promise ``` Writes to the document referred to by the specified reference. If the document does not yet exist, it will be created. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetDocumentOptions` | **Since:** 5.2.0 ______________________________________________________________________ ### updateDocument(...) ``` updateDocument(options: UpdateDocumentOptions) => Promise ``` Updates fields in the document referred to by the specified reference. | Param | Type | | ------------- | ----------------------- | | **`options`** | `UpdateDocumentOptions` | **Since:** 5.2.0 ______________________________________________________________________ ### useEmulator(...) ``` useEmulator(options: UseEmulatorOptions) => Promise ``` Instrument your app to talk to the Firestore emulator. | Param | Type | | ------------- | -------------------- | | **`options`** | `UseEmulatorOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### writeBatch(...) ``` writeBatch(options: WriteBatchOptions) => Promise ``` Execute multiple write operations as a single batch. | Param | Type | | ------------- | ------------------- | | **`options`** | `WriteBatchOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### Interfaces #### AddDocumentResult | Prop | Type | Description | Since | | --------------- | ------------------- | ------------------------------------------ | ----- | | **`reference`** | `DocumentReference` | The reference of the newly added document. | 5.2.0 | #### DocumentReference | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------ | ----- | | **`id`** | `string` | The document's identifier within its collection. | 5.2.0 | | **`path`** | `string` | The path of the document. | 5.2.0 | #### AddDocumentOptions | Prop | Type | Description | Since | | --------------- | -------------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | | **`data`** | `DocumentData` | An object containing the data for the new document. | 5.2.0 | #### DocumentData #### AddCollectionGroupSnapshotListenerOptions | Prop | Type | Description | Since | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 6.1.0 | | **`compositeFilter`** | `QueryCompositeFilterConstraint` | The filter to apply. | 6.1.0 | | **`queryConstraints`** | `QueryNonFilterConstraint[]` | Narrow or order the set of documents to retrieve, but do not explicitly filter for document fields. | 6.1.0 | #### QueryCompositeFilterConstraint | Prop | Type | Description | Since | | ---------------------- | ------------------------- | --------------------- | --------------------------- | | **`type`** | \`'and' | 'or'\` | The type of the constraint. | | **`queryConstraints`** | `QueryFilterConstraint[]` | The filters to apply. | 5.2.0 | #### QueryFieldFilterConstraint | Prop | Type | Description | Since | | --------------- | --------------- | ------------------------------ | ----- | | **`type`** | `'where'` | The type of the constraint. | 5.2.0 | | **`fieldPath`** | `string` | The path to compare. | 5.2.0 | | **`opStr`** | `QueryOperator` | The operation string to apply. | 5.2.0 | | **`value`** | `any` | The value for comparison. | 5.2.0 | #### QueryOrderByConstraint | Prop | Type | Description | Since | | ------------------ | ------------------ | --------------------------- | ----- | | **`type`** | `'orderBy'` | The type of the constraint. | 5.2.0 | | **`fieldPath`** | `string` | The path to compare. | 5.2.0 | | **`directionStr`** | `OrderByDirection` | The direction to sort by. | 5.2.0 | #### QueryLimitConstraint | Prop | Type | Description | Since | | ----------- | --------- | -------------------------------------- | --------------------------- | | **`type`** | \`'limit' | 'limitToLast'\` | The type of the constraint. | | **`limit`** | `number` | The maximum number of items to return. | 5.2.0 | #### QueryStartAtConstraint | Prop | Type | Description | Since | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------- | | **`type`** | \`'startAt' | 'startAfter'\` | The type of the constraint. | | **`reference`** | `string` | The reference to start at or after as a string, with path components separated by a forward slash (`/`). **Attention**: This requires an additional document read. | 5.2.0 | #### QueryEndAtConstraint | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | | **`type`** | \`'endAt' | 'endBefore'\` | The type of the constraint. | | **`reference`** | `string` | The reference as to end at or before as a string, with path components separated by a forward slash (`/`). **Attention**: This requires an additional document read. | 5.2.0 | #### GetCollectionGroupResult | Prop | Type | Description | Since | | --------------- | ----------------------- | -------------------------------- | ----- | | **`snapshots`** | `DocumentSnapshot[]` | The documents in the collection. | 5.2.0 | #### DocumentSnapshot | Prop | Type | Description | Since | | -------------- | ------------------ | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------- | | **`id`** | `string` | The document's identifier within its collection. | 5.2.0 | | **`path`** | `string` | The path of the document. | 5.2.0 | | **`data`** | \`T | null\` | An object containing the data for the document. Returns `null` if the document doesn't exist. | | **`metadata`** | `SnapshotMetadata` | Metadata about the snapshot, concerning its source and if it has local modifications. | 6.2.0 | #### SnapshotMetadata | Prop | Type | Description | Since | | ---------------------- | --------- | --------------------------------------------------------- | ----- | | **`fromCache`** | `boolean` | True if the snapshot was created from cached data. | 6.2.0 | | **`hasPendingWrites`** | `boolean` | True if the snapshot was created from pending write data. | 6.2.0 | #### AddCollectionSnapshotListenerOptions | Prop | Type | Description | Since | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | | **`compositeFilter`** | `QueryCompositeFilterConstraint` | The filter to apply. | 5.2.0 | | **`queryConstraints`** | `QueryNonFilterConstraint[]` | Narrow or order the set of documents to retrieve, but do not explicitly filter for document fields. | 5.2.0 | #### GetCollectionResult | Prop | Type | Description | Since | | --------------- | ----------------------- | -------------------------------- | ----- | | **`snapshots`** | `DocumentSnapshot[]` | The documents in the collection. | 5.2.0 | #### AddDocumentSnapshotListenerOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | #### GetDocumentResult | Prop | Type | Description | Since | | -------------- | --------------------- | ------------------------------ | ----- | | **`snapshot`** | `DocumentSnapshot` | The current document contents. | 5.2.0 | #### DeleteDocumentOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | #### EnablePersistenceOptions | Prop | Type | Description | Default | Since | | --------------------- | --------- | -------------------------------------------------------------------------------- | -------------------- | ----- | | **`cacheSizeBytes`** | `number` | The cache size in bytes. | `104857600 (100 MB)` | 8.2.0 | | **`synchronizeTabs`** | `boolean` | Whether to synchronize persistence across multiple tabs. Only available for Web. | `false` | 8.2.0 | #### GetCollectionOptions | Prop | Type | Description | Since | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | | **`compositeFilter`** | `QueryCompositeFilterConstraint` | The filter to apply. | 5.2.0 | | **`queryConstraints`** | `QueryNonFilterConstraint[]` | Narrow or order the set of documents to retrieve, but do not explicitly filter for document fields. | 5.2.0 | #### GetCollectionGroupOptions | Prop | Type | Description | Since | | ---------------------- | -------------------------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | | **`compositeFilter`** | `QueryCompositeFilterConstraint` | The filter to apply. | 5.2.0 | | **`queryConstraints`** | `QueryNonFilterConstraint[]` | Narrow or order the set of documents to retrieve, but do not explicitly filter for document fields. | 5.2.0 | #### GetCountFromServerResult | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------ | ----- | | **`count`** | `number` | The number of documents in the collection. | 6.4.0 | #### GetCountFromServerOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 6.4.0 | #### GetDocumentOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | #### RemoveSnapshotListenerOptions | Prop | Type | Since | | ---------------- | ------------ | ----- | | **`callbackId`** | `CallbackId` | 5.2.0 | #### SetDocumentOptions | Prop | Type | Description | Default | Since | | --------------- | -------------- | ----------------------------------------------------------------------------------- | ------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | | 5.2.0 | | **`data`** | `DocumentData` | An object containing the data for the new document. | | 5.2.0 | | **`merge`** | `boolean` | Whether to merge the provided data with an existing document. | `false` | 5.2.0 | #### UpdateDocumentOptions | Prop | Type | Description | Since | | --------------- | -------------- | ----------------------------------------------------------------------------------- | ----- | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 5.2.0 | | **`data`** | `DocumentData` | An object containing the data for the new document. | 5.2.0 | #### UseEmulatorOptions | Prop | Type | Description | Default | Since | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`host`** | `string` | The emulator host without any port or scheme. Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer. | | 6.1.0 | | **`port`** | `number` | The emulator port. | `8080` | 6.1.0 | #### WriteBatchOptions | Prop | Type | Description | Since | | ---------------- | ----------------------- | --------------------------------------- | ----- | | **`operations`** | `WriteBatchOperation[]` | The operations to execute in the batch. | 6.1.0 | #### WriteBatchOperation | Prop | Type | Description | Since | | --------------- | -------------- | ----------------------------------------------------------------------------------- | ---------- | | **`type`** | \`'set' | 'update' | 'delete'\` | | **`reference`** | `string` | The reference as a string, with path components separated by a forward slash (`/`). | 6.1.0 | | **`data`** | `DocumentData` | An object containing the data for the new document. | 6.1.0 | | **`options`** | `SetOptions` | An object to configure the set behavior. | 7.3.0 | #### SetOptions | Prop | Type | Description | Default | Since | | ----------- | --------- | -------------------------------------------------------------------------- | ------- | ----- | | **`merge`** | `boolean` | Whether a merge should be performed or the document should be overwritten. | `false` | 7.3.0 | ### Type Aliases #### QueryFilterConstraint `QueryFieldFilterConstraint | QueryCompositeFilterConstraint` #### QueryOperator `'<' | '<=' | '==' | '>=' | '>' | '!=' | 'array-contains' | 'array-contains-any' | 'in' | 'not-in'` #### QueryNonFilterConstraint `QueryOrderByConstraint | QueryLimitConstraint | QueryStartAtConstraint | QueryEndAtConstraint` #### OrderByDirection `'desc' | 'asc'` #### AddCollectionGroupSnapshotListenerCallback `(event: AddCollectionGroupSnapshotListenerCallbackEvent | null, error: any): void` #### AddCollectionGroupSnapshotListenerCallbackEvent `GetCollectionGroupResult` #### CallbackId `string` #### AddCollectionSnapshotListenerCallback `(event: AddCollectionSnapshotListenerCallbackEvent | null, error: any): void` #### AddCollectionSnapshotListenerCallbackEvent `GetCollectionResult` #### AddDocumentSnapshotListenerCallback `(event: AddDocumentSnapshotListenerCallbackEvent | null, error: any): void` #### AddDocumentSnapshotListenerCallbackEvent `GetDocumentResult` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/firestore/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/firestore/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/functions Unofficial Capacitor plugin for [Firebase Cloud Functions](https://firebase.google.com/docs/functions/).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/functions` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/functions npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://firebase.google.com/docs/android/setup) / [iOS](https://firebase.google.com/docs/ios/setup)). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseFunctionsVersion` version of `com.google.firebase:firebase-functions` (default: `22.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Usage ``` import { FirebaseFunctions } from '@capacitor-firebase/functions'; const callByName = async () => { const { data } = await FirebaseFunctions.callByName({ name: 'helloWorld', data: { string: 'Hello World!', number: 123, boolean: true, array: [1, 2, 3], object: { key: 'value' } } }); return data; }; const callByUrl = async () => { const { data } = await FirebaseFunctions.callByUrl({ url: 'https://us-central1-YOUR_PROJECT_ID.cloudfunctions.net/helloWorld', data: { string: 'Hello World!', number: 123, boolean: true, array: [1, 2, 3], object: { key: 'value' } } }); return data; }; const useEmulator = async () => { await FirebaseFunctions.useEmulator({ host: '10.0.2.2', port: 9001, }); }; ``` ## API - [`callByName(...)`](#callbyname) - [`callByUrl(...)`](#callbyurl) - [`useEmulator(...)`](#useemulator) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### callByName(...) ``` callByName(options: CallByNameOptions) => Promise> ``` Call a callable function by name. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `CallByNameOptions` | **Returns:** `Promise>` **Since:** 6.1.0 ______________________________________________________________________ ### callByUrl(...) ``` callByUrl(options: CallByUrlOptions) => Promise> ``` Call a callable function by URL. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `CallByUrlOptions` | **Returns:** `Promise>` **Since:** 6.1.0 ______________________________________________________________________ ### useEmulator(...) ``` useEmulator(options: UseEmulatorOptions) => Promise ``` Instrument your app to talk to the Cloud Functions emulator. On Android, the cleartext traffic must be allowed. On the Capacitor configuration: ``` { server: { cleartext: true } } ``` **The cleartext traffic is not intended for use in production.** | Param | Type | | ------------- | -------------------- | | **`options`** | `UseEmulatorOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### Interfaces #### CallResult | Prop | Type | Description | Since | | ---------- | -------------- | ------------------------------------ | ----- | | **`data`** | `ResponseData` | The result of the callable function. | 6.1.0 | #### CallByNameOptions | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------ | ----- | | **`name`** | `string` | The name of the callable function. | 6.1.0 | | **`region`** | `string` | The region of the callable function. | 6.1.0 | #### CallByUrlOptions | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`url`** | `string` | The URL of the callable function. | 6.1.0 | #### UseEmulatorOptions | Prop | Type | Description | Default | Since | | -------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`host`** | `string` | The emulator host without any port or scheme. Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer. | | 6.1.0 | | **`port`** | `number` | The emulator port. | `5001` | 6.1.0 | | **`regionOrCustomDomain`** | `string` | The region the callable functions are located in or a custom domain hosting the callable functions. | | | ### Type Aliases #### CallByNameResult `CallResult` #### CallByUrlResult `CallResult` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/functions/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/functions/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/messaging Unofficial Capacitor plugin for [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging).[1](#fn:1) ## Guides - [The Push Notifications Guide for Capacitor](https://capawesome.io/blog/the-push-notifications-guide-for-capacitor/) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/messaging` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/messaging firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseMessagingVersion` version of `com.google.firebase:firebase-messaging` (default: `25.0.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. #### Push Notification Icon The Push Notification icon with the appropriate name should be added to the `android/app/src/main/AndroidManifest.xml` file: ``` ``` If no icon is specified, Android uses the application icon, but the push icon should be white pixels on a transparent background. Since the application icon does not usually look like this, it shows a white square or circle. Therefore, it is recommended to provide a separate icon for push notifications. #### Prevent auto initialization When a registration token is generated, the library uploads the identifier and configuration data to Firebase. If you prefer to prevent token autogeneration, disable Analytics collection and FCM auto initialization by adding these metadata values to the `android/app/src/main/AndroidManifest.xml` file: ``` ``` ### iOS > **Important**: Make sure that no other Capacitor Push Notification plugin is installed (see [here](https://github.com/capawesome-team/capacitor-firebase/pull/267#issuecomment-1328885820)). See [Prerequisites](https://capacitorjs.com/docs/guides/push-notifications-firebase#prerequisites) and complete the prerequisites first. See [Upload the APNS Certificate or Key to Firebase](https://capacitorjs.com/docs/guides/push-notifications-firebase#upload-the-apns-certificate-or-key-to-firebase) and follow the instructions to upload the APNS Certificate or APNS Auth Key to Firebase. > If you have difficulties with the instructions, you can also look at the corresponding sections of [this guide](https://capawesome.io/blog/the-push-notifications-guide-for-capacitor/#ios). Add the following to your app's `AppDelegate.swift`: ``` func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { NotificationCenter.default.post(name: Notification.Name.init("didReceiveRemoteNotification"), object: completionHandler, userInfo: userInfo) } ``` **Attention**: If you use this plugin in combination with `@capacitor-firebase/authentication`, then add the following to your app's `AppDelegate.swift`: ``` + import FirebaseAuth func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool { + if Auth.auth().canHandle(url) { + return true + } return ApplicationDelegateProxy.shared.application(app, open: url, options: options) } ``` #### Prevent auto initialization When a registration token is generated, the library uploads the identifier and configuration data to Firebase. If you prefer to prevent token autogeneration, disable FCM auto initialization by editing your `ios/App/App/Info.plist` and set `FirebaseMessagingAutoInitEnabled` key to `NO`. ### Web 1. See [Configure Web Credentials with FCM](https://firebase.google.com/docs/cloud-messaging/js/client#configure_web_credentials_with) and follow the instructions to configure your web credentials correctly. 1. Add a `firebase-messaging-sw.js` file to the root of your domain. This file can be empty if you do not want to receive push notifications in the background. See [Setting notification options in the service worker](https://firebase.google.com/docs/cloud-messaging/js/receive#setting_notification_options_in_the_service_worker) for more information. ## Configuration On iOS you can configure the way the push notifications are displayed when the app is in foreground. | Prop | Type | Description | Default | Since | | ------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----- | | **`presentationOptions`** | `PresentationOption[]` | This is an array of strings you can combine. Possible values in the array are: - `badge`: badge count on the app icon is updated (default value) - `sound`: the device will ring/vibrate when the push notification is received - `alert`: the push notification is displayed in a native dialog - `criticalAlert`: the push notification is displayed in a native dialog and bypasses the mute switch An empty array can be provided if none of the options are desired. Only available for iOS. | `["alert", "badge", "sound"]` | 0.2.2 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "FirebaseMessaging": { "presentationOptions": ["alert", "badge", "sound"] } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { FirebaseMessaging: { presentationOptions: ["alert", "badge", "sound"], }, }, }; export default config; ``` ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseMessaging } from '@capacitor-firebase/messaging'; const checkPermissions = async () => { const result = await FirebaseMessaging.checkPermissions(); return result.receive; }; const requestPermissions = async () => { const result = await FirebaseMessaging.requestPermissions(); return result.receive; }; const getToken = async () => { const result = await FirebaseMessaging.getToken(); return result.token; }; const deleteToken = async () => { await FirebaseMessaging.deleteToken(); }; const getDeliveredNotifications = async () => { const result = await FirebaseMessaging.getDeliveredNotifications(); return result.notifications; }; const removeDeliveredNotifications = async () => { await FirebaseMessaging.removeDeliveredNotifications({ notifications: [ { id: '798dfhliblqew89pzads', }, ], }); }; const removeAllDeliveredNotifications = async () => { await FirebaseMessaging.removeAllDeliveredNotifications(); }; const subscribeToTopic = async () => { await FirebaseMessaging.subscribeToTopic({ topic: 'news' }); }; const unsubscribeFromTopic = async () => { await FirebaseMessaging.unsubscribeFromTopic({ topic: 'news' }); }; const addTokenReceivedListener = async () => { await FirebaseMessaging.addListener('tokenReceived', event => { console.log('tokenReceived', { event }); }); }; const addNotificationReceivedListener = async () => { await FirebaseMessaging.addListener('notificationReceived', event => { console.log('notificationReceived', { event }); }); }; const addNotificationActionPerformedListener = async () => { await FirebaseMessaging.addListener('notificationActionPerformed', event => { console.log('notificationActionPerformed', { event }); }); }; const removeAllListeners = async () => { await FirebaseMessaging.removeAllListeners(); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`isSupported()`](#issupported) - [`getToken(...)`](#gettoken) - [`deleteToken()`](#deletetoken) - [`getDeliveredNotifications()`](#getdeliverednotifications) - [`removeDeliveredNotifications(...)`](#removedeliverednotifications) - [`removeAllDeliveredNotifications()`](#removealldeliverednotifications) - [`subscribeToTopic(...)`](#subscribetotopic) - [`unsubscribeFromTopic(...)`](#unsubscribefromtopic) - [`createChannel(...)`](#createchannel) - [`deleteChannel(...)`](#deletechannel) - [`listChannels()`](#listchannels) - [`addListener('tokenReceived', ...)`](#addlistenertokenreceived-) - [`addListener('notificationReceived', ...)`](#addlistenernotificationreceived-) - [`addListener('notificationActionPerformed', ...)`](#addlistenernotificationactionperformed-) - [`addListener('apnsTokenReceived', ...)`](#addlistenerapnstokenreceived-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### checkPermissions() ``` checkPermissions() => Promise ``` Check permission to receive push notifications. On **Android**, this method only needs to be called on Android 13+. **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to receive push notifications. On **Android**, this method only needs to be called on Android 13+. **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### isSupported() ``` isSupported() => Promise ``` Checks if all required APIs exist. Always returns `true` on Android and iOS. **Returns:** `Promise` **Since:** 0.3.1 ______________________________________________________________________ ### getToken(...) ``` getToken(options?: GetTokenOptions | undefined) => Promise ``` Register the app to receive push notifications. Returns a FCM token that can be used to send push messages to that Messaging instance. This method also re-enables FCM auto-init. | Param | Type | | ------------- | ----------------- | | **`options`** | `GetTokenOptions` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### deleteToken() ``` deleteToken() => Promise ``` Delete the FCM token and unregister the app to stop receiving push notifications. Can be called, for example, when a user signs out. **Since:** 0.2.2 ______________________________________________________________________ ### getDeliveredNotifications() ``` getDeliveredNotifications() => Promise ``` Get a list of notifications that are visible on the notifications screen. Note: This will return all delivered notifications, including local notifications, and not just FCM notifications. On Android, the data field of the FCM notification will NOT be included. **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### removeDeliveredNotifications(...) ``` removeDeliveredNotifications(options: RemoveDeliveredNotificationsOptions) => Promise ``` Remove specific notifications from the notifications screen. | Param | Type | | ------------- | ------------------------------------- | | **`options`** | `RemoveDeliveredNotificationsOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### removeAllDeliveredNotifications() ``` removeAllDeliveredNotifications() => Promise ``` Remove all notifications from the notifications screen. Note: This will remove all delivered notifications, including local notifications, and not just FCM notifications. **Since:** 0.2.2 ______________________________________________________________________ ### subscribeToTopic(...) ``` subscribeToTopic(options: SubscribeToTopicOptions) => Promise ``` Subscribes to topic in the background. Only available for Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SubscribeToTopicOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### unsubscribeFromTopic(...) ``` unsubscribeFromTopic(options: UnsubscribeFromTopicOptions) => Promise ``` Unsubscribes from topic in the background. Only available for Android and iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `UnsubscribeFromTopicOptions` | **Since:** 0.2.2 ______________________________________________________________________ ### createChannel(...) ``` createChannel(options: CreateChannelOptions) => Promise ``` Create a notification channel. Only available for Android (SDK 26+). | Param | Type | | ------------- | --------- | | **`options`** | `Channel` | **Since:** 1.4.0 ______________________________________________________________________ ### deleteChannel(...) ``` deleteChannel(options: DeleteChannelOptions) => Promise ``` Delete a notification channel. Only available for Android (SDK 26+). | Param | Type | | ------------- | ---------------------- | | **`options`** | `DeleteChannelOptions` | **Since:** 1.4.0 ______________________________________________________________________ ### listChannels() ``` listChannels() => Promise ``` List the available notification channels. Only available for Android (SDK 26+). **Returns:** `Promise` **Since:** 1.4.0 ______________________________________________________________________ ### addListener('tokenReceived', ...) ``` addListener(eventName: 'tokenReceived', listenerFunc: TokenReceivedListener) => Promise ``` Called when a new FCM token is received. Only available for Android and iOS. | Param | Type | | ------------------ | ----------------------- | | **`eventName`** | `'tokenReceived'` | | **`listenerFunc`** | `TokenReceivedListener` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### addListener('notificationReceived', ...) ``` addListener(eventName: 'notificationReceived', listenerFunc: NotificationReceivedListener) => Promise ``` Called when a new push notification is received. On **Android**, this listener is called for every push notification if the app is in the *foreground*. If the app is in the *background*, then this listener is only called on data push notifications. See https://firebase.google.com/docs/cloud-messaging/android/receive#handling_messages for more information. On **iOS**, this listener is called for every push notification if the app is in the *foreground*. If the app is in the *background*, then this listener is only called for silent push notifications (messages with the `content-available` key). See https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html for more information. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'notificationReceived'` | | **`listenerFunc`** | `NotificationReceivedListener` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### addListener('notificationActionPerformed', ...) ``` addListener(eventName: 'notificationActionPerformed', listenerFunc: NotificationActionPerformedListener) => Promise ``` Called when a new push notification action is performed. Only available for Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'notificationActionPerformed'` | | **`listenerFunc`** | `NotificationActionPerformedListener` | **Returns:** `Promise` **Since:** 0.2.2 ______________________________________________________________________ ### addListener('apnsTokenReceived', ...) ``` addListener(eventName: 'apnsTokenReceived', listenerFunc: ApnsTokenReceivedListener) => Promise ``` Called when the APNs token is received. Only available for iOS. | Param | Type | | ------------------ | --------------------------- | | **`eventName`** | `'apnsTokenReceived'` | | **`listenerFunc`** | `ApnsTokenReceivedListener` | **Returns:** `Promise` **Since:** 8.2.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.2.2 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Since | | ------------- | ----------------- | ----- | | **`receive`** | `PermissionState` | 0.2.2 | #### IsSupportedResult | Prop | Type | Since | | ----------------- | --------- | ----- | | **`isSupported`** | `boolean` | 0.3.1 | #### GetTokenResult | Prop | Type | Since | | ----------- | -------- | ----- | | **`token`** | `string` | 0.2.2 | #### GetTokenOptions | Prop | Type | Description | | ------------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`vapidKey`** | `string` | Your VAPID public key, which is required to retrieve the current registration token on the web. Only available for Web. | | **`serviceWorkerRegistration`** | `ServiceWorkerRegistration` | The service worker registration for receiving push messaging. If the registration is not provided explicitly, you need to have a `firebase-messaging-sw.js` at your root location. Only available for Web. | #### GetDeliveredNotificationsResult | Prop | Type | Since | | ------------------- | ---------------- | ----- | | **`notifications`** | `Notification[]` | 0.2.2 | #### Notification | Prop | Type | Description | Since | | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------- | ----- | | **`body`** | `string` | The notification payload. | 0.2.2 | | **`clickAction`** | `string` | The action to be performed on the user opening the notification. Only available for Android. | 0.2.2 | | **`data`** | `unknown` | Any additional data that was included in the push notification payload. | 0.2.2 | | **`id`** | `string` | The notification identifier. | 0.2.2 | | **`image`** | `string` | The URL of an image that is downloaded on the device and displayed in the notification. Only available for Web. | 0.2.2 | | **`link`** | `string` | Deep link from the notification. Only available for Android. | 0.2.2 | | **`subtitle`** | `string` | The notification subtitle. Only available for iOS. | 0.2.2 | | **`tag`** | `string` | The notification string identifier. Only available for Android. | 0.4.0 | | **`title`** | `string` | The notification title. | 0.2.2 | #### RemoveDeliveredNotificationsOptions | Prop | Type | Since | | ------------------- | ---------------- | ----- | | **`notifications`** | `Notification[]` | 0.4.0 | #### SubscribeToTopicOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------- | ----- | | **`topic`** | `string` | The name of the topic to subscribe. | 0.2.2 | #### UnsubscribeFromTopicOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------ | ----- | | **`topic`** | `string` | The name of the topic to unsubscribe from. | 0.2.2 | #### Channel | Prop | Type | Description | Since | | ----------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`description`** | `string` | The description of this channel (presented to the user). | 1.4.0 | | **`id`** | `string` | The channel identifier. | 1.4.0 | | **`importance`** | `Importance` | The level of interruption for notifications posted to this channel. | 1.4.0 | | **`lightColor`** | `string` | The light color for notifications posted to this channel. Only supported if lights are enabled on this channel and the device supports it. Supported color formats are `#RRGGBB` and `#RRGGBBAA`. | 1.4.0 | | **`lights`** | `boolean` | Whether notifications posted to this channel should display notification lights, on devices that support it. | 1.4.0 | | **`name`** | `string` | The name of this channel (presented to the user). | 1.4.0 | | **`sound`** | `string` | The sound that should be played for notifications posted to this channel. Notification channels with an importance of at least `3` should have a sound. The file name of a sound file should be specified relative to the android app `res/raw` directory. | 1.4.0 | | **`vibration`** | `boolean` | Whether notifications posted to this channel should vibrate. | 1.4.0 | | **`visibility`** | `Visibility` | The visibility of notifications posted to this channel. This setting is for whether notifications posted to this channel appear on the lockscreen or not, and if so, whether they appear in a redacted form. | 1.4.0 | #### DeleteChannelOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------- | ----- | | **`id`** | `string` | The channel identifier. | 1.4.0 | #### ListChannelsResult | Prop | Type | | -------------- | ----------- | | **`channels`** | `Channel[]` | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### TokenReceivedEvent | Prop | Type | Since | | ----------- | -------- | ----- | | **`token`** | `string` | 0.2.2 | #### NotificationReceivedEvent | Prop | Type | Since | | ------------------ | -------------- | ----- | | **`notification`** | `Notification` | 0.2.2 | #### NotificationActionPerformedEvent | Prop | Type | Description | Since | | ------------------ | -------------- | ---------------------------------------------------------------- | ----- | | **`actionId`** | `string` | The action performed on the notification. | 0.2.2 | | **`inputValue`** | `string` | Text entered on the notification action. Only available for iOS. | 0.2.2 | | **`notification`** | `Notification` | The notification in which the action was performed. | 0.2.2 | #### ApnsTokenReceivedEvent | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------- | ----- | | **`token`** | `string` | The native APNs token as an uppercase hex-encoded string. | 8.2.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### CreateChannelOptions `Channel` #### TokenReceivedListener Callback to receive the token received event. `(event: TokenReceivedEvent): void` #### NotificationReceivedListener Callback to receive the notification received event. `(event: NotificationReceivedEvent): void` #### NotificationActionPerformedListener Callback to receive the notification action performed event. `(event: NotificationActionPerformedEvent): void` #### ApnsTokenReceivedListener Callback to receive the APNs token received event. `(event: ApnsTokenReceivedEvent): void` ### Enums #### Importance | Members | Value | Since | | ------------- | ----- | ----- | | **`Min`** | `1` | 1.4.0 | | **`Low`** | `2` | 1.4.0 | | **`Default`** | `3` | 1.4.0 | | **`High`** | `4` | 1.4.0 | | **`Max`** | `5` | 1.4.0 | #### Visibility | Members | Value | Since | | ------------- | ----- | ----- | | **`Secret`** | `-1` | 1.4.0 | | **`Private`** | `0` | 1.4.0 | | **`Public`** | `1` | 1.4.0 | ## FAQ ### What is the difference between the Capacitor Firebase Cloud Messaging plugin and the Capacitor Push Notifications plugin? The [Capacitor Push Notifications](https://capacitorjs.com/docs/apis/push-notifications) plugin supports Android (FCM) and iOS (APNs). The Capacitor Firebase Cloud Messaging plugin, on the other hand, uses the Firebase SDK for [Android](https://firebase.google.com/docs/cloud-messaging/android/client), [iOS](https://firebase.google.com/docs/cloud-messaging/ios/client) and [Web](https://firebase.google.com/docs/cloud-messaging/js/client) and provides additional features such as topic subscriptions and the ability to receive notifications in the foreground. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/messaging/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/messaging/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/storage Unofficial Capacitor plugin for [Firebase Cloud Storage](https://firebase.google.com/docs/storage/).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/storage` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/storage npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://firebase.google.com/docs/android/setup) / [iOS](https://firebase.google.com/docs/ios/setup)). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseStorageVersion` version of `com.google.firebase:firebase-storage` (default: `22.0.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseStorage } from '@capacitor-firebase/storage'; import { Filesystem, Directory } from '@capacitor/filesystem'; const uploadFile = async () => { return new Promise((resolve, reject) => { await FirebaseStorage.uploadFile( { path: 'images/mountains.png', uri: 'file:///var/mobile/Containers/Data/Application/E397A70D-67E4-4258-236E-W1D9E12111D4/Library/Caches/092F8464-DE60-40B3-8A23-EB83160D9F9F/mountains.png', }, (event, error) => { if (error) { reject(error); } else if (event?.completed) { resolve(); } } ); }); }; const downloadFileWithFirebaseStorage = async () => { return new Promise((resolve, reject) => { await FirebaseStorage.downloadFile( { path: 'images/mountains.png', uri: 'file:///var/mobile/Containers/Data/Application/E397A70D-67E4-4258-236E-W1D9E12111D4/Library/Caches/mountains.png', // Only available for Android and iOS }, (event, error) => { if (error) { reject(error); } else if (event?.completed) { // On Web, the downloaded file is available as a Blob in event.blob resolve(event?.blob); } } ); }); }; const downloadFileWithFilesystem = async () => { const { downloadUrl } = await FirebaseStorage.getDownloadUrl({ path: 'images/mountains.png', }); const { path } = await Filesystem.downloadFile({ url: downloadUrl, path: 'mountains.png', directory: Directory.Cache, }); return path; }; const getDownloadUrl = async () => { const { downloadUrl } = await FirebaseStorage.getDownloadUrl({ path: 'images/mountains.png', }); return downloadUrl; }; const deleteFile = async () => { await FirebaseStorage.deleteFile({ path: 'images/mountains.png', }); }; const listFiles = async () => { const { items } = await FirebaseStorage.listFiles({ path: 'images', }); return items; }; const getMetadata = async () => { const result = await FirebaseStorage.getMetadata({ path: 'images/mountains.png', }); return result; }; const updateMetadata = async () => { await FirebaseStorage.updateMetadata({ path: 'images/mountains.png', metadata: { contentType: 'image/png', customMetadata: { foo: 'bar', }, }, }); }; const useEmulator = async () => { await FirebaseStorage.useEmulator({ host: '10.0.2.2', port: 9001, }); }; ``` ## API - [`deleteFile(...)`](#deletefile) - [`getDownloadUrl(...)`](#getdownloadurl) - [`getMetadata(...)`](#getmetadata) - [`listFiles(...)`](#listfiles) - [`updateMetadata(...)`](#updatemetadata) - [`downloadFile(...)`](#downloadfile) - [`uploadFile(...)`](#uploadfile) - [`useEmulator(...)`](#useemulator) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### deleteFile(...) ``` deleteFile(options: DeleteFileOptions) => Promise ``` Delete a file. | Param | Type | | ------------- | ------------------- | | **`options`** | `DeleteFileOptions` | **Since:** 5.3.0 ______________________________________________________________________ ### getDownloadUrl(...) ``` getDownloadUrl(options: GetDownloadUrlOptions) => Promise ``` Get the download url for a file. | Param | Type | | ------------- | ----------------------- | | **`options`** | `GetDownloadUrlOptions` | **Returns:** `Promise` **Since:** 5.3.0 ______________________________________________________________________ ### getMetadata(...) ``` getMetadata(options: GetMetadataOptions) => Promise ``` Get the metadata for a file. | Param | Type | | ------------- | -------------------- | | **`options`** | `GetMetadataOptions` | **Returns:** `Promise` **Since:** 5.3.0 ______________________________________________________________________ ### listFiles(...) ``` listFiles(options: ListFilesOptions) => Promise ``` List files in a directory. | Param | Type | | ------------- | ------------------ | | **`options`** | `ListFilesOptions` | **Returns:** `Promise` **Since:** 5.3.0 ______________________________________________________________________ ### updateMetadata(...) ``` updateMetadata(options: UpdateMetadataOptions) => Promise ``` Update the metadata for a file. | Param | Type | | ------------- | ----------------------- | | **`options`** | `UpdateMetadataOptions` | **Since:** 5.3.0 ______________________________________________________________________ ### downloadFile(...) ``` downloadFile(options: DownloadFileOptions, callback: DownloadFileCallback) => Promise ``` Download a file. On **Android** and **iOS**, the file is downloaded to the local file system using the `uri` option. On **Web**, the file is downloaded as a `Blob` and returned in the callback event. | Param | Type | | -------------- | ---------------------- | | **`options`** | `DownloadFileOptions` | | **`callback`** | `DownloadFileCallback` | **Returns:** `Promise` **Since:** 8.2.0 ______________________________________________________________________ ### uploadFile(...) ``` uploadFile(options: UploadFileOptions, callback: UploadFileCallback) => Promise ``` Upload a file. | Param | Type | | -------------- | -------------------- | | **`options`** | `UploadFileOptions` | | **`callback`** | `UploadFileCallback` | **Returns:** `Promise` **Since:** 5.3.0 ______________________________________________________________________ ### useEmulator(...) ``` useEmulator(options: UseEmulatorOptions) => Promise ``` Instrument your app to talk to the Cloud Storage emulator. On Android, the cleartext traffic must be allowed. On the Capacitor configuration: ``` { server: { cleartext: true } } ``` **The cleartext traffic is not intended for use in production.** | Param | Type | | ------------- | -------------------- | | **`options`** | `UseEmulatorOptions` | **Since:** 6.1.0 ______________________________________________________________________ ### Interfaces #### DeleteFileOptions | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------- | ----- | | **`path`** | `string` | The full path to the file to delete, including the file name. | 5.3.0 | #### GetDownloadUrlResult | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------ | ----- | | **`downloadUrl`** | `string` | The download url for the file. | 5.3.0 | #### GetDownloadUrlOptions | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The full path to the file to get the download url for, including the file name. | 5.3.0 | #### GetMetadataResult | Prop | Type | Description | Since | | ------------------------ | ---------------------------- | --------------------------------------------------------------------------------- | ----- | | **`bucket`** | `string` | The bucket this file is contained in. | 5.3.0 | | **`createdAt`** | `number` | The timestamp at which the file was created in milliseconds since the epoch. | 5.3.0 | | **`generation`** | `string` | The object's generation. | 5.3.0 | | **`md5Hash`** | `string` | The md5 hash of the file. | 5.3.0 | | **`metadataGeneration`** | `string` | The object's metadata generation. | 5.3.0 | | **`name`** | `string` | The short name of this file, which is the last component of the full path. | 5.3.0 | | **`path`** | `string` | The full path to the file, including the file name. | 5.3.0 | | **`size`** | `number` | The size of the file in bytes. | 5.3.0 | | **`updatedAt`** | `number` | The timestamp at which the file was last updated in milliseconds since the epoch. | 5.3.0 | | **`cacheControl`** | `string` | Served as the `Cache-Control` header on object download. | 6.1.0 | | **`contentDisposition`** | `string` | Served as the `Content-Disposition` header on object download. | 6.1.0 | | **`contentEncoding`** | `string` | Served as the `Content-Encoding` header on object download. | 6.1.0 | | **`contentLanguage`** | `string` | Served as the `Content-Language` header on object download. | 6.1.0 | | **`contentType`** | `string` | Served as the `Content-Type` header on object download. | 6.1.0 | | **`customMetadata`** | `{ [key: string]: string; }` | Additional user-defined custom metadata. | 6.1.0 | #### GetMetadataOptions | Prop | Type | Description | Since | | ---------- | -------- | --------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The full path to the file to get the metadata for, including the file name. | 5.3.0 | #### ListFilesResult | Prop | Type | Description | Since | | ------------------- | -------------------- | ------------------------------------------------------------------------------------- | ----- | | **`items`** | `StorageReference[]` | The list of files in the directory. | 5.3.0 | | **`nextPageToken`** | `string` | If set, there might be more results for this list. Use this token to resume the list. | 5.3.0 | #### StorageReference | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------------------------------------------------- | ----- | | **`bucket`** | `string` | The bucket this file is contained in. | 5.3.0 | | **`path`** | `string` | The full path to the file, including the file name. | 5.3.0 | | **`name`** | `string` | The short name of this file, which is the last component of the full path. | 5.3.0 | #### ListFilesOptions | Prop | Type | Description | Default | Since | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`path`** | `string` | The full path to the directory to list files for. | | 5.3.0 | | **`maxResults`** | `number` | The maximum number of results to return. | `1000` | 5.3.0 | | **`pageToken`** | `string` | The page token, returned by a previous call to this method. If provided, listing is resumed from the previous position. | | 5.3.0 | #### UpdateMetadataOptions | Prop | Type | Description | Since | | -------------- | ------------------ | ------------------------------------------------------------------------------ | ----- | | **`path`** | `string` | The full path to the file to update the metadata for, including the file name. | 5.3.0 | | **`metadata`** | `SettableMetadata` | The metadata to update. | 5.3.0 | #### SettableMetadata | Prop | Type | Description | Since | | ------------------------ | ---------------------------- | -------------------------------------------------------------- | ----- | | **`cacheControl`** | `string` | Served as the `Cache-Control` header on object download. | 5.3.0 | | **`contentDisposition`** | `string` | Served as the `Content-Disposition` header on object download. | 5.3.0 | | **`contentEncoding`** | `string` | Served as the `Content-Encoding` header on object download. | 5.3.0 | | **`contentLanguage`** | `string` | Served as the `Content-Language` header on object download. | 5.3.0 | | **`contentType`** | `string` | Served as the `Content-Type` header on object download. | 5.3.0 | | **`customMetadata`** | `{ [key: string]: string; }` | Additional user-defined custom metadata. | 5.3.0 | #### DownloadFileOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------------------- | ----- | | **`path`** | `string` | The full path to the file to download, including the file name. | 8.2.0 | | **`uri`** | `string` | The uri to download the file to. Only available for Android and iOS. | 8.2.0 | #### DownloadFileCallbackEvent | Prop | Type | Description | Since | | ---------------------- | --------- | ----------------------------------------------------------------------------------- | ----- | | **`progress`** | `number` | The download progress, as a percentage between 0 and 1. | 8.2.0 | | **`bytesTransferred`** | `number` | The number of bytes that have been transferred. Only available for Android and Web. | 8.2.0 | | **`totalBytes`** | `number` | The total number of bytes to be transferred. Only available for Android and Web. | 8.2.0 | | **`completed`** | `boolean` | Whether the download is completed or not. | 8.2.0 | | **`blob`** | `Blob` | The downloaded file as a Blob. Only available for Web. | 8.2.0 | #### UploadFileOptions | Prop | Type | Description | Since | | -------------- | ---------------- | --------------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The data to upload. Only available for Web. | 5.3.0 | | **`path`** | `string` | The full path where data should be uploaded, including the file name. | 5.3.0 | | **`uri`** | `string` | The uri to the file to upload. Only available for Android and iOS. | 5.3.0 | | **`metadata`** | `UploadMetadata` | The metadata to set for the file. | 5.4.0 | #### UploadMetadata | Prop | Type | Description | Since | | ------------- | -------- | ---------------------------------------------------------------- | ----- | | **`md5Hash`** | `string` | The base64-encoded MD5 hash of the file. Only available for Web. | 5.4.0 | #### UploadFileCallbackEvent | Prop | Type | Description | Since | | ---------------------- | --------- | ----------------------------------------------------------------------------------- | ----- | | **`progress`** | `number` | The upload progress, as a percentage between 0 and 1. | 5.3.0 | | **`bytesTransferred`** | `number` | The number of bytes that have been transferred. Only available for Android and Web. | 5.3.0 | | **`totalBytes`** | `number` | The total number of bytes to be transferred. Only available for Android and Web. | 5.3.0 | | **`completed`** | `boolean` | Whether the upload is completed or not. | 5.3.0 | #### UseEmulatorOptions | Prop | Type | Description | Default | Since | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`host`** | `string` | The emulator host without any port or scheme. Note when using a Android Emulator device: 10.0.2.2 is the special IP address to connect to the 'localhost' of the host computer. | | 6.1.0 | | **`port`** | `number` | The emulator port. | `9199` | 6.1.0 | ### Type Aliases #### DownloadFileCallback `(event: DownloadFileCallbackEvent | null, error: any): void` #### CallbackId `string` #### UploadFileCallback `(event: UploadFileCallbackEvent | null, error: any): void` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/storage/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/storage/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/crashlytics Unofficial Capacitor plugin for [Firebase Crashlytics](https://firebase.google.com/docs/crashlytics/).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/crashlytics` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/crashlytics npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios)). ### Android #### Crashlytics Gradle plugin First, add the dependency for the Crashlytics Gradle plugin to your root-level (project-level) Gradle file (`/build.gradle`): ``` buildscript { dependencies { + // Add the dependency for the Crashlytics Gradle plugin + classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' } } ``` Add the Crashlytics Gradle plugin to your module (app-level) Gradle file (usually `//build.gradle`): ``` apply plugin: 'com.google.firebase.crashlytics' ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseCrashlyticsVersion` version of `com.google.firebase:firebase-crashlytics` (default: `20.0.3`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS To generate human readable crash reports, Crashlytics needs your project's debug symbol (dSYM) files. The following steps describe how to automatically upload dSYM files to Firebase whenever you build your app: 1. Open your project's Xcode workspace, then select its project file in the left navigator. 1. From the **TARGETS** list, select your main build target. 1. Click the **Build Settings** tab, then complete the following steps so that Xcode produces dSYMs for your builds. 1. Click **All**, then search for `debug information format`. 1. Set **Debug Information Format** to `DWARF with dSYM File` for all your build types. 1. Click the **Build Phases** tab and complete the following steps: 1. Click the **+** button, then select **New Run Script Phase**. Make sure this new **Run Script** phase is your project's last build phase; otherwise, Crashlytics can't properly process dSYMs. 2. Expand the new **Run Script** section. 3. In the script field (located under the *Shell* label), add the run script based on your package manager: **CocoaPods:** ``` "${PODS_ROOT}/FirebaseCrashlytics/run" ``` **Swift Package Manager:** ``` "${BUILD_DIR%/Build/*}/SourcePackages/checkouts/firebase-ios-sdk/Crashlytics/run" ``` 1. In the **Input Files** section, add the paths for the locations of the following files based on your package manager: **CocoaPods:** ``` ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${TARGET_NAME} ``` ``` $(SRCROOT)/$(BUILT_PRODUCTS_DIR)/$(INFOPLIST_PATH) ``` **Swift Package Manager:** ``` ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME} ``` ``` ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME} ``` ``` ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Info.plist ``` ``` $(TARGET_BUILD_DIR)/$(UNLOCALIZED_RESOURCES_FOLDER_PATH)/GoogleService-Info.plist ``` ``` $(TARGET_BUILD_DIR)/$(EXECUTABLE_PATH) ``` If you have `ENABLE_USER_SCRIPT_SANDBOXING=YES` and `ENABLE_DEBUG_DYLIB=YES` in your project build settings, also include: ``` ${DWARF_DSYM_FOLDER_PATH}/${DWARF_DSYM_FILE_NAME}/Contents/Resources/DWARF/${PRODUCT_NAME}.debug.dylib ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseCrashlytics } from '@capacitor-firebase/crashlytics'; const crash = async () => { await FirebaseCrashlytics.crash({ message: 'Test' }); }; const setCustomKey = async () => { await FirebaseCrashlytics.setCustomKey({ key: 'page', value: 'home', type: 'string', }); }; const setUserId = async () => { await FirebaseCrashlytics.setUserId({ userId: '123', }); }; const log = async () => { await FirebaseCrashlytics.log({ message: 'Test', }); }; const setEnabled = async () => { await FirebaseCrashlytics.setEnabled({ enabled: true, }); }; const isEnabled = async () => { const { enabled } = await FirebaseCrashlytics.isEnabled(); return enabled; }; const didCrashOnPreviousExecution = async () => { const { crashed } = await FirebaseCrashlytics.didCrashOnPreviousExecution(); return crashed; }; const sendUnsentReports = async () => { await FirebaseCrashlytics.sendUnsentReports(); }; const deleteUnsentReports = async () => { await FirebaseCrashlytics.deleteUnsentReports(); }; const recordException = async () => { await FirebaseCrashlytics.recordException({ message: 'This is a non-fatal message.', }); }; import * as StackTrace from 'stacktrace-js'; const recordExceptionWithStacktrace = async (error: Error) => { const stacktrace = await StackTrace.fromError(error); await FirebaseCrashlytics.recordException({ message: 'This is a non-fatal message.', stacktrace, }); }; ``` ## API - [`crash(...)`](#crash) - [`setCustomKey(...)`](#setcustomkey) - [`setUserId(...)`](#setuserid) - [`log(...)`](#log) - [`setEnabled(...)`](#setenabled) - [`isEnabled()`](#isenabled) - [`didCrashOnPreviousExecution()`](#didcrashonpreviousexecution) - [`sendUnsentReports()`](#sendunsentreports) - [`deleteUnsentReports()`](#deleteunsentreports) - [`recordException(...)`](#recordexception) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### crash(...) ``` crash(options: CrashOptions) => Promise ``` Forces a crash to test the implementation. Only available for Android and iOS. | Param | Type | | ------------- | -------------- | | **`options`** | `CrashOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setCustomKey(...) ``` setCustomKey(options: SetCustomKeyOptions) => Promise ``` Sets a custom key and value that is associated with subsequent fatal and non-fatal reports. Only available for Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `CustomKeyAndValue` | **Since:** 0.1.0 ______________________________________________________________________ ### setUserId(...) ``` setUserId(options: SetUserIdOptions) => Promise ``` Sets a user ID (identifier) that is associated with subsequent fatal and non-fatal reports. Only available for Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetUserIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### log(...) ``` log(options: LogOptions) => Promise ``` Adds a custom log message that is sent with your crash data to give yourself more context for the events leading up to a crash. Only available for Android and iOS. | Param | Type | | ------------- | ------------ | | **`options`** | `LogOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setEnabled(...) ``` setEnabled(options: SetEnabledOptions) => Promise ``` Enables/disables automatic data collection. The value does not apply until the next run of the app. Only available for Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetEnabledOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Returns whether or not automatic data collection is enabled. Only available for iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### didCrashOnPreviousExecution() ``` didCrashOnPreviousExecution() => Promise ``` Returns whether the app crashed during the previous execution. Only available for Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### sendUnsentReports() ``` sendUnsentReports() => Promise ``` Uploads any unsent reports to Crashlytics at next startup. When automatic data collection is enabled, Crashlytics automatically uploads reports at startup. Only available for Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### deleteUnsentReports() ``` deleteUnsentReports() => Promise ``` Deletes any unsent reports on the device. Only available for Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### recordException(...) ``` recordException(options: RecordExceptionOptions) => Promise ``` Records a non-fatal report to send to Crashlytics. Only available for Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `RecordExceptionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### CrashOptions | Prop | Type | Since | | ------------- | -------- | ----- | | **`message`** | `string` | 0.1.0 | #### CustomKeyAndValue | Prop | Type | Since | | ----------- | ---------- | --------- | | **`key`** | `string` | 7.1.0 | | **`value`** | \`string | number | | **`type`** | \`'string' | 'boolean' | #### SetUserIdOptions | Prop | Type | Since | | ------------ | -------- | ----- | | **`userId`** | `string` | 0.1.0 | #### LogOptions | Prop | Type | Since | | ------------- | -------- | ----- | | **`message`** | `string` | 0.1.0 | #### SetEnabledOptions | Prop | Type | Since | | ------------- | --------- | ----- | | **`enabled`** | `boolean` | 0.1.0 | #### IsEnabledResult | Prop | Type | Since | | ------------- | --------- | ----- | | **`enabled`** | `boolean` | 0.1.0 | #### DidCrashOnPreviousExecutionResult | Prop | Type | Since | | ------------- | --------- | ----- | | **`crashed`** | `boolean` | 0.1.0 | #### RecordExceptionOptions | Prop | Type | Description | Since | | ------------------- | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`message`** | `string` | The message to record as a non-fatal exception. | 0.1.0 | | **`code`** | `number` | Error code within a specific error domain. **Attention:** This option is ignored on iOS if `stacktrace` is provided. Only available for iOS. | 0.1.0 | | **`domain`** | `string` | A string containing the error domain. **Attention:** This option is ignored on iOS if `stacktrace` is provided. Only available for iOS. | 0.1.0 | | **`keysAndValues`** | `CustomKeyAndValue[]` | An array of keys and the values to associate with the non fatal exception, in addition to the app level custom keys. **Attention:** This option is ignored on iOS if `stacktrace` is provided. | 7.1.0 | | **`stacktrace`** | `StackFrame[]` | A stacktrace generated by stacktrace.js. | 1.1.0 | #### StackFrame Subset of the Stacktrace generated by stacktrace.js. | Prop | Type | | ------------------ | -------- | | **`lineNumber`** | `number` | | **`fileName`** | `string` | | **`functionName`** | `string` | ### Type Aliases #### SetCustomKeyOptions `CustomKeyAndValue` ## Test your implementation [Here](https://firebase.google.com/docs/crashlytics/force-a-crash) you can find more information on how to test the Firebase Crashlytics implementation. Among other things, you will find information on how to correctly [adjust the project's debug settings](https://firebase.google.com/docs/crashlytics/force-a-crash?platform=ios#adjust_your_projects_debug_settings) under iOS and how to [test it out](https://firebase.google.com/docs/crashlytics/force-a-crash?platform=ios#test_it_out). If you get obfuscated crash reports for iOS, make sure you have [initialized Crashlytics](https://firebase.google.com/docs/crashlytics/get-started?platform=ios#initialize-crashlytics) correctly and take a look at [this guide](https://firebase.google.com/docs/crashlytics/get-deobfuscated-reports?platform=ios), which provides some ways to troubleshoot if Crashlytics can't find your app's dSYM. ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/crashlytics/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/crashlytics/LICENSE). ## Credits This plugin is based on the [Capacitor Community Firebase Crashlytics](https://github.com/capacitor-community/firebase-crashlytics) plugin. Thanks to everyone who contributed to the project! ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/performance Unofficial Capacitor plugin for [Firebase Performance Monitoring](https://firebase.google.com/docs/perf-mon).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | | 1.x.x | 4.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/performance` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/performance firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android See [Add the Performance Monitoring plugin to your app](https://firebase.google.com/docs/perf-mon/get-started-android#add-perfmon-plugin) and follow the instructions to set up your app correctly. #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebasePerfVersion` version of `com.google.firebase:firebase-perf` (default: `22.0.4`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Usage ``` import { FirebasePerformance } from '@capacitor-firebase/performance'; const startTrace = async () => { await FirebasePerformance.startTrace({ traceName: 'test_trace' }); }; const stopTrace = async () => { await FirebasePerformance.stopTrace({ traceName: 'test_trace' }); }; const incrementMetric = async () => { await FirebasePerformance.incrementMetric({ traceName: 'test_trace', metricName: 'item_cache_hit', incrementBy: 1, }); }; const setEnabled = async () => { await FirebasePerformance.setEnabled({ enabled: true }); }; const isEnabled = async () => { const result = await FirebasePerformance.isEnabled(); return result.enabled; }; const putAttribute = async () => { await FirebasePerformance.putAttribute({ traceName: 'test_trace', attribute: 'user_id', value: '123', }); }; const getAttribute = async () => { const result = await FirebasePerformance.getAttribute({ traceName: 'test_trace', attribute: 'user_id', }); return result.attributes; }; const getAttributes = async () => { const result = await FirebasePerformance.getAttributes({ traceName: 'test_trace' }); return result.attributes; }; const removeAttribute = async () => { await FirebasePerformance.removeAttribute({ traceName: 'test_trace', attribute: 'user_id', }); }; const putMetric = async () => { await FirebasePerformance.putMetric({ traceName: 'test_trace', metricName: 'item_cache_hit', num: 1, }); }; const getMetric = async () => { const result = await FirebasePerformance.getMetric({ traceName: 'test_trace', metricName: 'item_cache_hit', }); return result.value; }; const record = async () => { await FirebasePerformance.record({ traceName: 'test_trace', startTime: Date.now(), duration: 1000, options: { metrics: { item_cache_hit: 1, }, attributes: { user_id: '123', }, }, }); }; ``` ## API - [`startTrace(...)`](#starttrace) - [`stopTrace(...)`](#stoptrace) - [`incrementMetric(...)`](#incrementmetric) - [`setEnabled(...)`](#setenabled) - [`isEnabled()`](#isenabled) - [`putAttribute(...)`](#putattribute) - [`getAttribute(...)`](#getattribute) - [`getAttributes(...)`](#getattributes) - [`removeAttribute(...)`](#removeattribute) - [`putMetric(...)`](#putmetric) - [`getMetric(...)`](#getmetric) - [`record(...)`](#record) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### startTrace(...) ``` startTrace(options: StartTraceOptions) => Promise ``` Starts a trace. | Param | Type | | ------------- | ------------------- | | **`options`** | `StartTraceOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### stopTrace(...) ``` stopTrace(options: StopTraceOptions) => Promise ``` Stops a trace. | Param | Type | | ------------- | ------------------ | | **`options`** | `StopTraceOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### incrementMetric(...) ``` incrementMetric(options: IncrementMetricOptions) => Promise ``` Atomically increments the metric with the given name for the selected trace by the `incrementBy` value. | Param | Type | | ------------- | ------------------------ | | **`options`** | `IncrementMetricOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setEnabled(...) ``` setEnabled(options: SetEnabledOptions) => Promise ``` Enables or disables performance monitoring. Will be applied with the next start of the app. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetEnabledOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isEnabled() ``` isEnabled() => Promise ``` Determines whether performance monitoring is enabled or disabled. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### putAttribute(...) ``` putAttribute(options: PutAttributeOptions) => Promise ``` Sets a custom attribute of a trace to a given value. | Param | Type | | ------------- | --------------------- | | **`options`** | `PutAttributeOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### getAttribute(...) ``` getAttribute(options: GetAttributeOptions) => Promise ``` Returns the value of a custom attribute of a trace. | Param | Type | | ------------- | --------------------- | | **`options`** | `GetAttributeOptions` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### getAttributes(...) ``` getAttributes(options: GetAttributesOptions) => Promise ``` Gets the all the custom attributes of a trace with their values. | Param | Type | | ------------- | ---------------------- | | **`options`** | `GetAttributesOptions` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### removeAttribute(...) ``` removeAttribute(options: RemoveAttributeOptions) => Promise ``` Removes a custom attribute from a trace given its name. | Param | Type | | ------------- | --------------------- | | **`options`** | `GetAttributeOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### putMetric(...) ``` putMetric(options: PutMetricOptions) => Promise ``` Sets the value of a custom metric. | Param | Type | | ------------- | ------------------ | | **`options`** | `PutMetricOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### getMetric(...) ``` getMetric(options: GetMetricOptions) => Promise ``` Get the value of a custom metric by name. | Param | Type | | ------------- | ------------------ | | **`options`** | `GetMetricOptions` | **Returns:** `Promise` **Since:** 6.3.0 ______________________________________________________________________ ### record(...) ``` record(options: RecordOptions) => Promise ``` Records a trace given its name and options. Only available on web. | Param | Type | | ------------- | --------------- | | **`options`** | `RecordOptions` | **Since:** 6.3.0 ______________________________________________________________________ ### Interfaces #### StartTraceOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`traceName`** | `string` | Custom trace name. Names for custom code traces must meet the following requirements: no leading or trailing whitespace, no leading underscore (\_) character, and max length is 100 characters. | 0.1.0 | #### StopTraceOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace that was set with `startTrace`. | 0.1.0 | #### IncrementMetricOptions | Prop | Type | Description | Default | Since | | ----------------- | -------- | ------------------------------------------------- | ------- | ----- | | **`traceName`** | `string` | Name of the trace that was set with `startTrace`. | | 0.1.0 | | **`metricName`** | `string` | Name of the metric to be incremented. | | 0.1.0 | | **`incrementBy`** | `number` | Amount by which the metric has to be incremented. | `1` | 0.1.0 | #### SetEnabledOptions | Prop | Type | Description | Since | | ------------- | --------- | ----------------------------------------- | ----- | | **`enabled`** | `boolean` | Should performance monitoring be enabled. | 0.1.0 | #### IsEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | --------------------------------------------------------------- | ----- | | **`enabled`** | `boolean` | `true` if performance monitoring is enabled, otherwise `false`. | 0.1.0 | #### PutAttributeOptions | Prop | Type | Description | Since | | --------------- | -------- | --------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace to set its attribute. | 6.3.0 | | **`attribute`** | `string` | Name of the attribute to set its value. | 6.3.0 | | **`value`** | `string` | The value to set to the attribute. | 6.3.0 | #### GetAttributeResult | Prop | Type | Description | Since | | ----------- | -------- | ----------- | ---------------------------------- | | **`value`** | \`string | null\` | The value of the custom attribute. | #### GetAttributeOptions | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace to set its attribute. | 6.3.0 | | **`attribute`** | `string` | Name of the attribute to retrieve its value. | 6.3.0 | #### GetAttributesResult | Prop | Type | Description | Since | | ---------------- | ---------------------------- | ------------------------------------------------------------ | ----- | | **`attributes`** | `{ [key: string]: string; }` | A map of all custom attributes of a trace with their values. | 6.3.0 | #### GetAttributesOptions | Prop | Type | Description | Since | | --------------- | -------- | ---------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace to get its attributes. | 6.3.0 | #### PutMetricOptions | Prop | Type | Description | Since | | ---------------- | -------- | ---------------------------------------------------------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace to set its metric. | 6.3.0 | | **`metricName`** | `string` | The metric name. | 6.3.0 | | **`num`** | `number` | The value to set for the metric. The given value is floored down to the nearest integer. | 6.3.0 | #### GetMetricResult | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------- | ----- | | **`value`** | `number` | The value of the metric if exists. | 6.3.0 | #### GetMetricOptions | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------ | ----- | | **`traceName`** | `string` | Name of the trace to get its metric. | 6.3.0 | | **`metricName`** | `string` | The metric name. | 6.3.0 | #### RecordOptions | Prop | Type | Description | Since | | --------------- | ------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- | ----- | | **`traceName`** | `string` | Name of the trace to record. | 6.3.0 | | **`startTime`** | `number` | Start time of the trace since epoch in milliseconds. | 6.3.0 | | **`duration`** | `number` | The duration of the trace in milliseconds. | 6.3.0 | | **`options`** | `{ metrics?: { [key: string]: number; }; attributes?: { [key: string]: string; }; }` | An optional object that holds optional maps of custom metrics and attributes. | 6.3.0 | ### Type Aliases #### RemoveAttributeOptions `GetAttributeOptions` ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/performance/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/performance/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-firebase/remote-config Unofficial Capacitor plugin for [Firebase Remote Config](https://firebase.google.com/docs/remote-config).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-firebase/remote-config` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-firebase/remote-config firebase npx cap sync ``` Add Firebase to your project if you haven't already ([Android](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#android) / [iOS](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#ios) / [Web](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md#web)). ### Android Google Analytics is required for the [conditional targeting of app instances](https://firebase.google.com/docs/remote-config/parameters#conditions_rules_and_conditional_values) to user properties and audiences. Make sure that you install the [Capacitor Firebase Analytics](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/analytics) plugin in your project. #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$firebaseConfigVersion` version of `com.google.firebase:firebase-config` (default: `23.0.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-firebase-plugin-demo](https://github.com/robingenz/capacitor-firebase-plugin-demo) ## Starter templates The following starter templates are available: - [Ionstarter Angular Firebase](https://ionstarter.dev/) ## Usage ``` import { FirebaseRemoteConfig } from '@capacitor-firebase/remote-config'; const activate = async () => { await FirebaseRemoteConfig.activate(); }; const fetchAndActivate = async () => { await FirebaseRemoteConfig.fetchAndActivate(); }; const fetchConfig = async () => { await FirebaseRemoteConfig.fetchConfig({ minimumFetchIntervalInSeconds: 1200, }); }; const getBoolean = async () => { const { value } = await FirebaseRemoteConfig.getBoolean({ key: 'is_sale', }); return value; }; const getNumber = async () => { const { value } = await FirebaseRemoteConfig.getNumber({ key: 'upcoming_maintenance', }); return value; }; const getString = async () => { const { value } = await FirebaseRemoteConfig.getString({ key: 'license_key', }); return value; }; const setSettings = async () => { await FirebaseRemoteConfig.setSettings({ fetchTimeoutInSeconds: 10, minimumFetchIntervalInSeconds: 0, }); }; const addConfigUpdateListener = async () => { const callbackId = await FirebaseRemoteConfig.addConfigUpdateListener( (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; const removeConfigUpdateListener = async (callbackId: string) => { await FirebaseRemoteConfig.removeConfigUpdateListener({ callbackId, }); }; const removeAllListeners = async () => { await FirebaseRemoteConfig.removeAllListeners(); }; ``` ## API - [`activate()`](#activate) - [`fetchAndActivate()`](#fetchandactivate) - [`fetchConfig(...)`](#fetchconfig) - [`getBoolean(...)`](#getboolean) - [`getNumber(...)`](#getnumber) - [`getString(...)`](#getstring) - [`getInfo()`](#getinfo) - [`setMinimumFetchInterval(...)`](#setminimumfetchinterval) - [`setDefaults(...)`](#setdefaults) - [`setSettings(...)`](#setsettings) - [`addConfigUpdateListener(...)`](#addconfigupdatelistener) - [`removeConfigUpdateListener(...)`](#removeconfigupdatelistener) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### activate() ``` activate() => Promise ``` Make the last fetched configuration available to the getters. **Since:** 1.3.0 ______________________________________________________________________ ### fetchAndActivate() ``` fetchAndActivate() => Promise ``` Perform fetch and activate operations. **Since:** 1.3.0 ______________________________________________________________________ ### fetchConfig(...) ``` fetchConfig(options?: FetchConfigOptions | undefined) => Promise ``` Fetch and cache configuration from the Remote Config service. | Param | Type | | ------------- | -------------------- | | **`options`** | `FetchConfigOptions` | **Since:** 1.3.0 ______________________________________________________________________ ### getBoolean(...) ``` getBoolean(options: GetBooleanOptions) => Promise ``` Get the value for the given key as a boolean. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### getNumber(...) ``` getNumber(options: GetNumberOptions) => Promise ``` Get the value for the given key as a number. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### getString(...) ``` getString(options: GetStringOptions) => Promise ``` Get the value for the given key as a string. | Param | Type | | ------------- | ------------ | | **`options`** | `GetOptions` | **Returns:** `Promise` **Since:** 1.3.0 ______________________________________________________________________ ### getInfo() ``` getInfo() => Promise ``` Get information about the last fetch operation. **Returns:** `Promise` **Since:** 7.5.0 ______________________________________________________________________ ### setMinimumFetchInterval(...) ``` setMinimumFetchInterval(options: SetMinimumFetchIntervalOptions) => Promise ``` Set the minimum fetch interval. Only available for Web. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SetMinimumFetchIntervalOptions` | **Since:** 1.3.0 ______________________________________________________________________ ### setDefaults(...) ``` setDefaults(options: SetDefaultsOptions) => Promise ``` Sets config defaults for parameter keys and values in the default namespace config. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetDefaultsOptions` | **Since:** 8.3.0 ______________________________________________________________________ ### setSettings(...) ``` setSettings(options: SetSettingsOptions) => Promise ``` Set the remote config settings. On Android, the settings values are persisted in SharedPreferences. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetSettingsOptions` | **Since:** 6.2.0 ______________________________________________________________________ ### addConfigUpdateListener(...) ``` addConfigUpdateListener(callback: AddConfigUpdateListenerOptionsCallback) => Promise ``` Add a listener for the config update event. Only available for Android and iOS. | Param | Type | | -------------- | ---------------------------------------- | | **`callback`** | `AddConfigUpdateListenerOptionsCallback` | **Returns:** `Promise` **Since:** 5.4.0 ______________________________________________________________________ ### removeConfigUpdateListener(...) ``` removeConfigUpdateListener(options: RemoveConfigUpdateListenerOptions) => Promise ``` Remove a listener for the config update event. Only available for Android and iOS. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `RemoveConfigUpdateListenerOptions` | **Since:** 5.4.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 5.4.0 ______________________________________________________________________ ### Interfaces #### FetchConfigOptions | Prop | Type | Description | Default | Since | | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`minimumFetchIntervalInSeconds`** | `number` | Define the maximum age in seconds of an entry in the config cache before it is considered stale. During development, it's recommended to set a relatively low minimum fetch interval. Only available for Android and iOS. | `43200` | 1.3.0 | #### GetBooleanResult | Prop | Type | Description | Since | | ------------ | ---------------- | ----------------------------------------------------------------------------------- | ----- | | **`value`** | `boolean` | The value for the given key as a boolean. | 1.3.0 | | **`source`** | `GetValueSource` | Indicates at which source this value came from. Only available for Android and iOS. | 1.3.0 | #### GetOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`key`** | `string` | The key of the value to get. | 1.3.0 | #### GetNumberResult | Prop | Type | Description | Since | | ------------ | ---------------- | ----------------------------------------------------------------------------------- | ----- | | **`value`** | `number` | The value for the given key as a number. | 1.3.0 | | **`source`** | `GetValueSource` | Indicates at which source this value came from. Only available for Android and iOS. | 1.3.0 | #### GetStringResult | Prop | Type | Description | Since | | ------------ | ---------------- | ----------------------------------------------------------------------------------- | ----- | | **`value`** | `string` | The value for the given key as a string. | 1.3.0 | | **`source`** | `GetValueSource` | Indicates at which source this value came from. Only available for Android and iOS. | 1.3.0 | #### GetInfoResult | Prop | Type | Description | Since | | --------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`lastFetchTime`** | `number` | The Unix timestamp in milliseconds of the last successful fetch, or -1 if no fetch has occurred or initialization is incomplete. | 7.5.0 | | **`lastFetchStatus`** | `LastFetchStatus` | The status of the last fetch attempt. | 7.5.0 | #### SetMinimumFetchIntervalOptions | Prop | Type | Description | Default | Since | | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`minimumFetchIntervalInSeconds`** | `number` | Define the maximum age in seconds of an entry in the config cache before it is considered stale. During development, it's recommended to set a relatively low minimum fetch interval. | `43200` | 1.3.0 | #### SetDefaultsOptions | Prop | Type | Description | Since | | -------------- | ------------------------ | ----------- | ---------- | | **`defaults`** | \`Record\\` | #### SetSettingsOptions | Prop | Type | Description | Default | Since | | ----------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`fetchTimeoutInSeconds`** | `number` | Defines the maximum amount of milliseconds to wait for a response when fetching configuration from the Remote Config server. | `60` | 6.2.0 | | **`minimumFetchIntervalInSeconds`** | `number` | Define the maximum age in seconds of an entry in the config cache before it is considered stale. During development, it's recommended to set a relatively low minimum fetch interval. | `43200` | 6.2.0 | #### AddConfigUpdateListenerOptionsCallbackEvent | Prop | Type | Description | Since | | ----------------- | ---------- | ---------------------------------------------------------------------------------- | ----- | | **`updatedKeys`** | `string[]` | Parameter keys whose values have been updated from the currently activated values. | 5.4.0 | #### RemoveConfigUpdateListenerOptions | Prop | Type | Description | Since | | -------- | ------------ | --------------------------------- | ----- | | **`id`** | `CallbackId` | The id of the listener to remove. | 5.4.0 | ### Type Aliases #### GetBooleanOptions `GetOptions` #### GetNumberOptions `GetOptions` #### GetStringOptions `GetOptions` #### AddConfigUpdateListenerOptionsCallback `(event: AddConfigUpdateListenerOptionsCallbackEvent | null, error: any): void` #### CallbackId `string` ### Enums #### GetValueSource | Members | Value | Description | Since | | ------------- | ----- | --------------------------------------------------------------------------------------- | ----- | | **`Static`** | `0` | Indicates that the value returned is the static default value. | 1.3.0 | | **`Default`** | `1` | Indicates that the value returned was retrieved from the defaults set by the client. | 1.3.0 | | **`Remote`** | `2` | Indicates that the value returned was retrieved from the Firebase Remote Config Server. | 1.3.0 | #### LastFetchStatus | Members | Value | | ---------------- | ----- | | **`NoFetchYet`** | `0` | | **`Success`** | `1` | | **`Failure`** | `2` | | **`Throttled`** | `3` | ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/remote-config/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/remote-config/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # ML Kit Plugins This is a list of our Capacitor ML Kit plugins: - [Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) - [Document Scanner](https://capawesome.io/plugins/mlkit/document-scanner/index.md) - [Face Detection](https://capawesome.io/plugins/mlkit/face-detection/index.md) - [Face Mesh Detection](https://capawesome.io/plugins/mlkit/face-mesh-detection/index.md) - [Selfie Segmentation](https://capawesome.io/plugins/mlkit/selfie-segmentation/index.md) - [Subject Segmentation](https://capawesome.io/plugins/mlkit/subject-segmentation/index.md) - [Translation](https://capawesome.io/plugins/mlkit/translation/index.md) # @capacitor-mlkit/barcode-scanning Unofficial Capacitor plugin for [ML Kit Barcode Scanning](https://developers.google.com/ml-kit/vision/barcode-scanning).[1](#fn:1)[2](#fn:2) ## Features - 🧩 Optional ready-to-use interface without webview customizations - 🏎️ Extremely fast - 📷 Scan multiple barcodes at once - ⏺️ Define detection area - 🏞️ Reading barcodes from images - 🔦 Torch and Autofocus support - 🔋 Supports Android, iOS and web For a complete list of **supported barcodes**, see [BarcodeFormat](#barcodeformat). ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Demo A working example can be found here: | Android | | ------- | | | ## Guides - [Announcing the Capacitor ML Kit Barcode Scanning Plugin](https://capawesome.io/blog/announcing-the-capacitor-mlkit-barcode-scanner-plugin/) - [How to build an Ionic Barcode Scanner with Capacitor](https://ionic.io/blog/how-to-build-an-ionic-barcode-scanner-with-capacitor) ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/barcode-scanning` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/barcode-scanning npx cap sync ``` **Attention**: This plugin **only supports CocoaPods** for iOS dependency management. Swift Package Manager (SPM) is not supported for the ML Kit SDK, see [this comment](https://github.com/googlesamples/mlkit/issues/180#issuecomment-1298964099). ### Android #### Permissions This API requires the following permissions be added to your `AndroidManifest.xml` before the `application` tag: ``` ``` You also need to add the following meta data **in** the `application` tag in your `AndroidManifest.xml`: ``` ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$androidxCameraCamera2Version` version of `androidx.camera:camera-camera2` (default: `1.5.2`) - `$androidxCameraCoreVersion` version of `androidx.camera:camera-core` (default: `1.5.2`) - `$androidxCameraLifecycleVersion` version of `androidx.camera:camera-lifecycle` (default: `1.5.2`) - `$androidxCameraViewVersion` version of `androidx.camera:camera-view` (default: `1.5.2`) - `$mlkitBarcodeScanningVersion` version of `com.google.mlkit:barcode-scanning` (default: `17.3.0`) - `$playServicesCodeScannerVersion` version of `com.google.android.gms:play-services-code-scanner` (default: `16.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Minimum Deployment Target Make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`: ``` platform :ios, '15.5' ``` ### Usage Description Add the `NSCameraUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why the app needs to use the camera: ``` + NSCameraUsageDescription + The app enables the scanning of various barcodes. ``` ### Web #### Polyfill This plugin uses the [Barcode Detection API](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API) to scan barcodes in the browser. This API is not yet supported in all browsers. You can check the compatibility [here](https://caniuse.com/mdn-api_barcodedetector). For this reason, we recommend installing the [barcode-detector](https://www.npmjs.com/package/barcode-detector) package for a better compatibility: ``` npm install barcode-detector ``` This package provides a polyfill that uses [ZXing-C++ WebAssembly](https://github.com/Sec-ant/zxing-wasm) under the hood. After installing the package, you just need to import the polyfill in your code: ``` import "barcode-detector/polyfill"; ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-mlkit-plugin-demo](https://github.com/robingenz/capacitor-mlkit-plugin-demo) ## Usage ``` import { BarcodeScanner, BarcodeFormat, LensFacing, } from '@capacitor-mlkit/barcode-scanning'; import { Torch } from '@capawesome/capacitor-torch'; const startScan = async () => { // The camera is visible behind the WebView, so that you can customize the UI in the WebView. // However, this means that you have to hide all elements that should not be visible. // You can find an example in our demo repository. // In this case we set a class `barcode-scanner-active`, which then contains certain CSS rules for our app. document.querySelector('body')?.classList.add('barcode-scanner-active'); // Add the `barcodeScanned` listener const listener = await BarcodeScanner.addListener( 'barcodeScanned', async result => { console.log(result.barcode); }, ); // Start the barcode scanner await BarcodeScanner.startScan(); }; const stopScan = async () => { // Make all elements in the WebView visible again document.querySelector('body')?.classList.remove('barcode-scanner-active'); // Remove all listeners await BarcodeScanner.removeAllListeners(); // Stop the barcode scanner await BarcodeScanner.stopScan(); }; const scanSingleBarcode = async () => { return new Promise(async resolve => { document.querySelector('body')?.classList.add('barcode-scanner-active'); const listener = await BarcodeScanner.addListener( 'barcodeScanned', async result => { await listener.remove(); document .querySelector('body') ?.classList.remove('barcode-scanner-active'); await BarcodeScanner.stopScan(); resolve(result.barcode); }, ); await BarcodeScanner.startScan(); }); }; const scan = async () => { const { barcodes } = await BarcodeScanner.scan({ formats: [BarcodeFormat.QrCode], autoZoom: true, }); return barcodes; }; const isSupported = async () => { const { supported } = await BarcodeScanner.isSupported(); return supported; }; const enableTorch = async () => { await Torch.enable(); }; const disableTorch = async () => { await Torch.disable(); }; const toggleTorch = async () => { await Torch.toggle(); }; const isTorchEnabled = async () => { const { enabled } = await Torch.isEnabled(); return enabled; }; const isTorchAvailable = async () => { const { available } = await Torch.isAvailable(); return available; }; const setZoomRatio = async () => { await BarcodeScanner.setZoomRatio({ zoomRatio: 0.5 }); }; const getZoomRatio = async () => { const { zoomRatio } = await BarcodeScanner.getZoomRatio(); return zoomRatio; }; const getMinZoomRatio = async () => { const { zoomRatio } = await BarcodeScanner.getMinZoomRatio(); return zoomRatio; }; const getMaxZoomRatio = async () => { const { zoomRatio } = await BarcodeScanner.getMaxZoomRatio(); return zoomRatio; }; const openSettings = async () => { await BarcodeScanner.openSettings(); }; const isGoogleBarcodeScannerModuleAvailable = async () => { const { available } = await BarcodeScanner.isGoogleBarcodeScannerModuleAvailable(); return available; }; const installGoogleBarcodeScannerModule = async () => { await BarcodeScanner.installGoogleBarcodeScannerModule(); }; const checkPermissions = async () => { const { camera } = await BarcodeScanner.checkPermissions(); return camera; }; const requestPermissions = async () => { const { camera } = await BarcodeScanner.requestPermissions(); return camera; }; ``` An example of the CSS class `barcode-scanner-active` **with** Ionic Framework could be: ``` // Hide all elements body.barcode-scanner-active { visibility: hidden; --background: transparent; --ion-background-color: transparent; } // Show only the barcode scanner modal .barcode-scanner-modal { visibility: visible; } @media (prefers-color-scheme: dark) { .barcode-scanner-modal { --background: transparent; --ion-background-color: transparent; } } ``` An example of the CSS class `barcode-scanner-active` **without** Ionic Framework could be: ``` // Hide all elements body.barcode-scanner-active { visibility: hidden; } // Show only the barcode scanner modal .barcode-scanner-modal { visibility: visible; } ``` If you can't see the camera view, make sure all elements in the DOM are not visible or have a transparent background to debug the issue. ## API - [`startScan(...)`](#startscan) - [`stopScan()`](#stopscan) - [`readBarcodesFromImage(...)`](#readbarcodesfromimage) - [`scan(...)`](#scan) - [`isSupported()`](#issupported) - [`enableTorch()`](#enabletorch) - [`disableTorch()`](#disabletorch) - [`toggleTorch()`](#toggletorch) - [`isTorchEnabled()`](#istorchenabled) - [`isTorchAvailable()`](#istorchavailable) - [`setZoomRatio(...)`](#setzoomratio) - [`getZoomRatio()`](#getzoomratio) - [`getMinZoomRatio()`](#getminzoomratio) - [`getMaxZoomRatio()`](#getmaxzoomratio) - [`openSettings()`](#opensettings) - [`isGoogleBarcodeScannerModuleAvailable()`](#isgooglebarcodescannermoduleavailable) - [`installGoogleBarcodeScannerModule()`](#installgooglebarcodescannermodule) - [`checkPermissions()`](#checkpermissions) - [`requestPermissions()`](#requestpermissions) - [`addListener('barcodesScanned', ...)`](#addlistenerbarcodesscanned-) - [`addListener('scanError', ...)`](#addlistenerscanerror-) - [`addListener('googleBarcodeScannerModuleInstallProgress', ...)`](#addlistenergooglebarcodescannermoduleinstallprogress-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### startScan(...) ``` startScan(options?: StartScanOptions | undefined) => Promise ``` Start scanning for barcodes. | Param | Type | | ------------- | ------------------ | | **`options`** | `StartScanOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### stopScan() ``` stopScan() => Promise ``` Stop scanning for barcodes. **Since:** 0.0.1 ______________________________________________________________________ ### readBarcodesFromImage(...) ``` readBarcodesFromImage(options: ReadBarcodesFromImageOptions) => Promise ``` Read barcodes from an image. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `ReadBarcodesFromImageOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### scan(...) ``` scan(options?: ScanOptions | undefined) => Promise ``` Scan a barcode with a ready-to-use interface without WebView customization. On **Android**, this method is only available on devices with Google Play Services installed. Therefore, no camera permission is required. **Attention:** Before using this method on *Android*, first check if the Google [Barcode](#barcode) Scanner module is available by using `isGoogleBarcodeScannerModuleAvailable()`. Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `ScanOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### isSupported() ``` isSupported() => Promise ``` Returns whether or not the barcode scanner is supported. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### enableTorch() ``` enableTorch() => Promise ``` Enable camera's torch (flash) during a scan session. Only available on Android and iOS. **Since:** 7.2.0 ______________________________________________________________________ ### disableTorch() ``` disableTorch() => Promise ``` Disable camera's torch (flash) during a scan session. Only available on Android and iOS. **Since:** 7.2.0 ______________________________________________________________________ ### toggleTorch() ``` toggleTorch() => Promise ``` Toggle camera's torch (flash) during a scan session. Only available on Android and iOS. **Since:** 7.2.0 ______________________________________________________________________ ### isTorchEnabled() ``` isTorchEnabled() => Promise ``` Returns whether or not the camera's torch (flash) is enabled. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### isTorchAvailable() ``` isTorchAvailable() => Promise ``` Returns whether or not the camera's torch (flash) is available. Only available on Android and iOS. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### setZoomRatio(...) ``` setZoomRatio(options: SetZoomRatioOptions) => Promise ``` Set the zoom ratio of the camera. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `SetZoomRatioOptions` | **Since:** 5.4.0 ______________________________________________________________________ ### getZoomRatio() ``` getZoomRatio() => Promise ``` Get the zoom ratio of the camera. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.4.0 ______________________________________________________________________ ### getMinZoomRatio() ``` getMinZoomRatio() => Promise ``` Get the minimum zoom ratio of the camera. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.4.0 ______________________________________________________________________ ### getMaxZoomRatio() ``` getMaxZoomRatio() => Promise ``` Get the maximum zoom ratio of the camera. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.4.0 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the settings of the app so that the user can grant the camera permission. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### isGoogleBarcodeScannerModuleAvailable() ``` isGoogleBarcodeScannerModuleAvailable() => Promise ``` Check if the Google [Barcode](#barcode) Scanner module is available. If the Google [Barcode](#barcode) Scanner module is not available, you can install it by using `installGoogleBarcodeScannerModule()`. Only available on Android. **Returns:** `Promise` **Since:** 5.1.0 ______________________________________________________________________ ### installGoogleBarcodeScannerModule() ``` installGoogleBarcodeScannerModule() => Promise ``` Install the Google [Barcode](#barcode) Scanner module. **Attention**: This only starts the installation. The `googleBarcodeScannerModuleInstallProgress` event listener will notify you when the installation is complete. Only available on Android. **Since:** 5.1.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check camera permission. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request camera permission. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('barcodesScanned', ...) ``` addListener(eventName: 'barcodesScanned', listenerFunc: (event: BarcodesScannedEvent) => void) => Promise ``` Called when barcodes are scanned. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'barcodesScanned'` | | **`listenerFunc`** | `(event: BarcodesScannedEvent) => void` | **Returns:** `Promise` **Since:** 6.2.0 ______________________________________________________________________ ### addListener('scanError', ...) ``` addListener(eventName: 'scanError', listenerFunc: (event: ScanErrorEvent) => void) => Promise ``` Called when an error occurs during the scan. Available on Android and iOS. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'scanError'` | | **`listenerFunc`** | `(event: ScanErrorEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('googleBarcodeScannerModuleInstallProgress', ...) ``` addListener(eventName: 'googleBarcodeScannerModuleInstallProgress', listenerFunc: (event: GoogleBarcodeScannerModuleInstallProgressEvent) => void) => Promise ``` Called when the Google [Barcode](#barcode) Scanner module is installed. Available on Android. | Param | Type | | ------------------ | ----------------------------------------------------------------- | | **`eventName`** | `'googleBarcodeScannerModuleInstallProgress'` | | **`listenerFunc`** | `(event: GoogleBarcodeScannerModuleInstallProgressEvent) => void` | **Returns:** `Promise` **Since:** 5.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### StartScanOptions | Prop | Type | Description | Default | Since | | ------------------------------------ | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- | | **`formats`** | `BarcodeFormat[]` | Improve the speed of the barcode scanner by configuring the barcode formats to scan for. Only available on Android and iOS. | | 0.0.1 | | **`lensFacing`** | `LensFacing` | Configure the camera (front or back) to use. | | 0.0.1 | | **`resolution`** | `Resolution` | Configure the resolution of the captured image that is used for barcode scanning. If the resolution is not supported by the device, the closest supported resolution will be used. Only available on Android and iOS. | `Resolution['1280x720']` | 7.0.0 | | **`enableMultitaskingCameraAccess`** | `boolean` | Allow camera usage on iPad while in multitasking mode. Only available on iOS (16.0+). | `false` | 7.5.0 | | **`videoElement`** | `HTMLVideoElement` | The HTML video element to use for the camera preview. Only available on web. | | 7.1.0 | #### ReadBarcodesFromImageResult | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------- | ----- | | **`barcodes`** | `Barcode[]` | The detected barcodes. | 0.0.1 | #### Barcode | Prop | Type | Description | Since | | ------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bytes`** | `number[]` | Raw bytes as it was encoded in the barcode. | 0.0.1 | | **`calendarEvent`** | `BarcodeCalendarEvent` | Calendar event info. | 7.0.0 | | **`contactInfo`** | `BarcodeContactInfo` | Person's or organization's business card. | 7.0.0 | | **`cornerPoints`** | `[[number, number], [number, number], [number, number], [number, number]]` | The four corner points of the barcode in clockwise order starting with top-left. This property is currently only supported by the `startScan(...)` method. | 0.0.1 | | **`displayValue`** | `string` | The barcode value in a human readable format. | 0.0.1 | | **`driverLicense`** | `BarcodeDriverLicense` | Driver license or ID card. | 7.0.0 | | **`email`** | `BarcodeEmail` | An email message from a 'MAILTO:'. | 7.0.0 | | **`format`** | `BarcodeFormat` | The barcode format. | 0.0.1 | | **`geoPoint`** | `BarcodeGeoPoint` | GPS coordinates from a 'GEO:'. | 7.0.0 | | **`phone`** | `BarcodePhone` | Phone number info. | 7.0.0 | | **`rawValue`** | `string` | The barcode value in a machine readable format. This value is only available if the barcode is encoded in the UTF-8 character set. Otherwise, the `bytes` property should be used. | 0.0.1 | | **`sms`** | `BarcodeSms` | A sms message from a 'SMS:'. | 7.0.0 | | **`urlBookmark`** | `BarcodeUrlBookmark` | A URL and title from a 'MEBKM:'. | 7.0.0 | | **`valueType`** | `BarcodeValueType` | The barcode value type. | 0.0.1 | | **`wifi`** | `BarcodeWifi` | A wifi network parameters from a 'WIFI:'. | 7.0.0 | #### BarcodeCalendarEvent | Prop | Type | Description | Since | | ----------------- | -------- | ---------------------------------------- | ----- | | **`description`** | `string` | The event description. | 7.0.0 | | **`end`** | `string` | The event end date as ISO 8601 string. | 7.0.0 | | **`location`** | `string` | The event location. | 7.0.0 | | **`organizer`** | `string` | The event organizer. | 7.0.0 | | **`start`** | `string` | The event start date as ISO 8601 string. | 7.0.0 | | **`status`** | `string` | The event status. | 7.0.0 | | **`summary`** | `string` | The event summary. | 7.0.0 | #### BarcodeContactInfo | Prop | Type | Description | Since | | ------------------ | ---------------- | --------------------------- | ----- | | **`addresses`** | `Address[]` | The contact's addresses. | 7.0.0 | | **`emails`** | `BarcodeEmail[]` | The contact's emails. | 7.0.0 | | **`personName`** | `PersonName` | The contact's name. | 7.0.0 | | **`organization`** | `string` | The contact's organization. | 7.0.0 | | **`phones`** | `BarcodePhone[]` | The contact's phones. | 7.0.0 | | **`title`** | `string` | The contact's title. | 7.0.0 | | **`urls`** | `string[]` | The contact's urls. | 7.0.0 | #### Address | Prop | Type | Description | Since | | ------------------ | ------------- | --------------------------------------------------- | ----- | | **`addressLines`** | `string[]` | Formatted address, multiple lines when appropriate. | 7.0.0 | | **`type`** | `AddressType` | [Address](#address) type. | 7.0.0 | #### BarcodeEmail | Prop | Type | Description | Since | | ------------- | ----------------- | ----------------------- | ----- | | **`address`** | `string` | The email address. | 7.0.0 | | **`body`** | `string` | The email body. | 7.0.0 | | **`subject`** | `string` | The email subject. | 7.0.0 | | **`type`** | `EmailFormatType` | The email address type. | 7.0.0 | #### PersonName | Prop | Type | Description | Since | | ------------------- | -------- | ------------------------------------------------------------------------------------ | ----- | | **`first`** | `string` | First name. | 7.0.0 | | **`formattedName`** | `string` | The formatted name. | 7.0.0 | | **`last`** | `string` | Last name. | 7.0.0 | | **`middle`** | `string` | Middle name. | 7.0.0 | | **`prefix`** | `string` | Name prefix. | 7.0.0 | | **`pronunciation`** | `string` | Text string to be set as the kana name in the phonebook. Used for Japanese contacts. | 7.0.0 | | **`suffix`** | `string` | Name suffix. | 7.0.0 | #### BarcodePhone | Prop | Type | Description | Since | | ------------ | ----------------- | ---------------------- | ----- | | **`number`** | `string` | The phone number. | 7.0.0 | | **`type`** | `PhoneFormatType` | The phone number type. | 7.0.0 | #### BarcodeDriverLicense | Prop | Type | Description | Since | | -------------------- | -------- | -------------------------------------------------- | ----- | | **`addressCity`** | `string` | City of holder's address. | 7.0.0 | | **`addressState`** | `string` | State of holder's address. | 7.0.0 | | **`addressStreet`** | `string` | Street of holder's address. | 7.0.0 | | **`addressZip`** | `string` | Postal code of holder's address. | 7.0.0 | | **`birthDate`** | `string` | Birthdate of the holder. | 7.0.0 | | **`documentType`** | `string` | "DL" for driver's licenses, "ID" for ID cards. | 7.0.0 | | **`expiryDate`** | `string` | Expiration date of the license. | 7.0.0 | | **`firstName`** | `string` | Holder's first name. | 7.0.0 | | **`gender`** | `string` | Holder's gender. | 7.0.0 | | **`issueDate`** | `string` | Issue date of the license. | 7.0.0 | | **`issuingCountry`** | `string` | ISO 3166-1 alpha-3 code in which DL/ID was issued. | 7.0.0 | | **`lastName`** | `string` | Holder's last name. | 7.0.0 | | **`licenseNumber`** | `string` | Driver license ID number. | 7.0.0 | | **`middleName`** | `string` | Holder's middle name. | 7.0.0 | #### BarcodeGeoPoint | Prop | Type | Description | Since | | --------------- | -------- | ----------- | ----- | | **`latitude`** | `number` | Latitude. | 7.0.0 | | **`longitude`** | `number` | Longitude. | 7.0.0 | #### BarcodeSms | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------- | ----- | | **`phoneNumber`** | `string` | The phone number of the sms. | 7.0.0 | | **`message`** | `string` | The message content of the sms. | 7.0.0 | #### BarcodeUrlBookmark | Prop | Type | Description | Since | | ----------- | -------- | -------------------------- | ----- | | **`url`** | `string` | The URL of the bookmark. | 7.0.0 | | **`title`** | `string` | The title of the bookmark. | 7.0.0 | #### BarcodeWifi | Prop | Type | Description | Since | | -------------------- | -------------------- | ----------------------------- | ----- | | **`encryptionType`** | `WifiEncryptionType` | Encryption type of the WI-FI. | 7.0.0 | | **`password`** | `string` | Password of the WI-FI. | 7.0.0 | | **`ssid`** | `string` | SSID of the WI-FI. | 7.0.0 | #### ReadBarcodesFromImageOptions | Prop | Type | Description | Since | | ------------- | ----------------- | ---------------------------------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The blob instance of the image file. Only available on Web. | 7.4.0 | | **`formats`** | `BarcodeFormat[]` | Improve the speed of the barcode scanner by configuring the barcode formats to scan for. | 0.0.1 | | **`path`** | `string` | The local path to the image file. Only available on Android and iOS. | 0.0.1 | #### ScanResult | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------- | ----- | | **`barcodes`** | `Barcode[]` | The detected barcodes. | 0.0.1 | #### ScanOptions | Prop | Type | Description | Since | | -------------- | ----------------- | ---------------------------------------------------------------------------------------- | ----- | | **`formats`** | `BarcodeFormat[]` | Improve the speed of the barcode scanner by configuring the barcode formats to scan for. | 0.0.1 | | **`autoZoom`** | `boolean` | Toggle the auto zoom feature. | 7.4.0 | #### IsSupportedResult | Prop | Type | Description | Since | | --------------- | --------- | --------------------------------------------------------------------------------------- | ----- | | **`supported`** | `boolean` | Whether or not the barcode scanner is supported by checking if the device has a camera. | 0.0.1 | #### IsTorchEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------ | ----- | | **`enabled`** | `boolean` | Whether or not the torch is enabled. | 0.0.1 | #### IsTorchAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not the torch is available. | 0.0.1 | #### SetZoomRatioOptions | Prop | Type | Description | Since | | --------------- | -------- | ---------------------- | ----- | | **`zoomRatio`** | `number` | The zoom ratio to set. | 5.4.0 | #### GetZoomRatioResult | Prop | Type | Description | Since | | --------------- | -------- | --------------- | ----- | | **`zoomRatio`** | `number` | The zoom ratio. | 5.4.0 | #### GetMinZoomRatioResult | Prop | Type | Description | Since | | --------------- | -------- | ----------------------- | ----- | | **`zoomRatio`** | `number` | The minimum zoom ratio. | 5.4.0 | #### GetMaxZoomRatioResult | Prop | Type | Description | Since | | --------------- | -------- | ----------------------- | ----- | | **`zoomRatio`** | `number` | The maximum zoom ratio. | 5.4.0 | #### IsGoogleBarcodeScannerModuleAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not the Google [Barcode](#barcode) Scanner module is available. | 5.1.0 | #### PermissionStatus | Prop | Type | Since | | ------------ | ----------------------- | ----- | | **`camera`** | `CameraPermissionState` | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### BarcodesScannedEvent | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------- | ----- | | **`barcodes`** | `Barcode[]` | The detected barcodes. | 6.2.0 | #### ScanErrorEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 0.0.1 | #### GoogleBarcodeScannerModuleInstallProgressEvent | Prop | Type | Description | Since | | -------------- | ---------------------------------------- | -------------------------------------------------------------- | ----- | | **`state`** | `GoogleBarcodeScannerModuleInstallState` | The current state of the installation. | 5.1.0 | | **`progress`** | `number` | The progress of the installation in percent between 0 and 100. | 5.1.0 | ### Type Aliases #### CameraPermissionState `PermissionState | 'limited'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### BarcodeFormat | Members | Value | Description | Since | | ---------------- | --------------- | ---------------------------------- | ----- | | **`Aztec`** | `'AZTEC'` | Only available on Android and iOS. | 0.0.1 | | **`Codabar`** | `'CODABAR'` | Only available on Android and iOS. | 0.0.1 | | **`Code39`** | `'CODE_39'` | Only available on Android and iOS. | 0.0.1 | | **`Code93`** | `'CODE_93'` | Only available on Android and iOS. | 0.0.1 | | **`Code128`** | `'CODE_128'` | Only available on Android and iOS. | 0.0.1 | | **`DataMatrix`** | `'DATA_MATRIX'` | Only available on Android and iOS. | 0.0.1 | | **`Ean8`** | `'EAN_8'` | Only available on Android and iOS. | 0.0.1 | | **`Ean13`** | `'EAN_13'` | Only available on Android and iOS. | 0.0.1 | | **`Itf`** | `'ITF'` | Only available on Android and iOS. | 0.0.1 | | **`Pdf417`** | `'PDF_417'` | Only available on Android and iOS. | 0.0.1 | | **`QrCode`** | `'QR_CODE'` | Only available on Android and iOS. | 0.0.1 | | **`UpcA`** | `'UPC_A'` | Only available on Android and iOS. | 0.0.1 | | **`UpcE`** | `'UPC_E'` | Only available on Android and iOS. | 0.0.1 | #### LensFacing | Members | Value | Since | | ----------- | --------- | ----- | | **`Front`** | `'FRONT'` | 0.0.1 | | **`Back`** | `'BACK'` | 0.0.1 | #### Resolution | Members | Value | Since | | ----------------- | ----- | ----- | | **`'640x480'`** | `0` | 7.0.0 | | **`'1280x720'`** | `1` | 7.0.0 | | **`'1920x1080'`** | `2` | 7.0.0 | | **`'3840x2160'`** | `3` | 7.2.0 | #### AddressType | Members | Value | Since | | ------------- | ----- | ----- | | **`HOME`** | `0` | 7.0.0 | | **`UNKNOWN`** | `1` | 7.0.0 | | **`WORK`** | `2` | 7.0.0 | #### EmailFormatType | Members | Value | Since | | ------------- | ----- | ----- | | **`HOME`** | `0` | 7.0.0 | | **`UNKNOWN`** | `1` | 7.0.0 | | **`WORK`** | `2` | 7.0.0 | #### PhoneFormatType | Members | Value | Since | | ------------- | ----- | ----- | | **`FAX`** | `0` | 7.0.0 | | **`HOME`** | `1` | 7.0.0 | | **`MOBILE`** | `2` | 7.0.0 | | **`UNKNOWN`** | `3` | 7.0.0 | | **`WORK`** | `4` | 7.0.0 | #### BarcodeValueType | Members | Value | Since | | -------------------- | ------------------- | ----- | | **`CalendarEvent`** | `'CALENDAR_EVENT'` | 0.0.1 | | **`ContactInfo`** | `'CONTACT_INFO'` | 0.0.1 | | **`DriversLicense`** | `'DRIVERS_LICENSE'` | 0.0.1 | | **`Email`** | `'EMAIL'` | 0.0.1 | | **`Geo`** | `'GEO'` | 0.0.1 | | **`Isbn`** | `'ISBN'` | 0.0.1 | | **`Phone`** | `'PHONE'` | 0.0.1 | | **`Product`** | `'PRODUCT'` | 0.0.1 | | **`Sms`** | `'SMS'` | 0.0.1 | | **`Text`** | `'TEXT'` | 0.0.1 | | **`Url`** | `'URL'` | 0.0.1 | | **`Wifi`** | `'WIFI'` | 0.0.1 | | **`Unknown`** | `'UNKNOWN'` | 0.0.1 | #### WifiEncryptionType | Members | Value | Since | | ---------- | ----- | ----- | | **`OPEN`** | `1` | 7.0.0 | | **`WEP`** | `2` | 7.0.0 | | **`WPA`** | `3` | 7.0.0 | #### GoogleBarcodeScannerModuleInstallState | Members | Value | Since | | --------------------- | ----- | ----- | | **`UNKNOWN`** | `0` | 5.1.0 | | **`PENDING`** | `1` | 5.1.0 | | **`DOWNLOADING`** | `2` | 5.1.0 | | **`CANCELED`** | `3` | 5.1.0 | | **`COMPLETED`** | `4` | 5.1.0 | | **`FAILED`** | `5` | 5.1.0 | | **`INSTALLING`** | `6` | 5.1.0 | | **`DOWNLOAD_PAUSED`** | `7` | 5.1.0 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/barcode-scanning/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/barcode-scanning/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") 1. `QR Code` is a registered trademark of DENSO WAVE INCORPORATED. [↩](#fnref:2 "Jump back to footnote 2 in the text") # @capacitor-mlkit/document-scanner Unofficial Capacitor plugin for [ML Kit Document Scanner](https://developers.google.com/ml-kit/vision/doc-scanner).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/document-scanner` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/document-scanner npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$mlkitDocumentScannerVersion` version of `com.google.android.gms:play-services-mlkit-document-scanner` (default: `16.0.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Usage ``` import { DocumentScanner } from '@capacitor-mlkit/document-scanner'; const scanDocument = async () => { const result = await DocumentScanner.scanDocument({ galleryImportAllowed: true, pageLimit: 5, resultFormats: 'JPEG_PDF', scannerMode: 'FULL', }); console.log('Scanned images:', result.scannedImages); console.log('PDF info:', result.pdf); }; const isGoogleDocumentScannerModuleAvailable = async () => { const result = await DocumentScanner.isGoogleDocumentScannerModuleAvailable(); console.log('Is Google Document Scanner module available:', result.available); }; const installGoogleDocumentScannerModule = async () => { await DocumentScanner.installGoogleDocumentScannerModule(); console.log('Google Document Scanner module installation started.'); }; DocumentScanner.addListener('googleDocumentScannerModuleInstallProgress', (event) => { console.log('Installation progress:', event.progress, '%'); console.log('Current state:', event.state); }); ``` ## API - [`scanDocument(...)`](#scandocument) - [`isGoogleDocumentScannerModuleAvailable()`](#isgoogledocumentscannermoduleavailable) - [`installGoogleDocumentScannerModule()`](#installgoogledocumentscannermodule) - [`addListener('googleDocumentScannerModuleInstallProgress', ...)`](#addlistenergoogledocumentscannermoduleinstallprogress-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### scanDocument(...) ``` scanDocument(options: ScanOptions) => Promise ``` Starts the document scanning process. Only available on Android. | Param | Type | | ------------- | ------------- | | **`options`** | `ScanOptions` | **Returns:** `Promise` **Since:** 7.3.0 ______________________________________________________________________ ### isGoogleDocumentScannerModuleAvailable() ``` isGoogleDocumentScannerModuleAvailable() => Promise ``` Check if the Google Document Scanner module is available. If the Google Document Scanner module is not available, you can install it by using `installGoogleDocumentScannerModule()`. Only available on Android. **Returns:** `Promise` **Since:** 7.3.0 ______________________________________________________________________ ### installGoogleDocumentScannerModule() ``` installGoogleDocumentScannerModule() => Promise ``` Install the Google Document Scanner module. **Attention**: This only starts the installation. The `googleDocumentScannerModuleInstallProgress` event listener will notify you when the installation is complete. Only available on Android. **Since:** 7.3.0 ______________________________________________________________________ ### addListener('googleDocumentScannerModuleInstallProgress', ...) ``` addListener(eventName: 'googleDocumentScannerModuleInstallProgress', listenerFunc: (event: GoogleDocumentScannerModuleInstallProgressEvent) => void) => Promise ``` Called when the Google Document Scanner module is installed. Only available on Android. | Param | Type | | ------------------ | ------------------------------------------------------------------ | | **`eventName`** | `'googleDocumentScannerModuleInstallProgress'` | | **`listenerFunc`** | `(event: GoogleDocumentScannerModuleInstallProgressEvent) => void` | **Returns:** `Promise` **Since:** 7.3.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available on Android. **Since:** 7.3.0 ______________________________________________________________________ ### Interfaces #### ScanResult Result of a document scan operation. | Prop | Type | Description | Since | | ------------------- | ---------- | -------------------------------------------------------------------------------------------------------------------- | ----- | | **`scannedImages`** | `string[]` | An array of URIs for the scanned image pages (JPEG). Present if 'JPEG' or 'JPEG_PDF' was requested in resultFormats. | 7.3.0 | | **`pdf`** | `PdfInfo` | Information about the generated PDF. Present if 'PDF' or 'JPEG_PDF' was requested in resultFormats. | 7.3.0 | #### PdfInfo | Prop | Type | Description | Since | | --------------- | -------- | ---------------------------------- | ----- | | **`uri`** | `string` | The URI of the generated PDF file. | 7.3.0 | | **`pageCount`** | `number` | The number of pages in the PDF. | 7.3.0 | #### ScanOptions | Prop | Type | Description | Default | Since | | -------------------------- | --------- | -------------------------------------------------- | -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`galleryImportAllowed`** | `boolean` | Whether to allow importing from the photo gallery. | `false` | 7.3.0 | | **`pageLimit`** | `number` | The maximum number of pages that can be scanned. | `10` | 7.3.0 | | **`resultFormats`** | \`'JPEG' | 'PDF' | 'JPEG_PDF'\` | The desired result formats. Can be 'JPEG', 'PDF', or 'JPEG_PDF'. | | **`scannerMode`** | \`'FULL' | 'BASE' | 'BASE_WITH_FILTER'\` | The scanner mode. BASE: Basic editing capabilities (crop, rotate, reorder pages, etc.). BASE_WITH_FILTER: Adds image filters (grayscale, auto image enhancement, etc.) to the BASE mode. FULL: Adds ML-enabled image cleaning capabilities (erase stains, fingers, etc.) to the BASE_WITH_FILTER mode. This mode will also allow future major features to be automatically added along with Google Play services updates, while the other two modes will maintain their current feature sets and only receive minor refinements. | #### IsGoogleDocumentScannerModuleAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | --------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not the Google Document Scanner module is available. | 7.3.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### GoogleDocumentScannerModuleInstallProgressEvent | Prop | Type | Description | Since | | -------------- | ----------------------------------------- | -------------------------------------------------------------- | ----- | | **`state`** | `GoogleDocumentScannerModuleInstallState` | The current state of the installation. | 7.3.0 | | **`progress`** | `number` | The progress of the installation in percent between 0 and 100. | 7.3.0 | ### Enums #### GoogleDocumentScannerModuleInstallState | Members | Value | Since | | --------------------- | ----- | ----- | | **`UNKNOWN`** | `0` | 7.3.0 | | **`PENDING`** | `1` | 7.3.0 | | **`DOWNLOADING`** | `2` | 7.3.0 | | **`CANCELED`** | `3` | 7.3.0 | | **`COMPLETED`** | `4` | 7.3.0 | | **`FAILED`** | `5` | 7.3.0 | | **`INSTALLING`** | `6` | 7.3.0 | | **`DOWNLOAD_PAUSED`** | `7` | 7.3.0 | ## Notes - The ML Kit Document Scanner models, scanning logic, and UI flow are dynamically downloaded by Google Play services. Users might have to wait for these to download before the first use. You can use the isGoogleDocumentScannerModuleAvailable and installGoogleDocumentScannerModule methods to check for and install the module, and listen to the googleDocumentScannerModuleInstallProgress event for progress updates. - This API requires Android API level 21 or above. - It also requires a minimal device total RAM of 1.7GB. If lower, it returns an `MlKitException` with error code `UNSUPPORTED` when calling the API (this plugin will reject the promise). - Consider that generating document files takes time and requires processing power, so only request the output formats (JPEG, or PDF, or both) you actually need via the `resultFormats` option. ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/document-scanner/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/document-scanner/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-mlkit/face-detection Unofficial Capacitor plugin for [ML Kit Face Detection](https://developers.google.com/ml-kit/vision/face-detection).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/face-detection` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/face-detection npx cap sync ``` **Attention**: This plugin **only supports CocoaPods** for iOS dependency management. Swift Package Manager (SPM) is not supported for the ML Kit SDK, see [this comment](https://github.com/googlesamples/mlkit/issues/180#issuecomment-1298964099). ### Android #### Dependencies You need to add the following meta data **in** the `application` tag in your `AndroidManifest.xml`: ``` ``` #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$mlkitFaceDetectionVersion` version of `com.google.mlkit:face-detection` (default: `16.1.7`) - `$playServicesMlkitFaceDetectionVersion` version of `com.google.android.gms:play-services-mlkit-face-detection` (default: `17.1.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Minimum Deployment Target Make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`: ``` platform :ios, '15.5' ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-mlkit-plugin-demo](https://github.com/robingenz/capacitor-mlkit-plugin-demo) ## Usage ``` import { FaceDetection, PerformanceMode, LandmarkMode, ContourMode, ClassificationMode } from '@capacitor-mlkit/face-detection'; const processImage = async () => { const { faces } = await FaceDetection.processImage({ path: 'path/to/image.jpg', performanceMode: PerformanceMode.Fast, landmarkMode: LandmarkMode.All, contourMode: ContourMode.All, classificationMode: ClassificationMode.All, minFaceSize: 0.1, enableTracking: false, }); return faces; }; ``` ## API - [`processImage(...)`](#processimage) - [Interfaces](#interfaces) - [Enums](#enums) ### processImage(...) ``` processImage(options: ProcessImageOptions) => Promise ``` Detects human faces from the supplied image. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ProcessImageOptions` | **Returns:** `Promise` **Since:** 5.1.0 ______________________________________________________________________ ### Interfaces #### ProcessImageResult | Prop | Type | Description | Since | | ----------- | -------- | ------------------- | ----- | | **`faces`** | `Face[]` | The detected faces. | 5.1.0 | #### Face Represents a face detected by `FaceDetector`. | Prop | Type | Description | Since | | ----------------------------- | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bounds`** | `Rect` | Returns the axis-aligned bounding rectangle of the detected face. | 5.1.0 | | **`landmarks`** | `FaceLandmark[]` | Returns a list of face landmarks. | 5.1.0 | | **`contours`** | `FaceContour[]` | Returns a list of face contours. | 5.1.0 | | **`trackingId`** | `number` | Returns the tracking ID if the tracking is enabled. | 5.1.0 | | **`headEulerAngleX`** | `number` | Returns the rotation of the face about the horizontal axis of the image. Positive euler X is the face is looking up. | 5.1.0 | | **`headEulerAngleY`** | `number` | Returns the rotation of the face about the vertical axis of the image. Positive euler y is when the face turns toward the right side of the image that is being processed. | 5.1.0 | | **`headEulerAngleZ`** | `number` | Returns the rotation of the face about the axis pointing out of the image. Positive euler z is a counter-clockwise rotation within the image plane. | 5.1.0 | | **`smilingProbability`** | `number` | Returns a value between 0.0 and 1.0 giving a probability that the face is smiling. | 5.1.0 | | **`leftEyeOpenProbability`** | `number` | Returns a value between 0.0 and 1.0 giving a probability that the face's left eye is open. | 5.1.0 | | **`rightEyeOpenProbability`** | `number` | Returns a value between 0.0 and 1.0 giving a probability that the face's right eye is open. | 5.1.0 | #### Rect [Rect](#rect) holds four integer coordinates for a rectangle. | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------------- | ----- | | **`left`** | `number` | The X coordinate of the left side of the rectangle. | 5.1.0 | | **`top`** | `number` | The Y coordinate of the top of the rectangle. | 5.1.0 | | **`right`** | `number` | The X coordinate of the right side of the rectangle. | 5.1.0 | | **`bottom`** | `number` | The Y coordinate of the bottom of the rectangle. | 5.1.0 | #### FaceLandmark Represent a face landmark. A landmark is a point on a detected face, such as an eye, nose, or mouth. | Prop | Type | Description | Since | | -------------- | -------------- | ------------------------------------------------------------------------------------------ | ----- | | **`type`** | `LandmarkType` | Gets the [FaceLandmark](#facelandmark).[LandmarkType](#landmarktype) type. | 5.1.0 | | **`position`** | `Point` | Gets a 2D point for landmark position, where (0, 0) is the upper-left corner of the image. | 5.1.0 | #### Point [Point](#point) holds two coordinates | Prop | Type | | ------- | -------- | | **`x`** | `number` | | **`y`** | `number` | #### FaceContour Represent a face contour. A contour is a list of points on a detected face, such as the mouth. | Prop | Type | Description | Since | | ------------ | ------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`type`** | `ContourType` | Gets the [FaceContour](#facecontour).[ContourType](#contourtype) type. | 5.1.0 | | **`points`** | `Point[]` | Gets a list of 2D points for this face contour, where (0, 0) is the upper-left corner of the image. | 5.1.0 | #### ProcessImageOptions | Prop | Type | Description | Default | Since | | ------------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------- | ----- | | **`path`** | `string` | The local path to the image file. | | 5.1.0 | | **`performanceMode`** | `PerformanceMode` | Defines options to control accuracy / speed trade-offs in performing face detection. | `PerformanceMode.Fast` | 5.1.0 | | **`landmarkMode`** | `LandmarkMode` | Defines options to enable face landmarks or not. | `LandmarkMode.None` | 5.1.0 | | **`contourMode`** | `ContourMode` | Defines options to enable face contours or not. | `ContourMode.None` | 5.1.0 | | **`classificationMode`** | `ClassificationMode` | Defines options for characterizing attributes such as "smiling" * and "eyes open". | `ClassificationMode.None` | 5.1.0 | | **`minFaceSize`** | `number` | Sets the smallest desired face size, expressed as a proportion of the width of the head to the image width. | `0.1` | 5.1.0 | | **`enableTracking`** | `boolean` | Enables face tracking, which will maintain a consistent ID for each face when processing consecutive frames. Tracking should be disabled for handling a series of non-consecutive still images. | `false` | 5.1.0 | ### Enums #### LandmarkType | Members | Value | Description | Since | | ----------------- | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`MouthBottom`** | `0` | The center of the subject's bottom lip. | 5.1.0 | | **`LeftCheek`** | `1` | The midpoint between the subject's left mouth corner and the outer corner of the subject's left eye. For full profile faces, this becomes the centroid of the nose base, nose tip, left ear lobe and left ear tip. | 5.1.0 | | **`LeftEar`** | `3` | The midpoint of the subject's left ear tip and left ear lobe. | 5.1.0 | | **`LeftEye`** | `4` | The center of the subject's left eye cavity. | 5.1.0 | | **`MouthLeft`** | `5` | The subject's left mouth corner where the lips meet. | 5.1.0 | | **`NoseBase`** | `6` | The midpoint between the subject's nostrils where the nose meets the face. | 5.1.0 | | **`RightCheek`** | `7` | The midpoint between the subject's right mouth corner and the outer corner of the subject's right eye. For full profile faces, this becomes the centroid of the nose base, nose tip, right ear lobe and right ear tip. | 5.1.0 | | **`RightEar`** | `9` | The midpoint of the subject's right ear tip and right ear lobe. | 5.1.0 | | **`RightEye`** | `10` | The center of the subject's right eye cavity. | 5.1.0 | | **`MouthRight`** | `11` | The subject's right mouth corner where the lips meet. | 5.1.0 | #### ContourType | Members | Value | Description | Since | | ------------------------ | ----- | -------------------------------------------------- | ----- | | **`Face`** | `1` | The outline of the subject's face. | 5.1.0 | | **`LeftEyebrowTop`** | `2` | The top outline of the subject's left eyebrow. | 5.1.0 | | **`LeftEyebrowBottom`** | `3` | The bottom outline of the subject's left eyebrow. | 5.1.0 | | **`RightEyebrowTop`** | `4` | The top outline of the subject's right eyebrow. | 5.1.0 | | **`RightEyebrowBottom`** | `5` | The bottom outline of the subject's right eyebrow. | 5.1.0 | | **`LeftEye`** | `6` | The outline of the subject's left eye cavity. | 5.1.0 | | **`RightEye`** | `7` | The outline of the subject's right eye cavity. | 5.1.0 | | **`UpperLipTop`** | `8` | The top outline of the subject's upper lip. | 5.1.0 | | **`UpperLipBottom`** | `9` | The bottom outline of the subject's upper lip. | 5.1.0 | | **`LowerLipTop`** | `10` | The top outline of the subject's lower lip. | 5.1.0 | | **`LowerLipBottom`** | `11` | The bottom outline of the subject's lower lip. | 5.1.0 | | **`NoseBridge`** | `12` | The outline of the subject's nose bridge. | 5.1.0 | | **`NoseBottom`** | `13` | The outline of the subject's nose bridge. | 5.1.0 | | **`LeftCheek`** | `14` | The center of the left cheek. | 5.1.0 | | **`RightCheek`** | `15` | The center of the right cheek. | 5.1.0 | #### PerformanceMode | Members | Value | Description | Since | | -------------- | ----- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Fast`** | `1` | Indicates a preference for speed in the options that may make an accuracy vs. speed trade-off. This will tend to detect fewer faces and may be less precise in determining values such as position, but will run faster. | 5.1.0 | | **`Accurate`** | `2` | Indicates a preference for accuracy in the options that may make an accuracy vs. speed trade-off. This will tend to detect more faces and may be more precise in determining values such as position, at the cost of speed. | 5.1.0 | #### LandmarkMode | Members | Value | Description | Since | | ---------- | ----- | ------------------------------------------------------- | ----- | | **`None`** | `1` | Does not perform landmark detection. | 5.1.0 | | **`All`** | `2` | Detects [FaceLandmark](#facelandmark) for a given face. | 5.1.0 | #### ContourMode | Members | Value | Description | Since | | ---------- | ----- | ---------------------------------------------------------------------------------------------------------- | ----- | | **`None`** | `1` | Does not perform contour detection. | 5.1.0 | | **`All`** | `2` | Detects [FaceContour](#facecontour) for a given face. Note that it would return contours for up to 5 faces | 5.1.0 | #### ClassificationMode | Members | Value | Description | Since | | ---------- | ----- | -------------------------------------------------- | ----- | | **`None`** | `1` | Does not perform classification. | 5.1.0 | | **`All`** | `2` | Performs "eyes open" and "smiling" classification. | 5.1.0 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-detection/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-detection/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-mlkit/face-mesh-detection Unofficial Capacitor plugin for [ML Kit Face Mesh Detection](https://developers.google.com/ml-kit/vision/face-mesh-detection).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 6.x.x | 6.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/face-mesh-detection` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/face-mesh-detection npx cap sync ``` ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$mlkitFaceMeshDetectionVersion` version of `com.google.mlkit:face-mesh-detection` (default: `16.0.0-beta1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-mlkit-plugin-demo](https://github.com/robingenz/capacitor-mlkit-plugin-demo) ## Usage ``` import { FaceMeshDetection, UseCase } from '@capacitor-mlkit/face-mesh-detection'; const processImage = async () => { const { faceMeshs } = await FaceMeshDetection.processImage({ path: 'path/to/image.jpg', useCase: UseCase.FaceMesh, }); return faceMeshs; }; ``` ## API - [`processImage(...)`](#processimage) - [Interfaces](#interfaces) - [Enums](#enums) ### processImage(...) ``` processImage(options: ProcessImageOptions) => Promise ``` Detects face mesh from the supplied image. Only available on Android. | Param | Type | | ------------- | --------------------- | | **`options`** | `ProcessImageOptions` | **Returns:** `Promise` **Since:** 5.3.0 ______________________________________________________________________ ### Interfaces #### ProcessImageResult | Prop | Type | Description | Since | | --------------- | ------------ | ------------------------ | ----- | | **`faceMeshs`** | `FaceMesh[]` | The detected face meshs. | 5.3.0 | #### FaceMesh Represents a face mesh detected by `FaceMeshDetector`. When `BoundingBoxOnly` is selected, [`FaceMesh`](#facemesh) only contains valid bounding box. When [`FaceMesh`](#facemesh) is selected, [`FaceMesh`](#facemesh) also contains a group of 468 3D face mesh points and related triangle information. Each point is represented by [`FaceMeshPoint`](#facemeshpoint) describing a specific position in detected face. The triangle information is a group of 3 `FaceMeshPoint`s representing a valid surface on Face (e.g. a valid small surface on nose tip). | Prop | Type | Description | Since | | -------------------- | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bounds`** | `Rect` | Returns the axis-aligned bounding rectangle of the detected face mesh. | 5.3.0 | | **`contours`** | `Contours` | Returns contours with a list of [`FaceMeshPoint`](#facemeshpoint) representing the detected face. | 5.3.0 | | **`faceMeshPoints`** | `FaceMeshPoint[]` | Returns a list of [`FaceMeshPoint`](#facemeshpoint) representing the whole detected face. | 5.3.0 | | **`triangles`** | `Triangle[]` | Returns a list of [`Triangle`](#triangle) representing logical triangle surfaces of detected face. Each [`Triangle`](#triangle) contains 3 [`FaceMeshPoint`](#facemeshpoint), representing 3 points of the triangle surface. The sequence of the 3 points are constant and always counter clockwise in face mesh. | 5.3.0 | #### Rect [Rect](#rect) holds four integer coordinates for a rectangle. | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------------- | ----- | | **`left`** | `number` | The X coordinate of the left side of the rectangle. | 5.3.0 | | **`top`** | `number` | The Y coordinate of the top of the rectangle. | 5.3.0 | | **`right`** | `number` | The X coordinate of the right side of the rectangle. | 5.3.0 | | **`bottom`** | `number` | The Y coordinate of the bottom of the rectangle. | 5.3.0 | #### Contours Represents contours with their face mesh points. | Prop | Type | Description | Since | | ------------------------ | ----------------- | -------------------------------------------------------- | ----- | | **`faceOval`** | `FaceMeshPoint[]` | Returns all points for the `FaceOval` contour. | 5.3.0 | | **`leftEyebrowTop`** | `FaceMeshPoint[]` | Returns all points for the `LeftEyebrowTop` contour. | 5.3.0 | | **`leftEyebrowBottom`** | `FaceMeshPoint[]` | Returns all points for the `LeftEyebrowBottom` contour. | 5.3.0 | | **`rightEyebrowTop`** | `FaceMeshPoint[]` | Returns all points for the `RightEyebrowTop` contour. | 5.3.0 | | **`rightEyebrowBottom`** | `FaceMeshPoint[]` | Returns all points for the `RightEyebrowBottom` contour. | 5.3.0 | | **`leftEye`** | `FaceMeshPoint[]` | Returns all points for the `LeftEye` contour. | 5.3.0 | | **`rightEye`** | `FaceMeshPoint[]` | Returns all points for the `RightEye` contour. | 5.3.0 | | **`upperLipTop`** | `FaceMeshPoint[]` | Returns all points for the `UpperLipTop` contour. | 5.3.0 | | **`upperLipBottom`** | `FaceMeshPoint[]` | Returns all points for the `UpperLipBottom` contour. | 5.3.0 | | **`lowerLipTop`** | `FaceMeshPoint[]` | Returns all points for the `LowerLipTop` contour. | 5.3.0 | | **`lowerLipBottom`** | `FaceMeshPoint[]` | Returns all points for the `LowerLipBottom` contour. | 5.3.0 | | **`noseBridge`** | `FaceMeshPoint[]` | Returns all points for the `NoseBridge` contour. | 5.3.0 | #### FaceMeshPoint Represents a 3D point in face mesh. The index is an unique ID meaning a fixed position on face, ranging from 0 to 467. In [`Point3D`](#point3d), `x` and `y` are pixel location of detected face in `InputImage`. `z` is also scaled to image size, while the origin will be somewhere in the center of all 468 face mesh points. | Prop | Type | Description | Since | | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`index`** | `number` | Gets the index of the face mesh point, ranging from 0 to 467. For each specific point, the index is a constant value. | 5.3.0 | | **`point`** | `Point3D` | Gets a 3D point in face mesh. Inside [`Point3D`](#point3d), `X` and `Y` means a 2D position in original image. More information on the `Z` value: - The unit of measure for the `Z` value is the same as `X` and `Y`. - The smaller the `Z` value, the closer that landmark is to the camera. - The `Z` origin is approximately at the center of all 468 face mesh points. `Z` value will be negative if the point is close to camera and will be positive if the point is away from the camera. | 5.3.0 | #### Point3D Represents a 3D point. | Prop | Type | Description | Since | | ------- | -------- | --------------------------------- | ----- | | **`x`** | `number` | Returns the X value of the point. | 5.3.0 | | **`y`** | `number` | Returns the Y value of the point. | 5.3.0 | | **`z`** | `number` | Returns the Z value of the point. | 5.3.0 | #### Triangle Represents a triangle with 3 generic points. | Prop | Type | Description | Since | | ------------ | ----------------- | ------------------------------------------------------ | ----- | | **`points`** | `FaceMeshPoint[]` | Returns all points inside the [`Triangle`](#triangle). | 5.3.0 | #### ProcessImageOptions | Prop | Type | Description | Default | Since | | ------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | | **`path`** | `string` | The local path to the image file. | | 5.3.0 | | **`useCase`** | `UseCase` | Sets the use case. When `BoundingBoxOnly` is selected, the returned [`FaceMesh`](#facemesh) only contains bounding box. When [`FaceMesh`](#facemesh) is selected, the returned [`FaceMesh`](#facemesh) contains bounding box as well as 468 [`FaceMeshPoint`](#facemeshpoint) and triangle information. It detects at most 2 faces in this case and it is slower than `BoundingBoxOnly`. | `UseCase.FaceMesh` | 5.3.0 | ### Enums #### UseCase | Members | Value | Description | Since | | --------------------- | ----- | ------------------------------------------------------------------------------------- | ----- | | **`BoundingBoxOnly`** | `0` | Return bounding box for detected face. | 5.3.0 | | **`FaceMesh`** | `1` | Return face mesh info for detected face. It detects at most 2 faces in this use case. | 5.3.0 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-mesh-detection/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-mesh-detection/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-mlkit/selfie-segmentation Unofficial Capacitor plugin for [ML Kit Selfie Segmentation](https://developers.google.com/ml-kit/vision/selfie-segmentation).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/selfie-segmentation` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/selfie-segmentation npx cap sync ``` **Attention**: This plugin **only supports CocoaPods** for iOS dependency management. Swift Package Manager (SPM) is not supported for the ML Kit SDK, see [this comment](https://github.com/googlesamples/mlkit/issues/180#issuecomment-1298964099). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$mlkitSelfieSegmentationVersion` version of `com.google.mlkit:segmentation-selfie` (default: `16.0.0-beta6`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Minimum Deployment Target Make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`: ``` platform :ios, '15.5' ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-mlkit-plugin-demo](https://github.com/robingenz/capacitor-mlkit-plugin-demo) | Android | iOS | | ------- | --- | | | | ## Usage ``` import { SelfieSegmentation } from '@capacitor-mlkit/selfie-segmentation'; const processImage = async () => { const { path } = await SelfieSegmentation.processImage({ path: 'path/to/image.jpg', confidence: 0.7, }); return path; }; ``` ## API - [`processImage(...)`](#processimage) - [Interfaces](#interfaces) ### processImage(...) ``` processImage(options: ProcessImageOptions) => Promise ``` Performs segmentation on an input image. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ProcessImageOptions` | **Returns:** `Promise` **Since:** 5.2.0 ______________________________________________________________________ ### Interfaces #### ProcessImageResult | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------- | ----- | | **`path`** | `string` | The path to the segmented image file. | 5.2.0 | | **`width`** | `number` | Returns the width of the image file. | 5.2.0 | | **`height`** | `number` | Returns the height of the image file. | 5.2.0 | #### ProcessImageOptions | Prop | Type | Description | Default | Since | | ---------------- | -------- | ----------------------------------------------------------------------------------------- | ------- | ----- | | **`path`** | `string` | The local path to the image file. | | 5.2.0 | | **`width`** | `number` | Scale the image to this width. If no `height` is given, it will respect the aspect ratio. | | 5.2.0 | | **`height`** | `number` | Scale the image to this height. If no `width` is given, it will respect the aspect ratio. | | 5.2.0 | | **`confidence`** | `number` | Sets the confidence threshold. | `0.9` | 5.2.0 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/selfie-segmentation/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/selfie-segmentation/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-mlkit/subject-segmentation Unofficial Capacitor plugin for [ML Kit Subject Segmentation](https://developers.google.com/ml-kit/vision/subject-segmentation).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/subject-segmentation` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/subject-segmentation npx cap sync ``` ### Android #### API level This plugin requires a minimum API level of 24. #### Permissions This API requires the following permissions be added to your `AndroidManifest.xml` before the `application` tag: ``` ``` You also need to add the following meta data **in** the `application` tag in your `AndroidManifest.xml`: ``` ``` ## Usage ``` import { SubjectSegmentation } from '@capacitor-mlkit/subject-segmentation'; const processImage = async () => { const { path } = await SubjectSegmentation.processImage({ path: 'path/to/image.jpg', confidence: 0.7, }); return path; }; ``` ## API - [`processImage(...)`](#processimage) - [`isGoogleSubjectSegmentationModuleAvailable()`](#isgooglesubjectsegmentationmoduleavailable) - [`installGoogleSubjectSegmentationModule()`](#installgooglesubjectsegmentationmodule) - [`addListener('googleSubjectSegmentationModuleInstallProgress', ...)`](#addlistenergooglesubjectsegmentationmoduleinstallprogress-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### processImage(...) ``` processImage(options: ProcessImageOptions) => Promise ``` Performs segmentation on an input image. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ProcessImageOptions` | **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### isGoogleSubjectSegmentationModuleAvailable() ``` isGoogleSubjectSegmentationModuleAvailable() => Promise ``` Check if the Google Subject Segmentation module is available. If the Google Subject Segmentation module is not available, you can install it by using `installGoogleSubjectSegmentationModule()`. Only available on Android. **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### installGoogleSubjectSegmentationModule() ``` installGoogleSubjectSegmentationModule() => Promise ``` Install the Google Subject Segmentation module. **Attention**: This only starts the installation. The `googleSubjectSegmentationModuleInstallProgress` event listener will notify you when the installation is complete. Only available on Android. **Since:** 7.2.0 ______________________________________________________________________ ### addListener('googleSubjectSegmentationModuleInstallProgress', ...) ``` addListener(eventName: 'googleSubjectSegmentationModuleInstallProgress', listenerFunc: (event: GoogleSubjectSegmentationModuleInstallProgressEvent) => void) => Promise ``` Called when the Google Subject Segmentation module is installed. Only available on Android. | Param | Type | | ------------------ | ---------------------------------------------------------------------- | | **`eventName`** | `'googleSubjectSegmentationModuleInstallProgress'` | | **`listenerFunc`** | `(event: GoogleSubjectSegmentationModuleInstallProgressEvent) => void` | **Returns:** `Promise` **Since:** 7.2.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available on Android. **Since:** 7.2.0 ______________________________________________________________________ ### Interfaces #### ProcessImageResult | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------- | ----- | | **`path`** | `string` | The path to the segmented image file. | 7.2.0 | | **`width`** | `number` | Returns the width of the image file. | 7.2.0 | | **`height`** | `number` | Returns the height of the image file. | 7.2.0 | #### ProcessImageOptions | Prop | Type | Description | Default | Since | | ---------------- | -------- | ----------------------------------------------------------------------------------------- | ------- | ----- | | **`path`** | `string` | The local path to the image file. | | 7.2.0 | | **`width`** | `number` | Scale the image to this width. If no `height` is given, it will respect the aspect ratio. | | 7.2.0 | | **`height`** | `number` | Scale the image to this height. If no `width` is given, it will respect the aspect ratio. | | 7.2.0 | | **`confidence`** | `number` | Sets the confidence threshold. | `0.9` | 7.2.0 | #### IsGoogleSubjectSegmentationModuleAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not the Google Subject Segmentation module is available. | 7.2.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### GoogleSubjectSegmentationModuleInstallProgressEvent | Prop | Type | Description | Since | | -------------- | --------------------------------------------- | -------------------------------------------------------------- | ----- | | **`state`** | `GoogleSubjectSegmentationModuleInstallState` | The current state of the installation. | 7.2.0 | | **`progress`** | `number` | The progress of the installation in percent between 0 and 100. | 7.2.0 | ### Enums #### GoogleSubjectSegmentationModuleInstallState | Members | Value | Since | | --------------------- | ----- | ----- | | **`UNKNOWN`** | `0` | 7.2.0 | | **`PENDING`** | `1` | 7.2.0 | | **`DOWNLOADING`** | `2` | 7.2.0 | | **`CANCELED`** | `3` | 7.2.0 | | **`COMPLETED`** | `4` | 7.2.0 | | **`FAILED`** | `5` | 7.2.0 | | **`INSTALLING`** | `6` | 7.2.0 | | **`DOWNLOAD_PAUSED`** | `7` | 7.2.0 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/subject-segmentation/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/subject-segmentation/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # @capacitor-mlkit/translation Unofficial Capacitor plugin for [ML Kit Translation](https://developers.google.com/ml-kit/language/translation).[1](#fn:1) ## Newsletter Stay up to date with the latest news and updates about the Capawesome, Capacitor, and Ionic ecosystem by subscribing to our [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | | 6.x.x | 6.x.x | Deprecated | | 5.x.x | 5.x.x | Deprecated | ## Installation You can use our **AI-Assisted Setup** to install the plugin. Add the [Capawesome Skills](https://github.com/capawesome-team/skills) to your AI tool using the following command: ``` npx skills add capawesome-team/skills --skill capacitor-plugins ``` Then use the following prompt: ``` Use the `capacitor-plugins` skill from `capawesome-team/skills` to install the `@capacitor-mlkit/translation` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands and follow the platform-specific instructions below: ``` npm install @capacitor-mlkit/translation npx cap sync ``` **Attention**: This plugin **only supports CocoaPods** for iOS dependency management. Swift Package Manager (SPM) is not supported for the ML Kit SDK, see [this comment](https://github.com/googlesamples/mlkit/issues/180#issuecomment-1298964099). ### Android #### Variables If needed, you can define the following project variable in your app’s `variables.gradle` file to change the default version of the dependency: - `$mlkitTranslateVersion` version of `com.google.mlkit:translate` (default: `17.0.3`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Minimum Deployment Target Make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`: ``` platform :ios, '15.5' ``` ## Configuration No configuration required for this plugin. ## Demo A working example can be found here: [robingenz/capacitor-mlkit-plugin-demo](https://github.com/robingenz/capacitor-mlkit-plugin-demo) ## Usage ``` import { Translation, Language } from '@capacitor-mlkit/translation'; const deleteDownloadedModel = async () => { await Translation.deleteDownloadedModel({ language: Language.English, }); }; const downloadModel = async () => { await Translation.downloadModel({ language: Language.English, }); }; const getDownloadedModels = async () => { const { languages } = await Translation.getDownloadedModels(); return languages; }; const translate = async () => { const { text } = await Translation.translate({ text: 'Good morning!', sourceLanguage: Language.English, targetLanguage: Language.German, }); return text; }; ``` ## API - [`deleteDownloadedModel(...)`](#deletedownloadedmodel) - [`downloadModel(...)`](#downloadmodel) - [`getDownloadedModels()`](#getdownloadedmodels) - [`translate(...)`](#translate) - [Interfaces](#interfaces) - [Enums](#enums) ### deleteDownloadedModel(...) ``` deleteDownloadedModel(options: DeleteDownloadedModelOptions) => Promise ``` Delete the language model for the given language. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `DeleteDownloadedModelOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### downloadModel(...) ``` downloadModel(options: DownloadModel) => Promise ``` Download a language model for offline translation. Language models are around 30MB in size, so be sure to only download the models you need and only download them using a WiFi connection unless the user has specified otherwise. Only available on Android and iOS. | Param | Type | | ------------- | --------------- | | **`options`** | `DownloadModel` | **Since:** 0.0.1 ______________________________________________________________________ ### getDownloadedModels() ``` getDownloadedModels() => Promise ``` Get the languages for which a model has been downloaded. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### translate(...) ``` translate(options: TranslateOptions) => Promise ``` Translate the given text. If the language model for the given source and target languages is not downloaded, it will be downloaded automatically which may take some time. If you want to avoid this, use the `downloadModel(...)` method to download the model first. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `TranslateOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### DeleteDownloadedModelOptions | Prop | Type | Description | Since | | -------------- | ---------- | ------------------------------------------- | ----- | | **`language`** | `Language` | The language for which to delete the model. | 0.0.1 | #### DownloadModel | Prop | Type | Description | Since | | -------------- | ---------- | --------------------------------------- | ----- | | **`language`** | `Language` | The language to download the model for. | 0.0.1 | #### GetDownloadedModelsResult | Prop | Type | Description | | --------------- | ------------ | ---------------------------------------------------- | | **`languages`** | `Language[]` | The languages for which a model has been downloaded. | #### TranslateResult | Prop | Type | Description | Since | | ---------- | -------- | -------------------- | ----- | | **`text`** | `string` | The translated text. | 0.0.1 | #### TranslateOptions | Prop | Type | Description | Since | | -------------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`text`** | `string` | The text to translate. | 0.0.1 | | **`sourceLanguage`** | `Language` | The source language of the text. If you don't know the source language, you can use the [`Language Identification` plugin to detect it.](#language) | 0.0.1 | | **`targetLanguage`** | `Language` | The target language to translate the text to. | 0.0.1 | ### Enums #### Language | Members | Value | Since | | ---------------- | ------ | ----- | | **`Afrikaans`** | `'af'` | 0.0.1 | | **`Arabic`** | `'ar'` | 0.0.1 | | **`Belarusian`** | `'be'` | 0.0.1 | | **`Bulgarian`** | `'bg'` | 0.0.1 | | **`Bengali`** | `'bn'` | 0.0.1 | | **`Catalan`** | `'ca'` | 0.0.1 | | **`Czech`** | `'cs'` | 0.0.1 | | **`Welsh`** | `'cy'` | 0.0.1 | | **`Danish`** | `'da'` | 0.0.1 | | **`German`** | `'de'` | 0.0.1 | | **`Greek`** | `'el'` | 0.0.1 | | **`English`** | `'en'` | 0.0.1 | | **`Esperanto`** | `'eo'` | 0.0.1 | | **`Spanish`** | `'es'` | 0.0.1 | | **`Estonian`** | `'et'` | 0.0.1 | | **`Persian`** | `'fa'` | 0.0.1 | | **`Finnish`** | `'fi'` | 0.0.1 | | **`French`** | `'fr'` | 0.0.1 | | **`Irish`** | `'ga'` | 0.0.1 | | **`Galician`** | `'gl'` | 0.0.1 | | **`Gujarati`** | `'gu'` | 0.0.1 | | **`Hebrew`** | `'he'` | 0.0.1 | | **`Hindi`** | `'hi'` | 0.0.1 | | **`Croatian`** | `'hr'` | 0.0.1 | | **`Haitian`** | `'ht'` | 0.0.1 | | **`Hungarian`** | `'hu'` | 0.0.1 | | **`Indonesian`** | `'id'` | 0.0.1 | | **`Icelandic`** | `'is'` | 0.0.1 | | **`Italian`** | `'it'` | 0.0.1 | | **`Japanese`** | `'ja'` | 0.0.1 | | **`Georgian`** | `'ka'` | 0.0.1 | | **`Kannada`** | `'kn'` | 0.0.1 | | **`Korean`** | `'ko'` | 0.0.1 | | **`Lithuanian`** | `'lt'` | 0.0.1 | | **`Latvian`** | `'lv'` | 0.0.1 | | **`Macedonian`** | `'mk'` | 0.0.1 | | **`Marathi`** | `'mr'` | 0.0.1 | | **`Malay`** | `'ms'` | 0.0.1 | | **`Maltese`** | `'mt'` | 0.0.1 | | **`Dutch`** | `'nl'` | 0.0.1 | | **`Norwegian`** | `'no'` | 0.0.1 | | **`Polish`** | `'pl'` | 0.0.1 | | **`Portuguese`** | `'pt'` | 0.0.1 | | **`Romanian`** | `'ro'` | 0.0.1 | | **`Russian`** | `'ru'` | 0.0.1 | | **`Slovak`** | `'sk'` | 0.0.1 | | **`Slovenian`** | `'sl'` | 0.0.1 | | **`Albanian`** | `'sq'` | 0.0.1 | | **`Swedish`** | `'sv'` | 0.0.1 | | **`Swahili`** | `'sw'` | 0.0.1 | | **`Tamil`** | `'ta'` | 0.0.1 | | **`Telugu`** | `'te'` | 0.0.1 | | **`Thai`** | `'th'` | 0.0.1 | | **`Tagalog`** | `'tl'` | 0.0.1 | | **`Turkish`** | `'tr'` | 0.0.1 | | **`Ukrainian`** | `'uk'` | 0.0.1 | | **`Urdu`** | `'ur'` | 0.0.1 | | **`Vietnamese`** | `'vi'` | 0.0.1 | | **`Chinese`** | `'zh'` | 0.0.1 | ## Terms & Privacy This plugin uses the [Google ML Kit](https://developers.google.com/ml-kit): - [Terms & Privacy](https://developers.google.com/ml-kit/terms) - [Android Data Disclosure](https://developers.google.com/ml-kit/android-data-disclosure) - [iOS Data Disclosure](https://developers.google.com/ml-kit/ios-data-disclosure) ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/translation/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/translation/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Cloud # Capawesome Cloud **A comprehensive platform for building, updating, and deploying your Capacitor apps.** [Capawesome Cloud](https://cloud.capawesome.io/) provides everything you need to streamline your mobile app development and deployment workflow. Ship critical bug fixes instantly with [Live Updates](https://capawesome.io/cloud/live-updates/index.md), build native apps in the cloud with [Native Builds](https://capawesome.io/cloud/native-builds/index.md), and automate app store releases with [App Store Publishing](https://capawesome.io/cloud/app-store-publishing/index.md). Deploy changes in minutes, reduce costs by 90%+, and scale to millions of devices with our secure and reliable infrastructure. - **Explore Capawesome Cloud** ______________________________________________________________________ Visit our landing page to learn about features, pricing, and success stories from teams using Capawesome Cloud. ______________________________________________________________________ [Visit cloud.capawesome.io](https://cloud.capawesome.io/) - **Start for Free** ______________________________________________________________________ Create your account, set up your first app, and deploy your first update in minutes without a credit card. ______________________________________________________________________ [Get Started](https://console.cloud.capawesome.io) ## Getting Started - **Live Updates** ______________________________________________________________________ Deliver updates to your Capacitor app in real-time. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/setup/index.md) - **Native Builds** ______________________________________________________________________ Build native apps for Android and iOS in the cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/setup/index.md) ## Documentation - **API** ______________________________________________________________________ Manage your apps and resources programmatically. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/api/index.md) - **CLI** ______________________________________________________________________ Manage your apps and resources from the command line. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/cli/index.md) - **Console** ______________________________________________________________________ Manage your apps and resources from the Capawesome Cloud Console. ______________________________________________________________________ [Learn more](https://cloud.capawesome.io/) - **Live Updates** ______________________________________________________________________ Deliver updates to your Capacitor app in real-time. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/index.md) - **Native Builds** ______________________________________________________________________ Build native apps for Android and iOS in the cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/index.md) - **App Submissions** ______________________________________________________________________ Automate app store submissions to Google Play and the App Store. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/app-store-publishing/index.md) - **Integrations** ______________________________________________________________________ Connect Capawesome Cloud with your version control system. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/github/index.md) # API The Capawesome Cloud API helps you manage your apps and resources programmatically. You can use the API to create, update, and delete apps, bundles, channels, devices, and more. [API Documentation](https://api.cloud.capawesome.io/docs) # CLI The Capawesome Cloud Command Line Interface (CLI) can be used to manage your apps and resources from the command line, including Live Updates, Native Builds, and App Submissions. ## Installation The Capawesome CLI can be installed globally via [npm](https://www.npmjs.com/package/@capawesome/cli): ``` npm install -g @capawesome/cli@latest ``` ## Help The Capawesome CLI ships with command documentation that is accessible with the `--help` flag. ``` npx @capawesome/cli --help ``` ## Usage The Capawesome CLI uses the following syntax: ``` npx @capawesome/cli [options] ``` ### Authentication To use the Capawesome CLI, you need to authenticate with Capawesome Cloud. You can do this by running the following command: ``` npx @capawesome/cli login ``` For CI/CD environments, you can generate a token from the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens) and use it to log in non-interactively: ``` npx @capawesome/cli login --token ``` ## Command Reference - [`apps:create`](#appscreate) - [`apps:delete`](#appsdelete) - [`apps:link`](#appslink) - [`apps:transfer`](#appstransfer) - [`apps:unlink`](#appsunlink) - [`apps:builds:cancel`](#appsbuildscancel) - [`apps:builds:create`](#appsbuildscreate) - [`apps:builds:download`](#appsbuildsdownload) - [`apps:builds:logs`](#appsbuildslogs) - [`apps:certificates:create`](#appscertificatescreate) - [`apps:certificates:delete`](#appscertificatesdelete) - [`apps:certificates:get`](#appscertificatesget) - [`apps:certificates:list`](#appscertificateslist) - [`apps:certificates:update`](#appscertificatesupdate) - [`apps:channels:create`](#appschannelscreate) - [`apps:channels:delete`](#appschannelsdelete) - [`apps:channels:get`](#appschannelsget) - [`apps:channels:list`](#appschannelslist) - [`apps:channels:pause`](#appschannelspause) - [`apps:channels:resume`](#appschannelsresume) - [`apps:channels:update`](#appschannelsupdate) - [`apps:deployments:cancel`](#appsdeploymentscancel) - [`apps:deployments:create`](#appsdeploymentscreate) - [`apps:deployments:logs`](#appsdeploymentslogs) - [`apps:destinations:create`](#appsdestinationscreate) - [`apps:destinations:delete`](#appsdestinationsdelete) - [`apps:destinations:get`](#appsdestinationsget) - [`apps:destinations:list`](#appsdestinationslist) - [`apps:destinations:update`](#appsdestinationsupdate) - [`apps:devices:delete`](#appsdevicesdelete) - [`apps:devices:forcechannel`](#appsdevicesforcechannel) - [`apps:devices:probe`](#appsdevicesprobe) - [`apps:devices:unforcechannel`](#appsdevicesunforcechannel) - [`apps:environments:create`](#appsenvironmentscreate) - [`apps:environments:delete`](#appsenvironmentsdelete) - [`apps:environments:list`](#appsenvironmentslist) - [`apps:environments:set`](#appsenvironmentsset) - [`apps:environments:unset`](#appsenvironmentsunset) - [`apps:liveupdates:bundle`](#appsliveupdatesbundle) - [`apps:liveupdates:create`](#appsliveupdatescreate) - [`apps:liveupdates:generatemanifest`](#appsliveupdatesgeneratemanifest) - [`apps:liveupdates:generatesigningkey`](#appsliveupdatesgeneratesigningkey) - [`apps:liveupdates:rollback`](#appsliveupdatesrollback) - [`apps:liveupdates:rollout`](#appsliveupdatesrollout) - [`apps:liveupdates:upload`](#appsliveupdatesupload) - [`apps:liveupdates:register`](#appsliveupdatesregister) - [`apps:liveupdates:setnativeversions`](#appsliveupdatessetnativeversions) - [`doctor`](#doctor) - [`login`](#login) - [`logout`](#logout) - [`whoami`](#whoami) - [`organizations:create`](#organizationscreate) ### `apps:create` Create a new app in Capawesome Cloud. ``` npx @capawesome/cli apps:create [options] ``` **Options:** - `--json`: Output in JSON format. - `--link`: Connect the created app to the local git repository. - `--name`: The name of the app. - `--organization-id`: The ID of the organization to create the app in. - `--yes, -y`: Skip all confirmation prompts. ### `apps:delete` Delete an app from Capawesome Cloud. ``` npx @capawesome/cli apps:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--yes, -y`: Skip confirmation prompt. ### `apps:transfer` Transfer an app to another organization. ``` npx @capawesome/cli apps:transfer [options] ``` **Options:** - `--app-id`: The ID of the app. - `--organization-id`: The ID of the target organization. - `--yes, -y`: Skip confirmation prompt. ### `apps:link` Connect a git repository to an app. The repository information (provider, owner, and repository name) is automatically detected from the local git remote (`origin`). ``` npx @capawesome/cli apps:link [options] ``` **Options:** - `--app-id`: The ID of the app. ### `apps:unlink` Disconnect a git repository from an app. ``` npx @capawesome/cli apps:unlink [options] ``` **Options:** - `--app-id`: The ID of the app. - `--yes, -y`: Skip confirmation prompt. ### `apps:builds:cancel` Cancel an app build. ``` npx @capawesome/cli apps:builds:cancel [options] ``` **Options:** - `--app-id`: The ID of the app the build belongs to. - `--build-id`: The ID of the build to cancel. ### `apps:builds:create` Create a new app build. ``` npx @capawesome/cli apps:builds:create [options] ``` **Options:** - `--aab`: Download the generated AAB file (Android only). Optionally provide a file path. - `--apk`: Download the generated APK file (Android only). Optionally provide a file path. - `--app-id`: The ID of the app to create the build for. - `--certificate`: The name of the certificate to use for the build. - `--channel`: The name of the channel to deploy to (Web only). - `--destination`: The name of the destination to deploy to (Android/iOS only). - `--detached`: Exit immediately after creating the build without waiting for completion. - `--environment`: The name of the environment to use for the build. - `--git-ref`: The Git reference (branch, tag, or commit SHA) to build. - `--ipa`: Download the generated IPA file (iOS only). Optionally provide a file path. - `--json`: Output in JSON format. This will include additional information such as the build ID. - `--path`: Path to local source files to upload. Cannot be used together with `--git-ref`. - `--platform`: The platform for the build. Supported values are `android`, `ios`, and `web`. - `--stack`: The name of the stack to use for the build. Must be either `macos-sequoia` or `macos-tahoe`. - `--type`: The type of build. For Android, supported values are `debug` and `release`. For iOS, supported values are `simulator`, `development`, `ad-hoc`, `app-store`, and `enterprise`. For Web, no type is required. - `--url`: URL to a zip file to use as build source. Cannot be used together with `--git-ref` or `--path`. - `--variable`: Ad hoc environment variable in `key=value` format. Can be specified multiple times. - `--variable-file`: Path to a file containing ad hoc environment variables in `.env` format. - `--yes, -y`: Skip confirmation prompts. - `--zip`: Download the generated zip file (Web only). Optionally provide a file path. ### `apps:builds:download` Download the build artifacts for an app build. ``` npx @capawesome/cli apps:builds:download [options] ``` **Options:** - `--app-id`: The ID of the app the build belongs to. - `--build-id`: The ID of the build to download artifacts for. - `--aab`: Download the generated AAB file (Android only). Optionally provide a file path. - `--apk`: Download the generated APK file (Android only). Optionally provide a file path. - `--ipa`: Download the generated IPA file (iOS only). Optionally provide a file path. - `--zip`: Download the generated zip file (Web only). Optionally provide a file path. ### `apps:builds:logs` Display the logs for a ongoing or completed app build. ``` npx @capawesome/cli apps:builds:logs [options] ``` **Options:** - `--app-id`: The ID of the app the build belongs to. - `--build-id`: The ID of the build to display logs for. ### `apps:certificates:create` Create a new certificate for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:certificates:create [options] ``` **Options:** - `--app-id`: The ID of the app. - `--file`: Path to the certificate file. - `--key-alias`: Key alias for the certificate. - `--key-password`: Key password for the certificate. - `--name`: The name of the certificate. - `--password`: Password for the certificate. - `--platform`: The platform of the certificate. Supported values are `android`, `ios`, and `web`. - `--provisioning-profile`: Paths to provisioning profile files to upload and link. Can be specified multiple times. - `--type`: The type of the certificate. Supported values are `development` and `production`. - `--yes, -y`: Skip optional prompts. ### `apps:certificates:delete` Delete a certificate from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:certificates:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--certificate-id`: The ID of the certificate. - `--name`: The name of the certificate. - `--platform`: The platform of the certificate (`android`, `ios`, `web`). - `--type`: The type of the certificate. Supported values are `development` and `production`. - `--yes, -y`: Skip confirmation prompt. ### `apps:certificates:get` Get a certificate from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:certificates:get [options] ``` **Options:** - `--app-id`: The ID of the app. - `--certificate-id`: The ID of the certificate. - `--json`: Output in JSON format. - `--name`: The name of the certificate. - `--platform`: The platform of the certificate (`android`, `ios`, `web`). - `--type`: The type of the certificate. Supported values are `development` and `production`. ### `apps:certificates:list` List all certificates for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:certificates:list [options] ``` **Options:** - `--app-id`: The ID of the app. - `--json`: Output in JSON format. - `--limit`: The maximum number of certificates to return. - `--offset`: The offset to start returning certificates from. - `--platform`: Filter by platform. Supported values are `android`, `ios`, and `web`. - `--type`: Filter by type. Supported values are `development` and `production`. ### `apps:certificates:update` Update an existing certificate from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:certificates:update [options] ``` **Options:** - `--app-id`: The ID of the app. - `--certificate-id`: The ID of the certificate. - `--key-alias`: Key alias for the certificate. - `--key-password`: Key password for the certificate. - `--name`: The name of the certificate. - `--password`: Password for the certificate. - `--type`: The type of the certificate. Supported values are `development` and `production`. ### `apps:channels:create` Create a new channel for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:channels:create [options] ``` **Options:** - `--app-id`: The ID of the app. - `--ignore-errors`: Ignore errors when creating the channel. This is useful for CI/CD pipelines where the channel may already exist. Defaults to `false`. - `--name`: The name of the channel. - `--protected`: Whether to protect the channel or not. Defaults to `false`. ### `apps:channels:delete` Delete a channel from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:channels:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel-id`: The ID of the channel. Either the ID or the name of the channel must be provided. - `--name`: The name of the channel. Either the ID or the name of the channel must be provided. - `--yes, -y`: Skip confirmation prompt. ### `apps:channels:get` Get a channel from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:channels:get [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel-id`: The ID of the channel. Either the ID or the name of the channel must be provided. - `--json`: Output in JSON format. - `--name`: The name of the channel. Either the ID or the name of the channel must be provided. ### `apps:channels:list` List all channels for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:channels:list [options] ``` **Options:** - `--app-id`: The ID of the app. - `--json`: Output in JSON format. - `--limit`: The maximum number of channels to return. - `--offset`: The offset to start returning channels from. ### `apps:channels:pause` Pause an app channel. ``` npx @capawesome/cli apps:channels:pause [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel`: The name of the channel to pause. ### `apps:channels:resume` Resume an app channel. ``` npx @capawesome/cli apps:channels:resume [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel`: The name of the channel to resume. ### `apps:channels:update` Update an existing channel from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:channels:update [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel-id`: The ID of the channel. - `--name`: The name of the channel. - `--protected`: Whether to protect the channel or not. ### `apps:deployments:cancel` Cancel an ongoing app deployment. ``` npx @capawesome/cli apps:deployments:cancel [options] ``` **Options:** - `--app-id`: The ID of the app the deployment belongs to. - `--deployment-id`: The ID of the deployment to cancel. ### `apps:deployments:create` Create a new app deployment. ``` npx @capawesome/cli apps:deployments:create [options] ``` **Options:** - `--app-id`: The ID of the app to create the deployment for. - `--build-id`: The ID of the build to deploy. Alternative to `--build-number`. - `--build-number`: The build number to deploy (e.g., "1", "42"). Alternative to `--build-id`. - `--channel`: The name of the channel to deploy the build to (Web only). - `--destination`: The name of the destination to deploy to (Android/iOS only). - `--detached`: Exit immediately after creating the deployment without waiting for completion. ### `apps:deployments:logs` Display the logs for a ongoing or completed app deployment. ``` npx @capawesome/cli apps:deployments:logs [options] ``` **Options:** - `--app-id`: The ID of the app the deployment belongs to. - `--deployment-id`: The ID of the deployment to display logs for. ### `apps:destinations:create` Create a new destination for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:destinations:create [options] ``` **Options:** - `--app-id`: The ID of the app. - `--name`: The name of the destination. - `--platform`: The platform of the destination. Supported values are `android` and `ios`. - `--android-build-artifact-type`: Android build artifact type. Supported values are `aab` and `apk`. - `--android-package-name`: Android package name. - `--android-release-status`: Android release status. Supported values are `completed` and `draft`. - `--google-play-track`: Google Play track. - `--google-service-account-key-file`: Path to the Google service account key JSON file. - `--apple-api-key-file`: Path to the Apple API key (.p8) file. - `--apple-app-id`: Apple App ID. - `--apple-app-password`: Apple app-specific password. - `--apple-id`: Apple ID. - `--apple-issuer-id`: Apple Issuer ID. - `--apple-team-id`: Apple Team ID. ### `apps:destinations:delete` Delete a destination from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:destinations:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--destination-id`: The ID of the destination. - `--name`: The name of the destination. - `--platform`: The platform of the destination (`android`, `ios`). - `--yes, -y`: Skip confirmation prompt. ### `apps:destinations:get` Get a destination from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:destinations:get [options] ``` **Options:** - `--app-id`: The ID of the app. - `--destination-id`: The ID of the destination. - `--json`: Output in JSON format. - `--name`: The name of the destination. - `--platform`: The platform of the destination (`android`, `ios`). ### `apps:destinations:list` List all destinations for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:destinations:list [options] ``` **Options:** - `--app-id`: The ID of the app. - `--json`: Output in JSON format. - `--limit`: The maximum number of destinations to return. - `--offset`: The offset to start returning destinations from. - `--platform`: Filter by platform. Supported values are `android` and `ios`. ### `apps:destinations:update` Update an existing destination from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:destinations:update [options] ``` **Options:** - `--app-id`: The ID of the app. - `--destination-id`: The ID of the destination. - `--name`: The name of the destination. - `--android-build-artifact-type`: Android build artifact type. Supported values are `aab` and `apk`. - `--android-package-name`: Android package name. - `--android-release-status`: Android release status. Supported values are `completed` and `draft`. - `--app-google-service-account-key-id`: App Google Service Account Key ID. - `--google-play-track`: Google Play track. - `--apple-api-key-id`: Apple API Key ID. - `--apple-app-id`: Apple App ID. - `--apple-app-password`: Apple app-specific password. - `--apple-id`: Apple ID. - `--apple-issuer-id`: Apple Issuer ID. - `--apple-team-id`: Apple Team ID. - `--app-apple-api-key-id`: App Apple API Key ID. ### `apps:devices:delete` Delete a device from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:devices:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--device-id`: The ID of the device. - `--yes, -y`: Skip confirmation prompt. ### `apps:devices:forcechannel` Force a device to use a specific channel. ``` npx @capawesome/cli apps:devices:forcechannel [options] ``` **Options:** - `--app-id`: The ID of the app. - `--device-id`: The ID of the device. Can be specified multiple times. - `--channel`: The name of the channel to force. ### `apps:devices:probe` Check whether a device would receive a live update. This is useful for debugging and testing purposes, especially when setting up new devices or troubleshooting existing ones. ``` npx @capawesome/cli apps:devices:probe [options] ``` **Options:** - `--app-id`: The ID of the app. - `--device-id`: The ID of the device. - `--json`: Output in JSON format. ### `apps:devices:unforcechannel` Remove the forced channel from a device. ``` npx @capawesome/cli apps:devices:unforcechannel [options] ``` **Options:** - `--app-id`: The ID of the app. - `--device-id`: The ID of the device. Can be specified multiple times. ### `apps:environments:create` Create a new environment for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:environments:create [options] ``` **Options:** - `--app-id`: The ID of the app. - `--name`: The name of the environment. ### `apps:environments:delete` Delete an environment from an app in Capawesome Cloud. ``` npx @capawesome/cli apps:environments:delete [options] ``` **Options:** - `--app-id`: The ID of the app. - `--environment-id`: The ID of the environment. Either the ID or name must be provided. - `--name`: The name of the environment. Either the ID or name must be provided. - `--yes, -y`: Skip confirmation prompt. ### `apps:environments:list` List all environments for an app in Capawesome Cloud. ``` npx @capawesome/cli apps:environments:list [options] ``` **Options:** - `--app-id`: The ID of the app. - `--json`: Output in JSON format. - `--limit`: The maximum number of environments to return. - `--offset`: The offset to start returning environments from. ### `apps:environments:set` Set environment variables and secrets for an environment in Capawesome Cloud. ``` npx @capawesome/cli apps:environments:set [options] ``` **Options:** - `--app-id`: The ID of the app. - `--environment-id`: The ID of the environment. - `--variable`: Environment variable in `key=value` format. Can be specified multiple times. - `--variable-file`: Path to a file containing environment variables in `.env` format. - `--secret`: Environment secret in `key=value` format. Can be specified multiple times. - `--secret-file`: Path to a file containing environment secrets in `.env` format. ### `apps:environments:unset` Unset environment variables and secrets for an environment in Capawesome Cloud. ``` npx @capawesome/cli apps:environments:unset [options] ``` **Options:** - `--app-id`: The ID of the app. - `--environment-id`: The ID of the environment. - `--variable`: Key of the environment variable to unset. Can be specified multiple times. - `--secret`: Key of the environment secret to unset. Can be specified multiple times. ### `apps:liveupdates:bundle` Generate manifest file and compress web assets into a zip file. ``` npx @capawesome/cli apps:liveupdates:bundle [options] ``` **Options:** - `--input-path`: Path to the web assets directory. - `--output-path`: Output path for the generated artifact file. Defaults to `./bundle.zip`. - `--overwrite`: Overwrite output file if it already exists. Defaults to `false`. - `--skip-manifest`: Skip manifest file generation. Defaults to `false`. ### `apps:liveupdates:create` Create a new live update by building and deploying web assets using Capawesome Cloud Runners. ``` npx @capawesome/cli apps:liveupdates:create [options] ``` **Options:** - `--android-eq`: The exact Android versionCode for the live update. - `--android-max`: The maximum Android versionCode for the live update. - `--android-min`: The minimum Android versionCode for the live update. - `--app-id`: The ID of the app to create the live update for. - `--certificate`: The name of the certificate to use for the build. - `--channel`: The name of the channel to deploy to. Can be specified multiple times. - `--custom-property`: A custom property to assign to the build. Must be in the format `key=value`. Can be specified multiple times. - `--environment`: The name of the environment to use for the build. - `--git-ref`: The Git reference (branch, tag, or commit SHA) to build. - `--ios-eq`: The exact iOS CFBundleVersion for the live update. - `--ios-max`: The maximum iOS CFBundleVersion for the live update. - `--ios-min`: The minimum iOS CFBundleVersion for the live update. - `--json`: Output in JSON format. - `--path`: Path to local source files to upload. Cannot be used together with `--git-ref` or `--url`. - `--rollout-percentage`: The rollout percentage for the deployment (0-100). Defaults to `100`. - `--stack`: The build stack to use for the build process. Must be either `macos-sequoia` or `macos-tahoe`. - `--url`: URL to a zip file to use as build source. Cannot be used together with `--git-ref` or `--path`. - `--variable`: Ad hoc environment variable in `key=value` format. Can be specified multiple times. - `--variable-file`: Path to a file containing ad hoc environment variables in `.env` format. - `--yes, -y`: Skip confirmation prompts. ### `apps:liveupdates:generatemanifest` Generate a manifest file. ``` npx @capawesome/cli apps:liveupdates:generatemanifest [options] ``` **Options:** - `--path`: Path to the web assets folder (e.g. `www` or `dist`). ### `apps:liveupdates:generatesigningkey` Generate a new code signing key pair for Live Updates. This command creates an RSA key pair that can be used to sign and verify Live Updates bundles. ``` npx @capawesome/cli apps:liveupdates:generatesigningkey [options] ``` **Options:** - `--key-size`: The RSA key size in bits. Must be `2048`, `3072`, or `4096`. Defaults to `2048`. - `--public-key-path`: Path where the public key should be saved. Defaults to `public.pem`. - `--private-key-path`: Path where the private key should be saved. Defaults to `private.pem`. ### `apps:liveupdates:rollback` Rollback the active build in a channel to a previous deployment. ``` npx @capawesome/cli apps:liveupdates:rollback [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel`: The name of the channel to rollback. - `--steps`: The number of deployments to go back (1-5). ### `apps:liveupdates:rollout` Update the rollout percentage of the active build in a channel. ``` npx @capawesome/cli apps:liveupdates:rollout [options] ``` **Options:** - `--app-id`: The ID of the app. - `--channel`: The name of the channel to update rollout for. - `--percentage`: The rollout percentage (0-100). ### `apps:liveupdates:upload` Upload a locally built bundle and deploy it to a channel. ``` npx @capawesome/cli apps:liveupdates:upload [options] ``` **Options:** - `--android-eq`: The exact Android version code (`versionCode`) that the bundle does not support. - `--android-max`: The maximum Android version code (`versionCode`) that the bundle supports. - `--android-min`: The minimum Android version code (`versionCode`) that the bundle supports. - `--app-id`: The ID of the app. - `--artifact-type`: The type of artifact to upload. Must be `zip` or `manifest`. Defaults to `zip`. - `--channel`: The channel to deploy the bundle to. - `--commit-message`: The commit message of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--commit-ref`: The commit ref of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--commit-sha`: The commit SHA of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--custom-property`: A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times. - `--git-ref`: The Git reference (branch, tag, or commit SHA) to associate with the bundle. - `--ios-eq`: The exact iOS version code (`CFBundleVersion`) that the bundle does not support. - `--ios-max`: The maximum iOS version code (`CFBundleVersion`) that the bundle supports. - `--ios-min`: The minimum iOS version code (`CFBundleVersion`) that the bundle supports. - `--path`: The path to the bundle to upload. Must be a folder or zip archive. - `--private-key`: The private key to sign the bundle with. Can be a file path to a .pem file or the private key content as plain text. - `--rollout-percentage`: The percentage of devices to deploy the bundle to. Must be an integer between 0 and 100. - `--yes, -y`: Skip confirmation prompts. ### `apps:liveupdates:register` Register a self-hosted bundle URL and deploy it to a channel. ``` npx @capawesome/cli apps:liveupdates:register [options] ``` **Options:** - `--android-eq`: The exact Android version code (`versionCode`) that the bundle does not support. - `--android-max`: The maximum Android version code (`versionCode`) that the bundle supports. - `--android-min`: The minimum Android version code (`versionCode`) that the bundle supports. - `--app-id`: The ID of the app. - `--channel`: The channel to deploy the bundle to. - `--commit-message`: The commit message of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--commit-ref`: The commit ref of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--commit-sha`: The commit SHA of the Git commit that the bundle is linked to. Deprecated, use `--git-ref` instead. - `--custom-property`: A custom property to assign to the bundle. Must be in the format `key=value`. Can be specified multiple times. - `--git-ref`: The Git reference (branch, tag, or commit SHA) to associate with the bundle. - `--ios-eq`: The exact iOS version code (`CFBundleVersion`) that the bundle does not support. - `--ios-max`: The maximum iOS version code (`CFBundleVersion`) that the bundle supports. - `--ios-min`: The minimum iOS version code (`CFBundleVersion`) that the bundle supports. - `--path`: Path to zip file for code signing only. - `--private-key`: The private key to sign the bundle with. Can be a file path to a .pem file or the private key content as plain text. - `--rollout-percentage`: The percentage of devices to deploy the bundle to. Must be an integer between 0 and 100. - `--url`: The URL to the self-hosted bundle file. The URL must start with `https://`. - `--yes, -y`: Skip confirmation prompts. ### `apps:liveupdates:setnativeversions` Set native version constraints on a web build. ``` npx @capawesome/cli apps:liveupdates:setnativeversions [options] ``` **Options:** - `--app-id`: The ID of the app. - `--build-id`: The ID of the build to update. - `--android-eq`: The exact Android version code (`versionCode`) that the build supports. - `--android-max`: The maximum Android version code (`versionCode`) that the build supports. - `--android-min`: The minimum Android version code (`versionCode`) that the build supports. - `--ios-eq`: The exact iOS bundle version (`CFBundleVersion`) that the build supports. - `--ios-max`: The maximum iOS bundle version (`CFBundleVersion`) that the build supports. - `--ios-min`: The minimum iOS bundle version (`CFBundleVersion`) that the build supports. ### `doctor` Print various information about the Capawesome CLI and your environment. This is useful for debugging issues with the CLI or your environment. ``` npx @capawesome/cli doctor ``` ### `login` Log in to Capawesome Cloud. ``` npx @capawesome/cli login ``` **Options:** - `--token`: The token to use for authentication. ### `logout` Log out of Capawesome Cloud. ``` npx @capawesome/cli logout ``` ### `whoami` Show the current logged-in user. ``` npx @capawesome/cli whoami ``` ### `organizations:create` Create a new organization in Capawesome Cloud. ``` npx @capawesome/cli organizations:create [options] ``` **Options:** - `--name`: The name of the organization. ## Advanced ### Parsing JSON Output Many commands in the Capawesome CLI support a `--json` flag that outputs the command result in JSON format. This is useful for scripting and automation. You can use tools like [jq](https://stedolan.github.io/jq/) to parse the JSON output. For example, to get the ID of a newly created build, you can run: ``` OUTPUT=$(mktemp) npx @capawesome/cli apps:builds:create \ --app-id \ --platform \ --type \ --git-ref \ --json \ --yes | tee "$OUTPUT" BUILD_ID=$(sed -n '/^{/,$p' "$OUTPUT" | jq -r '.id') rm "$OUTPUT" ``` Then, you can use the `BUILD_ID` variable in subsequent commands, such as creating a deployment: ``` npx @capawesome/cli apps:deployments:create \ --app-id \ --build-id "$BUILD_ID" \ --destination ``` # FAQ ## General ### What is Capawesome Cloud? Capawesome Cloud is a comprehensive platform that provides a suite of tools to help you build, update, and deploy your Capacitor apps. The platform includes [Live Updates](https://capawesome.io/cloud/live-updates/index.md) for instant app updates, [Native Builds](https://capawesome.io/cloud/native-builds/configuration/index.md) for cloud-based app compilation, and [App Store Publishing](https://capawesome.io/cloud/app-store-publishing/index.md) for automated app store deployments. ## Account ### How can I delete my account? You can delete your account at any time by going to the [Account Settings](https://console.cloud.capawesome.io/settings/account) page in the Capawesome Cloud Console. After deleting your account, all data associated with your account will be permanently deleted and cannot be restored. ### How can I change my email address? You can change your email address at any time by going to the [Account Settings](https://console.cloud.capawesome.io/settings/account) page in the Capawesome Cloud Console. After changing your email address, you will receive a confirmation email to verify the new email address. ### How can I change my password? You can change your password at any time by going to the [Account Settings](https://console.cloud.capawesome.io/settings/account) page in the Capawesome Cloud Console. After changing your password, you will be logged out and need to log in again with the new password. ## Native Builds ### Is my source code permanently stored on Capawesome Cloud servers? No, we do not permanently store your source code on our servers. Your repository is only cloned at build time into a temporary, isolated virtual machine. Once the build job is finished, the VM and all its contents are immediately destroyed. This works the exact same way as any other CI/CD platform like GitHub Actions, GitLab CI, or Jenkins. Additionally, no person ever has access to those files at build time—the build process is fully automated and isolated. ## Billing ### How can I cancel my subscription? You can cancel your subscription at any time by going to the [Billing](https://console.cloud.capawesome.io/organizations/_/billing) page in the Capawesome Cloud Console. After canceling your subscription, you will still have access to the Capawesome Cloud until the end of the current billing period. # Support If you need help with Capawesome Cloud, you can contact our support team at [support@capawesome.io](mailto:support@capawesome.io). We strive to respond to all inquiries within 24 hours. For customers with a Service Level Agreement (SLA), guaranteed response times apply. See the [SLA documentation](https://capawesome.io/sla/index.md) for more information. ## Before contacting support Before reaching out, please check the following resources: - **[FAQ](https://capawesome.io/cloud/faq/index.md)**: Frequently asked questions about Capawesome Cloud. - **Documentation**: Review the documentation for the specific feature you're having trouble with. ## What to include in your support request To help us resolve your issue as quickly as possible, please include the following information in your support request: ### User ID Your User ID can be found in the [Account Settings](https://console.cloud.capawesome.io/settings/account) page of the Capawesome Cloud Console. Look for the **Support** section. ### Organization ID Your Organization ID can be found in the [Organization Settings](https://console.cloud.capawesome.io/organizations/_/settings) page of the Capawesome Cloud Console. Look for the **Support** section. ### App ID The App ID can be found in the [App Settings](https://console.cloud.capawesome.io/apps/_/settings) page of the Capawesome Cloud Console. Look for the **Support** section. ### Additional information Depending on the nature of your issue, you may also want to include: - A detailed description of the issue, including any error messages. - Steps to reproduce the issue. - Screenshots or logs that may help illustrate the problem. - The platform(s) affected (Android, iOS, Web). # Webhooks Webhooks allow you to receive real-time notifications when specific events occur in your app. When an event is triggered, Capawesome Cloud sends an HTTP POST request to your configured endpoint, enabling integrations with external services like CI/CD pipelines, Slack, or custom backends. ## Events The following events are currently supported: | Event | Description | | ------------------------ | ------------------------------------------- | | `app_build_created` | Triggered when a new app build is created. | | `app_deployment_created` | Triggered when a new deployment is created. | | `job_created` | Triggered when a new job is created. | | `job_finished` | Triggered when a job finishes. | Need an event that's not listed here? [Let us know](https://capawesome.io/cloud/support/index.md) and we'll look into adding it. ### Payload Each webhook request contains a JSON body with an `event` field indicating the event type and a `data` field containing the event payload. Here is an example of a `job_finished` event: ``` { "event": "job_finished", "data": { "id": "16555c02-a320-4bec-a14a-52754d783970", "appBuildId": "647d1fe4-9cb7-4f25-afaf-7a0c13a1bb8c", "appDeploymentId": null, "appId": "70922e93-0944-48cc-a560-61135ab291ad", "canceledAt": 1771143284003, "canceledBy": "f624e455-b19c-4f17-8282-e4a7a02597f3", "finishedAt": 1771143284003, "inProgressAt": 1771143275311, "inProgressTimeInSeconds": 8, "organizationId": "a7249949-a705-4952-a293-a06df84950dc", "pendingAt": 1771143256381, "pendingTimeInSeconds": 18, "queuedAt": null, "queueTimeInSeconds": null, "stack": "macos-tahoe", "status": "canceled", "totalTimeInSeconds": 27, "createdAt": 1771143256161, "createdBy": "f624e455-b19c-4f17-8282-e4a7a02597f3" } } ``` ## Managing Webhooks Webhooks are configured per app. To manage webhooks, navigate to **Settings > Webhooks** in your app's sidebar in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/). From there, you can create, edit, and delete webhooks. Each webhook has the following properties: - **Name** (required): A human-readable name to identify the webhook. - **URL** (required): The endpoint URL that receives the HTTP POST requests. - **Events** (required): One or more events that trigger the webhook. - **Signing Secret** (optional): A secret used to sign each request via the `X-Signature` header so the receiver can verify the payload authenticity. Testing You can use [webhook.cool](https://webhook.cool/) to generate a temporary URL for testing your webhook configuration. ## Verifying Signatures If a signing secret is configured, each webhook request includes an `X-Signature` header containing an HMAC-SHA256 signature of the request body. You should verify this signature to ensure the request is authentic and has not been tampered with. The following example shows how to verify the signature in Node.js: ``` import crypto from "node:crypto"; const secret = "SIGNING_SECRET"; const hmac = crypto.createHmac("sha256", secret); const digest = Buffer.from( hmac.update(request.rawBody).digest("hex"), "utf8" ); const signature = Buffer.from(request.get("X-Signature") || "", "utf8"); if (!crypto.timingSafeEqual(digest, signature)) { throw new Error("Invalid signature."); } ``` ## Delivery History Every webhook keeps a delivery log. Each delivery record includes: - **Event**: The event that triggered the delivery. - **Request Body**: The JSON payload sent to the endpoint. - **Response Status Code**: The HTTP status code returned by the endpoint. - **Response Body**: The response body returned by the endpoint. - **Sent At**: The timestamp of the delivery attempt. Deliveries with a `2xx` status code are marked as successful. Failed deliveries can be manually resent from the [Capawesome Cloud Console](https://console.cloud.capawesome.io/). # Authentication Capawesome Cloud provides multiple authentication methods to securely access your account. You can choose between email/password authentication and OAuth providers, or use both methods simultaneously. ## Authentication Methods Capawesome Cloud supports the following authentication methods: - **Email/Password**: Traditional authentication using your email address and a password. - **OAuth**: Single sign-on using your GitHub, GitLab, or Bitbucket account. Both authentication methods can be configured and managed through the [account settings](https://console.cloud.capawesome.io/settings/account). ## Setting Up Email/Password Authentication To enable email/password authentication: 1. Go to the [account settings](https://console.cloud.capawesome.io/settings/account) in the Capawesome Cloud Console. 1. Scroll to the **Password** section. 1. Set a password for your account by entering your desired password and confirming it. 1. Click **Save** to enable email/password authentication. Once configured, you can use your email address and password to sign in to Capawesome Cloud. ## Setting Up OAuth Authentication To set up OAuth authentication: 1. Go to the [account settings](https://console.cloud.capawesome.io/settings/account) in the Capawesome Cloud Console. 1. Scroll to the **Identities** section. 1. Click on the **Connect** button next to your preferred provider (GitHub, GitLab, Bitbucket, or Microsoft). 1. Follow the authorization flow to connect your account. Once linked, you can use the OAuth provider to sign in to Capawesome Cloud. You can add or remove OAuth providers at any time by visiting the account settings and clicking the **Disconnect** button next to the provider you want to remove. ## Two-Factor Authentication (MFA) Two-factor authentication adds an extra layer of security to your account by requiring a second form of verification in addition to your password. Capawesome Cloud supports time-based one-time passwords (TOTP) using authenticator apps. ### Enabling MFA To enable two-factor authentication: 1. Go to the [account settings](https://console.cloud.capawesome.io/settings/account) in the Capawesome Cloud Console. 1. Scroll to the **Two-Factor Authentication** section. 1. Follow the on-screen instructions to set up MFA: 1. Scan the QR code with your authenticator app (such as Google Authenticator or Authy). 1. Enter the verification code generated by your authenticator app. 1. Click **Enable** to activate MFA for your account. Once enabled, you will be prompted to enter a verification code from your authenticator app each time you sign in. ## Recovery Codes Recovery codes are backup codes that can be used to regain access to your account if you lose access to your authenticator device. It is highly recommended to generate and store these codes in a safe place when enabling MFA. ### Generating Recovery Codes To generate recovery codes: 1. Go to the [account settings](https://console.cloud.capawesome.io/settings/account) in the Capawesome Cloud Console. 1. Scroll to the **Recovery Codes** section. 1. Click on the **Generate** button. 1. Download or copy the generated recovery codes and store them securely. Each recovery code can only be used once. If you run out of recovery codes or lose them, you can regenerate new codes at any time by following the same steps above. # App Store Publishing Use [Capawesome Cloud](https://cloud.capawesome.io/) to automate your app store submissions. Submit builds directly to TestFlight, the Apple App Store, and Google Play Store tracks without manual uploads. ## Features - 🚀 **Automatic Submissions**: Submit builds directly to TestFlight, App Store, and Google Play Store tracks. - ⚙️ **One-Time Setup**: Configure your store destinations once, then deploy automatically with every build. - 🔗 **Seamless Integration**: Works perfectly with Native Builds to create a complete CI/CD pipeline. - 🎯 **Multiple Tracks**: Support for Internal, Alpha, Beta, and Production tracks on Google Play. - 🍎 **TestFlight Upload**: iOS builds are automatically uploaded to TestFlight for testing. - 📦 **Flexible Formats**: Submit Android apps as AAB or APK formats. - 🔐 **Secure Credentials**: Store App Store Connect and Google Play credentials securely. - 📱 **Multi-Platform**: Submit both iOS and Android apps from the same platform. - 📚 **Documentation**: Comprehensive documentation to help you get started. ## Documentation - **Destinations** ______________________________________________________________________ Learn how to create and manage store destinations for your apps. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/app-store-publishing/destinations/index.md) ## Guides Here are some guides to help you get started with Capawesome Cloud App Submissions: - [Announcing Capawesome Cloud Native Builds](https://capawesome.io/blog/announcing-capawesome-cloud-native-builds/index.md) # Destinations Configure destinations to automatically submit your native builds to app stores. Capawesome Cloud supports direct submissions to both the Google Play Store and Apple App Store, streamlining your release process. - **Apple App Store** ______________________________________________________________________ Configure automatic submissions to TestFlight and App Store. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/app-store-publishing/destinations/apple-app-store/index.md) - **Google Play Store** ______________________________________________________________________ Configure automatic submissions to Google Play Store tracks. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/app-store-publishing/destinations/google-play-store/index.md) # Apple App Store To submit your iOS app builds to the Apple App Store (TestFlight), you need to configure an Apple App Store destination in the Capawesome Cloud Console. App submissions are automatically uploaded to TestFlight for testing and distribution. AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up an Apple App Store destination in Capawesome Cloud. ``` ## Configuration To create an Apple App Store destination, navigate to the [Destinations](https://console.cloud.capawesome.io/apps/_/destinations) page in the Capawesome Cloud Console and provide the following information: - **Name**: A descriptive name for this destination (e.g., "Production iOS", "TestFlight Beta"). - **Platform**: Must be set to `iOS`. - **Authentication Method**: Choose how to authenticate with App Store Connect. You can use either `Apple ID + Password` or `API Key`. - **Team ID**: Your Apple Developer Team ID from the Membership Details section. Depending on the authentication method you choose, you will need to provide additional credentials: ### Apple ID + Password - **Apple ID**: Your Apple ID email address. - **Apple App ID**: The Apple ID property from the App Information section in App Store Connect. - **App-specific Password**: An app-specific password generated on your Apple ID account page. ### API Key - **API Key File**: The `.p8` private key file downloaded from App Store Connect. - **Key ID**: The Key ID associated with your API key. - **Issuer ID**: The Issuer ID from App Store Connect. Read on for instructions on obtaining these credentials. ## Obtaining Credentials ### Team ID 1. Sign in to your [Apple Developer account](https://developer.apple.com/account). 1. Scroll down to **Membership Details**. 1. Copy your **Team ID**. ### Apple ID + Password Credentials #### Apple ID This is the email address associated with your Apple Developer account. #### Apple App ID 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com). 1. Navigate to **Apps** and select your app. 1. Go to **App Information**. 1. Copy the **Apple-ID** value (a numeric identifier). #### App-specific Password 1. Sign in to your [Apple ID account page](https://appleid.apple.com). 1. Navigate to **Security** section. 1. Under **App-Specific Passwords**, click **Generate Password**. 1. Enter a descriptive label (e.g., "Capawesome Cloud"). 1. Copy the generated password and save it securely. ### API Key Credentials #### API Key File, Key ID, and Issuer ID 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com). 1. Navigate to **Users and Access**. 1. Select the **Integrations** tab, then select **App Store Connect API**. **Note**: If you do not see this tab, ensure that your account has the necessary permissions (Admin or App Manager role). 1. Click the **+** button to create a new **Team Key**. 1. Enter a name for the key (e.g., "Capawesome Cloud") and select **App Manager** as the access level. 1. Click **Generate**. 1. Copy the **Issuer ID** displayed at the top of the page. 1. Copy the **Key ID** from the newly created key row. 1. Click **Download API Key** to download the `.p8` file. **Note**: You can only download this file once, so store it securely. ## Common Issues ### Submission not appearing in TestFlight It may **take some time for the build to process** in App Store Connect before it appears in TestFlight. If your build does not appear after a reasonable amount of time, make sure to check your email inbox for any processing errors or issues notified by Apple. The most common reasons for this can be: - Build number was not incremented - Missing Privacy Descriptions in your app's `Info.plist` - Incorrect build settings or configurations - App Store Connect account permissions are insufficient (account must have at least App Manager role) # Google Play Store To submit your Android app builds to the Google Play Store, you need to configure a Google Play Store destination in the Capawesome Cloud Console. App submissions are uploaded to the specified track for testing and distribution. AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up a Google Play Store destination in Capawesome Cloud. ``` Initial Upload Requirement The very first version of your app **must be uploaded manually** to Google Play Console. Subsequent builds can then be submitted automatically via Capawesome Cloud. This is not a limitation of Capawesome Cloud, but a requirement from Google Play. ## Configuration To create a Google Play Store destination, navigate to the [Destinations](https://console.cloud.capawesome.io/apps/_/destinations) page in the Capawesome Cloud Console and provide the following information: - **Name**: A descriptive name for this destination (e.g., "Production Android", "Beta Release"). - **Type**: Must be set to `Android`. - **Track**: The release track (`Internal`, `Alpha`, `Beta`, or `Production`). - **Package Name**: Your Android application ID / package name (e.g., `com.example.app`). - **Publishing Format**: The app bundle format (`AAB` or `APK`). - **Release Status**: The release status (`Draft` or `Completed`). - **JSON Key File**: Service account JSON key file from Google Play Console. Read on for detailed instructions on obtaining the required credentials. ## Obtaining Credentials ### Package Name The package name (also called application ID) is defined in your Android app's `build.gradle` file. It typically follows the format `com.example.app` and uniquely identifies your application on Google Play. ### Track Choose the appropriate release track for your submissions: - **Internal**: For internal testing with a small group of trusted testers. - **Alpha**: For early testing with a limited audience. - **Beta**: For broader testing before production release. - **Production**: For public release to all users. ### Publishing Format Both Android App Bundles (AAB) and APKs are supported. The **Android App Bundle is the required format** for new apps on Google Play, while APKs can still be used for existing apps. ### Release Status The release status determines how your app is published to the selected track: - **Draft**: Choose this for initial releases that require manual review before publishing. This is useful for the very first release of your app on a track, where you may need to complete additional setup steps in Google Play Console. - **Completed**: Choose this for releases that should be automatically published in the selected track. This is the typical choice for subsequent releases after your initial setup is complete. ### JSON Key File A service account JSON key file is required to authenticate with Google Play Console. Tip If you see Google Play Console or Google Cloud Console in your local language, add `?hl=en` to the end of the URL to switch to English. To obtain the service account JSON key file: 1. Sign in to [Google Cloud Console](https://console.cloud.google.com/). 1. Navigate to **IAM & Admin** > **Service Accounts**. 1. Create a new service account by clicking **Create Service Account**. 1. In step 1, fill in the service account details and click **Create and Continue**. 1. In step 2, assign the role **Service Account User** and click **Continue**. 1. In step 3, you can leave the fields empty and click **Done**. 1. In the list of service accounts, locate the newly created service account. Copy its **email address**, which will be required later. Then, click on the menu in the Actions column and select **Manage keys**. 1. In the **Keys** tab, click **Add Key** > **Create new key**. 1. Select **JSON** as the key type and click **Create**. Save the downloaded JSON key file for later use. 1. Sign in to [Google Play Console](https://play.google.com/console). 1. Navigate to **Users and Permissions** and click **Invite new user**. Enter the email address which you copied in step 4. 1. Navigate to **Users and Permissions**. Click on the invited user and go to the **App permissions** tab. Add the desired applications to grant access to. 1. This will open a dialog where you can select the required permissions. Make sure to check at least all permissions in the **Release** section, then click **Apply**. 1. Finally, click **Invite User** to finish setting up the service account. Upload this JSON key file when configuring your Google Play Store destination in the Capawesome Cloud Console. ## Release Tracks After submission, your app will be available on the selected track in Google Play Console. You can manage releases, view analytics, and promote builds to higher tracks (e.g., from beta to production) directly in Google Play Console. # Automations Use [Capawesome Cloud](https://cloud.capawesome.io/) to automatically trigger builds when you push to a branch or create a tag. Set up an automation once and every matching Git event will kick off a build — no manual steps needed. ## Features - ⚡ **Git-Triggered Builds**: Automatically start builds when you push to a branch or create a tag. - 🎯 **Pattern Matching**: Target specific branches or tags using name patterns. - 💬 **Commit Message Filtering**: Optionally filter triggers based on the commit message content. - 📱 **Multi-Platform**: Configure automations per platform — Android, iOS, or Web. - ⚙️ **Build Settings**: Attach a signing certificate, environment, destination, or channel to each automation. - 🚫 **Skip CI**: Skip automations for specific commits by adding `[skip ci]` to the commit message. - 🔀 **Enable/Disable**: Toggle automations on or off without deleting them. ## Getting Started - **Setup Automations** ______________________________________________________________________ Create your first automation with Capawesome Cloud. ______________________________________________________________________ [Getting Started](https://capawesome.io/cloud/automations/setup/index.md) ## Documentation - **Webhooks** ______________________________________________________________________ Learn how to configure webhooks for your automations. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/automations/webhooks/index.md) - **Environments** ______________________________________________________________________ Use environment variables and secrets to customize your builds. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/environments/index.md) - **Signing Certificates** ______________________________________________________________________ Configure signing certificates and provisioning profiles for your apps. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/certificates/index.md) - **Build Types** ______________________________________________________________________ Understand the different build types available for iOS and Android. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/build-types/index.md) # Getting Started In this guide, you will learn how to create your first automation using Capawesome Cloud. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account and organization. - An app created in the console (see [Getting Started with Native Builds](https://capawesome.io/cloud/native-builds/setup/index.md)). - A Git repository connected to the app (see [Connect Git Repository](https://capawesome.io/cloud/native-builds/setup/#step-2-connect-git-repository)). - A Git provider account linked (see [Integrations](https://capawesome.io/cloud/integrations/index.md)). - An organization plan with build minutes. ## Step 1: Navigate to Automations Navigate to the [Automations](https://console.cloud.capawesome.io/apps/_/automations) page of your app in the Capawesome Cloud Console. ## Step 2: Create an Automation Click on the **Create Automation** button and configure the following properties: - **Name**: A display name to identify the automation. - **Platform**: The target platform — Android, iOS, or Web. - **Trigger Type**: The Git event that triggers the build — **Branch** (triggers on push to a branch) or **Tag** (triggers on tag creation). - **Trigger Pattern**: The branch or tag name to match. For example, `main` to trigger on pushes to the `main` branch, or `v*` to trigger on tags starting with `v`. - **Commit Message Pattern** (optional): A pattern to filter triggers based on the commit message content. ## Step 3: Configure Build Settings (Optional) You can optionally configure additional build settings for the automation: - **Build Type**: The type of build to create (see [Build Types](https://capawesome.io/cloud/native-builds/build-types/index.md)). - **Build Stack**: The build stack to use (see [Build Stacks](https://capawesome.io/cloud/native-builds/build-stacks/index.md)). - **Signing Certificate**: The signing certificate for the build (see [Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/index.md)). - **Environment**: The environment variables for the build (see [Environments](https://capawesome.io/cloud/native-builds/environments/index.md)). - **Destination**: The publish destination for the build artifact (see [Store Destinations](https://capawesome.io/cloud/app-store-publishing/destinations/index.md)). - **Channel**: The live update channel to deploy to (see [Channels](https://capawesome.io/cloud/live-updates/channels/index.md)). ## Step 4: Verify Webhook Setup When you create an automation, Capawesome Cloud automatically registers a webhook on your connected Git repository. This webhook listens for the configured Git events and triggers builds accordingly. If automatic registration fails, you can configure the webhook manually. See [Webhooks](https://capawesome.io/cloud/automations/webhooks/index.md) for instructions. ## Step 5: Test the Automation Push a commit or create a tag that matches your trigger pattern and verify that: 1. The automation's **Last triggered** timestamp updates on the Automations page. 1. A new build appears on the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page. Congratulations! You have successfully created your first automation using Capawesome Cloud. 🎉 ## Managing Automations After creating an automation, you can manage it from the Automations page: - **Edit**: Update the automation's configuration by clicking on the **Edit** button. - **Enable/Disable**: Toggle an automation on or off without deleting it. Disabled automations do not trigger builds. - **History**: View the build history for a specific automation by clicking on the **History** button. - **Delete**: Remove an automation by clicking on the **Delete** button and confirming the deletion. ## Skipping Automations You can skip automations for a specific push by including one of the following keywords in the **head commit** message: - `[skip ci]` - `[ci skip]` - `[skip capawesome]` - `[capawesome skip]` The keyword check is **case-insensitive** and can appear anywhere in the commit message. For example: ``` docs: update readme [skip ci] ``` This is useful for documentation-only changes or other commits that don't require a build. Note Only the head commit message (i.e. the latest commit in the push) is checked. ## Next Steps - **Webhooks** ______________________________________________________________________ Learn how to configure webhooks for your automations. [Webhooks](https://capawesome.io/cloud/automations/webhooks/index.md) - **Build Types** ______________________________________________________________________ Learn about different build types available for iOS and Android platforms. [Build Types](https://capawesome.io/cloud/native-builds/build-types/index.md) - **Environments** ______________________________________________________________________ Use environment variables and secrets to customize your builds. [Environments](https://capawesome.io/cloud/native-builds/environments/index.md) - **Signing Certificates** ______________________________________________________________________ Set up signing certificates for production builds on iOS and Android. [Certificates](https://capawesome.io/cloud/native-builds/certificates/index.md) # Webhooks Automations use webhooks to receive Git events from your provider. When you create an automation, Capawesome Cloud automatically registers a webhook on your connected Git repository. If automatic registration fails, you can configure the webhook manually. ## Automatic Setup When you create an automation, Capawesome Cloud attempts to register a webhook on your Git repository automatically. If the registration succeeds, no further action is needed — the webhook is ready to receive events. ## Manual Setup In some cases, automatic webhook registration may fail — for example, due to insufficient permissions or when using a self-hosted Git provider. In these cases, you can configure the webhook manually using the values provided in the **Setup** dialog. ### Access the Setup Dialog To access the Setup dialog, navigate to the [Automations](https://console.cloud.capawesome.io/apps/_/automations) page of your app in the Capawesome Cloud Console and click the **Setup** button. The dialog displays two values: - **Webhook URL**: The endpoint that receives Git events from your provider. - **Webhook Secret**: A secret used to verify incoming webhook payloads. Both values have copy-to-clipboard buttons for convenience. ### Configure Your Git Provider Add a new webhook in your Git provider's settings using the **Webhook URL** and **Webhook Secret** from the Setup dialog. The webhook should be configured to send push events. For provider-specific instructions on adding webhooks, refer to the following documentation: - [GitHub](https://capawesome.io/cloud/integrations/github/index.md) - [GitLab](https://capawesome.io/cloud/integrations/gitlab/index.md) - [Bitbucket](https://capawesome.io/cloud/integrations/bitbucket/index.md) - [Azure DevOps](https://capawesome.io/cloud/integrations/azure-devops/index.md) ## Verifying the Webhook To confirm that the webhook is working correctly: 1. Push a commit or create a tag that matches one of your automation's trigger patterns. 1. Navigate to the [Automations](https://console.cloud.capawesome.io/apps/_/automations) page and check that the automation's **Last triggered** timestamp has updated. 1. Navigate to the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page and verify that a new build was created. # Integrations Connect Capawesome Cloud with external services to streamline your workflow for Live Updates and Native Builds. ## Git Providers - **GitHub** ______________________________________________________________________ Connect your GitHub repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/github/index.md) - **GitHub Enterprise** ______________________________________________________________________ Connect your GitHub Enterprise repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/github-enterprise/index.md) - **GitLab** ______________________________________________________________________ Connect your GitLab repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/gitlab/index.md) - **GitLab Self-Managed** ______________________________________________________________________ Connect your GitLab Self-Managed repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/gitlab-self-managed/index.md) - **Bitbucket** ______________________________________________________________________ Connect your Bitbucket repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/bitbucket/index.md) - **Azure DevOps** ______________________________________________________________________ Connect your Azure DevOps repositories to Capawesome Cloud. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/azure-devops/index.md) ## Notifications - **Discord** ______________________________________________________________________ Receive webhook notifications in a Discord channel. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/discord/index.md) - **Microsoft Teams** ______________________________________________________________________ Receive webhook notifications in a Microsoft Teams channel. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/microsoft-teams/index.md) - **Slack** ______________________________________________________________________ Receive webhook notifications in a Slack channel. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/integrations/slack/index.md) # Azure DevOps Integration Connect your Azure DevOps repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code and automate deployments directly from your Azure DevOps repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A Microsoft account with access to Azure DevOps and the repository you want to connect. - Admin or owner permissions for the Azure DevOps repository. ## Connect your repository Follow these steps to connect your Azure DevOps account to Capawesome Cloud: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **Azure DevOps** tab in the **Git Providers** section. 1. Click the **Connect** button in the **Azure DevOps** section and authorize Capawesome Cloud to access your Microsoft account. Review the permissions requested and click **Authorize** to grant access. 1. After authorization, you will be redirected back to the Capawesome Cloud Console. In the **Git Repositories** section, select **Azure DevOps** as your Git provider, choose an organization and project, and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your Azure DevOps repository is now connected to Capawesome Cloud. ## Troubleshooting If you encounter issues while connecting your Azure DevOps account, consider the following common problems: - **Account type**: Only Business or School Microsoft accounts are supported. Personal accounts (e.g., @gmail.com) cannot be used. - **Custom domains**: Custom domains must be verified in Microsoft Entra. - **Organization connection**: Your Azure DevOps organization must be connected to your Microsoft Entra tenant (Organization Settings → Microsoft Entra → Connect). # Bitbucket Integration Connect your Bitbucket repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code and automate deployments directly from your Bitbucket repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A Bitbucket account with access to the repository you want to connect. - Admin or owner permissions for the Bitbucket repository. ## Connect your repository Follow these steps to connect your Bitbucket account to Capawesome Cloud: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **Bitbucket** tab in the **Git Providers** section. 1. Click the **Connect** button in the **Bitbucket** section and authorize Capawesome Cloud to access your Bitbucket account. Review the permissions requested and click **Authorize** to grant access. 1. After authorization, you will be redirected back to the Capawesome Cloud Console. In the **Git Repositories** section, select **Bitbucket** as your Git provider, choose an owner (your user account or a workspace), and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your Bitbucket repository is now connected to Capawesome Cloud. # Discord Integration Receive real-time [webhook](https://capawesome.io/cloud/webhooks/index.md) notifications from Capawesome Cloud directly in a Discord channel. No extra configuration is needed — the platform is automatically detected from the webhook URL. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A Discord server with the **Manage Webhooks** permission. ## Setup Follow these steps to set up Discord notifications for your app: 1. Create a webhook in your Discord server by navigating to **Server Settings > Integrations > Webhooks** and clicking **New Webhook**. Select the channel you want notifications posted to and copy the webhook URL. For detailed instructions, see the [Discord documentation](https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks). 1. Navigate to the [Webhooks](https://console.cloud.capawesome.io/apps/_/webhooks) page in the Capawesome Cloud Console. Create a new webhook (or edit an existing one) and paste the Discord webhook URL. 1. Select the [events](https://capawesome.io/cloud/webhooks/#events) you want to receive and save the webhook. That's it. Capawesome Cloud automatically detects `discord.com` in the URL and sends Discord-compatible messages. Notifications will appear in your selected channel whenever the configured events are triggered. For more details on supported events, payloads, signature verification, and delivery history, see the [Webhooks](https://capawesome.io/cloud/webhooks/index.md) documentation. # GitHub Enterprise Integration Connect your GitHub Enterprise repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code from your self-hosted or managed GitHub Enterprise instance and automate deployments directly from your repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - Access to a GitHub Enterprise instance. - A GitHub Enterprise personal access token with repository access permissions. - Admin or owner permissions for the GitHub Enterprise repository. ## Connect your repository Follow these steps to connect your GitHub Enterprise account to Capawesome Cloud: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **GitHub** tab in the **Git Providers** section. 1. A dialog will appear prompting you to enter your GitHub Enterprise instance URL and token. Fill in the required information and click **Connect**. 1. After connecting, select **GitHub Enterprise** as your Git provider within the **Git Repositories** section. 1. Choose an owner (organization) and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your GitHub Enterprise repository is now connected to Capawesome Cloud. # GitHub Integration Connect your GitHub repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code and automate deployments directly from your GitHub repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A GitHub account with access to the repository you want to connect. - Admin or owner permissions for the GitHub repository. ## Connect your repository Follow these steps to connect your GitHub account to Capawesome Cloud: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **GitHub** tab in the **Git Providers** section. 1. Click the **Connect** button in the **GitHub** section and authorize Capawesome Cloud to access your GitHub account. Review the permissions requested and click **Authorize** to grant access. 1. After authorization, you will be redirected back to the Capawesome Cloud Console. In the **Git Repositories** section, select **GitHub** as your Git provider, choose an owner (your user account or an organization), and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your GitHub repository is now connected to Capawesome Cloud. ## Troubleshooting ### Organization repository not visible If you are an **outside collaborator** of a GitHub organization, the organization's repositories may not appear in the repository selection dropdown. GitHub only grants access to organization resources for **members**, not outside collaborators. To fix this, ask the organization owner to change your role from **outside collaborator** to **member**. # GitLab Self-Managed Integration Connect your GitLab Self-Managed repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code from your self-hosted GitLab instance and automate deployments directly from your repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - Access to a GitLab Self-Managed instance. ## Create a personal access token To connect your GitLab Self-Managed instance to Capawesome Cloud, you need to create a personal access token with the appropriate permissions. Make sure the token owner has at least the **Reporter** role on the project you want to connect. 1. Log in to your GitLab Self-Managed instance. 1. In the upper-right corner, click on your avatar and select **Preferences**. 1. In the left sidebar, select **Personal access tokens**. 1. Click the **Add new token** button. 1. Enter a descriptive name for the token (e.g., "Capawesome Cloud"). 1. Set an expiration date for the token (optional, but recommended for security). 1. Select the following scopes: `api`, `read_user` and `read_repository`. 1. Click **Create personal access token**. 1. Copy the generated token immediately and store it securely. You will not be able to see it again. You will use this token when connecting your GitLab Self-Managed instance to Capawesome Cloud in the next step. ## Connect your repository To connect your GitLab Self-Managed repository to Capawesome Cloud, follow these steps: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **GitLab** tab in the **Git Providers** section. 1. A dialog will appear prompting you to enter your GitLab Self-Managed instance URL and token. Fill in the required information and click **Connect**. 1. After connecting, select **GitLab Self-Managed** as your Git provider within the **Git Repositories** section. 1. Choose an owner (group) and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your GitLab Self-Managed repository is now connected to Capawesome Cloud. # GitLab Integration Connect your GitLab repositories to Capawesome Cloud to streamline your workflow for Live Updates and Native Builds. This integration allows Capawesome Cloud to access your source code and automate deployments directly from your GitLab repository. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A GitLab account with access to the repository you want to connect. - Admin or owner permissions for the GitLab repository. ## Connect your repository Follow these steps to connect your GitLab account to Capawesome Cloud: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page in the Capawesome Cloud Console. 1. Click on the **GitLab** tab in the **Git Providers** section. 1. Click the **Connect** button in the **GitLab** section and authorize Capawesome Cloud to access your GitLab account. Review the permissions requested and click **Authorize** to grant access. 1. After authorization, you will be redirected back to the Capawesome Cloud Console. In the **Git Repositories** section, select **GitLab** as your Git provider, choose an owner (your user account or a group), and select the repository you want to connect to Capawesome Cloud. 1. Click the **Save** button to finalize the integration. Your GitLab repository is now connected to Capawesome Cloud. # Microsoft Teams Integration Receive real-time [webhook](https://capawesome.io/cloud/webhooks/index.md) notifications from Capawesome Cloud directly in a Microsoft Teams channel. No extra configuration is needed — the platform is automatically detected from the webhook URL. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A Microsoft Teams workspace with permission to create Workflows. ## Setup Follow these steps to set up Microsoft Teams notifications for your app: 1. Create an incoming webhook in Teams using Workflows. You can either use a webhook template (select **More options** next to a channel > **Workflows**) or create one from scratch using the **When a Teams webhook request is received** trigger. After creating the workflow, copy the generated HTTP POST URL. For detailed instructions, see the [Microsoft documentation](https://support.microsoft.com/en-us/office/create-incoming-webhooks-with-workflows-for-microsoft-teams-8ae491c7-0394-4861-ba59-055e33f75498). 1. Navigate to the [Webhooks](https://console.cloud.capawesome.io/apps/_/webhooks) page in the Capawesome Cloud Console. Create a new webhook (or edit an existing one) and paste the Teams webhook URL. 1. Select the [events](https://capawesome.io/cloud/webhooks/#events) you want to receive and save the webhook. That's it. Capawesome Cloud automatically detects `powerplatform.com` in the URL and sends messages as Adaptive Cards. Notifications will appear in your selected channel whenever the configured events are triggered. Tip If you need to retrieve the webhook URL later, you can find it by editing the workflow in Microsoft Teams. For more details on supported events, payloads, signature verification, and delivery history, see the [Webhooks](https://capawesome.io/cloud/webhooks/index.md) documentation. # Slack Integration Receive real-time [webhook](https://capawesome.io/cloud/webhooks/index.md) notifications from Capawesome Cloud directly in a Slack channel. No extra configuration is needed — the platform is automatically detected from the webhook URL. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account with an app created. - A Slack workspace where you have permission to install apps. ## Setup Follow these steps to set up Slack notifications for your app: 1. Create a Slack app with an incoming webhook by following the [Slack documentation](https://docs.slack.dev/messaging/sending-messages-using-incoming-webhooks/). Enable **Incoming Webhooks** in your app settings, add a new webhook to a workspace, select the target channel, and copy the generated webhook URL (e.g. `https://hooks.slack.com/services/...`). 1. Navigate to the [Webhooks](https://console.cloud.capawesome.io/apps/_/webhooks) page in the Capawesome Cloud Console. Create a new webhook (or edit an existing one) and paste the Slack webhook URL. 1. Select the [events](https://capawesome.io/cloud/webhooks/#events) you want to receive and save the webhook. That's it. Capawesome Cloud automatically detects `slack.com` in the URL and sends Slack-compatible messages. Notifications will appear in your selected channel whenever the configured events are triggered. Warning Your Slack webhook URL contains a secret. Do not share it publicly or commit it to version control. Slack actively searches for and revokes leaked webhook URLs. For more details on supported events, payloads, signature verification, and delivery history, see the [Webhooks](https://capawesome.io/cloud/webhooks/index.md) documentation. # Live Updates Use [Capawesome Cloud](https://cloud.capawesome.io/) to deliver updates to your Capacitor app in real-time. Ship critical bug fixes, new features, and content updates instantly without submitting a native update to the app stores. ## Features - 📱 **Cross-Platform**: Works with Android and iOS apps built with Capacitor. - 📦 **Bundle Management**: Upload, download and delete bundles. - 📺 **Channel Support**: Deliver bundles via multiple channels. - 🚀 **Rollout**: Gradually roll out new bundles to gather valuable feedback. - 💾 **Self-Hosting**: You can either host your bundles yourself or upload them to Capawesome Cloud. - 📈 **Analytics**: Get insights into the number of your monthly active users. - 💳 **Payments**: Upgrade and downgrade your plan at any time with monthly and annual payments. - 💼 **Enterprise**: Deploy live updates to millions of users in real-time. - 🔒 **Security**: Sign bundles with a private key to ensure that only you can publish updates. - 📚 **Documentation**: Comprehensive documentation to help you get started. ## Getting Started - **Setup Live Updates** ______________________________________________________________________ Get started by installing the Live Update plugin and publishing your first update. ______________________________________________________________________ [Getting Started](https://capawesome.io/cloud/live-updates/setup/index.md) ## Documentation - **Bundles** ______________________________________________________________________ Learn how to create and manage bundles for your app. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/bundles/index.md) - **Channels** ______________________________________________________________________ Learn how to create and manage channels for your app. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/channels/index.md) - **Devices** ______________________________________________________________________ Learn how to manage devices for your app. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/devices/index.md) - **Logs** ______________________________________________________________________ Learn how to debug your app using logs. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/logs/index.md) - **FAQ** ______________________________________________________________________ Find answers to frequently asked questions about Capawesome Cloud Live Updates. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/live-updates/faq/index.md) ## Guides Here are some guides to help you get started with Capawesome Cloud Live Updates: - [Announcing the Capacitor Live Update Plugin](https://capawesome.io/blog/announcing-the-capacitor-live-update-plugin/index.md) - [How to restrict Capacitor Live Updates to Native Versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/index.md) - [How to gradually roll out Capacitor Live Updates](https://capawesome.io/blog/how-to-gradually-roll-out-capacitor-live-updates/index.md) - [The App Update Delivery Guide for Capacitor](https://capawesome.io/blog/the-app-update-delivery-guide-for-capacitor/index.md) ______________________________________________________________________ 1. In order to use our servers in Germany, the Capacitor Live Update plugin must be configured accordingly. Further information can be found in the [Privacy](https://capawesome.io/cloud/live-updates/advanced/privacy/index.md) section. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Bundles A bundle (aka "Live Update") is an artifact of a build that is deployed to a live update channel within Capawesome Cloud. Bundles contain the web assets (HTML, CSS, JavaScript, images, etc.) of your Capacitor app that can be updated without going through the app store review process since no native code changes are involved. Relationship between Artifacts, Builds, Deployments and Channels in Capawesome Cloud Delivering a live update to your users typically involves the following steps: 1. **Build**: You create a web build of your Capacitor app (either locally or using the Capawesome Cloud build service) which generates the web assets (HTML, CSS, JavaScript, images, etc.). If building locally, you then upload these assets as a bundle to Capawesome Cloud. 1. **Deploy**: You deploy the bundle to a specific live update channel (e.g., "production", "staging", etc.) within Capawesome Cloud. 1. **Fetch**: Your Capacitor app running on users' devices fetches the latest bundle from the specified live update channel using the Capacitor [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. 1. **Apply**: The app applies the fetched bundle, updating the web assets without requiring a full app update through the app store. Capawesome Cloud provides multiple ways to create bundles for your Capacitor app, either by building them in the cloud or by uploading or registering them from your local machine or hosting service. Read on to learn more about how to create bundles in Capawesome Cloud. ## Create Bundles ### Build a bundle It's possible to create a bundle without any local tooling by **using the Capawesome Cloud build service**. This service allows you to create a build for your app directly in the cloud, which will generate a bundle that can be deployed to a live update channel. To create a bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:builds:create`](https://capawesome.io/cloud/cli/#appsbuildscreate) command: ``` npx @capawesome/cli apps:builds:create --platform web ``` If not specified, the CLI will prompt you to select the app, platform, and git reference (branch, tag, or commit SHA) you want to build. The CLI will then create the build in the Capawesome Cloud, which will start the build process and generate a build artifact. As soon as the build is complete, a build number (e.g., `#42`) will be logged to the console. You can then use this build number to create a deployment to a specific channel using the [`apps:deployments:create`](https://capawesome.io/cloud/cli/#appsdeploymentscreate) command: ``` npx @capawesome/cli apps:deployments:create ``` If not specified, the CLI will prompt you to select the app, build number, and channel you want to deploy the bundle to. To create a bundle using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to create the build for, and click on the [Builds](https://console.cloud.capawesome.io/apps/_/builds) menu item. Next, click on the "Build from Git" button, select the platform as "Web", and provide the git reference (branch, tag, or commit SHA) you want to build. Finally, click on the "Build" button to start the build process. As soon as the build is complete, you can create a deployment to a specific channel by navigating to the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) menu item, clicking on the "Create Deployment" button, selecting the build you just created, and choosing the channel you want to deploy the bundle to. ### Upload a bundle You can also upload a bundle directly from your local machine using the Capawesome CLI. This is useful if you want to speed up the process or if you don't want to spend build minutes on Capawesome Cloud. However, keep in mind that this way you will not have access to the environment variables and secrets stored in Capawesome Cloud. To upload a bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:liveupdates:upload`](https://capawesome.io/cloud/cli/#appsliveupdatesupload) command: ``` npx @capawesome/cli apps:liveupdates:upload ``` If not specified, the CLI will prompt you to select the app and channel you want to upload the bundle to, as well as the path to the web assets (HTML, CSS, JavaScript, images, etc.) of your Capacitor app (e.g., `dist` or `www`). The CLI will then upload the bundle as build artifact and create a deployment in the specified channel making the bundle instantly available to your users. ### Register a bundle If you prefer to host your bundles on your own infrastructure or a third-party service (e.g., AWS S3, Firebase Hosting, etc.), you can register a bundle by providing the URL where the bundle is hosted. Read more about [Self-Hosting](https://capawesome.io/cloud/live-updates/advanced/self-hosting/index.md). To register a bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:liveupdates:register`](https://capawesome.io/cloud/cli/#appsliveupdatesregister) command: ``` npx @capawesome/cli apps:liveupdates:register ``` If not specified, the CLI will prompt you to select the app and channel you want to register the bundle to, as well as the URL where the bundle is hosted. The URL must point to a ZIP file containing the web assets (HTML, CSS, JavaScript, images, etc.) of your Capacitor app. If you have not created the ZIP file yet, you can use the [`apps:liveupdates:bundle`](https://capawesome.io/cloud/cli/#appsliveupdatesbundle) command to bundle the web assets into a ZIP file. The CLI will then register the bundle as build artifact and create a deployment in the specified channel making the bundle instantly available to your users. ## Advanced ### Custom Properties You can add custom properties to bundles to provide additional information: ``` npx @capawesome/cli apps:liveupdates:upload --custom-property key1=value1 --custom-property key2=value2 ``` This can be useful for tracking metadata such as the version number, release notes, or any other information you want to associate with the bundle. Using the `fetchLatestBundle(...)` method of the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin, you can then access these properties in your app: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const fetchLatestBundle = async () => { const { customProperties } = await LiveUpdate.fetchLatestBundle(); // customProperties = { key1: 'value1', key2: 'value2' } }; ``` Custom properties can be updated at any time using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) or the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ### Expiration You can set a custom expiration date for build artifacts using the `--expires-in-days` flag: ``` npx @capawesome/cli apps:liveupdates:upload --expires-in-days 7 ``` This can be useful if you want to ensure that build artifacts are only available for a limited time, such as for beta testing or temporary releases. After the specified number of days, the build artifact will be automatically deleted from Capawesome Cloud. Please note that the custom expiration date cannot be higher than your plan's maximum retention period. ### Rollout If you want to gradually release a bundle to your users, you can use the rollout feature to control the percentage of users that receive the update. This allows you to monitor the update for any issues before rolling it out to all users. ``` npx @capawesome/cli apps:liveupdates:upload --rollout-percentage 10 ``` This command will upload the bundle with a rollout percentage of 10%, meaning that only 10% of users will receive the update initially. Read more about [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md). ### Versioning One way to restrict live updates to specific native versions is by using [Versioned Bundles](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-bundles). When uploading a bundle, you can specify the minimum, maximum, and equivalent version codes for both Android and iOS platforms. ``` npx @capawesome/cli apps:liveupdates:upload --android-eq 3 --android-max 5 --android-min 1 --ios-eq 1.0.3 --ios-max 1.0.5 --ios-min 1.0.0 ``` On Android, the `versionCode` from the `android/app/build.gradle` file is used (e.g., `versionCode 3`), while on iOS, the `CFBundleVersion` from the `Info.plist` file is used (e.g., `CFBundleVersion1.0.3` or `CFBundleVersion3`). We recommend to NOT use semantic versioning for the iOS `CFBundleVersion` to keep both platforms consistent. This allows you to control which native versions of your app can download and apply the live update bundle, ensuring compatibility and preventing potential issues with unsupported versions. Read more about [Versioned Bundles](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-bundles) and [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels). # Channels Every device must be assigned to a channel to receive live updates. Channels let you distribute different versions of your app to different groups of users, so you can test new features with a small group before rolling them out to everyone. If you don't need to assign specific channels per device, you can [set a default channel](#default-channel) that is used automatically when no specific channel is assigned. ## Default Channel Each app has a default channel named `default` to simplify the initial setup. This channel is used when no specific channel is specified during the bundle upload process or when fetching updates in your app using the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. A default channel is not required for your app to function. You can rename, delete, or [set another channel as the default channel](#set-default-channel) at any time. ## Protected Channels Channels can be marked as "protected" to enhance the security of your live updates. When a channel is protected, all builds associated with that channel must be code-signed before they can be distributed to users. This ensures that only authorized builds are delivered through the protected channel. Read more about [Code Signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md) in Capawesome Cloud. ## Manage Channels You can manage channels associated with your app using the Capawesome CLI or the Capawesome Cloud Console. ### Create a channel To create a channel using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:channels:create`](https://capawesome.io/cloud/cli/#appschannelscreate) command: ``` npx @capawesome/cli apps:channels:create ``` You will be prompted to select the app you want to create the channel for and to provide the name of the channel. The CLI will then create the channel in the Capawesome Cloud. To create a channel using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to create the channel for, and click on the [Channels](https://console.cloud.capawesome.io/apps/_/channels) menu item under the "Live Updates" section in the sidebar. Next, click on the "Create Channel" button and enter the name of the channel you want to create. ### Update a channel To update a channel using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:channels:update`](https://capawesome.io/cloud/cli/#appschannelsupdate) command: ``` npx @capawesome/cli apps:channels:update ``` You will be prompted to select the app you want to update the channel for and to provide the ID of the channel. The CLI will then update the channel in the Capawesome Cloud based on the provided information. To update a channel using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to update the channel for, and click on the [Channels](https://console.cloud.capawesome.io/apps/_/channels) menu item under the "Live Updates" section in the sidebar. Next, select the channel you want to update. In the menu, click on the "Edit" button to update the name and settings of the channel. ### Delete a channel To delete a channel using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:channels:delete`](https://capawesome.io/cloud/cli/#appschannelsdelete) command: ``` npx @capawesome/cli apps:channels:delete ``` You will be prompted to select the app you want to delete the channel for and to provide the name of the channel. The CLI will then delete the channel from the Capawesome Cloud. To delete a channel using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to delete the channel for, and click on the [Channels](https://console.cloud.capawesome.io/apps/_/channels) menu item under the "Live Updates" section in the sidebar. Next, select the channel you want to delete. In the menu, click on the "Delete" button to delete the channel. ### Set default channel To set a channel as the [default channel](#default-channel) using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to set the default channel for, and click on the [Settings](https://console.cloud.capawesome.io/apps/_/settings) menu item in the sidebar. Next, in the "Live Update Channels" section, enable the "Default Channel" toggle, select the channel you want to set as the default channel from the dropdown menu, and click on the "Save" button. ## Channel Discovery Channel discovery allows the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin to fetch the list of available channels without requiring authentication. This is useful for apps that want to let developers or QA select a channel from a list of available channels (e.g. a beta channel). To enable channel discovery using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to enable channel discovery for, and click on the [Settings](https://console.cloud.capawesome.io/apps/_/settings) menu item in the sidebar. Next, in the "Live Update Channels" section, enable the "Channel Discovery" toggle and click on the "Save" button. Once enabled, you can use the [`fetchChannels(...)`](https://capawesome.io/plugins/live-update/#fetchchannels) method of the Live Update plugin to fetch the list of available channels. # Devices Devices represent unique installations of your app that have connected to the Capawesome Cloud to check for live updates. Each device is identified by a unique device ID and can be associated with a specific channel to control which updates it receives. Managing devices allows you to monitor and control the distribution of your live updates effectively. ## Channel Assignment Each device can be assigned to a specific [Channel](https://capawesome.io/cloud/live-updates/channels/index.md) to control which live updates it receives. There are two ways to set a channel for a device: ### Set a channel You can set the channel programmatically by calling the [`setChannel(...)`](https://capawesome.io/plugins/live-update/#setchannel) method from the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setChannel = async () => { await LiveUpdate.setChannel({ channel: 'production' }); }; ``` This is the recommended approach as it allows the app to select the appropriate channel automatically without manual intervention. For example, you could dynamically assign a channel based on the app's native version code to ensure each version only receives compatible updates (see [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels)): ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { // Get the version code of the native app const { versionCode } = await LiveUpdate.getVersionCode(); // Automatically download and set the latest compatible bundle await LiveUpdate.sync({ channel: `production-${versionCode}` }); }; ``` ### Force a channel Alternatively, you can force a specific channel for a device via the Capawesome Cloud Console. Navigate to the app's [Devices](https://console.cloud.capawesome.io/apps/_/devices) page, select the device, and assign a channel on the detail page. A channel set through the Cloud Console **takes precedence** over any channel set by the Live Update plugin. This can be useful for testing purposes, such as directing a specific device to a staging channel regardless of what the app itself requests. ## Manage Devices You can manage devices associated with your app using the Capawesome CLI or the Capawesome Cloud Console. ### Create a device Devices are created automatically when a user checks for a live update for the first time by calling the [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchLatestBundle) or [`sync()`](https://capawesome.io/plugins/live-update/#sync) method from the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. ### Force a channel By default, devices receive live updates from the channel set by the Live Update SDK. You can override this by forcing a specific channel on a device. When a channel is forced, the SDK-selected channel is ignored and the device receives updates exclusively from the forced channel instead. This can be useful for debugging, testing, or manually managing the channel for a specific device. Read our [blog post](https://capawesome.io/blog/capawesome-cloud-forced-channel-assignments/index.md) for more details and use cases. To force a channel on a device using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:devices:forcechannel`](https://capawesome.io/cloud/cli/#appsdevicesforcechannel) command: ``` npx @capawesome/cli apps:devices:forcechannel ``` You will be prompted to select the app and provide the device ID and channel name. To remove the forced channel and return to normal SDK behavior, use the [`apps:devices:unforcechannel`](https://capawesome.io/cloud/cli/#appsdevicesunforcechannel) command: ``` npx @capawesome/cli apps:devices:unforcechannel ``` To force a channel on a device using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to force the channel for, and click on the [Devices](https://console.cloud.capawesome.io/apps/_/devices) menu item under the "Live Updates" section in the sidebar. Next, click on "Edit" in the "Actions" menu of the device you want to force a channel for. In the dialog, select the channel you want to force and confirm the changes. ### Delete a device A device can be deleted at any time. To delete a device using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:devices:delete`](https://capawesome.io/cloud/cli/#appsdevicesdelete) command: ``` npx @capawesome/cli apps:devices:delete ``` You will be prompted to select the app you want to delete the device for and to provide the ID of the device. The CLI will then delete the device from the Capawesome Cloud. To delete a device using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to delete the device for, and click on the [Devices](https://console.cloud.capawesome.io/apps/_/devices) menu item under the "Live Updates" section in the sidebar. Next, select the device you want to delete. In the menu, click on the "Delete" button to delete the device. Please note that deleting a device will **not lower your Monthly Active Users (MAU) count** as this is based on unique devices that have connected within the current billing period. # FAQ ## General ### Are Live Updates compliant with the Apple App Store policies? Yes, Live Updates are compliant with the Apple App Store policies. The [Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) states that interpreted code may be downloaded to an application as long as it does not change the primary purpose of the application and does not bypass signing, sandbox, or other security features of the OS: > Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS. So as long as you do not change the primary purpose of your app via Live Updates, they are fully compliant with the Apple App Store policies since they only update the web layer of your app. ### Are Live Updates compliant with the Google Play policies? Yes, Live Updates are compliant with the Google Play Policies. The third paragraph of [Device and Network Abuse](https://support.google.com/googleplay/android-developer/answer/9888379/) states that an app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. However, the same paragraph also states that this restriction does not apply to JavaScript running in a webview or browser: > This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser). As Live Updates can only update the web layer of your app, they are fully compliant with the Google Play Policies. ### What are "Binary Compatible Changes"? Binary compatible changes are changes that only affect your web application and do not require a native update. So as long as you only make changes to your web application, you can deploy Live Updates without having to resubmit your app to the app stores. As soon as you make a change to the native code, you must resubmit your app to the app stores. **Examples of binary compatible changes**: - Changes to your HTML, CSS, or JavaScript - Changes to your assets (images, fonts, etc.) **Examples of changes that are NOT binary compatible**: - Changes to native code (Java, Swift, Objective-C) - Changes to native dependencies (CocoaPods, Gradle, etc.) Read on the learn how to [restrict bundles to specific native versions](#how-can-i-restrict-bundles-to-specific-native-versions). ## Bundles ### How can I restrict bundles to specific native versions? You can restrict bundles to specific native versions by using [Versioned Builds](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/#versioned-builds) or [Versioned Channels](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/#versioned-channels). ### How can i roll back to a previous bundle? Check out the [Rollbacks](https://capawesome.io/cloud/live-updates/advanced/rollbacks/index.md) section for more information on how to roll back to a previous bundle. ### How can i roll out a bundle to only a subset of users? Check out the [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md) section for more information on how to roll out a bundle to only a subset of users. ### What is the maximum size of a bundle? Currently, the maximum size of a bundle provided via the Capawesome Cloud is **1 GB**. If you need to upload larger bundles, please reach out to us. 1. **Reduce your bundle size**: Bundle sizes can be reduced by removing source maps, optimizing assets, and choosing the right artifact type. Check out our guide on [how to reduce the bundle size of Capacitor live updates](https://capawesome.io/blog/how-to-reduce-the-bundle-size-of-capacitor-live-updates/index.md) for more details. 1. **Create a bundle with the artifact type `manifest`**: The files of a bundle with the artifact type `manifest` are saved individually (see [Delta Updates](https://capawesome.io/cloud/live-updates/advanced/delta-updates/index.md)), which means that the limit of 100MB applies to each file and not to the entire bundle. This means that significantly larger bundles can be uploaded. 1. **Host the bundle yourself**: It's possible to host larger bundles yourself and provide the URL to the file when [creating a new bundle](https://capawesome.io/cloud/live-updates/bundles/#create-a-bundle). ### What happens if i delete a bundle? If you delete a bundle, it will no longer be available for download by devices. However, devices that have already downloaded the bundle will continue to use it until they receive a new update. If more than one bundle exists (in the channel), then the previous bundle automatically becomes the new latest bundle and is available for download. ### How can I automatically delete old bundles? There are two options for automatically deleting old bundles so that you don't have to worry about the storage limit: 1. **Set a bundle limit**: You can set a limit for the number of bundles that can be assigned to a channel. If this limit is reached, the oldest bundle will be deleted automatically. 1. **Set an expiration date**: You can set an expiration date for a bundle. If this date is reached, the bundle will be deleted automatically. ## Channels ### What happens if i delete a channel? If you delete a channel, all bundles associated with this channel will be deleted as well. Devices that have already downloaded a bundle from this channel will continue to use it until they receive a new update. ## Devices ### What happens if i delete a device? If you delete a device, it will no longer be displayed in the device list. As soon as the device checks for a live update again, it will be restored in the device list. Deleting a device does **not** affect the Monthly Active Users (MAU) count. ## Billing ### How are Monthly Active Users (MAU) counted? A Monthly Active User (MAU) is counted as a unique device that has synced with the Capawesome Cloud in the current month. For this purpose, the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin generates a unique ID with which the device identifies itself to Capawesome Cloud. This ID is only valid for as long as the app is installed on the device. ### What happens if I reached the Monthly Active Users (MAU) limit? If you reach the Monthly Active Users (MAU) limit of your current plan, we will notify you via email and give you the opportunity to upgrade your plan. As long as the limit is reached, no more live updates will be delivered to new devices. However, existing devices will still receive updates and your app will continue to work as usual. ### What happens if I reached the Storage limit? If you reach the Storage limit of your current plan, you will be notified when you try to upload a new bundle. You can either delete old bundles to free up space or upgrade your plan to increase the storage limit. There are also various options for [automatically deleting old bundles](#how-can-i-automatically-delete-old-bundles) so that you don't have to worry about the storage limit. ## Privacy ### Is any Personally Identifiable Information (PII) collected? No. Only device generated identifiers are sent to Capawesome Cloud to be able to count Monthly Active Users (MAU). # Logs Logs are a powerful tool for debugging your app. They provide detailed information on each incoming request and outgoing response from devices, including the status code and body. ## View logs To view logs using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to view logs for, and click on the [Logs](https://console.cloud.capawesome.io/apps/_/logs) menu item under the "Live Updates" section in the sidebar. You can filter logs by ID and device ID. The following properties are available in the logs: - **ID**: The ID of the log entry. - **Device ID**: The ID of the device that made the request. - **Request URL**: The URL of the request. This URL contains various parameters, including the app ID, channel name, and device ID. Use this URL to check if the correct parameters are being sent to the Capawesome Cloud. - **Response Body**: The body of the response. This body contains the bundle ID and the URL of the bundle or the error message if the request failed. - **Response Status**: The status code and text of the response. This status code indicates whether the request was successful or not. A status code of 200 indicates success, while any other status code indicates an error. - **Created At**: The date and time when the request was made. This date and time is in UTC format. # Getting Started In this guide, you will learn how to set up Capawesome Cloud Live Updates in your Capacitor 6 app. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account and organization. - A Capacitor 6 app project on your local machine. - The latest version of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) installed. ## Step 1: Create an App In order for your app to identify itself to Capawesome Cloud, you must first create an app in Capawesome Cloud. If you have already created an app, you can **skip this step** and proceed to Step 2. To create an app using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:create`](https://capawesome.io/cloud/cli/#appscreate) command: ``` npx @capawesome/cli apps:create ``` You will be prompted to select the organization you want to create the app in and to provide a name for the app. The CLI will then create the app and display the app ID. To create an app using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/apps), select the organization you want to create the app in and click on the "Create App" button. \[ \](/assets/videos/cloud-app-create.mp4) Provide a name for the app and click on the "Create" button. Make sure to copy the app ID, as you will need it in the next step. ## Step 2: Install SDK Within your Capacitor app project, install the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin: ``` npm install @capawesome/capacitor-live-update@^6.0.0 ``` Next, configure the plugin by pasting the app ID from Step 1 in the [Capacitor configuration file](https://capacitorjs.com/docs/config) of your project: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", }, }, }; export default config; ``` capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000" } } } ``` Now sync your Capacitor project using the [Capacitor CLI](https://capacitorjs.com/docs/cli) to register the plugin and apply the configuration: ``` npx cap sync ``` Since Capacitor 6 does not support automatic update strategies, you need to manually implement the update logic using the [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) method. Here's an example using the "Always Latest" update strategy: ``` import { App } from "@capacitor/app"; import { LiveUpdate } from "@capawesome/capacitor-live-update"; // Notify the plugin that the app is ready to use and // no rollback is needed - must be called as soon as possible void LiveUpdate.ready(); App.addListener("resume", async () => { const { nextBundleId } = await LiveUpdate.sync(); if (nextBundleId) { // Ask the user if they want to apply the update immediately const shouldReload = confirm("A new update is available. Would you like to install it?"); if (shouldReload) { await LiveUpdate.reload(); } } }); ``` This code checks for updates every time the app resumes from the background. If an update is available, it prompts the user to install it immediately. ## Step 3: Publish First Update To publish your first live update, you need to deploy a build to a live update channel in Capawesome Cloud. This can be done easily using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). First, build the web assets of your Capacitor app: ``` npm run build ``` Then, upload those web assets as a live update bundle to Capawesome Cloud using the following command: ``` npx @capawesome/cli apps:liveupdates:upload ``` This command will ask you to enter the path to your web assets directory (e.g., `www` or `dist`) and the app you want to upload the bundle to. After the upload is complete, the bundle will be instantly available to your users. Navigate to the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) page in the Capawesome Cloud Console to see the details of your deployment. Congratulations! You have successfully set up Capawesome Cloud Live Updates in your Capacitor app. 🎉 ## Step 4: Test Your Setup To verify that everything is working correctly, you can make a small change to your web assets (e.g., change some text in your HTML or CSS files), build the web assets again, and upload a new live update bundle using the same command as before: ``` npx @capawesome/cli apps:liveupdates:upload ``` After uploading the new bundle, restart your app on a device or emulator. When the app resumes, it should check for updates, prompt you to install the new update, and apply it if you confirm. Debugging The Live Update SDK provides useful debugging information in Android's Logcat and iOS's Xcode console. If you encounter any issues during testing, **check the logs for more details**. ## Next Steps Now that you have Live Updates set up, explore the following guides to make the most out of it. - **Best Practices** ______________________________________________________________________ Learn about best practices for using Capawesome Cloud Live Updates in your Capacitor app. [Learn More](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) - **Advanced Usage** ______________________________________________________________________ Learn about advanced features and how to customize the Live Update plugin to your needs. [Learn More](https://capawesome.io/cloud/live-updates/guides/advanced-usage/index.md) # Getting Started In this guide, you will learn how to set up Capawesome Cloud Live Updates in your Capacitor app. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account and organization. - A Capacitor app project on your local machine. - The latest version of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) installed. ## Step 1: Create an App In order for your app to identify itself to Capawesome Cloud, you must first create an app in Capawesome Cloud. If you have already created an app, you can **skip this step** and proceed to Step 2. To create an app using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:create`](https://capawesome.io/cloud/cli/#appscreate) command: ``` npx @capawesome/cli apps:create ``` You will be prompted to select the organization you want to create the app in and to provide a name for the app. The CLI will then create the app and display the app ID. To create an app using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/apps), select the organization you want to create the app in and click on the "Create App" button. \[ \](/assets/videos/cloud-app-create.mp4) Provide a name for the app and click on the "Create" button. Make sure to copy the app ID, as you will need it in the next step. ## Step 2: Install SDK Within your Capacitor app project, install the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin: ``` npm install @capawesome/capacitor-live-update@^7.3.0 ``` Next, configure the plugin by pasting the app ID from Step 1 and setting the `autoUpdateStrategy` option to `background` in the [Capacitor configuration file](https://capacitorjs.com/docs/config) of your project: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", autoUpdateStrategy: "background" } } }; export default config; ``` capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000", "autoUpdateStrategy": "background" } } } ``` The `autoUpdateStrategy` option set to `background` is the **simplest and most effective way** to keep your users up to date. It automatically checks for updates at app startup and when the app resumes, downloads them in the background, and applies them on the next app launch—all without requiring any additional code or user interaction. This makes it the perfect default choice for most applications. See [Update Strategies](https://capawesome.io/cloud/live-updates/guides/update-strategies/index.md) for more information on alternative strategies. Finally, sync your Capacitor project using the [Capacitor CLI](https://capacitorjs.com/docs/cli) to register the plugin and apply the configuration: ``` npx cap sync ``` ## Step 3: Publish First Update To publish your first live update, you need to deploy a build to a live update channel in Capawesome Cloud. This can be done easily using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). First, build the web assets of your Capacitor app: ``` npm run build ``` Then, upload those web assets as a live update bundle to Capawesome Cloud using the following command: ``` npx @capawesome/cli apps:liveupdates:upload ``` This command will ask you to enter the path to your web assets directory (e.g., `www` or `dist`) and the app you want to upload the bundle to. After the upload is complete, the bundle will be instantly available to your users. Navigate to the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) page in the Capawesome Cloud Console to see the details of your deployment. Force Update Check When using an `autoUpdateStrategy`, updates are only checked on app resume if the last check was more than 15 minutes ago. To force a live update check during development, **force-close your app and restart it**. Congratulations! You have successfully set up Capawesome Cloud Live Updates in your Capacitor app. 🎉 ## Step 4: Test Your Setup To verify that everything is working correctly, you can make a small change to your web assets (e.g., change some text in your HTML or CSS files), build the web assets again, and upload a new live update bundle using the same command as before: ``` npx @capawesome/cli apps:liveupdates:upload ``` After uploading the new bundle, force-close your app and restart it. Since you have set the `autoUpdateStrategy` to `background`, the app should automatically download and apply the new bundle in the background. Wait around 15-30 seconds, then restart the app again to see the changes take effect. If you prefer to get a notification when an update is ready, you can extend your setup by implementing the [Always Latest](https://capawesome.io/cloud/live-updates/guides/update-strategies/#always-latest) update strategy. Debugging The Live Update SDK provides useful debugging information in Android's Logcat and iOS's Xcode console. If you encounter any issues during testing, **check the logs for more details**. ## Next Steps Now that you have Live Updates set up, explore the following guides to make the most out of it. - **Best Practices** ______________________________________________________________________ Learn about best practices for using Capawesome Cloud Live Updates in your Capacitor app. [Learn More](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) - **Advanced Usage** ______________________________________________________________________ Learn about advanced features and how to customize the Live Update plugin to your needs. [Learn More](https://capawesome.io/cloud/live-updates/guides/advanced-usage/index.md) # Getting Started In this guide, you will learn how to set up Capawesome Cloud Live Updates in your Capacitor app. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account and organization. - A Capacitor app project on your local machine. - The latest version of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) installed. ## AI-Assisted Setup (Recommended) The fastest way to get started is by using an AI coding assistant. First, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project: ``` npx skills add capawesome-team/skills --skill capawesome-cloud ``` Then, use the following prompt in your preferred AI tool (e.g. [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), or [GitHub Copilot](https://github.com/features/copilot)): ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up Capacitor Live Updates in my project. ``` The AI assistant will create an app in Capawesome Cloud, install and configure the Live Update SDK, publish your first update, and verify that everything works. Once complete, continue with [Next Steps](#next-steps). ## Manual Setup If you prefer to set things up manually, follow the steps below. ### Step 1: Create an App In order for your app to identify itself to Capawesome Cloud, you must first create an app in Capawesome Cloud. If you have already created an app, you can **skip this step** and proceed to Step 2. To create an app using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:create`](https://capawesome.io/cloud/cli/#appscreate) command: ``` npx @capawesome/cli apps:create ``` You will be prompted to select the organization you want to create the app in and to provide a name for the app. The CLI will then create the app and display the app ID. To create an app using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/apps), select the organization you want to create the app in and click on the "Create App" button. \[ \](/assets/videos/cloud-app-create.mp4) Provide a name for the app and click on the "Create" button. Make sure to copy the app ID, as you will need it in the next step. ### Step 2: Install SDK Within your Capacitor app project, install the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin: ``` npm install @capawesome/capacitor-live-update@latest ``` See the [Capacitor 7 Setup Guide](https://capawesome.io/cloud/live-updates/setup-capacitor-7/index.md) for instructions on installing and configuring the plugin with Capacitor 7. See the [Capacitor 6 Setup Guide](https://capawesome.io/cloud/live-updates/setup-capacitor-6/index.md) for instructions on installing and configuring the plugin with Capacitor 6. Next, configure the plugin by pasting the app ID from Step 1 and setting the `autoUpdateStrategy` option to `background` in the [Capacitor configuration file](https://capacitorjs.com/docs/config) of your project: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", autoUpdateStrategy: "background" } } }; export default config; ``` capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000", "autoUpdateStrategy": "background" } } } ``` The `autoUpdateStrategy` option set to `background` is the **simplest and most effective way** to keep your users up to date. It automatically checks for updates at app startup and when the app resumes, downloads them in the background, and applies them on the next app launch—all without requiring any additional code or user interaction. This makes it the perfect default choice for most applications. See [Update Strategies](https://capawesome.io/cloud/live-updates/guides/update-strategies/index.md) for more information on alternative strategies. Finally, sync your Capacitor project using the [Capacitor CLI](https://capacitorjs.com/docs/cli) to register the plugin and apply the configuration: ``` npx cap sync ``` ### Step 3: Publish Your First Update To publish your first live update, you need to deploy a build to a live update channel in Capawesome Cloud. This can be done easily using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). First, build the web assets of your Capacitor app: ``` npm run build ``` Then, upload those web assets as a live update bundle to Capawesome Cloud using the following command: ``` npx @capawesome/cli apps:liveupdates:upload ``` This command will ask you to enter the path to your web assets directory (e.g., `www` or `dist`) and the app you want to upload the bundle to. After the upload is complete, the bundle will be instantly available to your users. Navigate to the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) page in the Capawesome Cloud Console to see the details of your deployment. Force Update Check When using an `autoUpdateStrategy`, updates are only checked on app resume if the last check was more than 15 minutes ago. To force a live update check during development, **force-close your app and restart it**. Congratulations! You have successfully set up Capawesome Cloud Live Updates in your Capacitor app. 🎉 ### Step 4: Test Your Setup To verify that everything is working correctly, you can make a small change to your web assets (e.g., change some text in your HTML or CSS files), build the web assets again, and upload a new live update bundle using the same command as before: ``` npx @capawesome/cli apps:liveupdates:upload ``` After uploading the new bundle, force-close your app and restart it. Since you have set the `autoUpdateStrategy` to `background`, the app should automatically download and apply the new bundle in the background. Wait around 15-30 seconds, then restart the app again to see the changes take effect. If you prefer to get a notification when an update is ready, you can extend your setup by implementing the [Always Latest](https://capawesome.io/cloud/live-updates/guides/update-strategies/#always-latest) update strategy. Debugging The Live Update SDK provides useful debugging information in Android's Logcat and iOS's Xcode console. If you encounter any issues during testing, **check the logs for more details**. ## Next Steps Now that you have Live Updates set up, explore the following guides to make the most out of it. - **Best Practices** ______________________________________________________________________ Learn about best practices for using Capawesome Cloud Live Updates in your Capacitor app. [Learn More](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) - **Advanced Usage** ______________________________________________________________________ Learn about advanced features and how to customize the Live Update plugin to your needs. [Learn More](https://capawesome.io/cloud/live-updates/guides/advanced-usage/index.md) # Code Signing Code signing is a security feature that allows you to verify the authenticity (1) and integrity (2) of your code. This is especially important when you are distributing your code to others, as it ensures that the code has not been tampered with or altered in any way. 1. **Authenticity** means that the code comes from a trusted source and has not been tampered with. 1. **Integrity** means that the code has not been corrupted during the download process. ## Generate the key pair To sign your code, you will need a key pair consisting of a private key and a public key. The private key is used to sign the code and should be kept secret, while the public key is used to verify the signature and can be shared with others. You can generate a key pair using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md): ``` npx @capawesome/cli apps:liveupdates:generatesigningkey ``` This will create two files: - `private.pem`: Your private key file (2048-bit RSA key by default). Keep this file secure and do not share it with others. - `public.pem`: Your public key file that will be used to verify bundle signatures. You can optionally specify custom paths and key size: ``` npx @capawesome/cli apps:liveupdates:generatesigningkey --private-key-path=./keys/private.pem --public-key-path=./keys/public.pem --key-size=4096 ``` Keep Your Private Key Safe Make sure to keep your private key file secure and never commit it to version control. Add it to your `.gitignore` file to prevent accidental commits. ## Create a signed bundle To create a signed bundle on Capawesome Cloud, simply provide the path to the private key file using the `--private-key` option when creating the bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md): ``` npx @capawesome/cli apps:liveupdates:upload --private-key private.pem ``` This will sign the bundle with the private key and upload it to the Capawesome Cloud. ## Configure the plugin To verify the signed bundle in your app, you will need to configure the [Capacitor Live Update](https://capawesome.io/plugins/live-update/#configuration) plugin with the public key. This can be done in the [Capacitor Configuration](https://capacitorjs.com/docs/config) file. When you generate the key pair using the CLI, the command automatically outputs the configuration in the correct format: ``` { "plugins": { "LiveUpdate": { "publicKey": "-----BEGIN PUBLIC KEY-----MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDodf1SD0OOn6hIlDuKBza0Ed0OqtwyVJwiyjmE9BJaZ7y8ZUfcF+SKmd0l2cDPM45XIg2tAFux5n29uoKyHwSt+6tCi5CJA5Z1/1eZruRRqABLonV77KS3HUtvOgqRLDnKSV89dYZkM++NwmzOPgIF422mvc+VukcVOBfc8/AHQIDAQAB-----END PUBLIC KEY-----" } } } ``` Simply copy this configuration from the CLI output and merge it into your existing Capacitor Configuration file. If the plugin now downloads a bundle from the Capawesome Cloud, it will verify the signature using the public key and only apply the update if the signature is valid. This way, you can ensure that your app only receives updates that have been signed with your private key. No Line Breaks Required The `publicKey` value should **NOT contain any line breaks**. The CLI command automatically formats the `publicKey` value without line breaks, so you can copy and paste it directly from the output into your configuration. # Delta Updates Live updates can be significantly smaller and faster if only modified files are downloaded. For this, each bundle requires a manifest file. This is a simple JSON file containing information such as hashes about the files in a bundle. When performing a live update, the manifest of the new bundle is compared with the manifest of the current bundle, and only the changed files are downloaded. Try the Zip Artifact Type First We recommend starting with the `zip` artifact type, which is faster and more efficient for most apps. **Delta updates are only beneficial if your app contains large files that rarely change.** Most modern frameworks generate unique file hashes on each build, causing all files to be marked as changed. This makes delta updates ineffective and can result in longer download times and potentially hundreds of individual file requests instead of a single zip download. If you're unsure which option is right for your app, start with `zip` and only switch to `manifest` if you have a clear need for delta updates. For more tips on reducing your bundle size, check out our guide on [how to reduce the bundle size of Capacitor live updates](https://capawesome.io/blog/how-to-reduce-the-bundle-size-of-capacitor-live-updates/index.md). ## Generate the manifest file To generate a manifest file, run the [`apps:liveupdates:generatemanifest`](https://capawesome.io/cloud/cli/#appsliveupdatesgeneratemanifest) command using the Capawesome CLI: ``` npx @capawesome/cli apps:liveupdates:generatemanifest --path www ``` The `path` option is mandatory and specifies the path to the folder that contains the compiled web assets of your web app (e.g. `www` or `dist`). Make sure that the manifest file is regenerated after each build of your web app but before copying the files to the native projects: ``` npm run build npx @capawesome/cli apps:liveupdates:generatemanifest --path www npx cap copy ``` The easiest way to achieve this is to include the command directly in your build script: ``` { "scripts": { "build": "ng build && npx @capawesome/cli apps:liveupdates:generatemanifest --path www" } } ``` Once the command has been executed, a `capawesome-live-update-manifest.json` file is created in the specified folder. Manifest File Required Delta updates can only be performed if two manifest files are available for comparison. If one manifest file is missing, then all files of the bundle must be downloaded, which makes the update process significantly slower. Therefore, make sure that the manifest file is always included in native updates. ## Create a bundle To create a new bundle that supports delta updates, use the following command of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md): ``` npx @capawesome/cli apps:liveupdates:upload --artifact-type manifest ``` It is important that the `--artifact-type` option is set to `manifest` so that the files of the bundle are uploaded individually and not as a zip archive. CLI Only Delta updates cannot yet be created via the Capawesome Cloud Console. They must be created using the Capawesome CLI as described above. # Git Integration Capawesome Cloud offers Git integration for Live Updates, allowing you to link your uploaded bundles to specific Git commits, branches, or tags. This integration helps you track changes, versions, and updates of your app more effectively. ## Enable Git integration To enable Git integration, simply connect your Git repository to your Capawesome Cloud app in the Capawesome Cloud Console by navigating to the [Git](https://console.cloud.capawesome.io/apps/_/git) page of your app and selecting your Git provider and repository. ## Upload a bundle with Git reference When uploading a bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), you can now specify the Git reference (branch, tag, or commit hash) that the bundle should be linked to using the `--git-ref` option. For example, to upload a bundle and link it to the `main` branch, you can use the following command: ``` npx @capawesome/cli apps:liveupdates:upload --git-ref main ``` Capawesome Cloud will then fetch the necessary information from your connected Git repository and associate the uploaded bundle with the specified Git reference. We recommend using a CI/CD pipeline to automate the creation of bundles and linking them to Git commits. For example, you can use the following step in a GitHub Actions workflow to set the Git reference to the current commit SHA: ``` - name: Create a bundle on Capawesome Cloud run: | npx @capawesome/cli apps:liveupdates:upload \ --app-id 00000000-0000-0000-0000-000000000000 \ --path www \ --git-ref ${{ github.sha }} ``` # Privacy The privacy of your users is our first priority. That's why Capawesome Cloud can be used fully GDPR-compliant. Read on to find out [what data we collect](#data-collection) and how you can protect the privacy of your users by choosing the [location](#location) of our servers. ## Data Collection Capawesome Cloud does only collect the data that is necessary to provide the Live Updates feature. This includes the following information: - **App ID**: A unique identifier for your app that is used to associate the app with the correct account. - **App Version Code**: The version code of the app that is used to determine which updates are compatible with the app. - **App Version Name**: The version name of the app that is used for display purposes. - **Platform**: The platform (iOS, Android) of the app that is used to determine which updates are compatible with the app. - **Device ID**: A unique identifier for the [device](https://capawesome.io/cloud/live-updates/devices/index.md) that is used to deliver updates to a specific device and for billing purposes. This identifier is a random string that is created when the app is started for the first time. - **Bundle ID**: The unique identifier for the [bundle](https://capawesome.io/cloud/live-updates/bundles/index.md) that is currently installed on the device. - **Channel Name**: The name of the [channel](https://capawesome.io/cloud/live-updates/channels/index.md) that is selected to receive updates. - **OS Version**: The version of the operating system that is used to determine which updates are compatible with the device. - **Plugin Version**: The version of the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin that is used to deliver updates to the device. You can verify the data that is collected by inspecting the source code of the Capacitor Live Update plugin, which is open-source and available on [GitHub](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/live-update). ## Location As you can see above, we generally do not collect any personal data but only transmit information that is required to provide the Live Updates. However, we take this one step further and offer our customers the option of using a European cloud in line with the motto **"EU-based & hosted"** as a European company. This way, you can ensure that not even connection information such as IP addresses leave the European Union. To use the European cloud, simply set the `serverDomain` option in the [Capacitor Configuration](https://capacitorjs.com/docs/config) file to `api.cloud.capawesome.eu`: ``` { "plugins": { "LiveUpdate": { "serverDomain": "api.cloud.capawesome.eu", } } } ``` This way, all data is transmitted to our servers in the European Union. Feel free to check out this [Hosting Report](https://hosting-checker.net/websites/api.cloud.capawesome.eu) to see where our servers are located. If you do not specify the `serverDomain` option, the default domain `api.cloud.capawesome.io` will be used, which utilizes servers all around the world. You can find more information about our privacy practices in our [Privacy Policy](https://capawesome.io/privacy/index.md). # Rollbacks Rollbacks are a way to revert to a previous bundle of your app. This is useful when you have deployed a new bundle of your app and it is not working as expected. ## Automatic Rollbacks The Live Update SDK can automatically roll back to the previous bundle if the new bundle fails to load or if your app does not signal that it is ready within a specified timeout period. To enable automatic rollbacks, you need to configure the `readyTimeout` option in your Capacitor configuration file. capacitor.config.json ``` { "plugins": { "LiveUpdate": { "autoBlockRolledBackBundles": true, "readyTimeout": 10000 } } } ``` The `readyTimeout` option specifies the maximum time (in milliseconds) to wait for your app to signal that it is ready before rolling back to the built-in bundle. The `autoBlockRolledBackBundles` option automatically blocks bundles that caused a rollback and skips them in future operations, preventing your app from getting stuck in a rollback loop. Your app must call the `ready()` method directly at app startup before the timeout expires, otherwise the SDK will roll back to the built-in bundle. Call this method as early as possible in your app's lifecycle—for example, in the `ngOnInit` or `created` hooks (Angular/Vue) or in the `useEffect` hook (React) of your root component: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const initializeApp = async () => { await LiveUpdate.ready(); // Continue with app initialization... }; ``` Read more about this configuration options in the [Live Update](https://capawesome.io/plugins/live-update/#configuration) plugin documentation. ## Manual Rollbacks You can manually roll back to a previous bundle using either the Capawesome CLI or the Capawesome Cloud Console. To roll back to a previous bundle using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), you can use the `apps:liveupdates:rollback` command: ``` npx @capawesome/cli apps:liveupdates:rollback --help ``` You will be prompted to select the app and the channel you want to roll back. After selecting the app and channel, you will see a list of previous deployments. Select the deployment you want to roll back to, and confirm the rollback. To roll back to a previous bundle using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to roll back for, and click on the [Channels](https://console.cloud.capawesome.io/apps/_/channels) menu item under the "Live Updates" section in the sidebar. Next, navigate to the history page of the desired live update channel. You will see a list of previous deployments with a "Rollback" button next to each deployment. Click the "Rollback" button next to the deployment you want to roll back to, and confirm the rollback. A new deployment will be created that points to the selected previous bundle. # Rollouts Rollouts allow you to gradually release app updates by first rolling them out to a small percentage of users and then increasing the rollout to more users over time. This approach helps mitigate risks associated with new releases by limiting exposure to potential issues. ## Manage Rollouts You can manage rollouts associated with your app using the Capawesome CLI or the Capawesome Cloud Console. ### Start a Rollout Rollouts can be started when deploying a build to a live update channel. To start a rollout using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:liveupdates:upload`](https://capawesome.io/cloud/cli/#appsliveupdatesupload) command with the `--rollout-percentage` flag: ``` npx @capawesome/cli apps:liveupdates:upload --channel production --rollout-percentage 10 ``` You will be prompted to select the app you want to upload the bundle for and to provide the path to the bundle file. The CLI will then upload and deploy the bundle to the specified channel in the Capawesome Cloud, starting a rollout to the specified percentage of users. To start a rollout using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to start the rollout for, and click on the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) menu item in the sidebar. Next, click on the "Create Deployment" button, select the build to deploy, choose the live update channel, click on "Advanced", and set the "Rollout Percentage" to the desired initial percentage of users to receive the update. Click on the "Create" button to complete the deployment and start the rollout. ### Update Rollout Percentage Update the rollout percentage to increase or decrease the number of users receiving the update. To update the rollout percentage using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:liveupdates:rollout`](https://capawesome.io/cloud/cli/#appsliveupdatesrollout) command: ``` npx @capawesome/cli apps:liveupdates:rollout --channel production --percentage 50 ``` You will be prompted to select the app you want to update the rollout for and to provide the channel name and the new rollout percentage. The CLI will then update the rollout percentage for the latest deployment in this channel in the Capawesome Cloud. To update the rollout percentage using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to update the rollout for, and click on the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) menu item in the sidebar. Next, click on the deployment you want to update the rollout for, click on the "Settings" tab, and update the rollout percentage to the desired percentage of users to receive the update. Click on the "Save" button to complete the update of the rollout percentage. ### Complete a Rollout Complete a rollout to make the update available to all users. To complete a rollout using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:liveupdates:rollout`](https://capawesome.io/cloud/cli/#appsliveupdatesrollout) command with a percentage of `100`: ``` npx @capawesome/cli apps:liveupdates:rollout --channel production --percentage 100 ``` You will be prompted to select the app you want to complete the rollout for and to provide the channel name. The CLI will then complete the rollout for the latest deployment in this channel in the Capawesome Cloud. To complete a rollout using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to complete the rollout for, and click on the [Deployments](https://console.cloud.capawesome.io/apps/_/deployments) menu item in the sidebar. Next, click on the deployment you want to complete the rollout for, click on the "Settings" tab, and set the rollout percentage to 100 to make the update available to all users. Click on the "Save" button to complete the rollout. ## Advanced ### Multi-Channel Rollouts You can implement multi-channel rollouts by using separate channels for the rollout and stable releases. For example, you can first check for an update in a "production-rollout" channel and, if no update is available, check in the "production" channel for the stable release. This way, users in the rollout phase receive updates from the "production-rollout" channel, while all other users receive updates from the stable "production" channel. The deployment in the "production-rollout" channel can be gradually increased until it reaches 100%, at which point you can deploy the same bundle to the "production" channel for all users. The rollout percentage in the "production" channel should always be set to 100% to ensure all other users receive the stable update. Disable Automatic Updates If you are manually handling the update process, it is **strongly recommended** to set the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option to `none` to prevent conflicts with the automatic update mechanism. The following example demonstrates how to implement this logic using the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { // First, check for a rollout update in the "production-rollout" channel const firstResult = await LiveUpdate.sync({ channel: `production-rollout` }); if (firstResult.nextBundleId) { console.log(`App will be updated to rollout bundle ${firstResult.nextBundleId} on next restart`); return; } // If no rollout update is available, check for the stable update in the "production" channel const secondResult = await LiveUpdate.sync({ channel: `production` }); if (secondResult.nextBundleId) { console.log(`App will be updated to stable bundle ${secondResult.nextBundleId} on next restart`); } else { console.log('App is up to date'); } }; ``` If a device is part of the rollout, it will always receive the update from the "production-rollout" channel first until a new deployment is made to that channel. This ensures that devices do not get stuck in an update loop. Once the rollout is complete, you can deploy the same bundle to the "production" channel for all users. The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin will recognize that the device is already on the latest bundle and will not attempt to re-download it. You can then remove the "production-rollout" channel or keep it for the next rollout. ## Limitations ### Rollout Percentage Calculation Rollout percentages are calculated based on the **total number of devices in a channel**, not just those compatible with the deployed bundle. When using [Versioned Bundles](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-bundles), this means the actual rollout may reach fewer devices than the specified percentage if some devices are incompatible with the update. To ensure more accurate rollouts, use [Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels) so that only compatible devices are included in the rollout calculation. # Self-Hosting For our customers with strict security requirements, we offer the ability to self-host Live Updates. This means that you can host the bundles on your own server, and the only data that will be sent to our servers is the metadata required to check for updates. This has several advantages: - **Security**: You don't have to share your web artifacts with us and can keep them on your own servers. - **Data Transfer**: You are not affected by our data transfer limits. You can serve as many updates as you want. - **Storage**: You are not affected by our storage limits. You can store as many updates as you want. ## Requirements To self-host Live Updates, you need to meet the following requirements: - **Server**: You need to have a high-availability server that can serve static files and handle low to medium levels of traffic (varies based on number of app users). - **HTTPS**: Your server must support HTTPS. This is required to ensure secure communication between the client and your server. In addition, we recommend the following: - **CDN**: Use a Content Delivery Network (CDN) to serve the updates. This can help reduce latency and improve download speeds for users around the world. ## Restrictions There is one restriction right now: - **Delta Updates**: Delta updates are not yet supported when self-hosting. This means the artifact type must be `zip` (default). [Reach out to us](mailto:support@capawesome.io) if you need support for delta updates. ## How it works Flow diagram of self-hosted Live Updates The process of self-hosting Live Updates is similar to the regular process, with the difference that the bundle is hosted on your server. Here's how it works: 1. **Request metadata**: The client sends a request to Capawesome Cloud to check for updates. 1. **Receive metadata**: If an update is available, Capawesome Cloud responds with the metadata required to download the update. This can look like this: ``` { "artifactType": "zip", "bundleId": "1e42e1df-f4b3-4564-90d0-2303ca8fbd49", "url": "https://server.tld/downloads/1e42e1df-f4b3-4564-90d0-2303ca8fbd49.zip", } ``` 1. **Request file(s)**: The client requests the bundle from your server. 1. **Receive file(s)**: Your server responds with the bundle. After that, the client can install the update as usual. ## Create a new bundle To create a new self-hosted bundle on Capawesome Cloud, you can use the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). First, make sure you have the latest version of the CLI installed: ``` npm install -g @capawesome/cli@latest ``` After that, log in to your account: ``` npx @capawesome/cli login ``` Then, create a new bundle with the [`apps:liveupdates:register`](https://capawesome.io/cloud/cli/#appsliveupdatesregister) command: ``` npx @capawesome/cli apps:liveupdates:register --url https://domain.tld/bundle.zip ``` Replace `https://domain.tld/bundle.zip` with the URL of the bundle on your server. The bundle will be created and immediately available to all users. ## Bonus: Code Signing To help ensure secure end-to-end delivery, you can sign your updates with a private key. This way, the client can verify that the updates are coming from you and not a malicious actor. To sign your updates, you need to provide a private key and a local path to the artifact when creating the bundle: ``` npx @capawesome/cli apps:liveupdates:register --url https://domain.tld/bundle.zip --private-key ./key.pem --path ./bundle.zip ``` We recommend integrating code signing into your CI/CD pipeline to automate the process. This way you can create a signed bundle directly after uploading the artifact to your server. Check out our [Code Signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md) guide for more information on how to sign your updates. Note Code signing for self-hosted bundles is only supported in version **6.8.0** or from version **7.1.0** of the Live Update plugin. # Advanced Usage If you want more control over the update process, you can use the following methods: 1. [`fetchLatestBundle()`](https://capawesome.io/plugins/live-update/#fetchlatestbundle): Check for updates without downloading them. 1. [`downloadBundle()`](https://capawesome.io/plugins/live-update/#downloadbundle): Download updates without applying them. 1. [`setNextBundle()`](https://capawesome.io/plugins/live-update/#setnextbundle): Set a new bundle as the next bundle to be applied. 1. [`reload()`](https://capawesome.io/plugins/live-update/#reload): Apply the new bundle immediately. Disable Automatic Updates If you are manually handling the update process, it is **strongly recommended** to set the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option to `none` to prevent conflicts with the automatic update mechanism. ## Check for updates You can check for new bundles without downloading them by using the [`fetchLatestBundle()`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) method: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const checkForUpdates = async () => { const result = await LiveUpdate.fetchLatestBundle(); if (result.bundleId) { console.log("New update available: " + result.downloadUrl); } else { console.log("No update available"); } }; ``` This method returns all important information about the latest bundle, including the bundle ID and the download URL. ## Download updates You can then download bundles by using the [`downloadBundle()`](https://capawesome.io/plugins/live-update/#downloadbundle) method: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const downloadUpdate = async (bundleId: string, url: string) => { await LiveUpdate.downloadBundle({ bundleId, url }); }; ``` This method downloads the bundle, extracts the files, and moves them to the correct location in your app. ## Apply updates You can set a new bundle as the next bundle to be applied by using the [`setNextBundle()`](https://capawesome.io/plugins/live-update/#setnextbundle) method: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const setNextBundle = async (bundleId: string) => { await LiveUpdate.setNextBundle({ bundleId, }); }; ``` If the bundle should be applied immediately, you can also call the [`reload()`](https://capawesome.io/plugins/live-update/#reload) method: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const reload = async () => { await LiveUpdate.reload(); }; ``` # Best Practices Here are some best practices to follow when using the Live Update SDK to ensure a smooth user experience: - [Automatic Rollbacks](#automatic-rollbacks): Automatically revert to the previous version if an update fails. - [Binary Compatible Changes](#binary-compatible-changes): Ensure that your updates are binary compatible to avoid breaking changes. - [Bundle Size Optimization](#bundle-size-optimization): Reduce your bundle size to speed up updates and save data transfer. - [Code Signing](#code-signing): Sign your app bundles to ensure their integrity and authenticity. - [Reasonable Update Checks](#reasonable-update-checks): Implement a strategy for checking for updates without using too many device resources. ## Automatic Rollbacks We strongly recommend enabling automatic rollbacks to ensure that your users can always revert to a previous version of your app if an update fails or does not work as expected. For more information, check out the [Rollbacks](https://capawesome.io/cloud/live-updates/advanced/rollbacks/index.md) guide. ## Binary Compatible Changes It is important to make sure that only [Binary Compatible Changes](https://capawesome.io/cloud/live-updates/faq/#what-are-binary-compatible-changes) are delivered to your users to prevent incompatible updates. Capawesome Cloud offers two different ways to restrict live updates to specific native versions: - [Versioned Bundles](#versioned-bundles) - [Versioned Channels](#versioned-channels) (recommended) ### Versioned Bundles Versioned bundles allow you to restrict live updates to specific native versions by defining a range of version codes for each platform. Version Code The version code (named [`versionCode`](https://developer.android.com/studio/publish/versioning) on Android and [`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion) on iOS) is the internal version number of your app. It is used to determine whether one version is more recent than another and must be incremented each time you release a new version of your app. To create a versioned bundle, you only need to specify the minimum and maximum version codes for each platform: - **Minimum Version**: The native binary must have at least this version code to be compatible with the bundle. - **Maximum Version**: The native binary must have at most this version code to be compatible with the bundle. - **Equivalent**: If the native binary has this exact version code, do **NOT** download the bundle, because they are equal. To set the version code restrictions when uploading a live update using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the `--android-min`, `--android-max`, `--android-eq`, `--ios-min`, `--ios-max`, and `--ios-eq` options: ``` npx @capawesome/cli apps:liveupdates:upload --android-min 10 --android-max 12 --android-eq 11 --ios-min 10 --ios-max 12 --ios-eq 11 ``` In this example, we restrict the bundle to Android version codes `10` to `12` (excluding `11`) and iOS version codes `10` to `12` (excluding `11`). To set the version code restrictions using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to restrict the bundle for, and click on the [Builds](https://console.cloud.capawesome.io/apps/_/builds) menu item in the sidebar. Next, create a new build or select an existing build. On the build details page, click on the "Settings" tab and set the version code restrictions in the "Versioning" section. ### Versioned Channels Versioned channels allow you to restrict live updates to specific native versions by creating a channel for each version code. This is the **recommended approach**, as it leaves less room for mistakes and makes bundles easier to manage. Channels also offer advanced features such as bundle limits. To create a versioned channel, you can use the following [Capawesome CLI](https://capawesome.io/cloud/cli/#appschannelscreate) command: ``` npx @capawesome/cli apps:channels:create --name production-10 ``` In this example, we created a channel named `production-10` for the version code `10`. To upload a bundle to a specific channel, you can use the following [Capawesome CLI](https://capawesome.io/cloud/cli/#appsliveupdatesupload) command: ``` npx @capawesome/cli apps:liveupdates:upload --channel production-10 ``` Finally, we just need to set the correct channel in the app to ensure that only compatible bundles are downloaded. The recommended approach is to configure the channel natively at build time, so no TypeScript boilerplate is needed. Minimum Version The native channel configuration (`capawesome_live_update_default_channel` on Android and `CapawesomeLiveUpdateDefaultChannel` on iOS) requires `@capawesome/capacitor-live-update` version **8.2.0** or later. **Android**: android/app/build.gradle ``` android { defaultConfig { resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode } } ``` **iOS**: ios/App/App/Info.plist ``` CapawesomeLiveUpdateDefaultChannel production-$(CURRENT_PROJECT_VERSION) ``` Alternatively, you can set the channel dynamically in TypeScript: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { // Get the version code of the native app const { versionCode } = await LiveUpdate.getVersionCode(); // Automatically download and set the latest compatible bundle await LiveUpdate.sync({ channel: `production-${versionCode}` }); }; ``` ## Bundle Size Optimization Smaller bundles mean faster downloads, lower data transfer costs, and a better experience for your users. Common optimizations include removing source maps from your build output, choosing the right artifact type (`zip` vs `manifest`), and removing unnecessary static assets. For a detailed walkthrough, check out our guide on [how to reduce the bundle size of Capacitor live updates](https://capawesome.io/blog/how-to-reduce-the-bundle-size-of-capacitor-live-updates/index.md). ## Code Signing We strongly recommend signing your app bundles to ensure their integrity and authenticity. This way, you can be sure that the bundles have not been tampered with and are coming from a trusted source. For more information, check out the [Code Signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md) guide. ## Reasonable Update Checks While it's important to keep your app up-to-date, checking for updates too frequently can lead to a poor user experience. Not only does the device get **rate-limited** after a certain number of requests, but these requests also use up a lot of the device's resources like battery and data. The **simplest and most effective way** to handle update checks is to use the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option. This automatically checks for updates at app startup and when the app resumes (with a minimum 15-minute interval between checks), which provides a perfect balance between keeping your app up-to-date and preserving device resources. If you choose to implement manual update checks, you should definitely **NOT (!!!)** check for updates in fixed intervals: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; // Do NOT do this!!! setTimeout(() => { LiveUpdate.checkForUpdate(); }, 60000); ``` This approach can quickly lead to organizational rate-limiting and drains the device's battery and data. For manual implementation examples, check out the [Update Strategies](https://capawesome.io/cloud/live-updates/guides/update-strategies/index.md) guide. # Update Strategies There are several ways to implement over-the-air (OTA) updates in your Capacitor app using Capawesome Cloud Live Updates. Each strategy serves different use cases and user experience requirements. ## Background The most basic implementation automatically checks for updates in the background. Simply set the `autoUpdateStrategy` configuration option to `background` in your Capacitor configuration file: capacitor.config.ts ``` import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { LiveUpdate: { autoBlockRolledBackBundles: true, // Recommended autoUpdateStrategy: 'background', defaultChannel: 'production-5' // Optional } } }; export default config; ``` This automatically checks for the latest bundle **at app startup and when the app resumes** (if the last check was **more than 15 minutes ago**), downloads it in the background, and applies it on the next app launch. You can optionally use the `defaultChannel` option to specify which channel to pull updates from. This is equivalent to calling the `sync()` method manually on app start and resume, but without needing to write any code. It's the easiest way to ensure your users receive updates without any interruptions. Info The [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option was **introduced in version 7.3.0** of the Capacitor Live Update plugin. Make sure to update to the latest version to take advantage of this feature. ## Always Latest If you want to ensure that your users always have the latest version immediately, you can combine the background update strategy with the [`nextBundleSet`](https://capawesome.io/plugins/live-update/#addlistenernextbundleset) event listener to prompt users to apply updates as soon as they're downloaded. First, enable the background update strategy by setting `autoUpdateStrategy` to `background` in your Capacitor configuration file (see [Background](#background) section). Then, add the `nextBundleSet` listener: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const initializeApp = async () => { LiveUpdate.addListener('nextBundleSet', async () => { const shouldReload = confirm('A new update is available. Would you like to install it now?'); if (shouldReload) { // Reload the webview to apply the update immediately await LiveUpdate.reload(); } }); }; ``` The `nextBundleSet` event fires whenever a new bundle is downloaded and set as the next bundle, allowing you to provide users with the option to apply updates without restarting the app. Info The [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option was **introduced in version 7.3.0** of the Capacitor Live Update plugin. Make sure to update to the latest version to take advantage of this feature. ## Force Update If you need to ensure that users are always on the latest version when they open the app, you can implement a strategy that forces an update check and reloads the app if a new version is available before the splash screen is hidden. For this approach, you first need to configure the splash screen to not auto-hide using the [Capacitor Configuration](https://capacitorjs.com/docs/config) file: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { SplashScreen: { launchAutoHide: false } } }; export default config; ``` capacitor.config.json ``` { "plugins": { "SplashScreen": { "launchAutoHide": false } } } ``` Then you can implement the force update strategy as follows: ``` import { SplashScreen } from '@capacitor/splash-screen'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const initializeApp = async () => { const { nextBundleId } = await LiveUpdate.sync(); if (nextBundleId) { // Reload the webview to apply the update immediately await LiveUpdate.reload(); } else { // No update available, hide the splash screen await SplashScreen.hide(); } }; ``` This `initializeApp` function should be called **once at app startup**. It checks for updates and applies them immediately if available. If no updates are found, it hides the splash screen. This ensures that users always have the latest version of the app before they start using it. However, keep in mind that forcing updates in this way may lead to a poor user experience since users will have to wait for the update to download and apply before they can interact with the app. This can be a real problem if the update is large or the user's internet connection is slow. It's essential to balance the need for updates with the overall user experience. Disable Automatic Updates Since you are manually handling the update process, it is **strongly recommended** to set the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option to `none` to prevent conflicts with the automatic update mechanism. ## Instant For critical updates that need to be delivered immediately, you can use silent push notifications to trigger updates while users are actively using the app. Silent push notifications deliver data directly to the app without displaying a notification, so no permissions are required. Here's an example implementation using the [Capacitor Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin: ``` import { FirebaseMessaging } from '@capacitor-firebase/messaging'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const initializeApp = async () => { FirebaseMessaging.addListener('notificationReceived', async (notification) => { if (notification.data?.type === 'live-update') { const { nextBundleId } = await LiveUpdate.sync(); if (nextBundleId) { const shouldReload = confirm('A critical update is available. Would you like to install it now?'); if (shouldReload) { // Reload the webview to apply the update immediately await LiveUpdate.reload(); } } else { // Update already installed or no update available } } }); }; ``` This strategy requires a push notification service and backend infrastructure to send notifications when new bundles are deployed. This approach is also beneficial if you want to minimize connections to Capawesome Cloud, which can save battery and data transfer. Disable Automatic Updates Since you are manually handling the update process, it is **strongly recommended** to set the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option to `none` to prevent conflicts with the automatic update mechanism. ## Further Reading Want to combine live updates with native app store updates for a complete update delivery strategy? Check out [The App Update Delivery Guide for Capacitor](https://capawesome.io/blog/the-app-update-delivery-guide-for-capacitor/index.md). # Azure DevOps You can easily publish Live Updates to Capawesome Cloud using [Azure Pipelines](https://azure.microsoft.com/de-de/products/devops/pipelines). In this guide, we will show you how to create a Azure pipeline that builds your web assets and creates a bundle on Capawesome Cloud using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ## Preparation Before you start, make sure you have a Capawesome Cloud account and an app set up. Next, you need to create a Capawesome Cloud token and add it to your Azure Pipeline as a secure variable. ### Create a Capawesome Cloud token Create a new Capawesome Cloud token via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens). Just navigate to the `Tokens` page in the settings and click on the `Create Token` button. Choose a name for the token and click on the `Create` button. The token is now copied to your clipboard. ### Create a Azure Pipeline Variable Add the Capawesome Cloud token to your Azure Pipeline as a secure variable with the name `CAPAWESOME_CLOUD_TOKEN` as described in [Set secret variables](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables). ## Pipeline ### Create the Pipeline Create a new Azure Pipeline file in your repository under `azure-pipelines.yml` with the following content: ``` trigger: none parameters: - name: channel displayName: Channel type: string pool: vmImage: ubuntu-latest jobs: - job: Deploy displayName: Build web assets steps: - checkout: self displayName: Checkout - task: UseNode@1 inputs: version: 20.x displayName: Install Node.js - script: npm ci displayName: Install Dependencies - script: npm run build displayName: Build web assets - script: npm install -g @capawesome/cli@latest displayName: Install Capawesome CLI - script: npx @capawesome/cli apps:channels:create --app-id 00000000-0000-0000-0000-000000000000 --name $env:CHANNEL --ignore-errors displayName: Create a channel on Capawesome Cloud env: CAPAWESOME_CLOUD_TOKEN: $(CAPAWESOME_CLOUD_TOKEN) CHANNEL: ${{ parameters.channel }} - script: npx @capawesome/cli apps:liveupdates:upload --app-id 00000000-0000-0000-0000-000000000000 --channel $env:CHANNEL --path dist displayName: Create a bundle on Capawesome Cloud env: CAPAWESOME_CLOUD_TOKEN: $(CAPAWESOME_CLOUD_TOKEN) CHANNEL: ${{ parameters.channel }} ``` This pipeline will build your web assets, log in to Capawesome Cloud, create a channel (if it does not exist yet), and create a bundle for the specified channel. ### Trigger the Pipeline You can then trigger the pipeline manually by running it in the Azure Pipelines UI or by using the Azure Pipelines API. You will be prompted to enter the channel name. Of course, you can also trigger the pipeline automatically by specifying a different trigger. # Bitbucket Pipelines You can easily publish Live Updates to Capawesome Cloud using [Bitbucket Pipelines](https://www.atlassian.com/de/software/bitbucket/features/pipelines). In this guide, we will show you how to create a Bitbucket pipeline that builds your web assets and creates a bundle on Capawesome Cloud using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ## Preparation Before you start, make sure you have a Capawesome Cloud account and an app set up. Next, you need to create a Capawesome Cloud token and add it to your Bitbucket repository as a secure variable. ### Create a Capawesome Cloud token Create a new Capawesome Cloud token via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens). Just navigate to the `Tokens` page in the settings and click on the `Create Token` button. Choose a name for the token and click on the `Create` button. The token is now copied to your clipboard. ### Create a Bitbucket Repository Variable Add the Capawesome Cloud token to your Bitbucket repository as an encrypted secret with the name `CAPAWESOME_CLOUD_TOKEN` as described in [Variables and secrets](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/). ## Pipeline ### Create the Pipeline Create a new Bitbucket Pipeline file in your repository under `bitbucket-pipelines.yml` with the following content: ``` image: node:20 pipelines: custom: deploy-live-update: - variables: - name: Channel - step: name: Build web assets caches: - node script: - npm ci - npm run build - npm install -g @capawesome/cli@latest - npx @capawesome/cli apps:channels:create --app-id 00000000-0000-0000-0000-000000000000 --name $Channel --ignore-errors - npx @capawesome/cli apps:liveupdates:upload --app-id 00000000-0000-0000-0000-000000000000 --path dist --channel $Channel ``` This pipeline will build your web assets, log in to Capawesome Cloud, create a channel (if it does not exist yet), and create a bundle for the specified channel. ### Trigger the Pipeline You can then trigger the pipeline manually via the Bitbucket Pipelines UI as described in [Scheduled and manually triggered pipelines](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-triggers/). Of course, you can also trigger the pipeline automatically by specifying specific [Pipeline start conditions](https://support.atlassian.com/bitbucket-cloud/docs/pipeline-start-conditions/). # GitHub Actions You can easily publish Live Updates to Capawesome Cloud using [GitHub Actions](https://github.com/features/actions). In this guide, we will show you how to create a GitHub Action workflow that builds your web assets and creates a bundle on Capawesome Cloud using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ## Preparation Before you start, make sure you have a Capawesome Cloud account and an app set up. Next, you need to create a Capawesome Cloud token and add it to your GitHub repository as an encrypted secret. ### Create a Capawesome Cloud token Create a new Capawesome Cloud token via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens). Just navigate to the `Tokens` page in the settings and click on the `Create Token` button. Choose a name for the token and click on the `Create` button. The token is now copied to your clipboard. ### Create a GitHub Secret Add the Capawesome Cloud token to your GitHub repository as an encrypted secret with the name `CAPAWESOME_CLOUD_TOKEN` as described in [Creating encrypted secrets for a repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-encrypted-secrets-for-a-repository). ## Workflow ### Create the Workflow Create a new GitHub Actions workflow file in your repository under `.github/workflows/deploy-live-update.yml` with the following content: ``` name: Deploy Live Update on: workflow_dispatch: inputs: channel: description: 'Channel' required: true jobs: deploy: name: Build web assets runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Node.js 20 uses: actions/setup-node@v4 with: node-version: 20 - name: Install Dependencies run: npm ci - name: Build web assets run: npm run build - name: Install Capawesome CLI run: npm install -g @capawesome/cli@latest - name: Create a channel on Capawesome Cloud run: npx @capawesome/cli apps:channels:create --app-id 00000000-0000-0000-0000-000000000000 --name ${{ github.event.inputs.channel }} --ignore-errors env: CAPAWESOME_CLOUD_TOKEN: ${{ secrets.CAPAWESOME_CLOUD_TOKEN }} - name: Create a bundle on Capawesome Cloud run: npx @capawesome/cli apps:liveupdates:upload --app-id 00000000-0000-0000-0000-000000000000 --path dist --channel ${{ github.event.inputs.channel }} env: CAPAWESOME_CLOUD_TOKEN: ${{ secrets.CAPAWESOME_CLOUD_TOKEN }} ``` This workflow will build your web assets, log in to Capawesome Cloud, create a channel (if it does not exist yet), and create a bundle for the specified channel. ### Trigger the Workflow You can run the workflow manually by navigating to the `Actions` tab in your repository and clicking on the `Deploy Live Update` workflow. You will be prompted to enter the channel name. Of course, you can also trigger the workflow automatically by using a different event trigger like `push` or `pull_request`. # GitLab CI/CD You can easily publish Live Updates to Capawesome Cloud using [GitLab CI/CD](https://docs.gitlab.com/ci/). In this guide, we will show you how to create a GitLab CI/CD pipeline that builds your web assets and creates a bundle on Capawesome Cloud using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ## Preparation Before you start, make sure you have a Capawesome Cloud account and an app set up. Next, you need to create a Capawesome Cloud token and add it to your GitLab project as a CI/CD variable. ### Create a Capawesome Cloud token Create a new Capawesome Cloud token via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens). Just navigate to the `Tokens` page in the settings and click on the `Create Token` button. Choose a name for the token and click on the `Create` button. The token is now copied to your clipboard. ### Create a GitLab CI/CD Variable Add the Capawesome Cloud token to your GitLab project as a CI/CD variable with the name `CAPAWESOME_CLOUD_TOKEN` as described in [CI/CD variables](https://docs.gitlab.com/ci/variables/). Make sure to mark the variable as masked and protected. ## Pipeline ### Create the Pipeline Create a new GitLab CI/CD pipeline file in your repository under `.gitlab-ci.yml` with the following content: ``` spec: inputs: channel: description: The name of the channel to deploy the update to. default: production --- stages: - deploy deploy-live-update: stage: deploy image: node:20 before_script: - npm ci - npm run build - npm install -g @capawesome/cli@latest script: - npx @capawesome/cli apps:channels:create --app-id 00000000-0000-0000-0000-000000000000 --name $[[ inputs.channel ]] --ignore-errors - npx @capawesome/cli apps:liveupdates:upload --app-id 00000000-0000-0000-0000-000000000000 --path dist --channel $[[ inputs.channel ]] ``` This pipeline will build your web assets, log in to Capawesome Cloud (using the `CAPAWESOME_CLOUD_TOKEN` environment variable), create a channel (if it does not exist yet), and create a bundle for the specified channel. ### Trigger the Pipeline You can run the pipeline manually by navigating to the `CI/CD` > `Pipelines` section in your GitLab project and clicking on the `Run pipeline` button. You will be prompted to enter the channel name as a variable. Of course, you can also trigger the pipeline automatically by using different rules like `push` or `merge_request`. # Native Builds Use [Capawesome Cloud](https://cloud.capawesome.io/) to build native iOS and Android apps in the cloud. Get faster builds with optimized runner images, automatic signing and provisioning, and seamless integration with your Git provider. ## Features - 🚀 **Optimized Build Stacks**: Pre-configured with the latest stable versions of Node.js, Java, and Xcode running on macOS 15 with M4 instances. - 🔗 **Universal Git Integration**: Connect GitHub, GitLab, Bitbucket, or Azure DevOps—including self-hosted and enterprise instances. - 🔐 **Signing & Provisioning**: Securely manage Android keystores and iOS certificates for all build types. - ⚙️ **Advanced Configuration**: Simple `capawesome.config.json` with support for monorepos, custom build commands, and environment variables. - 🔒 **Encrypted Secrets**: Store sensitive data like API keys and tokens securely, never exposed in logs. - 📦 **Artifact Management**: All build outputs are stored securely and available for download. - 🪵 **Detailed Job Logs**: Comprehensive, easy-to-read logs make debugging straightforward. - 🏃 **Isolated Build Environments**: Every build runs in its own isolated environment for security and consistency. - 📱 **All Build Types**: Support for iOS Simulator, Development, Ad Hoc, App Store, and Enterprise builds, plus Android Debug, Release, and custom build types. - 📚 **Documentation**: Comprehensive documentation to help you get started. ## Getting Started - **Setup Native Builds** ______________________________________________________________________ Create your first native build with Capawesome Cloud Native Builds. ______________________________________________________________________ [Getting Started](https://capawesome.io/cloud/native-builds/setup/index.md) ## Documentation - **App Configuration** ______________________________________________________________________ Learn how to configure your app builds using `capawesome.config.json`. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/configuration/index.md) - **Build Types** ______________________________________________________________________ Understand the different build types available for iOS and Android. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/build-types/index.md) - **Build Stacks** ______________________________________________________________________ Learn about the software versions and tools available in each build stack. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/build-stacks/index.md) - **Signing Certificates** ______________________________________________________________________ Configure signing certificates and provisioning profiles for your apps. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/certificates/index.md) - **Environments** ______________________________________________________________________ Use environment variables and secrets to customize your builds. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/environments/index.md) - **Troubleshooting** ______________________________________________________________________ Find solutions to common build issues and errors. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/troubleshooting/index.md) ## Guides Here are some guides to help you get started with Capawesome Cloud Native Builds: - [Announcing Capawesome Cloud Native Builds](https://capawesome.io/blog/announcing-capawesome-cloud-native-builds/index.md) # Build Stacks In Capawesome Cloud, build stacks define a set of pre-configured software versions and tools used during the native build process. Selecting the appropriate build stack ensures compatibility with your application's dependencies and requirements. Available Build Stacks Capawesome Cloud currently offers only **two build stacks** named `macos-sequoia` and `macos-tahoe`. We will be adding more build stacks in the future to support a wider range of software versions and configurations. If you have specific requirements or suggestions for additional build stacks, please [contact our support team](https://cloud.capawesome.io/contact/). ## Configurations The following table lists the software and their respective versions included in each build stack: | Software | macos-sequoia | macos-tahoe | | --------- | ---------------------- | -------------------- | | CocoaPods | 1.16.2 | 1.16.2 | | Fastlane | 2.226.0 | 2.232.2 | | Java | 17, 21 (default) | 17, 21 (default) | | macOS | 15 | 26 | | npm | 11.6.1 | 11.9.0 | | Node.js | 20, 22, 24 (default) | 20, 22, 24 (default) | | Xcode | 16.4, 26.0.1 (default) | 26.2 (default) | Capawesome Cloud regularly updates the build stacks to include the latest stable versions of the software. For the most up-to-date information on the available build stacks and their configurations, please refer to this documentation or contact our support team. Customizing Versions You can override the default versions for Java, Node.js, and Xcode by setting the corresponding environment variables (`JAVA_VERSION`, `NODE_VERSION`, `XCODE_VERSION`) in your build configuration. For more information, see the [Environments](https://capawesome.io/cloud/native-builds/environments/#reserved-environment) documentation. # Build Types Build types determine how your app is compiled and signed, affecting its behavior and distribution method. When creating a build in Capawesome Cloud, you select the appropriate build type based on your needs. ## Android Build Types ### Debug The Debug build type is optimized for development and testing. It includes debugging information, is not optimized for performance, and is signed with a debug keystore. Use this type during active development when you need to test features quickly. ### Release The Release build type is optimized for production deployment. It includes code optimization, obfuscation (if configured), and must be signed with your release keystore. This build type is **required for publishing** to the Google Play Store. ### Custom Custom build types allow you to define additional build variants beyond Debug and Release. For example, you might create a `staging` build type for pre-production testing or a `qa` build type for quality assurance with specific configurations. To use a custom build type, set the `ANDROID_BUILD_TYPE` environment variable to your custom build type name. Learn more about environment variables in the [Environments](https://capawesome.io/cloud/native-builds/environments/index.md) documentation. ## iOS Build Types ### Simulator Simulator builds are compiled for the iOS Simulator on macOS. These builds run on your development machine and are useful for rapid testing during development without needing a physical device. Simulator builds **cannot be installed** on actual iOS devices. ### Development Development builds are signed with a development certificate and can be installed on devices registered in your Apple Developer account. This build type is ideal for testing on physical devices during development, allowing you to test device-specific features like camera, GPS, and push notifications. ### Ad Hoc Ad Hoc builds are signed with a distribution certificate and can be distributed to a limited number of devices (up to 100) registered in your Apple Developer account. This build type is perfect for beta testing with a specific group of users outside of TestFlight, or for internal distribution to team members. ### App Store App Store builds are signed with a distribution certificate and are intended for submission to the App Store. When you use this build type with Capawesome Cloud, your app is **automatically uploaded to TestFlight** for testing. From TestFlight, you can distribute to testers or promote the build to the App Store for production release. ### Enterprise Enterprise builds are signed with an Apple Developer Enterprise certificate and can be distributed internally within your organization without going through the App Store. This build type is only available if you have an **Apple Developer Enterprise Program membership** and is designed for in-house distribution to employees. # App Configuration Capawesome Cloud allows you to optionally define your app configuration in a `capawesome.config.json` file for advanced use cases such as monorepos, subdirectory apps, or custom build commands. By default, Capawesome Cloud uses sensible defaults that work for standard project setups. ## Configuration File The `capawesome.config.json` file should be placed in the root directory of your project. Below is an example configuration file: ``` { "cloud": { "apps": [ { "appId": "4e837195-e8d8-4ad1-8705-7a32e18a98cf", "baseDir": "apps/capacitor-sample-app", "dependencyInstallCommand": "npm install", "webBuildCommand": "npm run build" } ] } } ``` The following configuration options are available: - `cloud.apps`: An array of app configurations. - `appId`: The Capawesome Cloud App ID which can be found in the App Settings in Capawesome Cloud. This is required. - `baseDir`: The base directory of your app within the repository. This is the directory that Capawesome Cloud uses to build the app from. All commands including `dependencyInstallCommand` and `webBuildCommand` are called from this location. If left unset, Capawesome Cloud defaults to the root of the Git repository. - `dependencyInstallCommand`: The command to install dependencies for your app. Default is `npm install`. - `webBuildCommand`: The command to build the web assets for your app. Default is `npm run build`. ## Examples ### Subdirectory App This example shows how to configure multiple apps located in subdirectories: ``` ├─ apps/ │ ├─ my-app1/ │ └─ my-app2/ └─ capawesome.config.json ``` The corresponding `capawesome.config.json` would look like this: ``` { "cloud": { "apps": [ { "appId": "your-app-id-1", "baseDir": "apps/my-app1" }, { "appId": "your-app-id-2", "baseDir": "apps/my-app2" } ] } } ``` ### Monorepo For monorepos, you can specify the `baseDir` for each app accordingly: ``` { "cloud": { "apps": [ { "appId": "your-app-id-1", "baseDir": "packages/app1", "dependencyInstallCommand": "cd ../.. && npm install", "webBuildCommand": "cd ../.. && npm run build --workspace=app1" }, { "appId": "your-app-id-2", "baseDir": "packages/app2", "dependencyInstallCommand": "cd ../.. && npm install", "webBuildCommand": "cd ../.. && npm run build --workspace=app2" } ] } } ``` ### pnpm If you are using pnpm as your package manager, you must install pnpm before running `pnpm install`: ``` { "cloud": { "apps": [ { "appId": "your-app-id", "dependencyInstallCommand": "npm install -g pnpm && pnpm install", "webBuildCommand": "pnpm build" } ] } } ``` ### Yarn If you are using Yarn as your package manager, you must install Yarn before running `yarn install`: ``` { "cloud": { "apps": [ { "appId": "your-app-id", "dependencyInstallCommand": "npm install -g yarn && yarn install", "webBuildCommand": "yarn build" } ] } } ``` # Environments Environment variables allow you to customize your native builds by providing configuration values, secrets, and other data that can be accessed during the build process. Capawesome Cloud provides several types of environment variables to support different use cases. Step-by-Step Guide For a detailed walkthrough with practical examples, check out our blog post: [Using Environment Variables and Secrets in Capawesome Cloud Builds](https://capawesome.io/blog/using-environment-variables-and-secrets-in-capawesome-cloud-builds/index.md). ## Default Environment The following environment variables are automatically set by Capawesome Cloud for every build. These variables provide information about the build context and cannot be overridden. | Name | Type | Description | | ----------------------- | --------- | ------------------------------------------------------------------- | | `CI` | `boolean` | Indicates the build is running in a CI environment (always `true`). | | `CI_APP_ID` | `string` | The ID of the application. | | `CI_BUILD_ID` | `string` | Unique identifier for the build. | | `CI_BUILD_NUMBER` | `string` | Sequential number of the build. | | `CI_GIT_COMMIT_SHA` | `string` | The SHA hash of the Git commit. | | `CI_GIT_COMMIT_MESSAGE` | `string` | The commit message of the Git commit. | | `CI_GIT_REFERENCE` | `string` | The Git branch or tag name. | | `CI_PLATFORM` | `string` | The target platform (`ios` or `android`). | You can use these default environment variables in your build scripts to access information about the build context and make decisions based on that data. ## Reserved Environment The following environment variables are reserved for platform-specific build configuration. These variables can be set by you to customize the build process. | Name | Type | Description | | ------------------------------ | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `ANDROID_BUILD_TYPE` | `string` | The Android build type. | | `ANDROID_FLAVOR` | `string` | The Android product flavor. | | `IOS_PROVISIONING_PROFILE_MAP` | `string` | JSON object mapping bundle IDs to target names for custom provisioning profile configurations (see [guide](https://capawesome.io/cloud/native-builds/guides/custom-ios-provisioning-profiles/index.md)). | | `IOS_SCHEME` | `string` | The iOS scheme name. | | `NODE_VERSION` | `int` | The Node.js version (must be the major version number such as `22` or `24`). | | `JAVA_VERSION` | `int` | The Java version (must be the major version number such as `17` or `21`). | | `XCODE_VERSION` | `int` | The Xcode version (must be the major version number such as `16` or `26`). | For example, you can set the `JAVA_VERSION` variable to `17` to use Java 17 for your Android builds, or set the `IOS_SCHEME` variable to specify a custom scheme for your iOS builds. Build Stack The available build stacks and their supported versions for Node.js, Java, and Xcode can be found in the [Build Stacks](https://capawesome.io/cloud/native-builds/build-stacks/index.md) documentation. ## Custom Environments Custom environments allow you to define sets of environment variables for different build scenarios. For example, you can create separate environments for development, staging, and production builds, each with their own configuration values. To create a custom environment, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console and define your environment variables. You can then select which environment to use when triggering a build. Reserved Variable Names The environment variable `CI` and any variable starting with `CI_` are reserved for CI-related functionality and cannot be overridden by custom environments. If you attempt to set a variable with a reserved name, it will be skipped and a message will be logged during the build process. ### Secrets Secrets are encrypted environment variables that are used to store sensitive information such as API keys, certificates, and passwords. Unlike regular environment variables, secrets are encrypted at rest and in transit, and their values are never displayed in build logs. #### Add Secret To add a secret, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console, click on "Manage Secrets" in the "Actions" menu of an environment, and create a new secret. Secrets can be accessed in your build scripts using standard environment variable syntax, just like regular variables. Bulk Import You can bulk import multiple secrets at once by using the "Import" feature in the actions menu of the environment secrets page. This allows you to paste in multiple `KEY=VALUE` pairs, making it easier to set up your environment. #### Delete Secret To delete a secret, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console, click on "Manage Secrets" in the "Actions" menu of an environment. From there, click on "Delete" in the "Actions" menu of the secret you wish to delete, and confirm the deletion. ### Variables Variables are regular environment variables that can be used to store non-sensitive configuration values. Variables are useful for storing values that may change between builds or environments, such as API endpoints, feature flags, or version numbers. #### Add Variable To add a variable, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console, click on "Manage Variables" in the "Actions" menu of an environment, and create a new variable. Variables can be accessed in your build scripts using standard environment variable syntax. Bulk Import You can bulk import multiple variables at once by using the "Import" feature in the actions menu of the environment variables page. This allows you to paste in multiple `KEY=VALUE` pairs, making it easier to set up your environment. #### Delete Variable To delete a variable, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console, click on "Manage Variables" in the "Actions" menu of an environment. From there, click on "Delete" in the "Actions" menu of the variable you wish to delete, and confirm the deletion. ### Set Default Environment You can define a default environment in your app settings. When a default environment is set, it will be automatically preselected when creating a new build. This is especially useful if you have a single environment that should be applied to every build, for example because it contains a license key for a private npm package or other configuration that is always required. To set a default environment using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/), navigate to the app you want to configure, and click on the [Settings](https://console.cloud.capawesome.io/apps/_/settings) menu item in the sidebar. Next, in the "Builds" section, enable the "Default Environment" toggle, select the environment you want to set as the default from the dropdown menu, and click on the "Save" button. ## Ad-hoc Environments Ad-hoc environment variables are temporary variables that can be set when creating a build. Unlike custom environments, these variables are not persisted and only apply to the specific build being created. Ad-hoc environments are particularly useful for: - **Troubleshooting and testing**: Quickly test different configurations or build tool versions (e.g., setting `NODE_VERSION` to test a different Node.js version) without creating a new environment. - **Native configuration overrides**: Override native configuration values like version names or build numbers (see [Native Configurations](https://capawesome.io/cloud/native-builds/guides/native-configurations/index.md) for more details). Sensitive Data Ad-hoc environment variables are **not encrypted** and may be visible in build logs. Avoid using ad-hoc variables for sensitive information such as API keys or passwords. Instead, use secrets in custom environments for sensitive data. To use ad-hoc environment variables, specify them when creating a build through the Capawesome Cloud Console or API. These variables follow the same naming conventions and restrictions as custom environment variables, including the reservation of `CI` and `CI_` prefixed names. ### Add Variable To add an ad-hoc variable when creating a build, navigate to the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page in the Capawesome Cloud Console and click on "Build from Git". In the build creation dialog, enter your ad-hoc environment variables in the "Ad-hoc Environment" input field. You can add multiple variables by pressing "Enter" after each variable. Each variable must be in the format `KEY=VALUE`. # Getting Started In this guide, you will learn how to create your first native build using Capawesome Cloud Native Builds. ## Prerequisites Before you begin, ensure you have: - A [Capawesome Cloud](https://console.cloud.capawesome.io) account and organization. - A Capacitor app in a Git repository. ## Step 1: Create an App To identify your app in Capawesome Cloud, you need to create an app in the console. If you have already created an app, you can **skip this step** and proceed to Step 2. To create an app using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), use the [`apps:create`](https://capawesome.io/cloud/cli/#appscreate) command: ``` npx @capawesome/cli apps:create ``` You will be prompted to select the organization you want to create the app in and to provide a name for the app. The CLI will then create the app. To create an app using the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/apps), select the organization you want to create the app in and click on the "Create App" button. \[ \](/assets/videos/cloud-app-create.mp4) ## Step 2: Connect Git Repository Before you can create native builds, you need to connect your Git repository to Capawesome Cloud. This allows the build service to access your source code and build your app. Follow these steps to connect your repository: 1. Navigate to the [Git](https://console.cloud.capawesome.io/apps/_/git) page of your app in the Capawesome Cloud Console. 1. In the **Git Providers** section, select your Git provider (GitHub, GitLab, Bitbucket, etc.) and click **Connect** to authorize Capawesome Cloud to access your account. If you've already connected a provider, you can skip this step. 1. In the **Git Repositories** section, select your Git provider from the dropdown, choose the repository owner, and select the repository you want to connect. 1. Click **Save** to finalize the connection. Your Git repository is now connected to Capawesome Cloud and ready for building. ## Step 3: Trigger First Build Once your Git repository is connected, you can trigger your first build: 1. Navigate to the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page of your app in Capawesome Cloud. 1. Click on the **Build from Git** button. 1. Select the **Git reference** (branch, tag, or commit) you want to build from. 1. Select the **Platform** (iOS or Android) and **Build Type**. Choose **Debug** or **Simulator** for initial testing. 1. Click on **Build** to start the build process. Wait for the build to complete. You can monitor the build progress in the build details page and download the build artifact once it's finished. Congratulations! You have successfully created your first native build using Capawesome Cloud. 🎉 ## Bonus: Video Tutorials Want to see the entire process in action? Check out these video tutorials: This walkthrough covers the full Android build workflow — from triggering a build from your Git repository to downloading the artifact and testing it on your device. This walkthrough covers how to create an iOS Simulator build as well as a signed Development build using an Apple developer certificate and provisioning profile. ## Next Steps Now that you have Native Builds set up, explore the following guides to make the most out of it. - **Build Types** ______________________________________________________________________ Learn about different build types available for iOS and Android platforms. [Build Types](https://capawesome.io/cloud/native-builds/build-types/index.md) - **Certificates** ______________________________________________________________________ Set up signing certificates for production builds on iOS and Android. [Certificates](https://capawesome.io/cloud/native-builds/certificates/index.md) - **Configuration** ______________________________________________________________________ Configure build settings and customize your build process. [Configuration](https://capawesome.io/cloud/native-builds/configuration/index.md) - **Troubleshooting** ______________________________________________________________________ Find solutions to common issues and learn how to debug build problems. [Troubleshooting](https://capawesome.io/cloud/native-builds/troubleshooting/index.md) # Troubleshooting This troubleshooting guide addresses common issues encountered during native builds in Capawesome Cloud. If you experience any of the following errors, follow the provided solutions to resolve them. You can also [contact support](https://cloud.capawesome.io/contact/) for further assistance. ## Error `invalid source release: 21` This error typically occurs when the wrong Java version is being used for the build. To resolve this issue, ensure that you have set the `JAVA_VERSION` [environment variable](https://capawesome.io/cloud/native-builds/environments/#reserved-environment) to a supported version, such as `17` or `21`, in your build environment. For Capacitor 7, Java 21 is the default version. ## Error `JavaScript heap out of memory` This error occurs when Node.js runs out of memory during the web build step (e.g. `pnpm build` or `npm run build`). The default heap limit is approximately 2 GB, which may not be enough for large projects. To fix this, add `NODE_OPTIONS` as an [environment variable](https://capawesome.io/cloud/native-builds/environments/index.md) in your build environment with the value `--max-old-space-size=4096`. This increases the limit to 4 GB. If that's still not enough, you can try `8192` (8 GB). Alternatively, you can set `NODE_OPTIONS` directly in your build script in `package.json`: ``` "capawesome:build": "NODE_OPTIONS='--max-old-space-size=4096' npm run build" ``` # Signing Certificates When building apps, it is necessary to sign the resulting binaries to ensure their authenticity and integrity. Capawesome Cloud allows you to securely store and manage signing certificates for Android, iOS, and Web platforms. - **Android** ______________________________________________________________________ Configure keystores and signing keys for Android app builds. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/certificates/android/index.md) - **iOS** ______________________________________________________________________ Configure certificates and provisioning profiles for iOS app builds. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/certificates/ios/index.md) - **Web** ______________________________________________________________________ Configure PEM certificates for signing Live Update bundles. ______________________________________________________________________ [Learn more](https://capawesome.io/cloud/native-builds/certificates/web/index.md) # Android Signing Certificates To build signed Android apps in Capawesome Cloud, you need to configure signing certificates. Android apps are signed using a Java Keystore (JKS) file containing your signing key. This certificate ensures the authenticity and integrity of your application. AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up an Android signing certificate in Capawesome Cloud. ``` ## Configuration To create an Android signing certificate, navigate to the [Signing Certificates](https://console.cloud.capawesome.io/apps/_/certificates) page in the Capawesome Cloud Console and provide the following information: - **Name**: A descriptive name for the certificate (e.g., "Production Android Key"). - **Platform**: Must be set to `Android`. - **Type**: The certificate type (`Development` or `Production`). - **Keystore File**: The Java Keystore (JKS) file containing the signing key. Must have a `.jks` or `.keystore` extension. - **Keystore Password**: The password for the keystore file. - **Key Alias**: The alias of the key within the keystore. - **Key Password**: The password for the key alias. Read on for detailed instructions on creating and obtaining these credentials. ## Obtaining Credentials ### Name Choose a descriptive name that helps you identify the certificate's purpose (e.g., "Production Android Key", "Development Key", "Release Certificate"). This name is only used within Capawesome Cloud for organization purposes. ### Type Select the appropriate certificate type: - **Development**: For development and testing builds that are not distributed publicly. - **Production**: For release builds that will be distributed through Google Play Store or other channels. ### Keystore File A keystore file is required to sign your Android app. You can create a keystore using Android Studio or the `keytool` command-line utility. Keep Your Keystore Secure Store your keystore file and passwords securely. Losing access to your keystore or forgetting the passwords can prevent you from updating your app in the future, as you won't be able to sign new versions with the same key. #### Create using Online Generator The easiest way to create a keystore is using the [Capawesome Android Keystore Generator](https://cloud.capawesome.io/tools/android-keystore-generator/). Fill in the required fields and download the generated keystore file along with the credentials. #### Create using Android Studio 1. Open Android Studio and create a new project or open an existing one. 1. Click on **Build** in the top menu, then select **Generate Signed Bundle / APK**. 1. Choose either **Android App Bundle** or **APK** and click **Next**. 1. Click on **Create new...** to generate a new keystore. 1. Fill in the required fields: 1. **Key store path**: Choose a location to save your keystore file (e.g., `my-release-key.jks`). 1. **Password**: Set a strong password for the keystore. 1. **Alias**: Choose an alias for your key (e.g., `my-key-alias`). 1. **Password**: Set a password for the key (can be the same as the keystore password). 1. **Validity (years)**: Set the validity period for the key (e.g., 25 years). 1. **Certificate**: Fill in your first and last name, organizational unit, organization, city or locality, state or province, and country code (XX). 1. Click **OK** to create the keystore. 1. Complete the signing process by following the prompts to generate the signed bundle or APK. The keystore file will be saved to the location you specified. #### Create using keytool Alternatively, you can create a keystore using the `keytool` command-line utility that comes with the Java Development Kit (JDK): ``` keytool -genkey -v -keystore my-release-key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias my-key-alias ``` You will be prompted to: 1. Enter a password for the keystore 1. Re-enter the password to confirm 1. Enter your name and organizational information 1. Enter a password for the key (press Enter to use the same password as the keystore) The keystore file `my-release-key.jks` will be created in your current directory. ### Keystore Password This is the password you set when creating the keystore. It protects access to the keystore file itself. ### Key Alias The key alias is the name you assigned to the signing key within the keystore. If you created the keystore using Android Studio, this is the value you entered in the **Alias** field. If you used `keytool`, this is the value specified with the `-alias` parameter. ### Key Password This is the password for the specific key identified by the key alias. It may be the same as the keystore password or different, depending on how you created the keystore. # iOS Signing Certificates To build signed iOS apps in Capawesome Cloud, you need to configure signing certificates. iOS apps are signed using a certificate (`.p12` file) and one or more provisioning profiles (`.mobileprovision` files). These ensure the authenticity and integrity of your application and define which devices can run your app. If you're new to iOS code signing, read [iOS Certificates and Provisioning Profiles Explained](https://capawesome.io/blog/ios-certificates-and-provisioning-profiles-explained/index.md) for a beginner-friendly overview. AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up an iOS signing certificate in Capawesome Cloud. ``` ## Configuration To create an iOS signing certificate, navigate to the [Signing Certificates](https://console.cloud.capawesome.io/apps/_/certificates) page in the Capawesome Cloud Console and provide the following information: - **Name**: A descriptive name for the certificate (e.g., "Production iOS Certificate"). - **Platform**: Must be set to `iOS`. - **Type**: The certificate type (`Development` or `Production`). - **Certificate File**: The `.p12` file containing the signing certificate and private key. - **Certificate Password**: The password for the `.p12` file. - **Provisioning Profiles**: The `.mobileprovision` files associated with the app. In general, only one provisioning profile is needed. However, if you are using app extensions, you may need to upload multiple provisioning profiles (one for the main app and one for each extension). Read on for detailed instructions on creating and obtaining these credentials. ## Obtaining Credentials ### Name Choose a descriptive name that helps you identify the certificate's purpose (e.g., "Production iOS Certificate", "Development Certificate", "App Store Distribution"). This name is only used within Capawesome Cloud for organization purposes. ### Type Select the appropriate certificate type: - **Development**: For development and testing builds that run on registered devices. - **Production**: For distribution builds intended for TestFlight, App Store, or enterprise distribution. ### Certificate File A `.p12` file (Personal Information Exchange) containing your signing certificate and private key is required. You can export this file from Keychain Access or create a new certificate in the Apple Developer portal. #### Create using Online Generator The easiest way to create a certificate is using the [Capawesome iOS Certificate Generator](https://cloud.capawesome.io/tools/ios-certificate-generator/). The generator creates a signing certificate and private key, and exports them as a `.p12` file ready to upload to Capawesome Cloud. It also generates the Certificate Signing Request (CSR) file needed to create a certificate in the Apple Developer Portal. #### Create using Keychain Access If you need to create a new certificate: 1. Sign in to your [Apple Developer account](https://developer.apple.com/account). 1. Navigate to **Certificates, Identifiers & Profiles**. 1. Click on **Certificates** in the sidebar. 1. Click the **+** button to create a new certificate. 1. Select the appropriate certificate type: - For development: **Apple Development** - For production: **Apple Distribution** 1. Click **Continue**. 1. You'll be prompted to upload a Certificate Signing Request (CSR). To create a CSR: 1. Open **Keychain Access** on your Mac. 1. From the menu bar, select **Keychain Access > Certificate Assistant > Request a Certificate From a Certificate Authority**. 1. Enter your email address and name. 1. Select **Saved to disk** and click **Continue**. 1. Save the CSR file to your computer. 1. Upload the CSR file in the Apple Developer portal and click **Continue**. 1. Download the generated certificate file (`.cer`). 1. Double-click the downloaded `.cer` file to install it in Keychain Access. 1. Follow the **Export from Keychain Access** instructions above to export the certificate as a `.p12` file. #### Export from Keychain Access If you already have a signing certificate installed on your Mac: 1. Open **Keychain Access** (Applications > Utilities > Keychain Access). 1. In the left sidebar, select **login** and then **My Certificates**. 1. Look for certificates named: - **Apple Distribution: [Your Name]** (for App Store/Production builds) - **Apple Development: [Your Name]** (for development builds) 1. Expand the certificate to see the private key underneath (indicated by a key icon). 1. Select **both** the certificate **and** its private key by holding Command and clicking both items. 1. Right-click on the selection and choose **Export 2 items...**. 1. Choose a name for the file (e.g., `certificate.p12`) and select **Personal Information Exchange (.p12)** as the file format. 1. Click **Save**. 1. Set a password when prompted. This password will be required when uploading the certificate to Capawesome Cloud. 1. You may be prompted to enter your macOS user password to allow the export. Private key required You must export both the certificate and its private key together. If you don't see a private key under your certificate in Keychain Access, the certificate may have been installed without the private key, and you'll need to create a new certificate or obtain it from the original creator. ### Certificate Password This is the password you set when exporting the `.p12` file from Keychain Access. You'll need to provide this password when uploading the certificate to Capawesome Cloud. ### Provisioning Profiles Provisioning profiles are required to run your app on devices and define which devices and capabilities your app can use. You need to provide one or more `.mobileprovision` files. #### Find Locally If you've previously built your app in Xcode, provisioning profiles may already be available on your Mac: 1. Open **Terminal**. 1. Run the following command to list all installed provisioning profiles and their details: ``` for f in ~/Library/MobileDevice/Provisioning\ Profiles/*.mobileprovision; do echo "=== $f ==="; security cms -D -i "$f" 2>/dev/null | grep -A 1 'application-identifier\|Name\|ExpirationDate' | grep -v '^--$'; echo; done ``` 1. Identify the relevant provisioning profile(s) by looking for the **Name** and **application-identifier** fields. 1. Copy the desired `.mobileprovision` file(s) from `~/Library/MobileDevice/Provisioning Profiles/` to a location where you can easily access them for upload: ``` cp ~/Library/MobileDevice/Provisioning\ Profiles/.mobileprovision ~/Desktop/ ``` #### Create and Download from Apple Developer Portal To create a new provisioning profile or download an existing one: 1. Sign in to your [Apple Developer account](https://developer.apple.com/account). 1. Navigate to **Certificates, Identifiers & Profiles**. 1. Click on **Profiles** in the sidebar. 1. To create a new profile, click the **+** button: 1. Select the appropriate profile type: - For development: **iOS App Development** - For production: **App Store** (for App Store and TestFlight) or **Ad Hoc** (for enterprise distribution) 1. Click **Continue**. 1. Select your **App ID** from the dropdown menu and click **Continue**. 1. Select the certificate(s) to include in this profile and click **Continue**. 1. For development profiles, select the devices to include and click **Continue**. 1. Enter a **Provisioning Profile Name** and click **Generate**. 1. Download the generated provisioning profile (`.mobileprovision` file). 1. Repeat this process for any app extensions if your app includes them. Each extension requires its own provisioning profile matching its bundle identifier. Multiple provisioning profiles Apps with extensions (e.g., widgets, share extensions) require a separate provisioning profile for each target. Capawesome Cloud automatically matches profiles to targets based on bundle identifiers. For non-standard bundle ID patterns, see the [Custom iOS Provisioning Profiles](https://capawesome.io/cloud/native-builds/guides/custom-ios-provisioning-profiles/index.md) guide. Profile expiration Provisioning profiles expire after a certain period (typically one year). If your builds fail due to expired profiles, you'll need to regenerate and upload new ones in the Apple Developer portal. # Web Signing Certificates To sign Live Update bundles in Capawesome Cloud, you need to configure a signing certificate. Web apps use a PEM certificate file containing a private key to sign bundles, ensuring the authenticity and integrity of your Live Updates. AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up a Web signing certificate in Capawesome Cloud. ``` ## Configuration To create a web signing certificate, navigate to the [Signing Certificates](https://console.cloud.capawesome.io/apps/_/certificates) page in the Capawesome Cloud Console and provide the following information: - **Name**: A descriptive name for the certificate (e.g., "Production Web Certificate"). - **Platform**: Must be set to `Web`. - **Type**: The certificate type (`Development` or `Production`). - **Certificate File**: The PEM file containing the private key. Must have a `.pem` extension. Read on for detailed instructions on creating and obtaining these credentials. ## Obtaining Credentials ### Name Choose a descriptive name that helps you identify the certificate's purpose (e.g., "Production Web Certificate", "Development Certificate"). This name is only used within Capawesome Cloud for organization purposes. ### Type Select the appropriate certificate type: - **Development**: For development and testing builds that are not distributed publicly. - **Production**: For release builds that will be distributed to end users. ### Certificate File A PEM file containing your private key is required to sign Live Update bundles. You can generate a key pair using the Capawesome CLI: ``` npx @capawesome/cli apps:liveupdates:generatesigningkey ``` This creates a `private.pem` and `public.pem` file in the current directory. Upload the `private.pem` file as the certificate file in the Capawesome Cloud Console. You can also customize the output paths and key size: ``` npx @capawesome/cli apps:liveupdates:generatesigningkey --private-key /path/to/private.pem --public-key /path/to/public.pem --key-size 4096 ``` Keep your private key secure Store your private key in a secure location, add it to your `.gitignore`, and never commit it to version control. If you lose your private key, you will need to generate a new key pair and reconfigure your app. For more information on code signing, see the [Code Signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md) guide. # Auto-Increment Build Numbers Capawesome Cloud provides automatic build number incrementing through the `CI_BUILD_NUMBER` environment variable, which increments with each build. This guide shows you how to automatically apply this build number to your native Android and iOS projects. ## Prerequisites This guide assumes you have already set up Trapeze for native configurations. If you haven't done this yet, follow the [Overwrite Native Configurations](https://capawesome.io/cloud/native-builds/guides/native-configurations/index.md) guide first to: 1. Install Trapeze 1. Set up the build script in your `package.json` ## Configuration Create a configuration file named `capawesome.yml` in the root of your project with the following content: ``` vars: CI_BUILD_NUMBER: default: 1 platforms: android: versionCode: $CI_BUILD_NUMBER ios: buildNumber: $CI_BUILD_NUMBER ``` This configuration maps the `CI_BUILD_NUMBER` environment variable to the version code on Android and the build number on iOS. Default value The default value of `1` is used for local development when the `CI_BUILD_NUMBER` environment variable is not set. In Capawesome Cloud, this variable is automatically provided and increments with each build. ## How It Works When you trigger a build in Capawesome Cloud: 1. The `CI_BUILD_NUMBER` environment variable is automatically set to a sequential number 1. The [web build script](https://capawesome.io/cloud/native-builds/guides/web-build-script/index.md) runs Trapeze with your configuration 1. Trapeze updates the native project files with the current build number 1. Your app is built with the incremented build number This ensures each build has a unique, incrementing build number without manual intervention. ## Custom Starting Number If you need to start incrementing from a specific number (for example, to continue from your current build number), you can set the next build number directly in the [app settings](https://console.cloud.capawesome.io/apps/_/settings). The next build number must be at least `1` higher than the previous build number, or `1` if no build has been created yet. Alternatively, you can add an offset to the `CI_BUILD_NUMBER` variable in your build script: ``` { "scripts": { "capawesome:build": "CI_BUILD_NUMBER=$(($CI_BUILD_NUMBER + 1000)); if [ \"$CI_PLATFORM\" = \"ios\" ] || [ \"$CI_PLATFORM\" = \"android\" ]; then npx trapeze run capawesome.yml -y --$CI_PLATFORM; fi && npm run build" } } ``` Replace `1000` with your desired offset. For example, if your current build number is 150 and you want the next build to be 151, use an offset of 150. ## Version Name and Build Number You can combine build number incrementing with version name management: ``` vars: VERSION_NAME: CI_BUILD_NUMBER: default: 1 platforms: android: versionName: $VERSION_NAME versionCode: $CI_BUILD_NUMBER ios: version: $VERSION_NAME buildNumber: $CI_BUILD_NUMBER ``` Then set the `VERSION_NAME` variable in your Capawesome Cloud environment (e.g., `1.0.0`). This way: - The version name (e.g., `1.0.0`) remains consistent across builds - The build number increments automatically with each build # Build without Git Connection Capawesome Cloud [Native Builds](https://capawesome.io/cloud/native-builds/index.md) typically clone your Git repository to run a build. With the `--path` option, you can upload local source files directly instead of cloning from a Git repository. This is useful when: - You don't have access to a Mac and want to build iOS apps from your local machine. - You want to create debug or test builds without pushing to your remote repository first. - Your Git server is behind a firewall and you don't want to set up tunnels or mirroring (see [Access Git Behind a Firewall](https://capawesome.io/cloud/native-builds/guides/firewall-access/index.md)). - You're using a third-party CI/CD system that already checks out the code and you want to trigger builds from there. ## How It Works When you use the `--path` option, the CLI packages the contents of the specified directory into a compressed archive (respecting your `.gitignore` rules to exclude unnecessary files), uploads it to Capawesome Cloud, and triggers the build using those files instead of cloning from Git. ## Limitations Builds triggered with `--path` have the following limitations: - **No Git information**: The Cloud UI will not display Git-related metadata (commit SHA, branch, author) for the build. - **No Automations**: Builds from local files cannot be used with the [Automations](https://capawesome.io/cloud/automations/index.md) feature, which relies on Git events. - **No UI-triggered builds**: You cannot trigger or re-run these builds from the Cloud Console. They can only be started via the CLI. - **Cannot combine with `--git-ref`**: The `--path` and `--git-ref` options are mutually exclusive. ## Usage To trigger a build from your local source files, run the following command from your project directory: ``` npx @capawesome/cli apps:builds:create \ --app-id \ --path . \ --platform \ --type ``` Replace the following placeholders: - ``: The ID of your app in Capawesome Cloud. - ``: The target platform (`android`, `ios`, or `web`). - ``: The build type (e.g. `debug`, `release` for Android; `simulator`, `development`, `ad-hoc`, `app-store` for iOS). Not required for web builds. You can combine `--path` with other options like `--certificate`, `--environment`, or `--stack` just as you would for a regular Git-based build. Refer to the [CLI documentation](https://capawesome.io/cloud/cli/#appsbuildscreate) for the full list of available options. # Custom iOS Provisioning Profiles for Multiple Targets Capawesome Cloud automatically detects and applies provisioning profiles to all iOS targets based on their bundle identifiers. However, when working with multiple targets that use non-standard bundle ID patterns, you can manually configure the mapping between bundle IDs and target names. ## Automatic Provisioning Profile Detection By default, Capawesome Cloud automatically: 1. Scans all provisioning profiles uploaded to your build 1. Matches profiles to targets based on bundle identifiers 1. Configures code signing for each target 1. Generates export options with the correct profile mappings This automatic detection works seamlessly when your app extensions follow the standard iOS naming pattern where extension bundle IDs are prefixed with the main app bundle ID (e.g., `com.example.app` for the main app and `com.example.app.widget` for a widget extension). ## Configure Custom Mapping To override the automatic detection for non-standard bundle ID patterns, set the `IOS_PROVISIONING_PROFILE_MAP` environment variable: 1. Navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page 1. Select your environment or create a new one 1. Add a variable named `IOS_PROVISIONING_PROFILE_MAP` 1. Set its value to a JSON object mapping bundle IDs to target names JSON Format The value must be a valid JSON object with bundle IDs as keys and target names as values. Ensure the target names match exactly as they appear in your Xcode project. ## Example Consider a VPN app with the following targets: - Main app: `com.example.vpnapp` (target: `App`) - WireGuard extension: `com.example.wireguard` (target: `WireGuardExtension`) - Packet tunnel: `com.example.tunnelprovider` (target: `PacketTunnel`) To configure provisioning profiles for these targets: 1. Add an environment variable named `IOS_PROVISIONING_PROFILE_MAP` 1. Set its value to: ``` {"com.example.wireguard":"WireGuardExtension","com.example.tunnelprovider":"PacketTunnel"} ``` 1. Select this environment when triggering your build The specified mappings will be used in addition to the automatic detection for the main app target. # Access Git Behind a Firewall Capawesome Cloud [Native Builds](https://capawesome.io/cloud/native-builds/index.md) needs to clone your Git repository to run a build. If your Git server is behind a firewall, the build runner cannot reach it by default. This guide covers four approaches to solve this, each with different trade-offs for security, complexity, and maintenance. ## Reverse Tunnel (Recommended) A reverse tunnel lets you expose your internal Git server to the internet without opening inbound firewall ports. You run a small daemon inside your network that establishes an outbound-only connection to a tunnel provider. The provider assigns a public hostname (e.g., `git-tunnel.yourcompany.com`) that routes traffic through the tunnel to your internal Git server. This is the recommended approach for most teams because your firewall remains fully closed to inbound traffic while Native Builds can access your repository securely. There are several tunnel providers available, such as [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/), [ngrok](https://ngrok.com/), or [Tailscale Funnel](https://tailscale.com/kb/1223/funnel). Refer to your chosen provider's documentation for setup instructions. Once the tunnel is running, use the public hostname as the server URL when connecting Capawesome Cloud to your Git repository. ## Repository Mirroring You can set up a mirror of your internal Git repository on a public Git hosting service like [GitHub](https://github.com/) or [GitLab](https://gitlab.com/). Native Builds then clones from the mirror instead of your internal server, so no direct access to your network is needed. The most common approach is **push mirroring**, where your internal server pushes changes to the external mirror on a schedule. Some Git hosting services like GitLab offer [built-in mirroring](https://docs.gitlab.com/ee/user/project/repository/mirror/). For others, you can set up a cron job that runs `git push --mirror` to keep the mirror in sync. Sync Delay Mirrors are not real-time. There is always a delay between a push to your internal server and the mirror being updated. Make sure your mirroring schedule is frequent enough for your build workflow. ## IP Whitelisting You can whitelist the IP ranges of the infrastructure providers that Capawesome Cloud uses to run Native Builds. This allows the build runners to access your internal Git server directly through your firewall. ### Required IP Ranges You need to whitelist the IP ranges from the following providers: - [Cloudflare](https://www.cloudflare.com/ips/) - [Scaleway](https://www.scaleway.com/en/docs/account/reference-content/scaleway-network-information/) Security Considerations These IP ranges are shared across all customers of the respective providers. Whitelisting them allows any service hosted on those networks to reach your Git server, not just Capawesome Cloud. Make sure your Git server requires proper authentication (e.g., SSH keys or access tokens) in addition to the IP whitelist. ## Build without Git Connection Instead of making your Git server accessible to Capawesome Cloud, you can bypass the Git connection entirely by uploading your local source files directly using the CLI's `--path` option. This approach avoids any firewall or networking configuration, but trades off Git integration features such as commit metadata in the Cloud UI, Automations, and the ability to trigger builds from the Console. For details and usage instructions, see the [Build without Git Connection](https://capawesome.io/cloud/native-builds/guides/build-without-git/index.md) guide. ## Comparison | | Reverse Tunnel | Repository Mirroring | IP Whitelisting | Build without Git | | ------------------------------- | ------------------------------- | ----------------------------------- | ---------------------------- | -------------------------- | | **Firewall changes** | None | None | Inbound rules required | None | | **Security** | High (outbound-only, encrypted) | High (no exposure) | Low (broad shared IP ranges) | High (no network exposure) | | **Real-time repository access** | Yes | No (sync delay) | Yes | N/A | | **Complexity** | Medium | Low to medium | Low | Low | | **Maintenance** | Low | Medium (sync schedule, credentials) | Medium (IP range updates) | Low | | **Git integration** | Full | Full | Full | None | # Install an APK on an Android Device With [Capawesome Cloud](https://cloud.capawesome.io/), you can install an APK directly on a physical Android device — similar to how it works in platforms like Loadly or Diawi. All you need to do is create a build, allow installations from unknown sources, and scan a QR code or tap a download link. This guide walks you through the entire process using Capawesome Cloud, Capacitor, and your Android device. ## Create the Build First, trigger a new Android build in Capawesome Cloud: 1. Navigate to the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page of your app in Capawesome Cloud. 1. Click on the **Build from Git** button. 1. Select the **Git reference** (branch, tag, or commit) you want to build from. 1. Select **Android** as the platform. 1. Select **Debug** or **Release** as the build type. 1. Click on **Build** to start the build process. Wait for the build to complete. You can monitor the build progress on the build details page. Signing Certificates **Release** builds require a signing certificate (keystore). If you haven't set one up yet, refer to the [Android Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/android/index.md) documentation. For more detailed instructions on creating builds, refer to the [Getting Started](https://capawesome.io/cloud/native-builds/setup/index.md) guide. ## Allow Installations from Unknown Sources Before you can install an APK from outside the Google Play Store, you need to allow installations from unknown sources on your Android device. The browser you use to download the APK (e.g. Chrome) must be explicitly allowed to install apps. On Android 8.0 (Oreo) and later, you need to grant the install permission on a per-app basis: 1. Open **Settings** > **Apps** > **Special app access** > **Install unknown apps**. 1. Select the app you will use to download the APK (e.g. **Chrome**). 1. Enable **Allow from this source**. Device-specific path The exact path may vary depending on your device manufacturer. On Samsung devices, the setting can be found under **Settings** > **Biometrics and security** > **Install unknown apps**. On Android 7.x (Nougat) and earlier, there is a single global toggle: 1. Open **Settings** > **Security**. 1. Enable **Unknown sources**. ## Install the App Once the build is complete, you can install the APK on your device: 1. Open the build details page in Capawesome Cloud. 1. Click the **Install App** button. 1. Scan the **QR code** with your Android device or tap the **Download** button if you are already on your device. 1. Open the downloaded APK file to start the installation. \[ \](/assets/videos/cloud-install-app-android.mp4) Google Play Protect Google Play Protect may show a warning about the app being from an unknown developer. You can dismiss this warning to proceed with the installation. This is a normal security feature of Android and does not indicate an issue with your app. # Install an IPA on an iOS Device With [Capawesome Cloud](https://cloud.capawesome.io/), you can install an IPA directly on a physical iOS device — similar to how it works in platforms like Loadly or Diawi. All you need to do is create a signed build and scan a QR code or tap a download link. For **Development** and **Ad Hoc** builds, you also need to register your device in the Apple Developer account first. This guide walks you through the entire process using Capawesome Cloud, Capacitor, and your iOS device. ## Register the Device For **Development** and **Ad Hoc** builds, your iOS device must be registered in the [Apple Developer](https://developer.apple.com/) account and assigned to the provisioning profile used to sign the app. **Enterprise** builds do not require device registration and can be installed on any device. Enterprise Builds If you are using an **Enterprise** build type, you can skip this section and proceed directly to [Create the Build](#create-the-build). ### Find the Device UDID Every iOS device has a unique identifier (UDID) that is required for device registration. You can find your device's UDID using the [iOS UDID Finder](https://cloud.capawesome.io/tools/ios-udid-finder/) tool. ### Register the Device in Apple Developer Portal Once you have the UDID, register the device in the Apple Developer portal: 1. Sign in to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources). 1. Click **Devices** in the sidebar. 1. Click the **add button** (+) in the top left. 1. Select the **platform** (e.g. iOS). 1. Enter a **device name** (e.g. "Robin's iPhone 16"). 1. Enter the **UDID**. 1. Click **Continue**, then **Register**. Device Limit Apple limits the number of registered devices to 100 per product family (iPhone, iPad, etc.) per membership year. Disabling a device does not free up a slot. ### Update the Provisioning Profile If the device is not yet included in your provisioning profile, you need to update it: 1. Sign in to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources). 1. Click **Profiles** in the sidebar. 1. Select the provisioning profile you want to update. 1. Click **Edit**. 1. In the **Devices** step, check the newly registered device. 1. Click **Save** to regenerate the profile. 1. **Download** the updated provisioning profile. 1. Re-upload the updated profile to [Capawesome Cloud](https://console.cloud.capawesome.io/). Old Provisioning Profile Builds signed with the old provisioning profile will not include the new device. You must re-upload the updated profile and create a new build. ## Create the Build To install an IPA on a physical device, you need a signed build with the correct build type and signing certificate. 1. Navigate to the [Builds](https://console.cloud.capawesome.io/apps/_/builds) page of your app in Capawesome Cloud. 1. Click on the **Build from Git** button. 1. Select the **Git reference** (branch, tag, or commit) you want to build from. 1. Select **iOS** as the platform. 1. Select **Development**, **Ad Hoc**, or **Enterprise** as the build type. 1. Select a signing certificate that matches the chosen build type and has a provisioning profile with the device registered: - For **Development** builds, use a **Development** certificate. - For **Ad Hoc** builds, use a **Distribution** certificate (typically configured as **Production** in Capawesome Cloud) with an Ad Hoc provisioning profile. - For **Enterprise** builds, use an **Enterprise distribution** certificate (typically configured as **Production** in Capawesome Cloud) with an enterprise provisioning profile. 1. Click on **Build** to start the build process. Wait for the build to complete. You can monitor the build progress on the build details page. For more detailed instructions on creating builds and uploading signing certificates, refer to the [Getting Started](https://capawesome.io/cloud/native-builds/setup/index.md) guide and the [iOS Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/ios/index.md) documentation. ## Install the App Once the build is complete, you can install the IPA on your device: 1. Open the build details page in Capawesome Cloud. 1. Click the **Install App** button. 1. Scan the **QR code** with your iOS device or tap the **Download** button if you are already on your device. 1. The app will be installed on your device. \[ \](/assets/videos/cloud-install-app-ios.mp4) # Overwrite Native Configurations You can automatically overwrite your native Android and iOS project configurations before building in [Capawesome Cloud](https://cloud.capawesome.io/). This is useful for modifying app identifiers, display names, version numbers, and other native configurations based on your build environment or branch. This guide shows you how to use [Trapeze](https://trapeze.dev/), a mobile app configuration tool, to automate native project modifications during the build process. ## Installation First, install Trapeze as a development dependency in your project: ``` npm install --save-dev @trapezedev/configure ``` ## Configuration File Create a configuration file named `capawesome.yml` in the root of your project. This file defines the native configurations you want to overwrite. Here's a basic example that modifies the App ID and display name: ``` platforms: android: packageName: com.example.myapp appName: My App ios: bundleId: com.example.myapp displayName: My App ``` Platform-specific configurations Trapeze uses platform-specific keys for similar configurations. For example, Android uses `packageName` while iOS uses `bundleId`. Refer to the [Android](https://trapeze.dev/docs/Operations/android) and [iOS](https://trapeze.dev/docs/Operations/ios) operation guides for complete lists of available configurations. ## Build Script Add an npm script to your `package.json` that runs Trapeze for Android and iOS platforms before the web build process: ``` { "scripts": { "capawesome:build": "if [ \"$CI_PLATFORM\" = \"ios\" ] || [ \"$CI_PLATFORM\" = \"android\" ]; then npx trapeze run capawesome.yml -y --$CI_PLATFORM; fi && npm run build" } } ``` Capawesome Cloud automatically calls the [web build script](https://capawesome.io/cloud/native-builds/guides/web-build-script/index.md) during the build process. The script conditionally runs Trapeze only for iOS and Android platforms using the `$CI_PLATFORM` environment variable. ## Using Environment Variables You can use Capawesome Cloud's [environment variables](https://capawesome.io/cloud/native-builds/environments/index.md) to dynamically configure your native projects. This is particularly useful for overwriting configurations based on your build environment. ### Define Variables First, define variables in your configuration file: ``` vars: APP_ID: APP_NAME: platforms: android: packageName: $APP_ID appName: $APP_NAME ios: bundleId: $APP_ID displayName: $APP_NAME ``` ### Set Variable Values Then, set the variable values in your Capawesome Cloud environment: 1. Navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page 1. Select your environment or create a new one 1. Add variables with their values: 1. `APP_ID`: `com.example.myapp` 1. `APP_NAME`: `My App` You can create multiple environments with different values (e.g., development, staging, production) and select the appropriate environment when triggering a build. Default values You can provide default values for variables that will be used if no environment variable is set: ``` vars: APP_ID: default: com.example.default APP_NAME: default: Default App Name ``` # Install Private npm Packages You can use private npm packages within your [Capawesome Cloud](https://cloud.capawesome.io/) native builds by configuring a custom npm registry. This guide shows you how to set up authentication to access private packages from any npm registry provider. ## Add Environment Secret To access private npm packages, you need to configure an authentication token: 1. Obtain your authentication token from your npm registry provider 1. Add it to your Capawesome Cloud app as an environment secret with an appropriate name (e.g., `NPM_REGISTRY_TOKEN`) as described in [Environment secrets](https://capawesome.io/cloud/native-builds/environments/#secrets) Keep your tokens secure Never commit authentication tokens directly to your repository. Always use environment secrets to store sensitive information like authentication tokens. ## Configure npm Registry Create a new file named `.npmrc` in the root of your project with the following configuration: ``` @your-scope:registry=https://your-registry.example.com //your-registry.example.com/:_authToken=${YOUR_NPM_REGISTRY_TOKEN} ``` Replace the following placeholders: - `@your-scope`: Your package scope (e.g., `@mycompany`) - `https://your-registry.example.com`: Your npm registry URL - `${YOUR_NPM_REGISTRY_TOKEN}`: The name of your environment secret containing the authentication token This configuration tells npm to use your custom registry for packages under your scope and authenticate using the environment secret. Capawesome Insiders If you're looking to install private Capawesome Insiders npm packages, refer to the [Integrate Capawesome Insiders with Capawesome Cloud](https://capawesome.io/insiders/integrations/capawesome-cloud/index.md) guide for specific setup instructions. ## Manage Multiple Environments If your local development environment stops working with the new `.npmrc` file, you can create a separate configuration file specifically for Capawesome Cloud: 1. Create a new file named `.npmrc.capawesome` in the root of your project with the registry configuration 1. Navigate to your **Capawesome Cloud app > Settings > Environment variables** 1. Add a new environment variable named `NPM_CONFIG_USERCONFIG` 1. Set its value to `/tmp/capawesome/repo/.npmrc.capawesome` Custom file location If your `.npmrc.capawesome` file is not in your project's root directory, adjust the path in the `NPM_CONFIG_USERCONFIG` environment variable accordingly. This approach allows you to maintain separate npm configurations for local development and Capawesome Cloud builds. # Override Java Version on Android Builds Capawesome Cloud automatically detects the Android Gradle Plugin version used in your project and applies the corresponding Java version. However, you can manually override the Java version if needed. ## Automatic Java Version Detection Capawesome Cloud supports Gradle Plugin Version 8.0.0 and higher. The Java version is automatically selected based on your Android Gradle Plugin version: | Android Gradle Plugin | Java Version | | --------------------- | ------------ | | 8.0.0 - 8.7.1 | 17 | | >= 8.7.2 | 21 | This automatic detection ensures your builds use the appropriate Java version without additional configuration. ## Override Java Version To override the automatic Java version selection, set the `JAVA_VERSION` environment variable: 1. Navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page 1. Select your environment or create a new one 1. Add a variable named `JAVA_VERSION` 1. Set its value to the major version number (e.g., `17` or `21`) Version constraints The `JAVA_VERSION` environment variable must be set to an integer representing the major version, such as `17` or `21`. For available Java versions, refer to the [Build Stacks](https://capawesome.io/cloud/native-builds/build-stacks/index.md) documentation. ## Example To use Java 17 for your Android builds regardless of your Gradle Plugin version: 1. Add an environment variable named `JAVA_VERSION` with the value `17` 1. Select this environment when triggering your build The specified Java version will be used instead of the automatically detected version. # Configure Web Build Script When building native apps with [Capawesome Cloud](https://cloud.capawesome.io/), the platform needs to generate your web assets before creating the native builds. This guide explains how Capawesome Cloud determines which npm script to use for building your web application. ## Build Script Priority Capawesome Cloud follows a specific priority order when determining which script to execute for generating your web build: 1. **`capawesome:build` script**: If defined in your `package.json`, this script will be used. 1. **`build` script**: If `capawesome:build` is not defined, the standard `build` script will be used. 1. **Build failure**: If neither script exists, the web build will fail. For most projects, the standard `build` script is sufficient: ``` { "scripts": { "build": "ionic build" } } ``` If you need different build configurations for Capawesome Cloud builds versus local builds, define a `capawesome:build` script: ``` { "scripts": { "build": "ionic build", "capawesome:build": "ionic build --prod" } } ``` This is useful when you need to apply production-specific optimizations, enable different build flags, or run additional build steps for native apps. ## Configuration File Override You can also explicitly configure the web build command in your `capawesome.config.json` file: ``` { "cloud": { "apps": [ { "appId": "your-app-id", "webBuildCommand": "npm run custom-build" } ] } } ``` This approach gives you complete control over the build command and takes precedence over the automatic script detection. Learn more about configuration options in the [App Configuration](https://capawesome.io/cloud/native-builds/configuration/index.md) documentation. # Audit Logs Audit logs provide a detailed record of actions taken within Capawesome Cloud. They are essential for tracking changes, monitoring user activity, and ensuring compliance with organizational policies. Audit logs can be accessed through the Capawesome Cloud Console. ## Accessing Audit Logs To access audit logs, you can use the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/audit-logs). Simply navigate to the "Audit Logs" section under your organization. Here, you can filter logs by entity type, entity ID, action type and user. Only organization owners and admins can access audit logs. ## Entities The following entities are tracked in the audit logs: - **Apps**: Actions related to app creation, updates, and deletions. - **Bundles**: Actions related to bundle creation, updates, and deletions. - **Channels**: Actions related to channel creation, updates, and deletions. - **Invitations**: Actions related to invitations sent, accepted, or revoked. - **Members**: Actions related to member role changes and removals. - **Organizations**: Actions related to organization settings and member management. - **Subscriptions**: Actions related to subscription changes and billing updates. We are continuously working to enhance our audit logging capabilities. If you have any suggestions or need additional features, please feel free to reach out to our support team. # Organization and User Management Capawesome Cloud allows you to manage users and organizations, providing a structured way to control access and permissions for your team. ## Memberships In Capawesome Cloud, memberships define the relationship between users and organizations. You can invite users to join your organization, assign them [roles](#roles-and-permissions), and manage their permissions. ## Roles and Permissions Capawesome Cloud provides several predefined roles that you can assign to users: - **Owner**: Full access to all resources and settings within the organization. - **Admin**: Can manage users and settings, but cannot delete the organization. - **Billing**: Can manage billing and subscription settings. - **Member**: Can access shared resources but has limited permissions. - **Viewer**: Can view apps and app resources. | Action | Owner | Admin | Billing | Member | Viewer | | ------------------------------------------------------------- | ----- | ----- | ------- | ------ | ------ | | Can see and edit billing information and subscription details | ✓ | | ✓ | | | | Can manage the ownership of the organization | ✓ | | | | | | Can manage the organization settings | ✓ | ✓ | | | | | Can create and manage apps | ✓ | ✓ | | | | | Can create and manage invitations | ✓ | ✓ | | | | | Can create and manage members | ✓ | ✓ | | | | | Can create and manage teams | ✓ | ✓ | | | | | Can view apps and app resources | ✓ | ✓ | | ✓ | ✓ | | Can create and manage app builds | ✓ | ✓ | | ✓ | | | Can create and manage app certificates | ✓ | ✓ | | ✓ | | | Can create and manage app channels | ✓ | ✓ | | ✓ | | | Can create and manage app deployments | ✓ | ✓ | | ✓ | | | Can create and manage app destinations | ✓ | ✓ | | ✓ | | | Can create and manage app devices | ✓ | ✓ | | ✓ | | | Can create and manage app environments | ✓ | ✓ | | ✓ | | | Can create and manage app webhooks | ✓ | ✓ | | ✓ | | | Can subscribe to usage limit notifications | ✓ | ✓ | | | | | Can delete an organization | ✓ | | | | | ## Inviting Users To invite users to your organization, follow these steps: 1. Go to the **Invitations** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/invitations). 1. Click on the **Create Invitation** button. 1. Enter the email address of the user you want to invite. 1. Select the role you want to assign to the user. 1. Click **Create** to send the invitation. Once the user accepts the invitation, they will become a member of your organization with the assigned role. ## Managing Users To manage users in your organization, you can: 1. Go to the **Members** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/members). 1. Here, you can see a list of all members, their roles, and their status. 1. You can change a user's role by clicking on the **Edit** button in the Actions column. 1. To remove a user from your organization, click on the **Delete** button next to their name. # Teams Teams allow you to organize members within your organization and control access to specific apps. By creating teams, you can ensure that members only have access to the apps they need to work with. ## How Teams Work Teams are groups of members within an organization that can be assigned access to specific apps. Each team can have multiple members, and each member can belong to multiple teams. Apps can be assigned to multiple teams as well. Relationship between Members, Teams, and Apps in Capawesome Cloud When a member is part of a team, they gain access to all apps assigned to that team. If a member is not part of any team, they will not have access to any apps in the organization (except for Owners and Admins). ## Creating Teams To create a team in your organization: 1. Go to the **Teams** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/teams). 1. Click on the **Create Team** button. 1. Enter a name and optional description for the team. 1. Click **Create** to create the team. 1. After creation, you can add apps and members to the team by clicking on "Manage Apps" and "Manage Members" in the "Actions" menu of the team. Team Assignments Required As soon as the first team is created in an organization, all existing members (except Owners and Admins) will lose access to all apps until they are assigned to a team. Make sure to assign members to teams after creating the first team to restore their access. ## Managing Apps To manage apps within a team: 1. Go to the **Teams** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/teams). 1. Click on "Manage Apps" in the "Actions" menu of the desired team. 1. Click on **Add Apps** to assign apps to the team. 1. Select the apps you want to add and click **Add**. ## Managing Members To manage members within a team: 1. Go to the **Teams** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/teams). 1. Click on "Manage Members" in the "Actions" menu of the desired team. 1. Click on **Add Members** to assign members to the team. 1. Select the members you want to add and click **Add**. # Two-Factor Authentication For enhanced security, Capawesome Cloud allows organizations to enforce two-factor authentication (2FA) for all members. When 2FA enforcement is enabled, all members of the organization must have 2FA enabled on their accounts to remain part of the organization. ## How 2FA Enforcement Works When 2FA enforcement is enabled for an organization, the following rules apply: - **Existing members retain access**: Members who are already part of the organization when 2FA enforcement is enabled will remain in the organization, even if they haven't enabled 2FA yet. They can continue to access the organization until they enable 2FA themselves or are manually removed and re-invited to enforce the requirement. - **New members must have 2FA enabled**: New members or members accepting invitations must have 2FA enabled on their account before they can join the organization. - **Members cannot disable 2FA**: Once a member has enabled 2FA and joined an organization with 2FA enforcement enabled, they cannot disable 2FA on their account while they remain a member of the organization. ## Enabling 2FA Enforcement To enable 2FA enforcement for your organization: 1. Go to the **Settings** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/settings). 1. Scroll to the **Two-Factor Authentication** section. 1. Click on the **Enable** button to activate 2FA enforcement. 1. Confirm the action when prompted. Once enabled, existing members will retain access to the organization regardless of their 2FA status. However, new members will be required to have 2FA enabled before they can join. To enforce 2FA for existing members without 2FA, you will need to manually remove them from the organization and re-invite them. ## Disabling 2FA Enforcement To disable 2FA enforcement for your organization: 1. Go to the **Settings** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/settings). 1. Scroll to the **Two-Factor Authentication** section. 1. Click on the **Disable** button to deactivate 2FA enforcement. 1. Confirm the action when prompted. Once disabled, members will no longer be required to have 2FA enabled, and they can disable 2FA on their accounts if they choose to do so. ## Setting Up 2FA For information on how to set up two-factor authentication on your account, please refer to the [Authentication](https://capawesome.io/cloud/accounts/authentication/#two-factor-authentication-mfa) guide. # Billing The Billing section covers everything related to subscriptions, payment methods, and invoices for your Capawesome Cloud organization. All payments and invoicing are handled by our Merchant of Record (MoR) partners, [Polar](https://polar.sh/) and [Lemon Squeezy](https://www.lemonsqueezy.com/), which take care of payment processing, tax compliance, and invoicing on our behalf. ## Accessing the Billing Page To manage billing for your organization, go to the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). Only members with the **Owner** or **Billing** role can access the Billing page. See [Organization and User Management](https://capawesome.io/cloud/organizations/memberships/index.md) for details on assigning roles. ## What You Can Do From the Billing page, you can: - [Manage Plans & Billing](https://capawesome.io/cloud/organizations/billing/manage/index.md) — subscribe to a plan, upgrade or downgrade, switch billing interval, cancel, and update payment or billing details. - [Invoices](https://capawesome.io/cloud/organizations/billing/invoices/index.md) — view payment history and download invoices and receipts. - [FAQ](https://capawesome.io/cloud/organizations/billing/faq/index.md) — answers to common billing questions. ## Merchant of Record Capawesome uses a Merchant of Record (MoR) model for all paid subscriptions. This means Polar or Lemon Squeezy is the seller of record for your subscription and is responsible for collecting payments, charging applicable taxes (such as VAT or sales tax), and issuing invoices. - All new customers are onboarded through [Polar](https://polar.sh/). - [Lemon Squeezy](https://www.lemonsqueezy.com/) is available as an alternative if you run into payment issues with Polar. If you need to switch your organization from one provider to the other, please [contact support](https://capawesome.io/cloud/support/index.md). # Billing FAQ Answers to the most common questions about billing for your Capawesome Cloud organization. For detailed walkthroughs, see [Manage Plans & Billing](https://capawesome.io/cloud/organizations/billing/manage/index.md) and [Invoices](https://capawesome.io/cloud/organizations/billing/invoices/index.md). ## Is there a free trial? Yes. New organizations can start a **14-day free trial** on any paid plan. A valid payment method is required upfront to prevent misuse of our Cloud infrastructure, but no charges are made during the trial. See [Free Trial](https://capawesome.io/cloud/organizations/billing/manage/#free-trial) for details. ## How do I cancel my subscription? Open the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing) and click **Cancel Subscription**. Cancellation takes effect at the end of the current billing period. See [Canceling Your Subscription](https://capawesome.io/cloud/organizations/billing/manage/#canceling-your-subscription). ## How do I upgrade or downgrade my plan? Upgrades take effect immediately and are invoiced right away. Downgrades take effect at the next renewal, with any price difference automatically applied as credit to upcoming invoices. See [Upgrading Your Plan](https://capawesome.io/cloud/organizations/billing/manage/#upgrading-your-plan) and [Downgrading Your Plan](https://capawesome.io/cloud/organizations/billing/manage/#downgrading-your-plan). ## How do I switch between monthly and yearly billing? You can switch billing intervals from the Billing page. Yearly plans are paid upfront for 12 months. See [Switching Billing Interval](https://capawesome.io/cloud/organizations/billing/manage/#switching-billing-interval). ## How do I update my billing information? Open the **Customer Portal** from the Billing page to update your company name, billing address, VAT ID, or billing email. See [Billing Details](https://capawesome.io/cloud/organizations/billing/manage/#billing-details). ## What payment methods are accepted? All major payment methods supported by our Merchant of Record partners, including credit and debit cards. The exact list depends on your country and your provider. See [Payment Methods](https://capawesome.io/cloud/organizations/billing/manage/#payment-methods). ## How do I view and download invoices? Invoices are available in the Customer Portal of your Merchant of Record. Open it from the Billing page and download any invoice as a PDF. See [Invoices](https://capawesome.io/cloud/organizations/billing/invoices/index.md). ## Why is my invoice from Polar or Lemon Squeezy? Capawesome uses a Merchant of Record (MoR) model, so [Polar](https://polar.sh/) or [Lemon Squeezy](https://www.lemonsqueezy.com/) is the seller of record for your subscription. They collect payment, charge applicable taxes, and issue your invoice. ## Can I switch from Polar to Lemon Squeezy (or vice versa)? Yes. New organizations use Polar by default, but you can request a switch to the other provider if you are experiencing payment issues. [Contact support](https://capawesome.io/cloud/support/index.md) to request the change. ## Can I get a refund? Refunds are handled on a case-by-case basis and must be requested through [support](https://capawesome.io/cloud/support/index.md). ## How do I contact support for billing issues? For general billing questions or refund requests, [contact Capawesome support](https://capawesome.io/cloud/support/index.md). For corrections to an existing invoice, contact your Merchant of Record directly — either [Polar Support](https://polar.sh/docs/support) or the [Lemon Squeezy Help Center](https://www.lemonsqueezy.com/help). # Invoices All invoices, receipts, and payment history for your Capawesome Cloud organization are available in the **Customer Portal** of your Merchant of Record — either [Polar](https://polar.sh/) or [Lemon Squeezy](https://www.lemonsqueezy.com/). ## Viewing Payment History To view your payment history: 1. Go to the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). 1. Click the button to open the **Customer Portal**. 1. Navigate to the invoices or payment history section of the portal. Only members with the **Owner** or **Billing** role can open the Customer Portal. ## Downloading Invoices and Receipts From the Customer Portal, you can download each invoice or receipt as a PDF. Open the invoice you need and use the download option provided by the portal. ## Invoice Issuer Invoices are issued by the Merchant of Record, not by Capawesome directly: - Organizations on Polar receive invoices from **Polar**. - Organizations on Lemon Squeezy receive invoices from **Lemon Squeezy**. This is why the seller name on your invoice is Polar or Lemon Squeezy rather than Capawesome or Genz IT Solutions GmbH. ## Requesting a Corrected Invoice Capawesome is not able to modify issued invoices. If you need a correction (for example, to update a VAT ID or billing address on a past invoice), you must contact your Merchant of Record directly: - Polar: [Polar Support](https://polar.sh/docs/support) - Lemon Squeezy: [Lemon Squeezy Help Center](https://www.lemonsqueezy.com/help) # Manage Plans & Billing All plan changes and payment details for your organization are managed from the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). Only members with the **Owner** or **Billing** role can make changes here. This page is split into two parts: **Plan Management** covers your subscription, and **Billing Information** covers how you pay. ## Plan Management ### Free Trial New organizations can start a **14-day free trial** on any paid plan. A valid payment method is required upfront to start the trial. This is to prevent misuse of our Cloud infrastructure and does not result in any charges during the trial period. When the trial ends, your organization is automatically moved to the selected paid plan and the first invoice is issued. You can cancel at any time during the trial to avoid being charged. ### Viewing Your Current Plan Your active plan, billing interval, and next renewal date are shown at the top of the Billing page. ### Subscribing to a Plan To subscribe to a paid plan: 1. Go to the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). 1. Select either **Monthly** or **Yearly** billing. 1. Choose the plan that best fits your needs. 1. Complete the checkout through Polar or Lemon Squeezy. For plan details and pricing, see the [pricing page](https://cloud.capawesome.io/pricing). ### Upgrading Your Plan Upgrades take effect **immediately**. You are invoiced for the new plan right away, and the new features become available as soon as the payment is processed. ### Downgrading Your Plan Downgrades take effect at the **next renewal date**. You keep access to your current plan until then. The price difference between your old and new plan is **automatically applied as credit** to your upcoming invoice(s) — no action is needed on your side. ### Switching Billing Interval You can switch between monthly and yearly billing at any time. Yearly plans are **paid upfront for 12 months** and typically come at a discount compared to monthly billing. Switching to a longer interval takes effect immediately and is invoiced immediately. Switching to a shorter interval takes effect at the next renewal, with any credit applied to upcoming invoices. ### Canceling Your Subscription To cancel your subscription: 1. Go to the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). 1. Click **Cancel Subscription** and follow the prompts. Cancellation takes effect at the **end of the current billing period**. You keep full access to your plan until then, after which your organization is moved to the free tier. You can re-subscribe at any time from the same page. ### Refunds Refunds are handled on a case-by-case basis and cannot be requested directly from the Billing page. If you believe you are eligible for a refund, please [contact support](https://capawesome.io/cloud/support/index.md). ## Billing Information ### Payment Methods We accept all major payment methods supported by our Merchant of Record partners, including credit and debit cards. The exact list depends on your country and the provider handling your organization. To update your payment method: 1. Go to the **Billing** page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/billing). 1. Click the button to open the **Customer Portal** (Polar or Lemon Squeezy). 1. Add or update your payment method directly in the portal. ### Billing Details Billing details such as company name, billing address, VAT ID, and billing email are managed in the Customer Portal of your provider. Open the Customer Portal from the Billing page to edit them. Tax (such as VAT or sales tax) is calculated and collected automatically by the Merchant of Record based on your billing address and tax ID. ### Identifying Your Billing Provider You can tell which provider handles your organization by opening the Customer Portal from the [Billing](https://console.cloud.capawesome.io/organizations/_/billing) page. If the portal shows Polar branding, your organization is on Polar. If it shows Lemon Squeezy branding, your organization is on Lemon Squeezy. All new organizations use Polar by default. If you are experiencing payment issues with your current provider, you can request a switch to the other provider by [contacting support](https://capawesome.io/cloud/support/index.md). # Single Sign-On (SSO) Capawesome Cloud supports Single Sign-On (SSO), allowing organizations to enforce centralized authentication through their Identity Provider (IdP). With SSO enabled, members of your organization authenticate using your corporate identity management system, providing enhanced security and streamlined access management. Supported Plans SSO is only available for organizations on the **Enterprise** plans with SSO add-on. ## How SSO Works SSO relies on a trust relationship between Capawesome Cloud (the Service Provider, or SP) and your organization's Identity Provider (IdP). When SSO is enabled for your organization, members must authenticate through your IdP to access organization resources: 1. A user attempts to access organization resources in Capawesome Cloud. 1. The user is redirected to your Identity Provider's login page. 1. After successful authentication with the IdP, the user is redirected back to Capawesome Cloud. 1. Capawesome Cloud verifies the authentication response and grants access to the organization. Users are identified by their email address. Make sure that the email address associated with your Identity Provider account matches the email address on your Capawesome Cloud account. ## Supported Protocols Capawesome Cloud supports SAML 2.0 for Single Sign-On. This means you can integrate with any SAML-compliant Identity Provider, including Azure AD (Microsoft Entra ID), Okta, OneLogin, Google Workspace, PingIdentity, and many others. ## Configuration Guides We provide step-by-step configuration guides for the following Identity Providers: | Identity Provider | Documentation | | ----------------------------- | ---------------------------------------------------------------------------------------- | | Azure AD (Microsoft Entra ID) | [Configuration Guide](https://capawesome.io/cloud/organizations/sso/azure-saml/index.md) | If your Identity Provider is not listed above, you can still configure SAML SSO by following the general SAML 2.0 setup process. The required configuration values (Entity ID, Assertion Consumer Service URL, and Sign on URL) are available in your organization's SSO settings. ## Configuring SSO To configure SSO for your organization: 1. Navigate to your [organization settings](https://console.cloud.capawesome.io/organizations/_/settings) in the Capawesome Cloud Console. 1. Scroll to the **Single Sign-On (SSO)** section. 1. Follow the configuration guide for your Identity Provider. Only organization owners and admins can configure SSO settings. ## Domain Verification After configuring SSO, you must verify ownership of your email domain before members can sign in via SSO. This prevents unauthorized SSO provider registration and ensures only domain owners can enable SSO for their domain. 1. After submitting the SSO configuration, click **Verify domain** in the success notification or in the SSO settings section. 1. Add the displayed **TXT** record to your domain's DNS configuration. Most DNS providers auto-append your domain to the host field. 1. Wait for DNS propagation (this can take up to 48 hours, but is typically much faster). 1. Click **Verify** to confirm domain ownership. Info SSO sign-in is blocked until domain verification is complete. Members can still sign in with their email and password during this time. ## User Provisioning Once SSO is configured and your domain is verified, new users are provisioned automatically — no invitation required. When a user signs in through your Identity Provider with an email address matching your organization's verified SSO domain, Capawesome Cloud will: 1. Create a new user account if one does not already exist. 1. Add the user to your organization with the `member` role. Organization owners and admins can adjust the user's role afterwards if needed. Existing users If a user already has a Capawesome Cloud account with an email that does not match your organization's verified SSO domain, they can enable SSO by updating their email address in their [account settings](https://console.cloud.capawesome.io/settings/account) to one matching the domain. The new email must be verified before SSO sign-in becomes available. ## Requirements Before configuring SSO, ensure you have: - An active Capawesome Cloud organization with an appropriate subscription plan. - Administrator access to your Identity Provider (e.g., Azure AD). - The ability to create and configure enterprise applications in your IdP. - Access to your domain's DNS settings for domain verification. # How to configure SAML SSO with Azure This guide walks you through configuring SAML-based Single Sign-On (SSO) with Azure AD (Microsoft Entra ID) as your Identity Provider (IdP) for your Capawesome Cloud organization. In this setup, Azure AD acts as the IdP and Capawesome Cloud acts as the Service Provider (SP). ## Prerequisites Before you begin, ensure you have: - Administrator access to your organization's [Microsoft Entra admin center](https://entra.microsoft.com) - Owner or Admin role in your Capawesome Cloud organization ## Step 1: Create an Application in your Identity Provider 1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com). 1. Navigate to **Entra ID** > **Enterprise apps** > **All applications**. 1. Click **New application**. 1. Click **Create your own application**. 1. Enter a name for the application (e.g., `Capawesome Cloud`). 1. Select **Integrate any other application you don't find in the gallery (Non-gallery)**. 1. Click **Create**. ## Step 2: Configure your Application 1. In your newly created application, navigate to **Manage** > **Single sign-on**. 1. Select **SAML** as the single sign-on method. The **Basic SAML Configuration** dialog will appear. 1. Click **Edit** on the **Basic SAML Configuration** section. 1. Open a new browser tab, navigate to your [organization's settings](https://console.cloud.capawesome.io/organizations/_/settings) in the Capawesome Cloud Console, scroll to the **Single Sign-On (SSO)** section, and click **Configure** to view the required URLs. 1. In Capawesome Cloud, copy the **SP Entity ID** and paste it into the **Identifier (Entity ID)** field in Azure. 1. Copy the **Callback URL** from Capawesome Cloud and paste it into the **Reply URL (Assertion Consumer Service URL)** field in Azure. 1. Leave **Sign on URL**, **Relay State** and **Logout URL** empty. 1. Click **Save**. ## Step 3: Configure Attributes and Claims Azure AD sends user information to Capawesome Cloud through SAML attributes. The default configuration should work for most cases, but verify the following claims are present: 1. Click **Edit** on the **Attributes & Claims** section. 1. Ensure these claims are configured: | Claim | Value | | -------------------------------- | ---------------- | | Unique User Identifier (Name ID) | user.mail | | emailaddress | user.mail | | givenname | user.givenname | | surname | user.surname | | name | user.displayname | The **Unique User Identifier (Name ID)** must be set to `user.mail` to properly identify users by their email address. ## Step 4: Complete Configuration in Capawesome Cloud 1. Return to your [organization's SSO settings](https://console.cloud.capawesome.io/organizations/_/settings) in the Capawesome Cloud Console. 1. Enter the **Microsoft Entra Identifier** from Azure AD into the **Issuer** field. 1. Enter the **Login URL** from Azure AD into the **Entry Point** field. 1. Download the **Certificate (Base64)** file from Azure AD, open it in a text editor, and enter the entire certificate content (including the `-----BEGIN CERTIFICATE-----` and `-----END CERTIFICATE-----` lines) into the **Public Certificate** field. 1. Enter your domain (e.g., `yourcompany.com`) into the **Domain** field. 1. Click **Configure** to save the SSO configuration. The certificate should be in PEM format: ``` -----BEGIN CERTIFICATE----- MIIDp...certificate content... -----END CERTIFICATE----- ``` ## Step 5: Verify Domain Ownership After configuring SSO, you must verify ownership of your domain before members can sign in via SSO. 1. After submitting the configuration, click **Verify domain** in the success notification or in the SSO settings section. 1. Add the displayed **TXT** record to your domain's DNS configuration. Most DNS providers auto-append your domain to the host field, so you only need to enter the subdomain identifier shown in the **Host** field. 1. Wait for DNS propagation (this can take up to 48 hours, but is typically much faster). You can check propagation status using `dig TXT .`. 1. Click **Verify** to confirm domain ownership. Once verified, organization members can authenticate through Azure AD to access organization resources. ## Troubleshooting ### AADSTS50011: Reply URL does not match This error occurs when the Reply URL configured in Azure AD does not match the Assertion Consumer Service URL in Capawesome Cloud. - Verify that the Reply URL in Azure AD exactly matches the URL shown in your Capawesome Cloud SSO settings. - Check for trailing slashes or protocol mismatches (http vs https). ### AADSTS700016: Application not found This error indicates that the application identifier cannot be found in the Azure AD tenant. - Verify that the Identifier (Entity ID) in Azure AD matches the Entity ID in Capawesome Cloud. - Ensure the application is properly configured and saved. ### Invalid Signature Error This error occurs when the certificate validation fails. - Ensure the certificate is in PEM format (Base64 encoded). - Verify that the certificate has not expired. - Check if the certificate has been rotated in Azure AD and update it in Capawesome Cloud if necessary. ### User Not Assigned Error If a user receives an error stating they are not assigned to the application: - Verify that the user is assigned to the enterprise application in Azure AD. - Check group memberships if using group-based assignment. ### Missing or Incorrect User Attributes If user information is not being correctly transferred: - Verify the attribute mappings in the **Attributes & Claims** section in Azure AD. - Ensure the user has the required attributes (email, name) populated in their Azure AD profile. # Insiders # Professional Capacitor Plugins for Production Apps Enterprise-grade plugins built and maintained by Ionic Developer Experts. Save weeks of development time with 22+ battle-tested plugins covering NFC, Biometrics, Bluetooth, Speech Recognition, and more. **Trusted by 500+ development teams** — including Fortune 500 companies — powering 1,000+ apps with millions of users. [View Pricing](https://capawesome.io/insiders/pricing/index.md) [Browse Plugins](#available-plugins) ______________________________________________________________________ ## Trusted by Leading Teams > "Saved us 4 weeks of development time" > > **Michael Wolz** — Head of Development, snapADDY GmbH ______________________________________________________________________ ## Why Choose Insiders? - **Ship Faster** ______________________________________________________________________ Pre-built solutions for complex native features. Focus on your app's core functionality instead of wrestling with platform APIs. - **Production-Ready** ______________________________________________________________________ Battle-tested in 1,000+ apps with millions of users. Over 1M downloads/month. Comprehensive documentation, examples, and edge case handling included. - **Always Maintained** ______________________________________________________________________ Regular updates for new OS versions, security patches, and feature enhancements. Never worry about breaking changes. - **Expert Support** ______________________________________________________________________ Direct access to official Ionic Developer Experts via priority support channels. Average 1-day response time. Get unblocked quickly when issues arise. ## Available Plugins The moment you [become an insider](#how-to-become-an-insider), you'll get **immediate access** to all plugins: ### Hardware & Sensors - [**Accelerometer**](https://capawesome.io/plugins/accelerometer/index.md) - Monitor device acceleration and motion - [**Barometer**](https://capawesome.io/plugins/barometer/index.md) - Measure atmospheric pressure - [**Pedometer**](https://capawesome.io/plugins/pedometer/index.md) - Track steps and walking activity - [**Bluetooth Low Energy**](https://capawesome.io/plugins/bluetooth-low-energy/index.md) - Connect to BLE devices with advanced features - [**NFC**](https://capawesome.io/plugins/nfc/index.md) - Read, write, and emulate NFC tags - [**Wifi**](https://capawesome.io/plugins/wifi/index.md) - Manage WiFi connections and networks ### Authentication & Security - [**Biometrics**](https://capawesome.io/plugins/biometrics/index.md) - Face and fingerprint authentication - [**OAuth**](https://capawesome.io/plugins/oauth/index.md) - OAuth 2.0 and OpenID Connect authentication - [**Secure Preferences**](https://capawesome.io/plugins/secure-preferences/index.md) - Encrypted key-value storage ### Media & Audio - [**Audio Player**](https://capawesome.io/plugins/audio-player/index.md) - Audio playback with background support - [**Audio Recorder**](https://capawesome.io/plugins/audio-recorder/index.md) - Record high-quality audio - [**Media Session**](https://capawesome.io/plugins/media-session/index.md) - Media controls and metadata management - [**Speech Recognition**](https://capawesome.io/plugins/speech-recognition/index.md) - Speech-to-text transcription - [**Speech Synthesis**](https://capawesome.io/plugins/speech-synthesis/index.md) - Text-to-speech conversion ### Data & Files - [**Contacts**](https://capawesome.io/plugins/contacts/index.md) - Read, write, and select device contacts - [**File Compressor**](https://capawesome.io/plugins/file-compressor/index.md) - Compress and decompress files - [**SQLite**](https://capawesome.io/plugins/sqlite/index.md) - Local database management - [**Zip**](https://capawesome.io/plugins/zip/index.md) - Create and extract ZIP archives ### Utilities - [**Geocoder**](https://capawesome.io/plugins/geocoder/index.md) - Geocoding and reverse geocoding - [**Printer**](https://capawesome.io/plugins/printer/index.md) - Print documents and images - [**Purchases**](https://capawesome.io/plugins/purchases/index.md) - In-app purchases and subscriptions - [**Share Target**](https://capawesome.io/plugins/share-target/index.md) - Receive shared content from other apps **New plugins added every other month.** Stay up-to-date with the latest native capabilities. ## Real-World Success Stories See how development teams are using Insider plugins to build better apps faster: - **BusinessCards by snapAddy** ______________________________________________________________________ Digital business card app serving **nearly 1 million users**. Uses 10+ Insider plugins including NFC, Contacts, and Share Target. - **Tapni - Digital Business Card** ______________________________________________________________________ NFC-powered networking platform with **nearly 1 million users**. Built with the Capacitor NFC plugin. - **DHBW VS Student App** ______________________________________________________________________ Official student app for Baden-Württemberg Cooperative State University serving **thousands of students**. Powered by the NFC plugin. - **MyBodyTutor** ______________________________________________________________________ Weight loss and nutrition coaching app helping **thousands of users** achieve their goals. Built with File Compressor and SQLite plugins. ## What Makes Insiders Different? Capawesome Insiders offers exclusive access to professional Capacitor plugins that aren't available in the open-source distribution. These plugins are licensed under the [Capawesome Insider License](https://capawesome.io/insiders/license/#insider-license), allowing you to use them in commercial and non-commercial projects while keeping most of our ecosystem open source and free. **Built by recognized experts:** - Official **Ionic Developer Experts** with deep Capacitor knowledge - Recommended in the official Capacitor plugin documentation - Backed by an active community of **500+ Discord members** and **1,000+ GitHub stars** **Key benefits:** - **Immediate access** - Start using plugins the moment you subscribe - **No vendor lock-in** - Perpetual licenses available for one-time purchase - **Transparent development** - Open issue trackers and public roadmap - **Cancel anytime** - Monthly and annual subscription options ## How to Become an Insider Choose the plan that fits your needs and get instant access to all Insider plugins. [View Pricing](https://capawesome.io/insiders/pricing/index.md) [View Documentation](https://capawesome.io/insiders/getting-started/index.md) After subscribing, your license key is displayed immediately on the checkout page. You can access it anytime in the [Polar Customer Portal](https://polar.sh/capawesome-team/portal). # FAQ ## Access ### Where can I find my license key? You can find your license key on the [Polar Customer Portal](https://polar.sh/capawesome-team/portal). Just log in with the email address you used to subscribe to Capawesome and copy the license key from the "License Keys" section. ### May I share my license key with my team? Polar provides each subscriber with only one license key. We recommend that teams subscribe using a single team account and share the license key among the team members. However, it's NOT allowed to share the license key with external parties. ### What happens if I cancel my subscription? If you cancel your subscription, you will lose access to the private npm registry and the plugins that are part of Insiders. ## Billing and Payment ### How can I cancel my subscription? You can cancel your subscription at any time by visiting the [Polar Customer Portal](https://polar.sh/capawesome-team/portal). Just log in with the email address you used to subscribe to Capawesome and click on the "Unsubscribe" button. ### Do you offer discounts? Yes, we offer discounts for the following groups: - Annual subscriptions receive **2 months free**. - Educational institutions receive a **30% discount**. - Non-profit organizations receive a **30% discount**. - Licenses limited to a single Insider plugin receive a **50% discount**. If you belong to one of these groups, please contact us at [support@capawesome.io](mailto:support@capawesome.io) to request your discount. # Getting started with Insiders Welcome to Capawesome Insiders! You've joined **500+ development teams** who trust our plugins to build production apps. You now have access to 17+ professional Capacitor plugins that will save you weeks of development time. ## What You Get As an Insider, you get immediate access to: - **Full source code** for all 17+ Insider plugins (1M+ downloads/month) - **Complete documentation** with examples and guides - **Priority support** from official Ionic Developer Experts (tier-dependent) - **Regular updates** for new OS versions and features - **Breaking change support** and migration guides - **New plugins** added every other month [Browse all available plugins](https://capawesome.io/insiders/#available-plugins) ## Retrieve Your License Key After subscribing, your license key is displayed immediately on the checkout page. You can retrieve it anytime: 1. Visit the [Polar Customer Portal](https://polar.sh/capawesome-team/portal) 1. Log in with the email address used for your subscription 1. Copy your license key from the "License Keys" section **Keep your license key secure** - it provides access to the private npm registry. ## Quick Start Get started in under 2 minutes. Follow these steps to install your first Insider plugin: ### Step 1: Configure npm Registry Configure npm to use the Capawesome private registry: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` Replace the placeholder Replace `` with your actual license key from the [Polar Customer Portal](https://polar.sh/capawesome-team/portal). ### Step 2: Install a Plugin Install any Insider plugin using npm: ``` npm install @capawesome-team/ npx cap sync ``` **Example:** To install the NFC plugin: ``` npm install @capawesome-team/capacitor-nfc npx cap sync ``` ### Step 3: Import and Use Import the plugin in your code and start using it: ``` import { Nfc } from '@capawesome-team/capacitor-nfc'; // Start using the plugin const result = await Nfc.isSupported(); console.log('NFC supported:', result.isSupported); ``` That's it! Check each plugin's documentation for detailed API reference and examples. ## What's Next? - **Explore Plugins** ______________________________________________________________________ Browse the complete list of available Insider plugins and their capabilities. [View all plugins](https://capawesome.io/insiders/#available-plugins) - **Read Documentation** ______________________________________________________________________ Each plugin has comprehensive documentation with examples and API reference. [Plugin documentation](https://capawesome.io/plugins/index.md) - **Get Support** ______________________________________________________________________ Need help? Access priority support via GitHub, Discord, or Email. [Learn about support](https://capawesome.io/insiders/support/index.md) - **CI/CD Setup** ______________________________________________________________________ Set up Insider plugins in your CI/CD pipeline for automated builds. [View integrations](#cicd-configuration) ______________________________________________________________________ ## CI/CD Configuration Using Insider plugins in your CI/CD pipeline requires configuring the private npm registry with your license key. We provide step-by-step guides for popular platforms: - **GitHub Actions** ______________________________________________________________________ Configure GitHub Actions to access Insider plugins in your workflows. [View guide](https://capawesome.io/insiders/integrations/github-actions/index.md) - **GitLab CI/CD** ______________________________________________________________________ Set up GitLab CI/CD pipelines with Insider plugin access. [View guide](https://capawesome.io/insiders/integrations/gitlab-ci/index.md) - **Azure DevOps** ______________________________________________________________________ Configure Azure Pipelines for Insider plugin builds. [View guide](https://capawesome.io/insiders/integrations/azure-devops/index.md) - **Bitbucket Pipelines** ______________________________________________________________________ Enable Insider plugins in Bitbucket CI/CD. [View guide](https://capawesome.io/insiders/integrations/bitbucket-pipelines/index.md) - **Ionic Appflow** ______________________________________________________________________ Use Insider plugins with Ionic's cloud build service. [View guide](https://capawesome.io/insiders/integrations/ionic-appflow/index.md) - **Vercel** ______________________________________________________________________ Deploy apps using Insider plugins on Vercel. [View guide](https://capawesome.io/insiders/integrations/vercel/index.md) - **Cloudflare Pages** ______________________________________________________________________ Build and deploy with Insider plugins on Cloudflare. [View guide](https://capawesome.io/insiders/integrations/cloudflare-pages/index.md) - **AWS Amplify** ______________________________________________________________________ Configure AWS Amplify for Insider plugin builds. [View guide](https://capawesome.io/insiders/integrations/aws-amplify/index.md) - **Jenkins** ______________________________________________________________________ Set up Jenkins pipelines with Insider plugin access. [View guide](https://capawesome.io/insiders/integrations/jenkins/index.md) ______________________________________________________________________ ## Troubleshooting ### Common Issues **Authentication failed when installing plugins:** - Verify your license key is correct in the [Polar Customer Portal](https://polar.sh/capawesome-team/portal) - Ensure you've configured both npm registry settings (registry URL and auth token) - Check that your subscription is active **Plugin not found:** - Make sure you're using the correct package name: `@capawesome-team/` - Verify your license hasn't expired (for perpetual licenses) **Need more help?** Contact our support team at [support@capawesome.io](mailto:support@capawesome.io) or visit our [support page](https://capawesome.io/insiders/support/index.md) for priority assistance. # License Our Insider plugins are licensed under the [Capawesome Insider License](#insider-license). This license allows you to use the software for your commercial or non-commercial projects but you are NOT allowed to publish or sell the software or any derived products. ## Insider License ``` Capawesome Insider License Copyright (c) 2026, Genz IT Solutions GmbH. Permission is hereby granted to any person purchasing a subscription or receiving a copy of this software and associated documentation files (the "Software"), to use, copy, modify, merge, distribute, and sublicense the Software as provided. This does not include the rights to publish and/or sell copies of the Software, source code, or products derived from it. The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS". TO THE FULLEST EXTENT PERMITTED BY LAW, WE EXPRESSLY DISCLAIM ALL WARRANTIES, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF CERTAIN WARRANTIES OR THE LIMITATION OR EXCLUSION OF LIABILITY FOR INCIDENTAL OR CONSEQUENTIAL OR CERTAIN OTHER DAMAGES, SO THE ABOVE LIMITATION AND EXCLUSIONS MAY NOT APPLY TO YOU. IN SUCH CASES, OUR LIABILITY WILL BE LIMITED TO THE GREATEST EXTENT PERMITTED BY LAW. By subscribing and using this Software, the person represents and warrants that they will at all times comply with applicable laws in their download, use, modification, and other dealings with the Software. ``` ## Questions If you have any questions about the license, please contact us at [support@capawesome.io](mailto:support@capawesome.io). We are happy to help you with any questions you may have. # Pricing Choose the plan that works for you. All plans include access to 17+ professional Capacitor plugins, comprehensive documentation, and priority support. **Trusted by 500+ teams worldwide** — including Fortune 500 companies — powering apps with millions of users. **Subscribe for ongoing updates** or **purchase once for perpetual access**. Cancel anytime, no long-term contracts. ## Choose Your Tier Choosing the right sponsorship tier is important to ensure that you are entitled to use the plugins. We offer three sponsorship tiers - each for a different purpose. The decision diagram will help you find the right sponsorship tier. Decision diagram of Capawesome Insiders licenses After choosing your tier, decide whether you want to subscribe or purchase a perpetual license. ## Subscription Plans Get continuous updates, new plugins, and priority support. **Annual plans save 2 months** compared to monthly billing. Cancel anytime\ Instant access to all current and future plugins\ All updates included ### Individual You are a **solo developer** and want to use the Insider plugins for **personal projects**. **Benefits**: - Access to 17+ Insider plugins - Use in **1-3 commercial or non-commercial apps** - Priority support via GitHub and Discord - Continuous updates for new OS versions - Cancel anytime, no commitments [Subscribe for **$29 / month**](https://buy.polar.sh/polar_cl_glVUF4TuVG5nKJSWCJQGylnxuEmBgIn9hh8XD1ETL7m) ### Business You are an **organization with less than 100 employees** or you want to use the Insider plugins for **non-personal projects** or **unlimited apps**. **Benefits**: - Access to 17+ Insider plugins - Use in **unlimited commercial or non-commercial apps** - Priority support via GitHub, Discord, and Email - Continuous updates for new OS versions - Faster response times than Individual tier - Cancel anytime, no commitments [Subscribe for **$99 / month**](https://buy.polar.sh/polar_cl_dmeoigNqlLC2LKiTw3S4mI6QsAeEzNO7LH4JL2ZQuWB) ### Enterprise You are an **organization with 100 or more employees** and want to use the Insider plugins for commercial, non-commercial or internal projects. **Benefits**: - Access to 17+ Insider plugins - Use in **unlimited commercial or non-commercial apps** - Priority support via GitHub, Discord, and Email - Continuous updates for new OS versions - **Dedicated support channel** on Discord or Slack - Direct access to core maintainers - Cancel anytime, no commitments [Subscribe for **$299 / month**](https://buy.polar.sh/polar_cl_Z6Etz3VXHjsVHBU1I33agPPt5i3HCvnc7z2BW0vgJMm) ## Perpetual License Prefer a one-time purchase? Pay once and use the plugins forever. Includes **1 year of updates and support**. Keep the plugins forever, even after 1 year\ No recurring payments\ Updates and support included for 1 year only[1](#fn:1) ### Individual You are a **solo developer** and want to use the Insider plugins for **personal projects** (no client work). **Benefits**: - Perpetual access to 17+ Insider plugins - Use in **1-3 commercial or non-commercial apps** - 1 year of updates and new plugins - 1 year of priority support via GitHub and Discord [Purchase for **$599**](https://buy.polar.sh/polar_cl_FKCMHh25MNFtITwGWXWwJfjzvTlYryuL0STdO3Hfpig) ### Business You are an **organization with less than 100 employees** or you want to use the Insider plugins for **non-personal projects** or **unlimited apps**. **Benefits**: - Perpetual access to 17+ Insider plugins - Use in **unlimited commercial or non-commercial apps** - 1 year of updates and new plugins - 1 year of priority support via GitHub, Discord, and Email [Purchase for **$1,999**](https://buy.polar.sh/polar_cl_Oa0ISTScdsRvw3U86NuDS90NNL2obXrWfMVYg0bM982) ### Enterprise You are an **organization with 100 or more employees** and want to use the Insider plugins for commercial, non-commercial or internal projects. **Benefits**: - Perpetual access to 17+ Insider plugins - Use in **unlimited commercial or non-commercial apps** - 1 year of updates and new plugins - 1 year of priority support via GitHub, Discord, and Email - 1 year of dedicated support channel on Discord or Slack [Purchase for **$5,999**](https://buy.polar.sh/polar_cl_dNDyCqTL4kUW0au0XHoFLcEM1SRXUrwhJiEIY1Lr8S8) ______________________________________________________________________ ## Frequently Asked Questions ### What payment methods do you accept? We accept all major credit cards through our payment processor Polar. Annual subscriptions and perpetual licenses can also be paid via invoice for the Enterprise tier. ### Can I upgrade or downgrade my plan? Yes, you can upgrade or downgrade your subscription at any time. Changes take effect immediately, and we'll prorate the difference. ### What happens when my subscription ends? If you cancel your subscription, you'll lose access to the private npm registry and won't receive updates. Your apps will continue to work with the last version you had installed. ### Do you offer educational or non-profit discounts? Yes! Educational institutions and non-profit organizations receive a **30% discount**. [Contact us](mailto:support@capawesome.io) with proof of status to request your discount. ### Can I try before buying? While we don't offer a free trial, we do offer a **14-day money-back guarantee** on all plans. If you're not satisfied, contact us for a full refund. ### What if I only need one plugin? We offer **50% off** for licenses limited to a single Insider plugin. [Contact us](mailto:support@capawesome.io) to discuss your needs. ______________________________________________________________________ ## What Our Customers Say > "Saved us 4 weeks of development time" > > **Michael Wolz** — Head of Development, snapADDY GmbH **Join 500+ development teams** building better apps faster with Capawesome Insiders. ______________________________________________________________________ 1. After one year, you keep perpetual access to the plugins but won't receive updates or support unless you renew or subscribe. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Support Get help from **official Ionic Developer Experts** and join a community of **500+ Discord members**. Average response time: **1 day**. ## Subscription support If you have questions about Insiders or are considering subscribing, please contact us at [support@capawesome.io](mailto:support@capawesome.io). We are happy to help you with any questions you may have. ## Technical support We are committed to providing technical support, ensuring all interactions remain transparent and accessible. Our primary channels for technical inquiries are our discussion boards and our issue trackers which are open to the entire community. **Our commitment:** With **1,000+ GitHub stars** and an active community, you're getting support backed by real-world experience from apps serving millions of users. ### Discussion boards Our discussion boards are the best place to ask questions, share knowledge, and provide feedback. They are open to the entire community and are a great place to get help from other developers who may have encountered similar issues. You can find our discussion boards on GitHub in our plugin repositories: - [capawesome-team/capacitor-firebase](https://github.com/capawesome-team/capacitor-firebase/discussions) - [capawesome-team/capacitor-plugins](https://github.com/capawesome-team/capacitor-plugins/discussions) - [capawesome-team/capacitor-mlkit](https://github.com/capawesome-team/capacitor-mlkit/discussions) Please make sure to search the discussion boards before posting a new question. This will help us keep the discussion boards organized and make it easier for you to find the information you need. ### Issue trackers For bug reports and feature requests, please use the issue tracker in the respective plugin repository: - [capawesome-team/capacitor-firebase](https://github.com/capawesome-team/capacitor-firebase/issues) - [capawesome-team/capacitor-plugins](https://github.com/capawesome-team/capacitor-plugins/issues) - [capawesome-team/capacitor-mlkit](https://github.com/capawesome-team/capacitor-mlkit/issues) This transparent approach ensures that solutions can benefit the entire community and feature requests can get upvoted by the community which will affect the speed of its development. It also allows us to keep track of the progress of each issue and communicate with you effectively. ### Prioritized support Subscribers at the "Business Insider" tier or higher are eligible for prioritized support. While we cannot guarantee immediate resolution, as some problems are harder to fix than others, it ensures that your issues are addressed with urgency. Please contact us at [support@capawesome.io](mailto:support@capawesome.io) if you want to request prioritized support. # AWS Amplify You can use private Capawesome npm packages within your [AWS Amplify](https://aws.amazon.com/amplify/) deployments by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create an AWS Amplify Environment Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your AWS Amplify app as an environment variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Environment variables](https://docs.aws.amazon.com/amplify/latest/userguide/setting-env-vars.html). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Configuration ### Configure the amplify.yml file Create or update the `amplify.yml` file in the root of your project and add the following commands to the `preBuild` phase: ``` - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during AWS Amplify builds. ### Example Here's a complete example of an `amplify.yml` file for a project using private Capawesome packages: ``` version: 1 frontend: phases: preBuild: commands: - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc - npm ci build: commands: - npm run build artifacts: baseDirectory: dist files: - '**/*' ``` # Azure DevOps You can use private Capawesome npm packages within your [Azure Pipelines](https://azure.microsoft.com/products/devops/pipelines) by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create an Azure Pipeline Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Azure Pipeline as a secret variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Set secret variables](https://learn.microsoft.com/en-us/azure/devops/pipelines/process/variables?view=azure-devops&tabs=yaml%2Cbatch#secret-variables). Make sure to mark the variable as secret. Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Pipeline Configuration ### Configure the npm registry Add the following script steps to your Azure Pipeline **before** installing npm dependencies: ``` - script: echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc displayName: Configure Capawesome npm registry - script: echo "//npm.registry.capawesome.io/:_authToken=$(CAPAWESOME_NPM_REGISTRY_TOKEN)" >> .npmrc displayName: Authenticate with Capawesome registry ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Azure DevOps builds. ### Example Here's a complete example of an `azure-pipelines.yml` file that builds an Ionic app using private Capawesome packages: ``` trigger: - main pool: vmImage: ubuntu-latest variables: - name: nodeVersion value: '20.x' jobs: - job: Build displayName: Build App steps: - checkout: self displayName: Checkout Repository - task: UseNode@1 inputs: version: $(nodeVersion) displayName: Setup Node.js $(nodeVersion) - script: echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc displayName: Configure Capawesome npm registry - script: echo "//npm.registry.capawesome.io/:_authToken=$(CAPAWESOME_NPM_REGISTRY_TOKEN)" >> .npmrc displayName: Authenticate with Capawesome registry - script: npm ci displayName: Install Dependencies - script: npx cap sync displayName: Sync Capacitor - script: npm run build displayName: Build App - task: PublishBuildArtifacts@1 inputs: pathtoPublish: 'dist' artifactName: 'build-output' displayName: Publish Build Artifacts ``` # Bitbucket Pipelines You can use private Capawesome npm packages within your [Bitbucket Pipelines](https://www.atlassian.com/software/bitbucket/features/pipelines) builds by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a Bitbucket Repository Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Bitbucket repository as a secured repository variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Variables and secrets](https://support.atlassian.com/bitbucket-cloud/docs/variables-and-secrets/). Make sure to mark the variable as secured. Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Pipeline Configuration ### Configure the npm registry Add the following script to your Bitbucket Pipeline **before** installing npm dependencies: ``` - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Bitbucket Pipelines builds. ### Example Here's a complete example of a `bitbucket-pipelines.yml` file that builds an Ionic app using private Capawesome packages: ``` image: node:20 pipelines: default: - step: name: Build App caches: - node script: - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc - npm ci - npx cap sync - npm run build artifacts: - dist/** ``` # Capawesome Cloud You can use private Capawesome npm packages within your [Capawesome Cloud](https://cloud.capawesome.io/) builds by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a Capawesome Cloud Environment Secret [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Capawesome Cloud app as an environment secret with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Environment secrets](https://capawesome.io/cloud/native-builds/environments/#secrets). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Configuration ### Configure the npm registry Create a new file named `.npmrc` in the root of your project and add the following content: ``` @capawesome-team:registry=https://npm.registry.capawesome.io //npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN} ``` This file will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Capawesome Cloud builds. ### Managing multiple environments In the event that your local development no longer works with your new `.npmrc` file, you can create a separate `.npmrc.capawesome` file specifically for Capawesome Cloud. Just navigate to your **Capawesome Cloud app > Settings > Environment variables**, add a new environment variable named `NPM_CONFIG_USERCONFIG` and set its value to `/tmp/capawesome/repo/.npmrc.capawesome`. If your `.npmrc.capawesome` file is not in your project's root directory, adjust this path accordingly. # Cloudflare Pages You can use private Capawesome npm packages within your [Cloudflare Pages](https://pages.cloudflare.com/) deployments by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a Cloudflare Pages Environment Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Cloudflare Pages project as an environment variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Environment variables](https://developers.cloudflare.com/pages/configuration/build-configuration/#environment-variables). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Configuration ### Configure the npm registry Create a new file named `.npmrc` in the root of your project and add the following content: ``` @capawesome-team:registry=https://npm.registry.capawesome.io //npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN} ``` This file will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Cloudflare Pages builds. ### Managing multiple environments In the event that your local development no longer works with your new `.npmrc` file, you can create a separate `.npmrc.pages` file specifically for Cloudflare Pages. Just navigate to your **Pages project > Settings > Environment variables**, add a new environment variable named `NPM_CONFIG_USERCONFIG` and set its value to `/opt/buildhome/repo/.npmrc.pages`. If your `.npmrc.pages` file is not in your project's root directory, adjust this path accordingly. # GitHub Actions You can use private Capawesome npm packages within your [GitHub Actions](https://github.com/features/actions) workflows by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a GitHub Secret [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your GitHub repository as an encrypted secret with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Creating encrypted secrets for a repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions#creating-encrypted-secrets-for-a-repository). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Workflow Configuration ### Configure the npm registry Add the following step to your GitHub Actions workflow **before** installing npm dependencies: ``` - name: Configure the Capawesome npm registry run: | echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc echo "//npm.registry.capawesome.io/:_authToken=${{ secrets.CAPAWESOME_NPM_REGISTRY_TOKEN }}" >> .npmrc ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during GitHub Actions builds. ### Example Here's a complete example of a GitHub Actions workflow that builds an Ionic app using private Capawesome packages: ``` name: Build App on: push: branches: - main pull_request: branches: - main jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - name: Set up Node.js 20 uses: actions/setup-node@v4 with: node-version: 20 - name: Configure the Capawesome npm registry run: | echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc echo "//npm.registry.capawesome.io/:_authToken=${{ secrets.CAPAWESOME_NPM_REGISTRY_TOKEN }}" >> .npmrc - name: Install Dependencies run: npm ci - name: Sync Capacitor run: npx cap sync - name: Build web assets run: npm run build ``` This workflow will authenticate with the Capawesome npm registry and install any private packages specified in your `package.json` file. # GitLab CI/CD You can use private Capawesome npm packages within your [GitLab CI/CD](https://docs.gitlab.com/ci/) pipelines by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a GitLab CI/CD Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your GitLab project as a CI/CD variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [CI/CD variables](https://docs.gitlab.com/ci/variables/). Make sure to mark the variable as masked and protected. Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Pipeline Configuration ### Configure the npm registry Add the following script to your GitLab CI/CD pipeline **before** installing npm dependencies: ``` before_script: - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during GitLab CI/CD builds. ### Example Here's a complete example of a GitLab CI/CD pipeline that builds an Ionic app using private Capawesome packages: ``` stages: - build build-app: stage: build image: node:20 before_script: - echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc - echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc - npm ci script: - npx cap sync - npm run build artifacts: paths: - dist/ expire_in: 1 hour rules: - if: $CI_COMMIT_BRANCH == "main" - if: $CI_PIPELINE_SOURCE == "merge_request_event" ``` This pipeline will authenticate with the Capawesome npm registry and install any private packages specified in your `package.json` file. # Ionic Appflow You can use private Capawesome npm packages within your [Ionic Appflow](https://ionic.io/appflow) builds by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create an Ionic Appflow Environment Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Ionic Appflow environment as an encrypted secret with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Using private npm modules](https://ionic.io/docs/appflow/cookbook/private_npm_modules). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Configuration ### Configure the npm registry Create a new file named `.npmrc` in the root of your project and add the following content: ``` @capawesome-team:registry=https://npm.registry.capawesome.io //npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN} ``` This file will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Ionic Appflow builds. # Jenkins You can use private Capawesome npm packages within your [Jenkins](https://www.jenkins.io/) pipelines by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create Jenkins Credentials [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Jenkins instance as a secret text credential with the ID `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Using credentials](https://www.jenkins.io/doc/book/using/using-credentials/). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Pipeline Configuration ### Configure the npm registry Add the following script to your Jenkins pipeline **before** installing npm dependencies: ``` withCredentials([string(credentialsId: 'CAPAWESOME_NPM_REGISTRY_TOKEN', variable: 'CAPAWESOME_NPM_REGISTRY_TOKEN')]) { sh ''' echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc ''' } ``` This will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Jenkins builds. ### Example Here's a complete example of a Jenkins pipeline that builds an Ionic app using private Capawesome packages: ``` pipeline { agent any environment { NODE_VERSION = '20' } stages { stage('Setup') { steps { // Install Node.js sh ''' curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | sudo -E bash - sudo apt-get install -y nodejs ''' } } stage('Configure Registry') { steps { withCredentials([string(credentialsId: 'CAPAWESOME_NPM_REGISTRY_TOKEN', variable: 'CAPAWESOME_NPM_REGISTRY_TOKEN')]) { sh ''' echo "@capawesome-team:registry=https://npm.registry.capawesome.io" >> .npmrc echo "//npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN}" >> .npmrc ''' } } } stage('Install Dependencies') { steps { sh 'npm ci' } } stage('Build') { steps { sh ''' npx cap sync npm run build ''' } } } post { always { // Clean up .npmrc file sh 'rm -f .npmrc' } } } ``` # Vercel You can use private Capawesome npm packages within your [Vercel](https://vercel.com/) deployments by configuring the Capawesome npm registry. This guide shows you how to set up authentication to access the private packages from the Capawesome Insiders program. ## Preparation ### Create a Vercel Environment Variable [Get your license key](https://capawesome.io/insiders/faq/#where-can-i-find-my-license-key) and add it to your Vercel project as an environment variable with the name `CAPAWESOME_NPM_REGISTRY_TOKEN` as described in [Environment Variables](https://vercel.com/docs/projects/environment-variables). Keep your license key secure Never commit your license key directly to your repository and always use environment variables to store sensitive information like authentication tokens. ## Configuration ### Configure the npm registry Create a `.npmrc` file in the root of your project with the following content: ``` @capawesome-team:registry=https://npm.registry.capawesome.io //npm.registry.capawesome.io/:_authToken=${CAPAWESOME_NPM_REGISTRY_TOKEN} ``` This file will automatically configure npm to use the Capawesome registry for `@capawesome-team` packages during Vercel builds. # Consulting ______________________________________________________________________ title: Capacitor Consulting Services description: Capawesome consulting: Capacitor app development, custom plugins, code review, and CI/CD setup. Get help from Ionic Developer Experts for your mobile projects. ______________________________________________________________________ # Consulting We offer various services around Capacitor. Whether you need help developing a mobile app, creating a custom Capacitor plugin, improving your code or setting up CI/CD. ## Testimonials > Our Capacitor app had several unique requirements for running operations in the background. The Capawesome team was able to quickly understand our needs and crafted a customized Capacitor plugin that exceeded our expectations. -- Christofer Huber, CTO at [WEBPUNKS GmbH](https://www.webpunks.at/) > We had to use an Android and iOS SDK in our Capacitor app, for which no plugin existed. Capawesome was able to create a fully functional Capacitor plugin for us within a few days. The communication was excellent and the plugin works perfectly. -- Daniel Ehrhardt, CEO at [Codext GmbH](https://codext.de/) ## Services - **Capacitor Apps** ______________________________________________________________________ You need help developing a mobile app with Capacitor? We can help you with the architecture, development, deployment and maintenance of your app. - **Capacitor Plugins** ______________________________________________________________________ You need a custom Capacitor plugin for your app? We develop individual plugins for your needs and help you integrate them into your app. - **Collaborative Support** ______________________________________________________________________ We can work alongside your team, offering specialized support to enhance your project's development. Get help with architecture, code reviews, debugging, and more. - **CI/CD** ______________________________________________________________________ We help you set up Continuous Integration (CI) and Continuous Deployment (CD) for your app. Automatically deploy your app to the app stores. ## Contact us We'd love to hear from you! Just send us a short email with the following information: - Your name and company - A brief description of your project - The services you are interested in - Any other relevant information We will get back to you within 24 hours to schedule a first meeting. [Contact us](mailto:support@capawesome.io) # Blog # Blog # Capawesome December 2024 Update The Capawesome December update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). There are also some news from the community. Let's take a look at the most important changes. ## CLI ### `login` command no longer creates tokens Until now, the `login` command created a new token every time you logged in using email and password. However, tokens were originally only intended for the API and CI/CD. To avoid confusion, the `login` command no longer creates tokens. Instead, a session is created, which is automatically deleted after 30 days of inactivity. ## Cloud ### Unsubscribe from notifications You can now unsubscribe from notifications via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/notifications). Simply navigate to the `Notifications` page and select the notifications you want to subscribe to or unsubscribe from. \[ \](/assets/videos/posts/cloud-notifications-subscriptions.mp4) ### Manage sessions You can now manage your sessions via the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/sessions). Simply navigate to the `Sessions` page and view all your active sessions. You can also revoke sessions if needed. \[ \](/assets/videos/posts/20241231_cloud-sessions.mp4) ## Plugins ### Android Foreground Service The [Android Foreground Service](https://capawesome.io/plugins/android-foreground-service/index.md) plugin now has a new `serviceType` option that allows you to specify the type of the foreground service: ``` import { ForegroundService, ServiceType } from '@capawesome-team/capacitor-android-foreground-service'; const startForegroundService = async () => { await ForegroundService.startForegroundService({ id: 1, title: 'Recording', body: 'Recording audio in the background', smallIcon: 'ic_stat_icon_config_sample', serviceType: ServiceType.Microphone }); }; ``` For now, only two service types are supported. Feel free to open an issue if you are missing a specific service type. ### App Shortcuts We have published a new [App Shortcuts](https://capawesome.io/plugins/app-shortcuts/index.md) plugin that allows you to manage app shortcuts and quick actions on Android and iOS. The plugin can be installed via the public npm registry: ``` npm install @capawesome/capacitor-app-shortcuts ``` Here is an example of how to use the plugin: ``` import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const set = async () => { await AppShortcuts.set({ shortcuts: [ { id: 'feedback', title: 'Feedback', } ], }); }; ``` | Android | iOS | | ------- | --- | | | | ### Firebase Analytics The [Firebase Analytics](https://capawesome.io/plugins/firebase/analytics/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase App The [Firebase App](https://capawesome.io/plugins/firebase/app/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase App Check The [Firebase App Check](https://capawesome.io/plugins/firebase/app-check/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase Authentication The [Firebase Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) plugin received multiple updates this month. ##### Firebase JS SDK 11 The plugin now supports the Firebase JS SDK 11. ##### Facebook iOS SDK The [Facebook iOS SDK](https://github.com/facebook/facebook-ios-sdk) was updated to version 17.1.0. ##### New `idTokenChange` listener The plugin now has a new `idTokenChange` listener that allows you to listen for changes to the ID token: ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const addListener = async () => { FirebaseAuthentication.addListener('idTokenChange', async (event) => { console.log(event); }); }; ``` ##### New `verifyBeforeUpdateEmail` method The plugin now has a new `verifyBeforeUpdateEmail` method that allows you to verify the email before updating it: ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const verifyBeforeUpdateEmail = async () => { await FirebaseAuthentication.verifyBeforeUpdateEmail({ newEmail: 'mail@example.com', actionCodeSettings: { url: 'https://www.example.com/cart?email=user@example.com&cartId=123', iOS: { bundleId: 'com.example.ios' }, android: { packageName: 'com.example.android', installApp: true, minimumVersion: '12' }, handleCodeInApp: true } }); }; ``` ### Firebase Cloud Firestore The [Firebase Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase Cloud Functions The [Firebase Cloud Functions](https://capawesome.io/plugins/firebase/cloud-functions/index.md) plugin received multiple updates this month. ##### Firebase JS SDK 11 The plugin now supports the Firebase JS SDK 11. ##### New `regionOrCustomDomain` option The `UseEmulatorOptions` interface now has a new `regionOrCustomDomain` option that allows you to specify the region or custom domain for the emulator: ``` import { FirebaseFunctions } from '@capacitor-firebase/functions'; const useEmulator = async () => { await FirebaseFunctions.useEmulator({ host: '10.0.2.2', port: 9001, regionOrCustomDomain: 'us-central1' }); }; ``` ### Firebase Cloud Messaging The [Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase Cloud Storage The [Firebase Cloud Storage](https://capawesome.io/plugins/firebase/cloud-storage/index.md) plugin now supports the Firebase JS SDK 11. ### Firebase Performance Monitoring The [Firebase Performance Monitoring](https://capawesome.io/plugins/firebase/performance-monitoring/index.md) plugin received multiple updates this month. ##### Firebase JS SDK 11 The plugin plugin now supports the Firebase JS SDK 11. ##### New methods The plugin got a few new methods that allow you to put and get attributes and metrics for a trace: ``` import { FirebasePerformance } from '@capacitor-firebase/performance'; const putAttribute = async () => { await FirebasePerformance.putAttribute({ traceName: 'test_trace', attribute: 'user_id', value: '123', }); }; const getAttribute = async () => { const result = await FirebasePerformance.getAttribute({ traceName: 'test_trace', attribute: 'user_id', }); return result.attributes; }; const getAttributes = async () => { const result = await FirebasePerformance.getAttributes({ traceName: 'test_trace' }); return result.attributes; }; const removeAttribute = async () => { await FirebasePerformance.removeAttribute({ traceName: 'test_trace', attribute: 'user_id', }); }; const putMetric = async () => { await FirebasePerformance.putMetric({ traceName: 'test_trace', metricName: 'item_cache_hit', num: 1, }); }; const getMetric = async () => { const result = await FirebasePerformance.getMetric({ traceName: 'test_trace', metricName: 'item_cache_hit', }); return result.value; }; const record = async () => { await FirebasePerformance.record({ traceName: 'test_trace', startTime: Date.now(), duration: 1000, options: { metrics: { item_cache_hit: 1, }, attributes: { user_id: '123', }, }, }); }; ``` ### Firebase Remote Config The [Firebase Remote Config](https://capawesome.io/plugins/firebase/remote-config/index.md) plugin now supports the Firebase JS SDK 11. ### Live Update The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin received multiple updates this month. ##### New `getCurrentBundle()` and `getNextBundle()` methods The plugin now has new [`getCurrentBundle()`](https://capawesome.io/plugins/live-update/#getcurrentbundle) and [`getNextBundle()`](https://capawesome.io/plugins/live-update/#getnextbundle) methods that allow you to get the current and next bundle: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; /** * Get the bundle identifier of the current bundle. * The current bundle is the bundle that is currently used by the app. */ const getCurrentBundle = async () => { const { bundleId } = await LiveUpdate.getCurrentBundle(); return bundleId; }; /** * Get the bundle identifier of the next bundle. * The next bundle is the bundle that will be used after calling `reload()` or restarting the app. */ const getNextBundle = async () => { const { bundleId } = await LiveUpdate.getNextBundle(); return bundleId; }; ``` This change deprecated the [`getBundle()`](https://capawesome.io/plugins/live-update/#getbundle) method. ##### New `setNextBundle(...)` method The plugin now has a new [`setNextBundle(...)`](https://capawesome.io/plugins/live-update/#setnextbundle) method that allows you to set the next bundle: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; /** * Set the next bundle. * The next bundle is the bundle that will be used after calling `reload()` or restarting the app. */ const setNextBundle = async () => { await LiveUpdate.setNextBundle({ bundleId: '4b678425-fe57-485d-a8ff-a08af915bd29', }); }; ``` This change deprecated the [`setBundle(...)`](https://capawesome.io/plugins/live-update/#setbundle) method. ##### New `channel` property The [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) and [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) methods now have a new `channel` property that allows you to specify the name of the channel where the latest bundle is fetched from: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; /** * Fetch the latest bundle from the specified channel. */ const fetchLatestBundle = async () => { const result = await LiveUpdate.fetchLatestBundle({ channel: 'production-5', }); return result; }; /** * Sync the latest bundle from the specified channel. */ const sync = async () => { const result = await LiveUpdate.sync({ channel: 'production-5', }); return result; }; ``` ##### New `FetchLatestBundleResult` properties The [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) method now returns a `artifactType` and `downloadUrl` property: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const fetchLatestBundle = async () => { const result = await LiveUpdate.fetchLatestBundle(); return { artifactType: result.artifactType, bundleId: result.bundleId, downloadUrl: result.downloadUrl, }; }; ``` ### NFC The [NFC](https://capawesome.io/plugins/nfc/index.md) plugin received multiple updates this month. ##### New `manufacturerCode` property The plugin is now also able to read the IC Manufacturer Code for ISO15693 tags: ``` import { Nfc, NfcTagTechType } from '@capawesome-team/capacitor-nfc'; const readManufacturerCode = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { // Stop the NFC scan session await Nfc.stopScanSession(); // Return the IC Manufacturer Code resolve(event.nfcTag.manufacturerCode); }); // Start the NFC scan session void Nfc.startScanSession(); }); }; ``` ##### HCE support The plugin now supports [Host Card Emulation (HCE)](https://developer.android.com/develop/connectivity/nfc/hce) on Android. This allows you to emulate an NFC tag on your device and respond to commands from an NFC reader. Here is an example of how to use this feature: ``` import { Nfc } from '@capawesome-team/capacitor-nfc'; const addListener = async () => { Nfc.addListener('commandReceived', async (event) => { // Do something with the received command console.log(event.data); // Respond to the command await respond({ data: [...] }); }); }; ``` ##### 0x24 command support The plugin now supports the Write Multiple Blocks command (0x24 command code), as defined in the ISO 15693-3 specification, also on iOS: ``` import { Nfc, NfcTagTechType } from '@capawesome-team/capacitor-nfc'; const writeMultipleBlocks = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { // Write multiple blocks to the NFC tag await Nfc.transceive({ techType: NfcTagTechType.NfcV, data: [...], iso15693RequestFlags: [...], iso15693CommandCode: 0x24 }); // Stop the NFC scan session await Nfc.stopScanSession(); resolve(); }); // Start the NFC scan session void Nfc.startScanSession(); }); }; ``` ### Printer The [Printer](https://capawesome.io/plugins/printer/index.md) plugin now also supports the web, at least with the `printWebView` method: ``` import { Printer } from '@capawesome-team/capacitor-printer'; const print = async () => { await Printer.printWebView({ content: '

Hello, World!

', }); }; ``` You can also use a print style sheet to customize the print output: ``` @media print { h1 { color: red; } } ``` For more information, check out the [mdn web docs](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Printing). ### Speech Recognition We have published a new [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin that allows you to transcribe speech to text. The plugin supports Android, iOS, and the web. It comes with a simple API that allows you to start and stop the recognition process: ``` import { SpeechRecognition } from '@capawesome-team/capacitor-speech-recognition'; const startListening = async () => { // Print the recognized text to the console await SpeechRecognition.addListener('result', (event) => { console.log('Result:', event.result); }); // Start listening for speech await SpeechRecognition.startListening({ language: 'en-US', silenceThreshold: 2000, }); }; ``` Besides that, there are a few special features like the ability to set a silence threshold. The plugin is now available to all [Capawesome Insiders](https://capawesome.io/insiders/index.md). ### Speech Synthesis We have also published a new [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/index.md) plugin that allows you to convert text to speech. The plugin supports Android, iOS, and the web. You can customize the voice, pitch, and rate of the speech. Here is an example of how to use the plugin: ``` import { SpeechSynthesis, QueueStrategy } from '@capawesome-team/capacitor-speech-synthesis'; const speak = async () => { // Print every spoken word to the console await SpeechSynthesis.addListener('boundary', (event) => { console.log('boundary', event); }); // Speak the text await SpeechSynthesis.speak({ language: 'en-US', pitch: 1.0, queueStrategy: QueueStrategy.Add, rate: 1.0, text: 'Hello, World!', voiceId: 'com.apple.ttsbundle.Samantha-compact', volume: 1.0, }); }; ``` The plugin is now available to all [Capawesome Insiders](https://capawesome.io/insiders/index.md). ### Torch The [Torch](https://capawesome.io/plugins/torch/index.md) plugin now also supports the web using the [Media Capture and Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Media_Capture_and_Streams_API): ``` import { Torch } from '@capawesome/capacitor-torch'; const enable = async () => { await Torch.enable(); }; ``` ### Wifi The [Wifi](https://capawesome.io/plugins/wifi/index.md) plugin got a new configuration option that allows you to force the usage of the deprecated `WifiManager` API on Android as there have been several reports that the new `WifiNetworkSpecifier` API is not yet working as expected on some devices. You can enable the `useWifiManager` option in the Capacitor configuration file: ``` { "plugins": { "Wifi": { "useWifiManager": true } } } ``` ## Community ### Build a mobile app with Qwik and Capacitor [@srapport](https://github.com/srapport) published the official [Qwik for iOS and Android](https://qwik.dev/docs/guides/capacitor/) guide this month. The guide explains how to create a mobile app for Android and iOS with Qwik using Capacitor and highlights what you need to pay attention to. This also includes a short introduction to the Capacitor Live Update plugin from Capawesome. Check out the guide [here](https://qwik.dev/docs/guides/capacitor/). # Capawesome November 2024 Update The Capawesome November update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Automatically delete old bundles There are now two new options available for automatically deleting old bundles so that you don't have to worry about the storage limit: 1. **Set a bundle limit**: You can set a limit for the number of bundles that can be assigned to a channel. If this limit is reached, the oldest bundle will be deleted automatically. For example, this is how you can set a bundle limit of 5 using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) when creating a channel: ``` npx @capawesome/cli apps:channels:create --bundle-limit 5 ``` 1. **Set an expiration date**: You can set an expiration date for a bundle. If this date is reached, the bundle will be deleted automatically. For example, this is how you can automatically delete a bundle after 30 days using the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) when creating a bundle: ``` npx @capawesome/cli apps:bundles:create --expires-in-days 30 ``` Both options are also available via the [Capawesome Cloud Console](https://console.cloud.capawesome.io). ### Delete multiple items at once You can now finally delete multiple Bundles, Channels, Devices and Tokens at once via the [Capawesome Cloud Console](https://console.cloud.capawesome.io). Simply select the items you want to delete and click the `Delete` button. \[ \](/assets/videos/posts/cloud-delete-multiple-items.mp4) ## Plugins ### Android Foreground Service The [Android Foreground Service](https://capawesome.io/plugins/android-foreground-service/index.md) plugin now has three new methods that allow you to update the foreground service notification and create/delete notification channels: ``` import { ForegroundService, Importance } from '@capawesome-team/capacitor-android-foreground-service'; /** * Update the foreground service notification. */ const updateForegroundService = async () => { await ForegroundService.updateForegroundService({ id: 1, title: 'Title', body: 'Body', smallIcon: 'ic_stat_icon_config_sample', }); }; /** * Create a notification channel. */ const createNotificationChannel = async () => { await ForegroundService.createNotificationChannel({ id: 'default', name: 'Default', description: 'Default channel', importance: Importance.Default, }); }; /** * Delete a notification channel. */ const deleteNotificationChannel = async () => { await ForegroundService.deleteNotificationChannel({ id: 'default', }); }; ``` Thanks to [@xsorifc28](https://github.com/xsorifc28) and [@ebarooni](https://github.com/ebarooni) for the contributions! ### App Review Say hi to our brand new [App Review](https://capawesome.io/plugins/app-review/index.md) plugin for Android and iOS! This plugin not only allows you to request in-app reviews but also to open the app store page of your app and, if possible, open the dialog to leave a review. Here is an example of how you can use this plugin: ``` import { AppReview } from '@capawesome/capacitor-app-review'; const openAppStore = async () => { await AppReview.openAppStore({ appId: '123456789' }); }; const requestReview = async () => { await AppReview.requestReview(); }; ``` Thanks to [@mertyldrr](https://github.com/mertyldrr) for the contribution! ### App Update The [App Update](https://capawesome.io/plugins/app-update/index.md) plugin now has a new `appId` property that allows you to specify the app ID of your app: ``` import { AppUpdate } from '@capawesome/capacitor-app-update'; const openAppStore = async () => { await AppUpdate.openAppStore({ appId: '123456789' }); }; ``` This solves the problem where the app was not always found in the App Store and speeds up the entire plugin call. ### File Picker The [File Picker](https://capawesome.io/plugins/file-picker/index.md) plugin now has a new `pickDirectory` method that allows you to pick a directory: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const pickDirectory = async () => { const result = await FilePicker.pickDirectory(); console.log('Picked directory path:', result.path); }; ``` In combination with the [Filesystem](https://capacitorjs.com/docs/apis/filesystem) plugin, you can now easily access multiple files in a directory at once without having to select each file individually. Thanks to [@ebarooni](https://github.com/ebarooni) for the contribution! ### ML Kit Barcode Scanning The [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin got two new features this month. ##### Compatibility with Torch Plugin The plugin is now compatible with the [Torch](https://capawesome.io/plugins/torch/index.md) plugin which means that you no longer have to use the deprecated torch methods of the ML Kit Barcode Scanning plugin. ##### Barcode Value Types The plugin now returns the value types of the scanned barcodes. The following value types are available: - [`BarcodeCalendarEvent`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodecalendarevent) - [`BarcodeContactInfo`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodecontactinfo) - [`BarcodeDriverLicense`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodedriverlicense) - [`BarcodeEmail`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodeemail) - [`BarcodeGeoPoint`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodegeopoint) - [`BarcodePhone`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodephone) - [`BarcodeSms`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodesms) - [`BarcodeUrlBookmark`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodeurlbookmark) - [`BarcodeWifi`](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodewifi) Thanks to [@mertyldrr](https://github.com/mertyldrr) for the contribution! ### NFC The [NFC](https://capawesome.io/plugins/nfc/index.md) plugin now has a new `compatibilityMode` flag that can help reading some special NDEF tags: ``` import { Nfc } from '@capawesome-team/capacitor-nfc'; const startScanSession = async () => { await Nfc.startScanSession({ compatibilityMode: true }); }; ``` Please be aware that this mode only supports reading NDEF tags and is only available on iOS. ### Torch The [Torch](https://capawesome.io/plugins/torch/index.md) plugin has also been updated to be compatible with the [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin. This means that you can now use the Torch plugin to toggle the flashlight even when scanning barcodes. ### Wifi The [Wifi](https://capawesome.io/plugins/wifi/index.md) plugin now returns the security types and signal strength for scanned networks: ``` import { Wifi } from '@capawesome-team/capacitor-wifi'; const scan = async () => { await Wifi.addListener('networksScanned', (event) => { const firstNetwork = event.networks[0]; console.log('First network security types:', firstNetwork.securityTypes); console.log('First network signal strength:', firstNetwork.rssi); }); await Wifi.startScan(); }; ``` # Capawesome April 2025 Update The Capawesome April update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## CLI ### Login via browser The [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) now supports logging in via the browser. This feature allows you to authenticate your CLI session using a web-based login flow, making it easier to manage your credentials and access the Capawesome Cloud. To get started, simply run the following command in your terminal: ``` npx @capawesome/cli login ``` This will ask you how you would like to authenticate. Select "Login with a web browser" and follow the instructions in your terminal. The CLI will generate a one-time code that you can use to authenticate in your browser. ``` ✔ How would you like to authenticate Capawesome CLI? Login with a web browser ╭──────────────────────────────────────╮ │ │ │ Copy your one-time code: VPD2-VCH4 │ │ │ ╰──────────────────────────────────────╯ ✔ Select Yes to continue in your browser or No to cancel the authentication. Yes ◐ Opening browser... ◐ Waiting for authentication... ◐ Signing in... ✔ Successfully signed in. ``` ## Cloud ### Account linking You can now set a password for your Capawesome Cloud account if you previously signed up using a third-party provider (e.g., GitHub, or GitLab). This feature also allows you to link one or more third-party accounts to your Capawesome Cloud account. This is useful if you want to use multiple authentication methods or if you want to switch from one provider to another. Capawesome Cloud Identities ### Open Source Program We have launched the [Capawesome Cloud Open Source Program](https://capawesome.io/blog/capawesome-cloud-open-source-program/index.md) to support open source projects and developers. This program provides free access to Capawesome Cloud for open source projects, allowing you to use our platform for Over-the-Air (OTA) updates. You can find more information about the program and how to apply in our [blog post](https://capawesome.io/blog/capawesome-cloud-open-source-program/index.md). ### Pricing We have doubled the MAU limit on all plans for both new and existing customers: - **Free**: 100 MAU (previously 50) - **Starter**: 1,000 MAU (previously 500) - **Professional**: 10,000 MAU (previously 5,000) - **Team**: 100,000 MAU (previously 50,000) This means that you can now use Capawesome Cloud for larger projects without having to upgrade to a higher plan. We believe that this change will make it easier for developers to get started with Capawesome Cloud and to scale their projects as needed. You can find more information about our pricing plans on our [pricing page](https://cloud.capawesome.io/#pricing). ### Two-factor authentication You can now enhance the security of your Capawesome Cloud account by enabling two-factor authentication (2FA). This feature adds an extra layer of protection to your account, requiring a second form of verification in addition to your password. To enable 2FA, go to your [account settings](https://console.cloud.capawesome.io/settings/account) and follow the instructions. \[ \](/assets/videos/posts/20250430_cloud-2fa.mp4) ## Plugins ### Android Edge-to-Edge Support The [Android Edge-to-Edge Support](https://capawesome.io/plugins/android-edge-to-edge-support/index.md) plugin got several new features and improvements this month. ##### New `enable()` method The `enable()` method allows you to re-enable the plugin after it has been disabled. This is useful if you want to temporarily disable the edge-to-edge support and then re-enable it later. ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; const enable = async () => { await EdgeToEdge.enable(); }; ``` ##### New `disable()` method The `disable()` method allows you to disable the edge-to-edge support. This is useful if you want to temporarily disable the edge-to-edge support without removing the plugin from your project. ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; const disable = async () => { await EdgeToEdge.disable(); }; ``` ##### New `getInsets()` method The `getInsets()` method allows you to retrieve the current insets that are applied by the plugin. This is useful if you need to pass these values to other plugins that require insets. ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; const getInsets = async () => { const { top, bottom, right, left } = await EdgeToEdge.getInsets(); return { top, bottom, right, left }; }; ``` ### Bluetooth Low Energy ##### Peripheral mode The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin now supports peripheral mode on Android on iOS. This feature allows your app to act as a BLE peripheral, enabling other devices to connect and interact with it. This is useful for applications that need to advertise data or provide services to nearby devices. ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const startAdvertising = async () => { await BluetoothLowEnergy.startAdvertising({ name: 'CapBLE', services: [ { id: '12345678-1234-1234-1234-1234567890AB', characteristics: [ { id: '87654321-4321-4321-4321-BA0987654321', descriptors: [], // Descriptors are ignored for now permissions: { read: true, write: true, }, properties: { indicate: true, notify: true, read: true, write: true, }, }, ], }, ], }); }; ``` The `startAdvertising` method allows you to specify the name of the peripheral and the services it provides. The services can include characteristics with various properties and permissions. You can also stop advertising by calling the `stopAdvertising` method: ``` const stopAdvertising = async () => { await BluetoothLowEnergy.stopAdvertising(); }; ``` ### Contacts ##### New `birthdate` property The [Contacts](https://capawesome.io/plugins/contacts/index.md) plugin now supports the `birthdate` property for contacts. This property allows you to store and retrieve the birthdate of a contact, making it easier to manage contact information. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const createContact = async () => { return Contacts.createContact({ contact: { birthday: { day: 1, month: 1, year: 1990 }, givenName: 'John', familyName: 'Doe' } }); }; ``` Please note that the `year` property is optional and can be omitted if not needed. ### Firebase #### Analytics ##### New initiate on-device conversion measurement methods The [Firebase Analytics](https://capawesome.io/plugins/firebase/analytics/index.md) plugin now supports multiple new methods for initiating on-device conversion measurement. These methods allow you to initiate conversion measurement using email addresses and phone numbers, as well as their hashed versions. This is useful for tracking conversions and measuring the effectiveness of your marketing campaigns. ``` import { FirebaseAnalytics } from '@capacitor-firebase/analytics'; const initiateOnDeviceConversionMeasurementWithEmailAddress = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithEmailAddress({ emailAddress: 'mail@example.com', }); }; const initiateOnDeviceConversionMeasurementWithPhoneNumber = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithPhoneNumber({ phoneNumber: '+49123456789', }); }; const initiateOnDeviceConversionMeasurementWithHashedEmailAddress = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithHashedEmailAddress({ emailAddressToHash: 'mail@example.com', }); }; const initiateOnDeviceConversionMeasurementWithHashedPhoneNumber = async () => { await FirebaseAnalytics.initiateOnDeviceConversionMeasurementWithHashedPhoneNumber({ phoneNumberToHash: '+49123456789', }); }; ``` ### App Check ##### New `tokenChanged` event The [Firebase App Check](https://capawesome.io/plugins/firebase/app-check/index.md) plugin now supports the `tokenChanged` event also on Android and iOS. This event is triggered when the App Check token changes, allowing you to handle token updates in your application. ``` import { FirebaseAppCheck } from '@capacitor-firebase/app-check'; const addListener = async () => { await FirebaseAppCheck.addListener('tokenChanged', (event) => { console.log('Token changed:', event.token); }); }; ``` ### Authentication The [Firebase Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) plugin got two new features this month. ##### Facebook Limited Login The plugin now supports Facebook Limited Login. This feature allows you to authenticate users with Facebook while limiting the amount of data shared with your app. This is useful for improving user privacy and security. ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const signInWithFacebook = async () => { const result = await FirebaseAuthentication.signInWithFacebook({ useLimitedLogin: true, }); return result.user; }; ``` If you don't want to use the limited login feature, you have to request the App Tracking Transparency permission first. This is required by Apple to use Facebook login without limited login: ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const signInWithFacebook = async () => { const { status } = await FirebaseAuthentication.requestAppTrackingTransparencyPermission(); if (status !== 'granted') { throw new Error('App Tracking Transparency permission not granted'); } const result = await FirebaseAuthentication.signInWithFacebook({ useLimitedLogin: false, }); return result.user; }; ``` ##### Disable the Android Credential Manager Since a few users have reported issues with the new Android Credential Manager (see [#848](https://github.com/capawesome-team/capacitor-firebase/issues/848)), we have added a new property to disable it: ``` import { FirebaseAuthentication } from '@capacitor-firebase/authentication'; const signInWithGoogle = async () => { const result = await FirebaseAuthentication.signInWithGoogle({ useCredentialManager: false, }); return result.user; }; ``` By default, the credential manager is enabled. This property is only available on Android. ### ML Kit #### Barcode Scanning ##### Add torch support The [Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now has its own methods to control the flashlight again. These methods were first removed and migrated to a separate [Torch](https://capawesome.io/plugins/torch/index.md) plugin. However, as this led to various problems, we have decided to bring the methods back into the barcode scanning plugin. ``` import { BarcodeScanning } from '@capacitor-mlkit/barcode-scanning'; const enableTorch = async () => { await Torch.enable(); }; const disableTorch = async () => { await Torch.disable(); }; const toggleTorch = async () => { await Torch.toggle(); }; const isTorchEnabled = async () => { const { enabled } = await Torch.isEnabled(); return enabled; }; const isTorchAvailable = async () => { const { available } = await Torch.isAvailable(); return available; }; ``` The methods are available on Android and iOS. ##### New `3840x2160` resolution The [Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now supports the `3840x2160` resolution for scanning barcodes. This resolution is available on Android and iOS devices with a camera that supports this resolution. The new resolution allows for higher quality scans and improved performance in low-light conditions. ``` import { BarcodeScanning, Resolution } from '@capacitor-mlkit/barcode-scanning'; const startScan = async () => { await BarcodeScanner.startScan({ resolution: Resolution['3840x2160'], }); }; ``` #### Subject Segmentation We have published a new [Subject Segmentation](https://capawesome.io/plugins/mlkit/subject-segmentation/index.md) plugin. This plugin allows you to segment subjects in images, making it easier to create custom image processing applications. The plugin is available on Android. ``` import { SubjectSegmentation } from '@capacitor-mlkit/subject-segmentation'; const processImage = async () => { const { path } = await SubjectSegmentation.processImage({ path: 'path/to/image.jpg', confidence: 0.7, }); return path; }; ``` The plugin provides a simple API for processing images and returning the segmented image. You can adjust the confidence level to control the sensitivity of the segmentation. ### Printer ##### New `printBase64(...)` method The [Printer](https://capawesome.io/plugins/printer/index.md) plugin now supports printing base64 encoded files. This feature allows you to print files that are not available on the device's file system, such as files that are generated in memory or downloaded from a remote server. The new `printBase64(...)` method accepts a base64 encoded string and prints it using the default printer. ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printBase64 = async () => { await Printer.printBase64({ name: 'My Document', data: 'JVBERi0...', }); } ``` The method is available on Android and iOS. Please note that large base64 strings may lead to app crashes or performance issues. We therefore recommend using this feature only for small files or images. For larger files, consider saving them to the device's file system and using the `printFile(...)` method. ##### New `printFile(...)` method The [Printer](https://capawesome.io/plugins/printer/index.md) plugin now also supports printing files (including images) from the device's file system. The new `printFile(...)` method accepts a file path and prints it using the default printer. ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printFile = async () => { await Printer.printFile({ mimeType: 'application/pdf', path: '/path/to/file.pdf', }); }; ``` The method is available on Android and iOS. # Capawesome August 2025 Update The Capawesome August update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ##### SOC 2 Type 2 Compliance We are excited to announce that Capawesome Cloud is now SOC 2 Type 2 compliant. This means that we have implemented rigorous security controls and processes to protect your data and ensure the highest level of privacy and security. Check out our [announcement post](https://capawesome.io/blog/capawesome-cloud-soc-2-type-2-compliance/index.md) for more details on our SOC 2 Type 2 compliance. ## Plugins ### Accelerometer We have published a new [Accelerometer](https://capawesome.io/plugins/accelerometer/index.md) plugin that allows you to access the device's accelerometer sensor data on iOS and Android. ``` import { Accelerometer } from '@capawesome-team/capacitor-accelerometer'; const getMeasurement = async () => { const measurement = await Accelerometer.getMeasurement(); console.log("X: ", measurement.x); console.log("Y: ", measurement.y); console.log("Z: ", measurement.z); }; ``` Check out the [documentation](https://capawesome.io/plugins/accelerometer/index.md) for more information on how to configure and use the plugin. ### Android Edge-to-Edge Support The [Android Edge-to-Edge Support](https://capawesome.io/plugins/android-edge-to-edge-support/index.md) plugin has received small bug fixes. Please check out the [CHANGELOG.md](https://capawesome.io/plugins/android-edge-to-edge-support/#changelog) for more details. ### Asset Manager The [Asset Manager](https://capawesome.io/plugins/asset-manager/index.md) plugin has received small bug fixes. Please check out the [CHANGELOG.md](https://capawesome.io/plugins/asset-manager/#changelog) for more details. ### Audio Recorder The [Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin now supports setting `audioSessionCategoryOptions` on iOS when starting a recording. This allows you to customize the audio session behavior, such as allowing mixing with other audio sources or enabling Bluetooth support. ``` import { AudioRecorder, AudioSessionCategoryOption } from '@capawesome-team/capacitor-audio-recorder'; const startRecording = async () => { await AudioRecorder.startRecording({ audioSessionCategoryOptions: [AudioSessionCategoryOption.DuckOthers], }); }; ``` ### Firebase All [Firebase plugins](https://capawesome.io/plugins/firebase/index.md) have been updated and received several small bug fixes. Please check out the `CHANGELOG.md` files of the respective plugins for more details. ### NFC The [NFC](https://capawesome.io/plugins/nfc/index.md) plugin has received several improvements and bug fixes. Notably, we have added a new `mapUriIdentifierCodeToString` utility function that maps URI identifier codes to their corresponding string representations. Additionally, the `convertBytesToString` method now accepts `Uint8Array` types, making it more versatile. ``` import { NfcUtils, UriIdentifierCode } from '@capawesome-team/capacitor-nfc'; const mapUriIdentifierCodeToString = () => { const utils = new NfcUtils(); const { prefix } = utils.mapUriIdentifierCodeToString({ identifierCode: UriIdentifierCode.HttpsWww }); console.log(prefix); // Outputs: "https://www." }; const convertBytesToString = () => { const utils = new NfcUtils(); const { text } = utils.convertBytesToString({ bytes: [72, 101, 108, 108, 111] }); console.log(text); // Outputs: "Hello" }; ``` ### Pedometer We have published a new [Pedometer](https://capawesome.io/plugins/pedometer/index.md) plugin that allows you to access step count and distance data from the device's built-in pedometer on iOS and Android. ``` import { Pedometer } from '@capawesome-team/capacitor-pedometer'; const getMeasurement = async () => { const { measurement } = await Pedometer.getMeasurement(); console.log("Average Active Pace:", measurement.averageActivePace); console.log("Current Cadence:", measurement.currentCadence); console.log("Current Pace:", measurement.currentPace); console.log("Distance:", measurement.distance); console.log("Floors Ascended:", measurement.floorsAscended); console.log("Floors Descended:", measurement.floorsDescended); console.log("Number of Steps:", measurement.numberOfSteps); }; ``` Check out the [documentation](https://capawesome.io/plugins/pedometer/index.md) for more information on how to configure and use the plugin. ### RealtimeKit We have published a new [RealtimeKit](https://capawesome.io/plugins/realtimekit/index.md) plugin that provides real-time communication capabilities for your Capacitor apps using the [RealtimeKit SDK](https://docs.realtime.cloudflare.com/). With RealtimeKit, you can easily integrate programmable, and easily customizable live video and voice. ``` import { RealtimeKit } from '@capawesome-team/capacitor-realtimekit'; const startMeeting = async () => { await RealtimeKit.startMeeting({ authToken: 'your-auth-token', enableAudio: true, enableVideo: true, }); }; ``` ### Screen Orientation The [Screen Orientation](https://capawesome.io/plugins/screen-orientation/index.md) plugin has received small bug fixes. Please check out the [CHANGELOG.md](https://capawesome.io/plugins/screen-orientation/#changelog) for more details. ### Share Target We have published a new [Share Target](https://capawesome.io/plugins/share-target/index.md) plugin that allows your app to receive shared content from other apps on Android, iOS and Web. This plugin enables your app to appear in the system share sheet, allowing users to share text, images, URLs, and other types of content directly to your app. Here's a simple example of how to use the Share Target plugin to listen for shared content: ``` import { ShareTarget } from '@capawesome-team/capacitor-share-target'; const addListener = async () => { await ShareTarget.addListener('shareReceived', (event) => { console.log('Share received:', event); // Handle shared files if (event.files) { event.files.forEach(async (fileUrl) => { const response = await fetch(fileUrl); const blob = await response.blob(); // Process the file... }); } }); }; ``` Make sure to check out the [documentation](https://capawesome.io/plugins/share-target/index.md) for more information on how to configure and use the plugin. ### Speech Recognition The [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin now supports a `soundLevel` listener that provides real-time updates on the sound level during speech recognition. This can be useful for visualizing the input volume or for implementing custom UI feedback. ``` import { SpeechRecognition } from '@capawesome-team/capacitor-speech-recognition'; const addListener = () => { SpeechRecognition.addListener('soundLevel', (event) => { console.log('Sound level:', event.level); }); }; ``` ### Speech Synthesis The [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/index.md) plugin has been updated to include `pause` and `resume` methods. This allows you to pause and resume speech playback, providing more control over the speech synthesis experience. ``` import { SpeechSynthesis } from '@capawesome-team/capacitor-speech-synthesis'; const pause = async () => { await SpeechSynthesis.pause(); }; const resume = async () => { await SpeechSynthesis.resume(); }; ``` ### Sqlite The [Sqlite](https://capawesome.io/plugins/sqlite/index.md) plugin has received small bug fixes. Please check out the [CHANGELOG.md](https://capawesome.io/plugins/sqlite/#changelog) for more details. # Capawesome December 2025 Update The Capawesome December update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Build Stacks We are excited to announce the launch of the new `macos-tahoe` build stack for [Capawesome Cloud](https://capawesome.io/cloud/index.md). This build stack provides support for Xcode 26.2, enabling you to build your iOS applications with the latest tools and features from Apple. The `macos-tahoe` build stack runs on macOS 26 and includes updated versions of key development tools. In addition to Xcode 26.2, it comes with Fastlane 2.229.1, npm 11.6.2, and Java 17/21 (with 21 as the default). Node.js versions 20, 22, and 24 are supported, with 24 as the default. This new build stack ensures you can take advantage of the latest iOS SDK features and improvements. You can select the `macos-tahoe` build stack in your build configuration to start using Xcode 26.2 today. For more information, check out the [Build Stacks documentation](https://capawesome.io/cloud/native-builds/build-stacks/index.md). ### Teams We are excited to introduce Teams to [Capawesome Cloud](https://capawesome.io/cloud/index.md)! This new feature enables organizations to organize members and control access to specific apps, providing better structure and security for larger teams. Teams function as access control groups where you can assign members and apps together. Each team can have multiple members, and members can belong to multiple teams. When a member is part of a team, they automatically gain access to all apps assigned to that team. Organization Owners and Admins maintain access to all apps regardless of team membership. \[ \](/assets/videos/posts/20251231_cloud-teams.mp4) To get started with Teams, navigate to the Teams page in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/organizations/_/teams). Check out the [Teams documentation](https://capawesome.io/cloud/organizations/teams/index.md) to learn more. ## Plugins All Capawesome plugins have been updated to support Capacitor 8! This major update brings improved performance, better error handling, and updated dependencies across all platforms. We've also updated all plugin dependencies to their latest stable versions to ensure compatibility and security. Check out our [Capacitor 8 migration guide](https://capawesome.io/blog/updating-to-capacitor-8/index.md) for detailed information about breaking changes and how to update your applications. The guide includes step-by-step instructions for migrating each plugin to the new version. ### Audio Player The [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin now supports remote URLs on Android, expanding playback capabilities across all platforms. Previously limited to web assets on Android, the plugin now allows you to play audio files from remote servers, matching the functionality available on iOS and Web. This enhancement means you can stream audio content directly from your CDN or API without needing to bundle files with your application. Both the `src` parameter for web assets and remote URLs, and the `uri` parameter for local file paths are now fully supported on Android, providing consistent behavior across all platforms. ### Age Signals The [Age Signals](https://capawesome.io/plugins/age-signals/index.md) plugin has been enhanced with new testing methods for Android and an eligibility check for iOS. Three testing methods—[`setUseFakeManager()`](https://capawesome.io/plugins/age-signals/#setusefakemanager), [`setNextAgeSignalsResult(...)`](https://capawesome.io/plugins/age-signals/#setnextagesignalsresult), and [`setNextAgeSignalsException(...)`](https://capawesome.io/plugins/age-signals/#setnextagesignalsexception)—enable comprehensive testing of age-related features without requiring actual Google Play integration. A new [`checkEligibility()`](https://capawesome.io/plugins/age-signals/#checkeligibility) method is now available on iOS to determine if users are eligible for age-gated features based on their region: ``` import { AgeSignals } from '@capawesome/capacitor-age-signals'; const checkEligibility = async () => { const result = await AgeSignals.checkEligibility(); console.log('Eligible:', result.eligible); }; ``` ### PostHog The [PostHog](https://capawesome.io/plugins/posthog/index.md) plugin now includes consent management and cookieless tracking features, giving you more control over user privacy and GDPR compliance. Three new methods—[`optIn()`](https://capawesome.io/plugins/posthog/#optin), [`optOut()`](https://capawesome.io/plugins/posthog/#optout), and [`isOptOut()`](https://capawesome.io/plugins/posthog/#isoptout)—manage user consent for event tracking: ``` import { Posthog } from '@capawesome/capacitor-posthog'; const handleConsent = async () => { await Posthog.optIn(); // User accepts tracking await Posthog.optOut(); // User rejects tracking const result = await Posthog.isOptOut(); console.log('Opted out:', result.optedOut); }; ``` The plugin also supports cookieless tracking on Web, enabling privacy-focused analytics without browser cookies. Configure the [`setup(...)`](https://capawesome.io/plugins/posthog/#setup) method with `cookielessMode` set to `always` for continuous cookieless tracking or `on_reject` to switch to cookieless mode when users opt out. This feature requires cookieless mode to be enabled in your PostHog project settings. ### Purchases The [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin now includes a new [`getProductById(...)`](https://capawesome.io/plugins/purchases/#getproductbyid) method that retrieves detailed information about a specific in-app product. This method simplifies product queries by allowing you to fetch details for a single product without needing to load your entire product catalog. ``` import { Purchases } from '@capawesome-team/capacitor-purchases'; const getProduct = async () => { const result = await Purchases.getProductById({ productId: 'com.example.premium' }); console.log('Product:', result.product.displayName); console.log('Price:', result.product.price); console.log('Currency:', result.product.currencyCode); }; ``` The method returns comprehensive product information including the display name, description, localized pricing, currency code, and product type. This is particularly useful when you need to display pricing information for a specific product or validate product availability before presenting purchase options to users. This method is available on Android and iOS 15.0+. ### Superwall We are excited to announce the initial release of the [Superwall](https://capawesome.io/plugins/superwall/index.md) plugin! This unofficial Capacitor plugin integrates the Superwall SDK into your mobile applications, providing powerful monetization and user engagement features for managing in-app subscriptions and paywalls. The plugin enables you to display remotely-configured paywalls without app updates, control feature access based on subscription status, and track user analytics across platforms. It includes built-in A/B testing support to optimize conversion rates and works seamlessly on both Android and iOS. Check out the [documentation](https://capawesome.io/plugins/superwall/index.md) to learn more about getting started with the Superwall plugin. # Capawesome February 2025 Update The Capawesome February update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Advanced Filtering We have added advanced filtering to the [Capawesome Cloud Console](https://console.cloud.capawesome.io/). This feature allows you to filter your bundles by various criteria, such as ID, channel, artifact type, and artifact status, making it easier to manage your live updates. \[ \](/assets/videos/posts/cloud-advanced-filtering.mp4) ### Force Code Signing You can now enforce code signing for all bundles in your app. This feature ensures that all bundles are signed with a private key before they are uploaded to the Capawesome Cloud, making sure that no unsigned bundles are distributed to your users. You can enable this feature in the settings of your app through the [Capawesome Cloud Console](https://console.cloud.capawesome.io/apps). \[ \](/assets/videos/posts/cloud-app-force-bundle-signatures.mp4) ### Self-Hosting Check out the new [Self-Hosting](https://capawesome.io/cloud/live-updates/advanced/self-hosting/index.md) guide to learn how to self-host your Live Updates. This feature allows you to host the bundles on your server instead of Capawesome Cloud. This can be useful if you have specific security requirements, or if you want to avoid being affected by data transfer or storage limitations. ## Plugins ### Android Edge-to-Edge Support We have published a new [Android Edge-to-Edge Support](https://capawesome.io/plugins/android-edge-to-edge-support/index.md) plugin to support [edge-to-edge](https://developer.android.com/develop/ui/views/layout/edge-to-edge) display on Android. This has been a real problem for many users who have updated to Capacitor 7, as Android 15 enforces the edge-to-edge display. This causes the web view to be displayed behind the status bar and navigation bar, which can lead to layout issues. The plugin restores the previous behavior and ensures that the web view is displayed correctly. ### Firebase #### App Check The [Firebase App Check](https://capawesome.io/plugins/firebase/app-check/index.md) plugin now supports all available providers. For this, a new `provider` option has been added to the `initialize(...)` method. ``` import { FirebaseAppCheck } from '@capacitor-firebase/app-check'; import { ReCaptchaV3Provider } from '@capacitor-firebase/app-check'; const initialize = async () => { await FirebaseAppCheck.initialize({ provider: new ReCaptchaV3Provider('myKey'); }); }; ``` #### Crashlytics ##### Custom Keys And Values The [Firebase Crashlytics](https://capawesome.io/plugins/firebase/crashlytics/index.md) plugin now supports custom keys and values for the `recordException(...)` method. This allows you to attach additional information to an exception, such as user-specific data or metadata, making it easier to debug and analyze non-fatal exceptions. ``` import { FirebaseCrashlytics } from '@capacitor-firebase/crashlytics'; const recordException = async () => { await FirebaseCrashlytics.recordException({ message: 'My error message', customKeysAndValues: [ { key: 'customKey1', value: 'customValue1', type: 'string', }, { key: 'customKey2', value: 123, type: 'int', } ], }); }; ``` ### Live Update ##### Code Signing for Self-Hosted Bundles The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin now supports code signing for self-hosted bundles. Code signing was previously only available for bundles hosted on Capawesome Cloud. With this update, you can now also host your bundles on your server and ensure that only signed bundles are installed on your users' devices. ``` import { LiveUpdate } from '@capawesome-team/capacitor-live-update'; const downloadLatestBundle = async () => { // Fetch the latest bundle from Capawesome Cloud const { bundleId, downloadUrl, signature } = await LiveUpdate.fetchLatestBundle(); // Download the bundle from your server await LiveUpdate.downloadBundle({ bundleId, signature, url: downloadUrl, }); }; ``` ### ML Kit #### Barcode Scanning ##### Auto Focus The [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now also supports auto-focus on iOS. This feature enables the camera to focus automatically on the barcode, making it easier to scan barcodes from various distances. You don't need to take any action to activate this feature; it is enabled automatically when you initiate the barcode scanning process. ##### Web Support The [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now also supports the web platform. You can use the plugin in your web app to scan barcodes directly from the browser thanks to the [Barcode Detection API](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API). ``` import { BarcodeScanner } from '@capacitor-mlkit/barcode-scanning'; const startScan = async () => { // Add the `barcodeScanned` listener const listener = await BarcodeScanner.addListener( 'barcodeScanned', async result => { console.log(result.barcode); }, ); // Start the barcode scanner await BarcodeScanner.startScan({ videoElement: document.getElementById('video'), }); }; ``` ### Speech Synthesis ##### New `synthesizeToFile(...)` method The [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/index.md) plugin now supports the `synthesizeToFile(...)` method. This method allows you to synthesize text to an audio file and save it to the device. The audio file can be played back or shared with other apps. ``` import { SpeechSynthesis } from '@capawesome-team/capacitor-speech-synthesis'; const synthesizeToFile = async () => { // Add an utterance to the utterance queue to be synthesized to a file const { path, utteranceId } = await SpeechSynthesis.synthesizeToFile({ language: 'en-US', pitch: 1.0, queueStrategy: QueueStrategy.Add, rate: 1.0, text: 'Hello, World!', voiceId: 'com.apple.ttsbundle.Samantha-compact', volume: 1.0, }); // Wait for the utterance to finish await new Promise(resolve => { void SpeechSynthesis.addListener('end', event => { if (event.utteranceId === utteranceId) { resolve(); } }); }); // Return the path to the synthesized audio file return path; }; ``` # Capawesome January 2025 Update The Capawesome January update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## CLI ### Custom Properties The Capawesome CLI now supports custom properties for the `apps:bundles:create` command: ``` npx @capawesome/cli apps:bundles:create --custom-property version=1.1.0 --custom-property silentUpdate=true ``` Custom properties allow you to store additional information in a bundle and can be retrieved by the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. This allows you to customize the behavior of your app for specific bundles. ## Cloud ### Landing Page We've just launched our brand new landing page for Capawesome Cloud. The new landing page is available at [cloud.capawesome.io](https://cloud.capawesome.io/) and provides an overview of all features and benefits of Capawesome Cloud, including customer testimonials and pricing information. The Capawesome Cloud Console, which was previously accessible at [console.capawesome.io](https://console.capawesome.io/), has been moved to [console.cloud.capawesome.io](https://console.cloud.capawesome.io/). Do you like the new landing page? Let us know what you think! ### GitHub Action The [Cloud Live Update Action](https://github.com/capawesome-team/cloud-live-update-action) now uses the latest version of the Capawesome CLI. This update includes various improvements and bug fixes. The Cloud Live Update Action allows you to deploy a live update to the Capawesome Cloud directly from your GitHub Actions workflow: ``` - uses: capawesome-team/cloud-live-update-action@v0.0.3 with: # The Capawesome Cloud app ID. # Required. appId: '' # The channel to deploy the update to. channel: '' # The path to the bundle to upload. Must be a folder or zip archive. # Required. path: '' # The Capawesome Cloud API token. # Required. token: '' ``` ## Plugins All plugins now support Capacitor 7 and the Swift Package Manager (SPM). This means that you can now use the plugins with the latest version of Capacitor and add them to your Xcode project using SPM. Read on to learn more about the latest changes to our plugins. Breaking Changes We have published some breaking changes to our plugins this month. Please make sure to carefully review the changes before updating your plugins. You can find more information about the breaking changes in the respective `BREAKING.md` files in the plugin repositories. ### App Shortcuts ##### New `icon` option The [App Shortcuts](https://capawesome.io/plugins/app-shortcuts/index.md) plugin now supports the `icon` option. This option allows you to specify an icon for the shortcut: ``` import { Capacitor } from '@capacitor/core'; import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const set = async () => { await AppShortcuts.set({ shortcuts: [ { id: 'feedback', title: 'Feedback', description: 'Send feedback to the app developers', icon: Capacitor.getPlatform() === 'ios' ? 6 : 17301547, } ], }); }; ``` The `icon` option accepts a number. On Android, the icon is the constant value of the [`R.drawable`](https://developer.android.com/reference/android/R.drawable) enum. On iOS, the icon is the constant value of the [`UIApplicationShortcutIcon.IconType`](https://developer.apple.com/documentation/uikit/uiapplicationshortcuticon/icontype) enum. ### Firebase #### Cloud Firestore ##### New `getCountFromServer` method The [Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin now supports a `getCountFromServer` method. This method allows you to get the number of documents in a collection from the server: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const getCountFromServer = async () => { const { count } = await FirebaseFirestore.getCountFromServer({ reference: 'users', }); return count; }; ``` ### Live Update ##### Automatic rollbacks disabled by default The default value of the `readyTimeout` configuration option has been changed from `10000` to `0` to disable the timeout by default. This should make it easier to get started with the plugin. This feature has often caused confusion and issues for users who were not aware of the timeout. However, it is strongly **recommended** to configure this option so that the plugin can roll back to the default bundle in case of problems: ``` { "plugins": { "LiveUpdate": { "readyTimeout": 10000 } } } ``` ##### New `ReadyResult` type The `ready()` method now returns a `ReadyResult` object with the following properties: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const ready = async () => { const result = await LiveUpdate.ready(); console.log('Previous Bundle ID: ', result.previousBundleId); console.log('Current Bundle ID: ', result.currentBundleId); console.log('Rollback performed? ', result.rollback); }; ``` This change allows you to get more information about the current state of the app after the `ready()` method has been called. For example, you can display a message to the user if an update has failed. ##### Custom properties The `fetchLatestBundle(...)` method now also returns the custom properties of the latest bundle: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const fetchLatestBundle = async () => { const { customProperties } = await LiveUpdate.fetchLatestBundle(); console.log('Custom Properties: ', customProperties); }; ``` This allows you to customize the behavior of your app based on the custom properties of the latest bundle. For example, you could use a custom property `silentUpdate` to decide whether the user should be asked before downloading the bundle or not. ##### New `downloadBundleProgress` listener The `downloadBundle(...)` method now emits a `downloadBundleProgress` event with the download progress: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const downloadBundle = async () => { // Listen for download progress await LiveUpdate.addListener('downloadBundleProgress', (event) => { console.log('Bundle ID: ', event.bundleId); console.log('Progress: ', event.progress); console.log('Downloaded bytes: ', event.downloadedBytes); console.log('Total bytes: ', event.totalBytes); }); // Download the latest bundle await LiveUpdate.downloadBundle(); }; ``` This change allows you to display a progress bar or other UI elements to the user while the bundle is being downloaded. ### ML Kit #### Barcode Scanning ##### New `resolution` option The [Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now supports a `resolution` option. This option allows you to specify the resolution of the barcode scanning process: ``` import { BarcodeScanner, Resolution } from '@capacitor-mlkit/barcode-scanning'; const startScan = async () => { await BarcodeScanner.startScan({ resolution: Resolution['1280x720'], }); }; ``` The possible values for the `resolution` option are: - `640x480` - `1280x720` - `1920x1080` ### Screenshot We have published a new [Screenshot](https://capawesome.io/plugins/screenshot/index.md) plugin. The Screenshot plugin allows you to take screenshots of the current screen and can be used for various purposes, such as error reporting or sharing content. ``` import { Screenshot } from '@capawesome/capacitor-screenshot'; const take = async () => { const { uri } = await Screenshot.take(); console.log('Screenshot saved at:', uri); }; ``` # Capawesome July 2025 Update The Capawesome July update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Blog We have made several updates to our blog, including new articles and tutorials that cover various topics related to Capacitor and our plugins. Be sure to check out the latest posts for valuable insights and tips. Here are some highlights: - [Encrypting SQLite databases in Capacitor](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md) - [Announcing the SQLite Plugin for Capacitor](https://capawesome.io/blog/announcing-the-capacitor-sqlite-plugin/index.md) - [Exploring the Capacitor Secure Preferences API](https://capawesome.io/blog/exploring-the-capacitor-secure-preferences-api/index.md) Besides these articles, we have also started a new series of showcases where we highlight some of the best apps built with Capacitor. These showcases will provide insights into how developers are using Capacitor to create amazing applications. Here are the first few showcases: - [Showcase: MyBodyTutor - A Personalized Nutrition and Weight Loss Coaching App](https://capawesome.io/blog/showcase-mybodytutor/index.md) - [Showcase: CostPal - Price tracking app for Costco](https://capawesome.io/blog/showcase-costpal/index.md) Make sure to check them out! ## Cloud ##### New `Billing` role We have introduced a new `Billing` role in Capawesome Cloud. This role allows users to manage billing-related tasks without granting full administrative access. Users with the `Billing` role can view and manage billing information, including invoices and payment methods. Check out the [documentation](https://capawesome.io/cloud/organizations/memberships/index.md) for more details on the `Billing` role and how to assign it to users. ## Plugins ### Barometer We have released a new [Barometer](https://capawesome.io/plugins/barometer/index.md) plugin for Android and iOS. This plugin allows you to access the device's barometer sensor, providing real-time atmospheric pressure data. You can use this data for various applications, such as weather forecasting or altitude measurement. ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const getMeasurement = async () => { const { measurement } = await Barometer.getMeasurement(); console.log('Current atmospheric pressure:', measurement.pressure, 'hPa'); }; ``` Check out the [documentation](https://capawesome.io/plugins/barometer/index.md) for more details on how to get started with the Barometer plugin. ### Bluetooth Low Energy ##### New `onConnectionStateChange` method in headless task The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin has been updated with a new `onConnectionStateChange` method in the `BluetoothLowEnergyHeadlessTask` class. This method allows you to run your own native code when the connection state of a Bluetooth device changes. This is useful for handling connection events in the background, such as when a device disconnects or reconnects. ``` import android.bluetooth.BluetoothGatt; import androidx.annotation.NonNull; public class BluetoothLowEnergyHeadlessTask { public void onConnectionStateChange(@NonNull BluetoothGatt gatt, int status, int newState) { // Your code here } } ``` Check out the [documentation](https://capawesome.io/plugins/bluetooth-low-energy/#headless-task) for more details on how to use the `onConnectionStateChange` method in your headless task. ### File Compressor ##### `width` and `height` options The [File Compressor](https://capawesome.io/plugins/file-compressor/index.md) plugin has been updated to support the `width` and `height` options when compressing images. This allows you to resize images while compressing them, which can help reduce file size without losing too much quality. ``` import { FileCompressor } from '@capawesome-team/capacitor-file-compressor'; const compressImage = async () => { const { path } = await FileCompressor.compressImage({ mimeType: 'image/jpeg', path: 'content://com.android.providers.downloads.documents/document/msf%3A1000000485', quality: 0.7, height: 1000, width: 1000, }); return path; }; ``` Check out the [documentation](https://capawesome.io/plugins/file-compressor/#compressimage) for more details on how to use the `width` and `height` options when compressing images. ### Firebase #### Authentication ##### `authDomain` configuration option The [Firebase Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) plugin has been updated to include a new `authDomain` configuration option. This option allows you to specify the authentication domain for your Firebase project, which is required for certain authentication methods. ``` { "plugins": { "FirebaseAuthentication": { "authDomain": "your-app.firebaseapp.com" } } } ``` Check out the [documentation](https://capawesome.io/plugins/firebase/authentication/#configuration) for more details on how to configure the `authDomain` option. #### Cloud Firestore ##### `SetOptions` interface The [Firebase Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin has been updated to include a new `SetOptions` interface. This interface allows you to specify options when setting documents in Firestore, such as merging data. ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const writeBatch = async () => { await FirebaseFirestore.writeBatch({ operations: [ { type: 'set', reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, options: { merge: true }, }, { type: 'delete', reference: 'users/Aorq09lkt1ynbR7xhTUx', }, ], }); }; ``` For more details on how to use the `SetOptions` interface, check out the [documentation](https://capawesome.io/plugins/firebase/cloud-firestore/#setoptions). ### ML Kit #### Document Scanner We have released a new [Document Scanner](https://capawesome.io/plugins/mlkit/document-scanner/index.md) plugin for Android based on the [ML Kit Document Scanner](https://developers.google.com/ml-kit/vision/doc-scanner) API. This plugin allows you to scan documents using the device's camera or import them from the gallery. It supports various result formats, including JPEG and PDF. ``` import { DocumentScanner } from '@capacitor-mlkit/document-scanner'; const scanDocument = async () => { const result = await DocumentScanner.scanDocument({ galleryImportAllowed: true, pageLimit: 5, resultFormats: 'JPEG_PDF', scannerMode: 'FULL', }); console.log('Result:', result); }; ``` Check out the [documentation](https://capawesome.io/plugins/mlkit/document-scanner/index.md) for more details on how to use the Document Scanner plugin. ### libSQL We have released a new [libSQL](https://capawesome.io/plugins/libsql/index.md) plugin for Android and iOS. This plugin provides a powerful and efficient way to manage SQLite databases using the [libSQL](https://docs.turso.tech/libsql) engine, which is a fork of SQLite from the team behind [Turso](https://turso.tech/). ``` import { Libsql } from '@capawesome-team/capacitor-libsql'; const insertUser = async (user: { id: number; name: string }) => { // Open the database const { connectionId } = await Libsql.connect({ database: 'my-database', }); // Insert a new row await Libsql.execute({ connectionId, statement: 'INSERT INTO users (id, name) VALUES (?, ?)', values: [user.id, user.name], }); // Close the database await Libsql.close({ connectionId }); }; ``` Check out the [documentation](https://capawesome.io/plugins/libsql/index.md) for more details on how to get started with the libSQL plugin. ### Live Update The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin has received a small bug fix to ensure that missing bundle files are handled correctly. This fix improves the reliability of the live update process, ensuring that your app can seamlessly update without issues. Make sure to update your Live Update plugin to the latest version to benefit from this fix. ### PostHog ##### New configuration options The [PostHog](https://capawesome.io/plugins/posthog/index.md) plugin has been updated with new configuration options. You can now set the `apiKey` and `host` when initializing the plugin, allowing you to track advanced analytics events like app installations and more. ``` { "plugins": { "Posthog": { "apiKey": 'phc_g8wMenebiIQ1pYd5v9Vy7oakn6MczVKIsNG5ZHCspdy', "host": 'https://eu.i.posthog.com' } } } ``` This also means that you don't need to call the `setup(...)` method anymore, as the plugin will automatically initialize with the provided configuration. ### Speech Recognition ##### Punctuation support The [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin has been updated to support punctuation in transcriptions. Just set the `enableFormatting` option to `true` when starting the speech recognition session, and the plugin will automatically format the transcriptions with punctuation. ``` import { SpeechRecognition } from '@capawesome-team/capacitor-speech-recognition'; const startListening = async () => { await SpeechRecognition.startListening({ enableFormatting: true, }); }; ``` Check out the [documentation](https://capawesome.io/plugins/speech-recognition/index.md) for more details. ### SQLite We have published a new [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin for Capacitor. This plugin provides a powerful and efficient way to manage SQLite databases on Android, iOS and web. It supports various features such as encryption, migrations, and more. Here's a quick example of how to use the SQLite plugin to open a database and insert a new row: ``` import { SQLite } from '@capawesome-team/capacitor-sqlite'; const insertUser = async (user: { id: number; name: string }) => { // Open the database const { databaseId } = await SQLite.open({ path: 'db.sqlite3', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)', ] } ], }); // Insert a new row await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: ['John Doe', 'john.doe@example.com'] }); // Close the database await SQLite.close({ databaseId }); }; ``` Check out the [announcement post](https://capawesome.io/blog/announcing-the-capacitor-sqlite-plugin/index.md) for more details on how to get started with the SQLite plugin. # Capawesome June 2025 Update The Capawesome June update is here! This update includes new features and improvements for the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). ## CLI ### Uploading bundles larger than 100 MB Starting with version 1.13.0, the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) now supports uploading bundles larger than 100 MB. This feature is particularly useful for apps with large assets or dependencies. The CLI automatically splits the bundle into smaller chunks and uploads them in parallel, ensuring a faster and more reliable upload process. ## Plugins We have made several improvements to our plugins this month, enhancing their functionality and usability. This section highlights the most significant changes. ### App Shortcuts ##### Support Base64 encoded icons The [App Shortcuts](https://capawesome.io/plugins/app-shortcuts/index.md) plugin has been updated to support Base64 encoded icons on Android. This allows developers to use icons directly from their code without needing to store them as separate files, simplifying the development process and allowing for more dynamic icon management. ### Biometrics ##### Add `enroll()` method The [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin now supports prompting users to enroll their biometrics. The new `enroll()` method allows developers to trigger the biometric enrollment process, enabling users to set up their biometrics directly from the app. This feature enhances user experience by making it easier to enroll biometrics without navigating to device settings. ``` import { Biometrics } from '@capawesome/capacitor-biometric'; const enroll = async () => { try { await Biometrics.enroll(); console.log('Biometric enrollment successful'); } catch (error) { console.error('Biometric enrollment failed:', error); } }; ``` This method is only available on Android. ### File Picker ##### Support for iOS for the `copyFile(...)` method The [File Picker](https://capawesome.io/plugins/file-picker/index.md) plugin has been enhanced with support for the `copyFile(...)` method on iOS. This feature allows developers to copy files within the app's file system, making it easier to manage picked files and organize them as needed. ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; import { Directory, Filesystem } from '@capacitor/filesystem'; const copyFile = async () => { const { uri: toUri } = await Filesystem.getUri({ directory: Directory.Documents, path: 'old/file.txt', }); const { uri: fromUri } = await Filesystem.getUri({ directory: Directory.Documents, path: 'new/file.txt', }); await FilePicker.copyFile({ from: fromUri, to: toUri, overwrite: true, }); }; ``` # Capawesome March 2025 Update The Capawesome March update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Console #### Force Code Signing You can now enforce code signing for all bundles in your app. This feature ensures that all bundles are signed with a private key before they are uploaded to the Capawesome Cloud, making sure that no unsigned bundles are distributed to your users. You can enable this feature in the settings of your app through the [Capawesome Cloud Console](https://console.cloud.capawesome.io/apps). \[ \](/assets/videos/posts/cloud-force-code-signing.mp4) #### Git Integration Capawesome Cloud offers a lightweight Git integration that allows you to link your bundles to Git commits. This way, you can easily track which version of your app is currently live and which changes have been made since the last update. To enable Git integration, simply edit the app in the Capawesome Cloud Console, enable the Git integration toggle and provide the URL of the Git repository. After that, you can use the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) to create a new bundle and link it to a specific commit: ``` npx @capawesome/cli apps:bundles:create --commit-message "feat: support in-app purchases" --commit-ref "main" --commit-sha "b0cb01e" ``` Check out the [documentation](https://capawesome.io/cloud/live-updates/advanced/git-integration/index.md) for more information. ### GitHub Action #### Git Integration The [GitHub Action](https://github.com/capawesome-team/cloud-live-update-action) also supports the new Git integration feature. ## Plugins ### App Shortcuts The [App Shortcuts](https://capawesome.io/plugins/app-shortcuts/index.md) plugin received various bug fixes and improvements. ### Audio Recorder We have published a new [Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin. This plugin allows you to record audio using the device's microphone. You can start, pause, resume, and stop the recording and get the audio blob or URI. The plugin is available on Android, iOS and Web. ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; import { NativeAudio } from '@capacitor-community/native-audio'; const startRecording = async () => { await AudioRecorder.startRecording(); }; const stopRecording = async () => { // Stop recording and get the audio blob or URI const { blob, uri } = await AudioRecorder.stopRecording(); // Play the audio if (blob) { // Only available on Web const audio = new Audio(); audio.src = URL.createObjectURL(blob); audio.play(); } else if (uri) { // Only available on Android and iOS await NativeAudio.preload({ assetId: 'recording', assetPath: uri, isUrl: true, }); await NativeAudio.play({ assetId: 'recording' }); } }; ``` Check out the [announcement](https://capawesome.io/blog/announcing-the-capacitor-audio-recorder-plugin/index.md) for more information. ### Bluetooth Low Energy The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin received various bug fixes. ### Contacts We have published a new [Contacts](https://capawesome.io/plugins/contacts/index.md) plugin. This plugin allows you to create, read, pick, and delete contacts on the device. The plugin is available on Android, iOS and Web. ``` import { Contacts, EmailAddressType, PhoneNumberType, PostalAddressType } from '@capawesome-team/capacitor-contacts'; const createContact = async () => { return Contacts.createContact({ contact: { givenName: 'John', familyName: 'Doe', emailAddresses: [ { value: 'mail@example.com', type: EmailAddressType.Home, isPrimary: true } ], phoneNumbers: [ { value: '1234567890', type: PhoneNumberType.Mobile, isPrimary: true } ], postalAddresses: [ { street: '123 Main St', city: 'Springfield', state: 'IL', postalCode: '62701', country: 'USA', type: PostalAddressType.Home, isPrimary: true } ] } }); }; ``` Check out the [announcement](https://capawesome.io/blog/announcing-the-capacitor-contacts-plugin/index.md) for more information. ### Nfc The [Nfc](https://capawesome.io/plugins/nfc/index.md) plugin received various bug fixes. ### PostHog ##### New `getFeatureFlagPayload(...)` method The [PostHog](https://capawesome.io/plugins/posthog/index.md) plugin now includes a new `getFeatureFlagPayload(...)` method. This method allows you to get the payload of a feature flag by its key: ``` import { Posthog } from "@capawesome-team/capacitor-posthog"; const getFeatureFlagPayload = async () => { const { value } = await Posthog.getFeatureFlagPayload({ key: "beta_feature", }); return value; }; ``` ### Speech Recognition The [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin received various bug fixes and improvements. ##### Contextual Strings You can now provide contextual strings to the `startListening(...)` method. Contextual strings are phrases that should be recognized, even if they are not in the system vocabulary. For this, a new option has been added to the `startListening(...)` method: ``` import { SpeechRecognition } from "@capawesome-team/capacitor-speech-recognition"; const startListening = async () => { await SpeechRecognition.startListening({ contextualStrings: ["Capacitor"], }); }; ``` ##### Background Audio You can now play background audio while using the Speech Recognition plugin. This allows you to play audio while the speech recognition is listening. For this, three new options have been added to the `startListening(...)` and `stopListening(...)` methods: ``` import { AudioSessionCategory, SpeechRecognition } from "@capawesome-team/capacitor-speech-recognition"; const startListening = async () => { await SpeechRecognition.startListening({ // Set the audio session category to play and record // for recording (input) and playback (output) of audio. audioSessionCategory: AudioSessionCategory.PlayAndRecord, // Do not deactivate the audio session when the plugin stops listening. // Otherwise, the background audio will be stopped as well. deactivateAudioSessionOnStop: false, }); }; const stopListening = async () => { await SpeechRecognition.stopListening({ // Do not deactivate the audio session when the plugin stops listening. // Otherwise, the background audio will be stopped as well. deactivateAudioSession: false, }); }; ``` ##### Permission Types The [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin now supports different permission types. You can now request the following permissions: ``` import { SpeechRecognition } from "@capawesome-team/capacitor-speech-recognition"; const requestPermissions = async () => { const { audioRecording, speechRecognition } = await SpeechRecognition.requestPermissions({ permissions: ["audioRecording", "speechRecognition"], }); }; ``` The `audioRecording` permission is available on Android and iOS, while the `speechRecognition` permission is only available on iOS. # Capawesome May 2025 Update The Capawesome May update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Blog Check out our latest blog post [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/index.md) to learn how to securely store credentials and authenticate users using the [Biometrics](https://capawesome.io/plugins/biometrics/index.md) and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugins. It's a cross-platform solution that works on both Android and iOS, and it provides a secure way to store sensitive information such as passwords, tokens, or other credentials. ## CLI The [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) has been updated with several new commands and improvements this month. ### New `apps:channels:get` command The new `apps:channels:get` command allows you to retrieve information about a specific channel in your app. You can use this command to get details such as the channel's ID, name, and metadata. In combination with the `--json` option, you can easily integrate this command into your scripts or workflows to automate tasks related to your app's channels. For example, the following command retrieves the information about a specific channel in JSON format and extracts the channel ID using [jq](https://github.com/jqlang/jq): ``` npx @capawesome/cli apps:channels:get --app-id my-app-id --channel-id my-channel-id --json | jq ".id" ``` ### New `apps:channels:list` command The new `apps:channels:list` command allows you to list all channels in your app. You can use this command to get an overview of all channels and their metadata. Again, you can use the `--json` option to get the output in JSON format, which makes it easy to integrate into your scripts or workflows. Thanks to the `--limit` and `--offset` options, you can also paginate through the list of channels. ``` npx @capawesome/cli apps:channels:list --app-id my-app-id --limit 10 --offset 0 --json ``` ### New `apps:channels:update` command The new `apps:channels:update` command allows you to update the name or bundle limit of a specific channel in your app. ``` npx @capawesome/cli apps:channels:update --app-id my-app-id --channel-id my-channel-id --name "New Channel Name" --bundle-limit 100 ``` ## Cloud ### New Logs page We have introduced a new Logs page in the Capawesome Cloud Console. This page allows you to view and filter logs for your app, making it easier to debug your implementation when getting started. View detailed information on requests and responses, including status and body, to help you identify issues before they become critical. Read more about the new Logs page in the [docs](https://capawesome.io/cloud/live-updates/logs/index.md). Capawesome Cloud App Logs ## Plugins ### Audio Recorder The [Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin got several new features and improvements this month. ##### New `bitRate` property The `StartRecordingOptions` now includes a new `bitRate` property. This property allows you to specify the audio encoding bit rate in bits per second (bps) for the recorded audio. This gives you more control over the audio quality and file size of the recording. ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const startRecording = async () => { await AudioRecorder.startRecording({ bitRate: 128000, // 128 kbps }); }; ``` ##### New `duration` property The `StopRecordingResult` and `RecordingStoppedEvent` now include a new `duration` property. This property provides the duration of the recorded audio in milliseconds, allowing you to easily access the length of the recording. ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const stopRecording = async () => { const { blob, duration, uri } = await AudioRecorder.stopRecording(); console.log(`Recording duration: ${duration} ms`); }; ``` ##### New `sampleRate` property The `StartRecordingOptions` now includes a new `sampleRate` property. This property allows you to specify the sample rate for the recorded audio. Just like the `bitRate` property, this gives you more control over the audio quality and file size of the recording. The sample rate is specified in Hertz (Hz) and can be set to common values such as 44100 Hz or 48000 Hz. ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const startRecording = async () => { await AudioRecorder.startRecording({ sampleRate: 44100, }); }; ``` This property is optional and defaults to 44100 if not specified. ##### New `recordingPaused` event In order to handle audio interruptions, the plugin now emits a new `recordingPaused` event. This event is triggered when the recording is paused, allowing you to handle any necessary actions in your app. ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; AudioRecorder.addListener('recordingPaused', () => { console.log('Recording paused'); }); ``` ### Biometrics We have published a new [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin. This plugin allows you to use biometric authentication (such as fingerprint or face recognition) in your Capacitor app. The plugin provides a simple API for authenticating users and checking if biometric authentication is available on the device. The plugin is available on Android and iOS. ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { try { await Biometrics.authenticate({ title: 'Authentication Required', subtitle: 'Please authenticate to continue', cancelButtonText: 'Cancel', iosFallbackButtonText: 'Use Passcode', }); } catch (error) { if (error.code === ErrorCode.USER_CANCELED) { console.log('User canceled the authentication.'); } else if (error.code === ErrorCode.NOT_ENROLLED) { console.log('No biometric authentication enrolled.'); } else if (error.code === ErrorCode.NOT_AVAILABLE) { console.log('Biometric authentication not available.'); } else { console.log('Another error occurred:', error); } } }; ``` Check out the [announcement post](https://capawesome.io/blog/announcing-the-capacitor-biometrics-plugin/index.md) for more details. ### Contacts The [Contacts](https://capawesome.io/plugins/contacts/index.md) plugin has received several new features and improvements this month. ##### New `displayContactById(...)` method The `displayContactById(...)` method allows you to display a contact in the system's default contacts app. This is useful for apps that need to show contact details without having to implement a custom UI. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const displayContact = async (id: string) => { await Contacts.displayContactById({ id, }); }; ``` You just need to pass the contact ID of the contact you want to display. The system will then open the default contacts app and show the contact details. ##### New `updateContactById(...)` and `displayUpdateContactById(...)` methods The `updateContactById(...)` method allows you to update an existing contact by its ID. This is useful for apps that need to modify contact details without having to create a new contact. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const updateContactById = async (id: string) => { await Contacts.updateContactById({ id, contact: { givenName: 'John', familyName: 'Doe' } }); }; ``` It's important to note that all fields are required to be provided when updating a contact, even if you only want to change one field. Fields that are not provided will be removed from the contact. This behavior will be improved in a future release to allow partial updates. In addition, the `displayUpdateContactById(...)` method allows you to display a contact in the system's default contacts app with the option to update it. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const displayUpdateContact = async (id: string) => { await Contacts.displayUpdateContactById({ id, }); }; ``` This way, you don't have to implement a custom UI for updating contacts, and you can leverage the system's default contacts app for this purpose. ##### New `countContacts(...)` method The `countContacts(...)` method allows you to count the number of contacts in the device's contacts database. This allows you to show the number of contacts in your app without having to fetch all contacts. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const countContacts = async () => { const { count } = await Contacts.countContacts(); console.log(`Number of contacts: ${count}`); }; ``` ##### Accounts and Groups support The plugin now supports Android accounts and iOS groups. This allows you to manage contacts that are associated with specific accounts or groups, making it easier to organize and access contacts in your app. For example, on Android, you can retrieve the accounts associated with the device using the `getAccounts(...)` method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getAccounts = async () => { const { accounts } = await Contacts.getAccounts(); return accounts; }; ``` You can then create a contact and associate it with an account using the `account` property of the `Contact` object: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createContact = async () => { return Contacts.createContact({ contact: { account: { name: 'john@doe.tld', type: 'com.google' }, givenName: 'John', familyName: 'Doe' }, }); }; ``` On iOS, you can retrieve the groups associated with the device using the `getGroups(...)` method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getGroups = async () => { const { groups } = await Contacts.getGroups(); return groups; }; ``` Just like with accounts, you can create a contact and associate it with one (or more) group(s) using the `groupIds` property of the `Contact` object: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createContact = async () => { return Contacts.createContact({ contact: { groupIds: ['904DE809-D144-4562-8552-DFEB91F0E4BD:ABGroup'], givenName: 'John', familyName: 'Doe' }, }); }; ``` You can even create a new group using the [`createGroup(...)`](https://capawesome.io/plugins/contacts/#creategroup) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createGroup = async () => { return Contacts.createGroup({ group: { name: 'Friends' } }); }; ``` ##### Pagination support That's not all! The plugin now supports pagination for fetching contacts. This allows you to fetch contacts in smaller chunks, which can improve performance and reduce memory usage, especially when dealing with a large number of contacts. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const getContacts = async () => { const { contacts } = await Contacts.getContacts({ limit: 10, // Fetch 10 contacts at a time offset: 0, // Start from the first contact }); console.log(contacts); }; ``` ### Secure Preferences We have published a new [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. This plugin is a drop-in replacement for the [Capacitor Preferences](https://capacitorjs.com/docs/apis/preferences) plugin and and allows you to securely store key/value pairs such as passwords, tokens or other sensitive information. ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const set = async () => { await SecurePreferences.set({ key: 'password', value: '123456', }); }; const get = async () => { const { value } = await SecurePreferences.get({ key: 'password', }); console.log(value); }; ``` Check out the [announcement post](https://capawesome.io/blog/announcing-the-capacitor-secure-preferences-plugin/index.md) for more details. ### Wifi #### New `addNetwork(...)` method The [Wifi](https://capawesome.io/plugins/wifi/index.md) plugin now includes a new `addNetwork(...)` method. This method displays a system dialog that allows the user to add a new Wi-Fi network with predefined credentials. This is useful for apps that require the user to connect to a specific Wi-Fi network. ``` import { Wifi } from '@capawesome-team/capacitor-wifi'; const addNetwork = async () => { await Wifi.addNetwork({ ssid: 'MyNetwork', password: 'MyPassword', }); } ``` Here is a short video demonstrating how this looks like in practice on Android: \[ \](/assets/videos/posts/20250601_plugins_wifi_addnetwork.mp4) # Capawesome November 2025 Update The Capawesome November update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Native Builds We are excited to announce the launch of Native Builds for [Capawesome Cloud](https://capawesome.io/cloud/index.md), a production-ready cloud platform for compiling iOS and Android applications. This service eliminates the need for developers to maintain local CI/CD infrastructure or complex build configurations. Native Builds provides pre-optimized environments with current Node.js, Java, and Xcode versions running on macOS 15 with M4 instances. You can connect your GitHub, GitLab, Bitbucket, or Azure DevOps repositories and trigger builds manually or automatically. The platform executes builds 3-5x faster compared to traditional CI/CD platforms like GitHub Actions. The service connects directly to TestFlight, the App Store, and Google Play Store, allowing one-click submissions following successful builds. You can also use the CLI to trigger builds from any operating system—Windows, Linux, or macOS—without requiring Xcode or Android Studio installation locally. Check out our [announcement post](https://capawesome.io/blog/announcing-capawesome-cloud-native-builds/) to learn more about Native Builds and how to get started. ## Plugins ### Age Signals The [Age Signals](https://capawesome.io/plugins/age-signals/index.md) plugin has been updated to version 0.1.1 and now includes support for iOS using the official `DeclaredAgeRange` API. This allows developers to access age-related signals on iOS devices, enabling age-appropriate content delivery and compliance with age-related regulations. ### Audio Player The [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin has received several bug fixes and improvements. Notably, we have added a new `stop` event that is emitted when audio playback stops. Additionally, we fixed an issue on Android where audio would continue playing even after the app was destroyed, and improved audio focus handling on Android to ensure proper playback behavior. ### Datetime Picker The [Datetime Picker](https://capawesome.io/plugins/datetime-picker/index.md) plugin now includes a new [`cancel()`](https://capawesome.io/plugins/datetime-picker/#cancel) method that dismisses an active datetime picker dialog. This is useful when you need to programmatically close the picker without waiting for user interaction, such as when implementing timeout logic or conditional cancellation based on app state changes. ``` import { DatetimePicker } from '@capawesome-team/capacitor-datetime-picker'; import { App } from '@capacitor/app'; App.addListener('appStateChange', ({ isActive }) => { if (!isActive) { DatetimePicker.cancel(); } }); ``` If there is no active picker, this method does nothing. This method is available on Android and iOS. ### Firebase #### Authentication The [Firebase Authentication](https://capawesome.io/plugins/firebase/authentication/index.md) plugin now includes a new [`getIdTokenResult(...)`](https://capawesome.io/plugins/firebase/authentication/#getidtokenresult) method that retrieves a deserialized JSON Web Token (JWT) containing user identification data. This provides detailed token information beyond just the token string, including authentication time, expiration time, sign-in provider, and custom claims. ``` import { FirebaseAuthentication } from '@capawesome-team/capacitor-firebase-authentication'; const getTokenDetails = async () => { const currentUser = await FirebaseAuthentication.getCurrentUser(); if (!currentUser) { return; } const result = await FirebaseAuthentication.getIdTokenResult({ forceRefresh: true }); console.log('Token claims:', result.claims); console.log('Expires:', new Date(result.expirationTime)); }; ``` This is particularly useful when you need to inspect token claims or verify authentication details programmatically. ### Media Session The [Media Session](https://capawesome.io/plugins/media-session/index.md) plugin has received a bug fix that ensures the media session is properly stopped when the app is closed on Android. This prevents media controls from remaining active after the app has been destroyed. ### ML Kit #### Barcode Scanning The [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin now supports an `autoZoom` option for the [`scan(...)`](https://capawesome.io/plugins/mlkit/barcode-scanning/#scan) method on Android. When enabled, this feature automatically adjusts the camera's zoom to optimize barcode detection, enhancing the scanning experience by dynamically focusing on barcodes at various distances. ``` import { BarcodeScanner, BarcodeFormat } from '@capawesome-team/capacitor-mlkit-barcode-scanning'; const scan = async () => { const { barcodes } = await BarcodeScanner.scan({ formats: [BarcodeFormat.QrCode], autoZoom: true, }); return barcodes; }; ``` This option is available on Android. ### Posthog The [Posthog](https://capawesome.io/plugins/posthog/index.md) plugin now supports session replay functionality, allowing you to record and playback user interactions within your Capacitor app. This feature captures visual snapshots of user sessions, enabling you to understand user behavior and troubleshoot issues. You can enable session replay by setting `enableSessionReplay: true` in your configuration: ``` import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { Posthog: { apiKey: 'your_key', enableSessionReplay: true, sessionReplayConfig: { screenshotMode: false, maskAllTextInputs: true, captureNetworkTelemetry: true } } } }; ``` The plugin provides granular control over what data is captured, including options to mask text inputs and images, enable network telemetry, and control the snapshot interval. Check out the [documentation](https://capawesome.io/plugins/posthog/index.md) to learn more about session replay configuration options. ### Purchases The [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin now supports Android, bringing in-app purchase functionality to both Android and iOS platforms. This plugin provides a simple API for managing purchases, subscriptions, and product information across mobile platforms. ### Screen Orientation The [Screen Orientation](https://capawesome.io/plugins/screen-orientation/index.md) plugin has been updated to make the `LockOptions` parameter optional for the [`lock(...)`](https://capawesome.io/plugins/screen-orientation/#lock) method. If no options are provided, the method now locks the screen orientation to the current orientation by default. ``` import { ScreenOrientation } from '@capawesome-team/capacitor-screen-orientation'; const lock = async () => { await ScreenOrientation.lock(); }; ``` This makes it easier to quickly lock the screen to its current orientation without needing to specify the orientation type explicitly. # Capawesome October 2025 Update The Capawesome October update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Equivalent Version Codes for Live Updates We have added support for Equivalent Version Codes in [Capawesome Cloud](https://capawesome.io/cloud/index.md), giving you more control over when live updates are delivered to your users. This new parameter works alongside minimum and maximum version code constraints to create precise compatibility rules. The Equivalent parameter functions as a skip condition: if a user's native app already has the exact version code you've marked as equivalent, the live update won't be downloaded. This prevents redundant downloads when the user's app already matches the bundle version. For example, when creating a bundle using the CLI: ``` npx @capawesome/cli apps:bundles:create --android-eq 11 ``` If a user's Android app is at version code 11, they won't receive this bundle since they already have the equivalent version. This feature helps reduce unnecessary data transfer and ensures users only download updates when needed. Check out our [documentation on versioned builds](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/#versioned-builds) to learn more about restricting live updates to specific native versions. ## Plugins ### Age Signals We have published a new [Age Signals](https://capawesome.io/plugins/age-signals/index.md) plugin that allows you to request user age signals from Google Play on Android. This plugin enables developers to access Google's Play Age Signals API to retrieve age-related information about users within their applications. The plugin provides key capabilities including age verification status, age range data for supervised accounts, and parental approval tracking. Here's a simple example of how to use the plugin: ``` import { AgeSignals } from '@capawesome-team/capacitor-age-signals'; const checkAgeSignals = async () => { const result = await AgeSignals.checkAgeSignals(); console.log('User Status:', result.userStatus); console.log('Age Lower:', result.ageLower); console.log('Age Upper:', result.ageUpper); }; ``` Check out the [documentation](https://capawesome.io/plugins/age-signals/index.md) to learn more about the plugin and its features. ### App Update The [App Update](https://capawesome.io/plugins/app-update/index.md) plugin now supports an `androidPackageName` option for the [`openAppStore(...)`](https://capawesome.io/plugins/app-update/#openappstore) method. This parameter allows you to specify which app to open in the Google Play Store, making it easy to direct users to a different application if needed. ``` import { AppUpdate } from '@capawesome-team/capacitor-app-update'; const openAppStore = async () => { await AppUpdate.openAppStore({ androidPackageName: 'com.example.app' }); }; ``` If you omit this parameter, the plugin defaults to opening the current app's Play Store listing. ### Audio Player The [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin now supports web asset paths, providing a streamlined approach for playing bundled audio resources. The new `src` parameter represents the path to the web asset file to play and works across all platforms. On Android, the plugin is limited to web assets only, while iOS and Web support both web assets and remote URLs. If you provide multiple audio sources, the plugin follows a clear priority: `blob` takes precedence over `src`, and `uri` takes priority over `src`. ### Bluetooth Low Energy ##### New `autoReconnect` option The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin now supports an `autoReconnect` option when establishing device connections. This feature enables automatic reconnection to peripherals when the connection is lost, improving reliability for background operations. The option is available on both Android and iOS (17.0+) and defaults to `false`. Here's how to use it with the [`connect(...)`](https://capawesome.io/plugins/bluetooth-low-energy/#connect) method: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; await BluetoothLowEnergy.connect({ deviceId: '00:00:00:00:00:00', autoReconnect: true }); ``` ##### New `isLocationEnabled()` method The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin now includes a new [`isLocationEnabled()`](https://capawesome.io/plugins/bluetooth-low-energy/#islocationenabled) method that checks whether location services are active on the device. This is particularly useful on Android, where location services are often required for BLE scanning operations to function properly. ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const result = await BluetoothLowEnergy.isLocationEnabled(); console.log(result.enabled); ``` This method is available on Android only. ### Datetime Picker The [Datetime Picker](https://capawesome.io/plugins/datetime-picker/index.md) plugin now supports a `minuteInterval` option for the [`present(...)`](https://capawesome.io/plugins/datetime-picker/#present) method. This parameter controls the granularity of the minute selector, allowing you to set incremental steps for minute selection rather than requiring users to pick every single minute. For example, setting it to 15 means users can only select times at 0, 15, 30, or 45 minutes past the hour: ``` import { DatetimePicker } from '@capawesome-team/capacitor-datetime-picker'; const present = async () => { const { value } = await DatetimePicker.present({ mode: 'time', minuteInterval: 15, theme: 'dark', locale: 'en-US', }); return value; }; ``` The value must be evenly divisible into 60 and defaults to 1 (every minute). This option is only available on iOS for time and datetime modes. ### Geocoder We have published a new [Geocoder](https://capawesome.io/plugins/geocoder/index.md) plugin that enables bidirectional location conversion on Android, iOS, and Web. The plugin translates addresses into geographic coordinates and converts coordinates back into human-readable addresses. The plugin integrates with native APIs on mobile platforms for accuracy and reliability, and supports multiple geocoding providers (Google Maps and OpenStreetMap) on web. Here are simple examples of both geocoding and reverse geocoding: ``` import { Geocoder } from '@capawesome-team/capacitor-geocoder'; const geocode = async () => { const result = await Geocoder.geocode({ address: '1600 Amphitheatre Parkway, Mountain View, CA', }); }; const geodecode = async () => { const result = await Geocoder.geodecode({ latitude: 37.422, longitude: -122.084, }); }; ``` Check out the [documentation](https://capawesome.io/plugins/geocoder/index.md) to learn more about the plugin and its features. ### Media Session We have published a new [Media Session](https://capawesome.io/plugins/media-session/index.md) plugin that enables you to manage media playback controls across Android, iOS, and Web platforms. The plugin facilitates interaction with hardware media keys, lock screen controls, and notification-based media management. Here's a basic example of how to use the plugin: ``` import { MediaSession, MediaSessionAction } from '@capawesome-team/capacitor-media-session'; const setupMediaSession = async () => { await MediaSession.setMetadata({ title: 'Song Name', artist: 'Artist Name', album: 'Album Name', }); await MediaSession.registerActionHandler({ action: MediaSessionAction.Play }); MediaSession.addListener('action', async (event) => { if (event.action === MediaSessionAction.Play) { // Handle play action } }); }; ``` Check out the [documentation](https://capawesome.io/plugins/media-session/index.md) to learn more about the plugin and its features. ### Wifi The [Wifi](https://capawesome.io/plugins/wifi/index.md) plugin now allows connection to networks without internet access on Android. This fix ensures that you can connect to local Wi-Fi networks that don't have internet connectivity, which is particularly useful for IoT devices and local network applications. # Capawesome September 2025 Update The Capawesome September update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Capver We have published a new CLI called [Capver](https://github.com/capawesome-team/capver) for managing versions in a Capacitor project across multiple platforms. The CLI helps you set, increment, and synchronize versions in your `package.json`, `capacitor.config.json`, `Info.plist` (iOS), and `build.gradle` (Android) files. This ensures that all platforms use the same version number, making it easier to manage and release your app. First, install the CLI using npm: ``` npm install -g @capawesome/capver ``` After installing, you can use the following commands to manage your app versions: ``` # Initialize a new Capacitor project with version 0.0.1 npx @capawesome/capver set 0.0.1 # Increment the patch version npx @capawesome/capver patch # Increment the minor version npx @capawesome/capver minor # Increment the major version npx @capawesome/capver major # Increment only the build number npx @capawesome/capver hotfix # Check version consistency across platforms npx @capawesome/capver get # Synchronize all platforms to use the highest version found npx @capawesome/capver sync ``` Check out the [documentation](https://github.com/capawesome-team/capver) to learn more about the CLI and its features. ## Cloud ### Audit Logs You can now view audit logs for your Capawesome Cloud organizations. Audit logs provide a detailed record of actions taken within your organization, including deployments, configuration changes, and user activities. This feature helps you monitor and track changes to your organization for security and compliance purposes. Read more about in our [documentation](https://capawesome.io/cloud/organizations/audit-logs/index.md). ## Newsletter After a long break, we have finally sent out a new [Capawesome Newsletter](https://cloud.capawesome.io/newsletter/) with some cool updates in offers exclusively for our newsletter subscribers. If you haven't subscribed yet, make sure to [sign up](https://cloud.capawesome.io/newsletter/) to stay updated with the latest news and updates from Capawesome. ## Plugins ### Audio Player We have published a new [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin that allows you to play audio files on Android, iOS and Web. Here's a simple example of how to use the plugin to play an audio file: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; import { Capacitor } from '@capacitor/core'; import { Filesystem } from '@capacitor/filesystem'; const play = async () => { if (Capacitor.getPlatform() === 'web') { const assetUrl = 'https://www.example.com/audio.mp3'; const response = await fetch(assetUrl); const blob = await response.blob(); await AudioPlayer.play({ blob, loop: false, volume: 100, position: 0 }); } else { const { uri } = await Filesystem.getUri({ directory: FilesystemDirectory.Documents, path: 'audio.mp3', }); await AudioPlayer.play({ uri, loop: false, volume: 100, position: 0 }); } }; ``` Check out the [documentation](https://capawesome.io/plugins/audio-player/index.md) to learn more about the plugin and its features. ### Bluetooth Low Energy The [Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin received multiple improvements this month. ##### Auto Initialization The plugin now automatically initializes itself on the first call to any of its methods. This makes it easier to use the plugin, as you no longer need to call the `initialize()` method manually. ##### Manufacturer Data You can now include manufacturer-specific data when advertising as a peripheral. This allows you to provide additional information about your device that can be used by other Bluetooth devices. Here's an example of how to start advertising with manufacturer data: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const startAdvertising = async () => { await BluetoothLowEnergy.startAdvertising({ manufacturerData: { 0xffff: [1, 2, 3] }, name: 'MyDevice', services: [] }); }; ``` ### Contacts The [Contacts](https://capawesome.io/plugins/contacts/index.md) plugin now includes a new `openSettings()` method that opens the device's settings page for the app. This is useful for prompting users to grant permissions if they have previously denied access to contacts. ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const openContactsSettings = async () => { await Contacts.openSettings(); }; ``` ### Live Update The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin has received small bug fixes. Please check out the [CHANGELOG.md](https://capawesome.io/plugins/live-update/#changelog) for more details. ### Speech Recognition The [Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin now supports a `taskHint` option for the `startListening()` method on iOS. This option allows you to specify the type of speech recognition task you are performing, which can help improve accuracy and performance. ``` import { SpeechRecognition, TaskHint } from '@capawesome-team/capacitor-speech-recognition'; const startListening = async () => { await SpeechRecognition.startListening({ taskHint: TaskHint.Dictation }); }; ``` Check out the `TaskHint` enum in the [documentation](https://capawesome.io/plugins/speech-recognition/#taskhint) for all available options. ### Speech Synthesis The [Speech Synthesis](https://capawesome.io/plugins/speech-synthesis/index.md) plugin now automatically initializes itself on the first call to any of its methods. This makes it easier to use the plugin, as you no longer need to call the `initialize()` method manually. ### Share Target The [Share Target](https://capawesome.io/plugins/share-target/index.md) plugin now provides the `mimeType` and `name` properties for shared files. This allows you to get more information about the files that are shared with your app. ``` import { Capacitor } from '@capacitor/core'; import { ShareTarget } from '@capawesome-team/capacitor-share-target'; const addListener = async () => { await ShareTarget.addListener('shareReceived', (event) => { const firstFile = event.files[0]; console.log('File URI: ', firstFile.uri); console.log('File MIME type:', firstFile.mimeType); console.log('File name:', firstFile.name); }); }; ``` # Capawesome February 2026 Update The Capawesome February update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### Automations We are excited to introduce Automations for [Capawesome Cloud](https://capawesome.io/cloud/index.md)! Automations let you trigger builds automatically from Git events — no external CI/CD required. You can configure branch push and tag creation triggers with pattern matching, filter by commit messages, and define full build settings per automation. \[ \](/assets/videos/posts/announcing-capawesome-cloud-automations/05f0e923-8ac3-4297-bb60-f4ad403851bf.mp4) When an automation is created, a webhook is automatically registered in your connected Git repository. You can also enable skip CI functionality by including keywords like `[skip ci]` in your commit messages. Check out the [announcement post](https://capawesome.io/blog/announcing-capawesome-cloud-automations/index.md) and the [Automations documentation](https://capawesome.io/cloud/automations/index.md) for more details. ### Webhooks Capawesome Cloud now supports outgoing webhooks, allowing you to receive real-time HTTP notifications for key events like builds, deployments, and jobs. Each webhook payload is signed using HMAC-SHA256 so you can verify authenticity, and a full delivery history is available in the Console for troubleshooting. On top of that, we added native notification support for Discord, Slack, and Microsoft Teams. When you register a webhook URL from one of these platforms, Capawesome Cloud automatically detects the platform and formats the notification accordingly. Check out the [Webhooks documentation](https://capawesome.io/cloud/webhooks/index.md) to learn more. ### Channel Discovery Channel discovery allows the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin to fetch the list of available channels without requiring authentication. This is useful for apps that want to let developers or QA select a channel from a list, for example to switch to a beta channel. To enable channel discovery, navigate to the [Settings](https://console.cloud.capawesome.io/apps/_/settings) page in the Capawesome Cloud Console and enable the "Channel Discovery" toggle in the "Live Update Channels" section. Once enabled, you can use the [`fetchChannels(...)`](https://capawesome.io/plugins/live-update/#fetchchannels) method of the Live Update plugin to retrieve the available channels. Check out the [Channels documentation](https://capawesome.io/cloud/live-updates/channels/#channel-discovery) to learn more. ### Build Artifact Deletion You can now delete build artifacts directly from the Capawesome Cloud Console. This gives you more control over storage usage and makes it easier to clean up old or unnecessary build outputs. ### Polar We have migrated our billing infrastructure to [Polar](https://polar.sh/). This change improves the checkout experience and provides a more streamlined subscription management process. Existing subscriptions through our previous payment provider Lemon Squeezy continue to work as before. If you experience any payment issues, please reach out to our support team so we can assist with the migration. ## Plugins ### Android Gradle Plugin 9 Compatibility All Capawesome plugins have been updated to support Android Gradle Plugin (AGP) 9.0. AGP 9 removed support for the legacy `proguard-android.txt` default ProGuard file, which caused build failures in projects using the latest Android tooling. All plugins now use the updated `proguard-android-optimize.txt` configuration, ensuring compatibility with AGP 9 out of the box. If you're upgrading to AGP 9, make sure to update all Capawesome plugins to their latest versions. Check out our [blog post](https://capawesome.io/blog/how-to-fix-capacitor-plugin-build-errors-with-agp-9/index.md) for more details on the issue and workarounds. ### Google Sign-In We are excited to announce the initial release of the [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) plugin! This plugin lets you add Google Sign-In to your Capacitor app on Android, iOS, and Web with a single, lightweight integration. The plugin supports authentication with ID tokens, authorization with OAuth scopes, and user profile retrieval. It returns comprehensive user data including email, display name, profile picture, and access tokens: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const signIn = async () => { const result = await GoogleSignIn.signIn(); console.log('Email:', result.email); console.log('Display Name:', result.displayName); }; ``` Check out the [documentation](https://capawesome.io/plugins/google-sign-in/index.md) to learn more about getting started with the Google Sign-In plugin. ### OAuth We are excited to announce the initial release of the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin! This plugin provides a complete OAuth 2.0 and OpenID Connect implementation for Capacitor with support for PKCE, auto-discovery, and token refresh on Android, iOS, and Web. The plugin includes methods for login, logout, token refresh, and ID token decoding: ``` import { OAuth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await OAuth.login({ authorizationEndpoint: 'https://example.com/authorize', tokenEndpoint: 'https://example.com/token', clientId: 'your-client-id', redirectUrl: 'your-redirect-url', scope: 'openid profile email', }); console.log('Access Token:', result.accessToken); }; ``` Check out the [documentation](https://capawesome.io/plugins/oauth/index.md) to learn more about getting started with the OAuth plugin. ### SQLite The [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin has received several notable updates this month. #### Key-Value Store A new `SqliteKeyValueStore` class provides a simple key-value API on top of SQLite, making it easy to store and retrieve data without writing SQL: ``` import { Sqlite, SqliteKeyValueStore } from '@capawesome-team/capacitor-sqlite'; const store = new SqliteKeyValueStore(Sqlite); await store.set({ key: 'username', value: 'robin' }); const result = await store.get({ key: 'username' }); console.log('Value:', result.value); ``` #### Electron Support The plugin now supports Electron using Node.js's native `node:sqlite` module, which requires Node.js 22.5.0+ and Electron 33+. Databases are stored in the `userData` directory on each platform. Note that encryption features are not supported on Electron. #### Bundled SQLite Engine A new opt-in option lets you bundle the [requery/sqlite-android](https://github.com/nicholasgasior/sqlite-android) library on Android, giving you control over the SQLite version used by your app instead of relying on the device's built-in version. ### PostHog The [PostHog](https://capawesome.io/plugins/posthog/index.md) plugin now includes a new [`getDistinctId()`](https://capawesome.io/plugins/posthog/#getdistinctid) method that returns the distinct ID of the current user. This is useful when you need to correlate PostHog events with your own backend or identify the current user in support workflows: ``` import { Posthog } from '@capawesome/capacitor-posthog'; const getDistinctId = async () => { const result = await Posthog.getDistinctId(); console.log('Distinct ID:', result.distinctId); }; ``` This method is available on Android, iOS, and Web. ### Purchases The [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin now supports `basePlanId` and `offerId` options in the [`purchaseProduct(...)`](https://capawesome.io/plugins/purchases/#purchaseproduct) method on Android. This allows you to specify which base plan and offer to use when purchasing a subscription, giving you more control over Google Play subscription pricing: ``` import { Purchases } from '@capawesome-team/capacitor-purchases'; const purchase = async () => { await Purchases.purchaseProduct({ productId: 'com.example.premium', basePlanId: 'monthly', offerId: 'intro-offer', }); }; ``` These options are available on Android. ### Firebase #### Cloud Functions The [Firebase Cloud Functions](https://capawesome.io/plugins/firebase/cloud-functions/index.md) plugin now supports a `timeout` option when calling functions, allowing you to configure how long the client waits before timing out. Additionally, the `regionOrCustomDomain` option is now properly supported on Android, ensuring consistent behavior across platforms. ### LibSQL The [LibSQL](https://capawesome.io/plugins/libsql/index.md) plugin now supports the [`sync(...)`](https://capawesome.io/plugins/libsql/#sync) method on Android, enabling you to synchronize your local embedded replica with the remote database: ``` import { Libsql } from '@capawesome/capacitor-libsql'; const sync = async () => { await Libsql.sync({ connectionId: 'my-connection', }); }; ``` This method was previously only available on iOS and is now supported on both Android and iOS. ## Try Capawesome Cloud If you're looking for a powerful cloud platform to build, deploy, and manage your Capacitor apps, check out [Capawesome Cloud](https://capawesome.io/cloud/index.md). It provides native builds, live updates, app store publishing, and more — all in one place. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion This was a packed month for Capawesome. On the Cloud side, Automations and Webhooks bring powerful CI/CD and notification capabilities, while several new plugins — including Google Sign-In, OAuth, and major SQLite enhancements — expand what you can build with Capacitor. If you have any questions or need help, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And don't forget to subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news and updates. # Capawesome January 2026 Update The Capawesome January update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Cloud ### App Store Connect API Key You can now authenticate with App Store Connect using an API Key when configuring an Apple App Store destination in [Capawesome Cloud](https://capawesome.io/cloud/index.md). Previously, authentication was only available via Apple ID and app-specific password. The new API Key method lets you upload a `.p8` private key file along with the Key ID and Issuer ID from App Store Connect, providing a more robust and streamlined way to submit your iOS builds to TestFlight. To get started, navigate to the [Destinations](https://console.cloud.capawesome.io/apps/_/destinations) page in the Capawesome Cloud Console and select `API Key` as the authentication method. Check out the [Apple App Store documentation](https://capawesome.io/cloud/app-store-publishing/destinations/apple-app-store/#api-key) for instructions on obtaining the required credentials. ### Play Store Release Status When configuring a Google Play Store destination, you can now choose the release status for your app submissions. Two options are available: - **Draft**: For initial releases that require manual review before publishing. This is useful for the very first release on a track where additional setup may be needed in Google Play Console. - **Completed**: For releases that should be automatically published to the selected track, which is the typical choice for subsequent releases. Check out the [Google Play Store documentation](https://capawesome.io/cloud/app-store-publishing/destinations/google-play-store/#release-status) to learn more. ### Protected Channels Channels can now be marked as "protected" to enhance the security of your live updates. When a channel is protected, all builds associated with that channel must be code-signed before they can be distributed to users. This ensures that only authorized builds are delivered through the protected channel. Check out the [Protected Channels documentation](https://capawesome.io/cloud/live-updates/channels/#protected-channels) to learn more. ### Pause and Resume Channels You can now pause and resume live update channels directly from the [Capawesome Cloud Console](https://console.cloud.capawesome.io/apps/_/channels) or via the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). When a channel is paused, devices stop receiving new live updates from that channel until you resume it. This is useful for temporary holds during maintenance windows, controlling production deployments while testing new bundles, or quickly halting problematic updates without deleting the channel. Check out the [blog post](https://capawesome.io/blog/capawesome-cloud-channel-pausing/index.md) for more details. ### Enforce 2FA for Organization Members Organizations can now enforce two-factor authentication (2FA) for all members. When 2FA enforcement is enabled, new members must have 2FA enabled on their account before they can join the organization. Members who are already part of the organization retain access, but they cannot disable 2FA while they remain a member. This gives organizations better control over account security across their teams. Check out the [Two-Factor Authentication documentation](https://capawesome.io/cloud/organizations/two-factor-authentication/index.md) to learn more. ## Plugins ### Android Edge-to-Edge Support The [Android Edge-to-Edge Support](https://capawesome.io/plugins/android-edge-to-edge-support/index.md) plugin has received important bug fixes and improvements. The plugin manages edge-to-edge display support on Android by applying proper insets to the webview, preventing Android's edge-to-edge changes from affecting apps that haven't been designed to support it. If you're targeting recent Android versions, make sure to update to the latest version of the plugin. ### Audio Player The [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin now supports adjustable playback rate with pitch preservation. You can control the playback speed using the [`setRate(...)`](https://capawesome.io/plugins/audio-player/#setrate) method, with values between 0.5 and 2.0 recommended for consistent behavior across devices: ``` import { AudioPlayer } from '@capawesome/capacitor-audio-player'; const setPlaybackRate = async () => { await AudioPlayer.setRate({ rate: 1.5 }); }; ``` This feature is available on Android (SDK 23+), iOS, and Web. ### Biometrics The [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin now includes a new [`getBiometricType()`](https://capawesome.io/plugins/biometrics/#getbiometrictype) method that returns the type of biometric authentication available on the device. If multiple biometric types are available, the method prioritizes them in the order Face > Iris > Fingerprint: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const getBiometricType = async () => { const result = await Biometrics.getBiometricType(); console.log('Biometric type:', result.type); }; ``` This method is available on Android and iOS. ### Purchases The [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin has received several new features this month. A new `productCategory` property is now available on Android to filter product queries. You can specify either `ProductCategory.InApp` for consumables and non-consumables or `ProductCategory.Subscription` for subscription products, giving you more control over which products are returned. The new [`getProductsByIds(...)`](https://capawesome.io/plugins/purchases/#getproductsbyids) method allows you to retrieve details for multiple products in a single call: ``` import { Purchases } from '@capawesome-team/capacitor-purchases'; const getProducts = async () => { const result = await Purchases.getProductsByIds({ productIds: ['com.example.premium', 'com.example.pro'] }); for (const product of result.products) { console.log('Product:', product.displayName); console.log('Price:', product.price); } }; ``` Transactions now include platform-specific verification data for server-side IAP validation. On iOS, transactions provide a JWS (JSON Web Signature) representation, while on Android they include the purchase token, original JSON data, and RSA signature. A new [`isIntroOfferAvailableForProduct(...)`](https://capawesome.io/plugins/purchases/#isintroofferavailableforproduct) method has been added to check whether a user is eligible for introductory offers on subscriptions. This leverages StoreKit 2's eligibility checks on iOS and examines available pricing phases on Android. ### Square Mobile Payments We are excited to announce the initial release of the [Square Mobile Payments](https://capawesome.io/plugins/square-mobile-payments/index.md) plugin! This unofficial Capacitor plugin integrates Square's Mobile Payments SDK, enabling you to accept in-person card payments through Square readers in your Android and iOS applications. The plugin supports multiple card entry methods including contactless (tap), chip insertion, swipe, and manual keying. It also includes reader management for pairing and monitoring, real-time event listeners for reader status and payment completion, and both online and offline payment processing with automatic synchronization. Check out the [documentation](https://capawesome.io/plugins/square-mobile-payments/index.md) to learn more about getting started with the Square Mobile Payments plugin. # Capawesome March 2026 Update The Capawesome March update is here! This update includes new features and improvements for [Capawesome Cloud](https://capawesome.io/cloud/index.md) and our [Plugins](https://capawesome.io/plugins/index.md). Let's take a look at the most important changes. ## Blog We published several new articles this month. Here are some highlights: - [Announcing Open Source AI Agent Skills for Capacitor](https://capawesome.io/blog/announcing-open-source-ai-agent-skills-for-capacitor/index.md) — We open sourced 7 AI agent skills that teach coding assistants like Claude and Cursor how to handle common Capacitor tasks, from plugin setup to version upgrades and cloud builds. If you haven't tried using AI agents with your Capacitor projects yet, this is a great place to start. - [How to Use SPM Package Traits in Capacitor 8](https://capawesome.io/blog/how-to-use-spm-package-traits-in-capacitor/index.md) — Learn how to use Swift Package Manager package traits to conditionally enable optional plugin dependencies like SQLCipher directly from your Capacitor config. - [How to Use Drizzle ORM with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/index.md) — A step-by-step guide to building type-safe database layers in your Capacitor app using Drizzle ORM and the SQLite plugin. - [How to Use Better Auth in Ionic and Capacitor Apps](https://capawesome.io/blog/how-to-use-better-auth-in-capacitor-apps/index.md) — Add mobile authentication to your Capacitor app with Better Auth, including social login with Apple and Google. ## Cloud ### iOS OTA Distribution You can now install iOS and Android builds directly on physical devices from the Capawesome Cloud Console. Each build artifact includes an install button and a QR code that you can share with testers and stakeholders — no TestFlight review delays, no Play Store submission, and no extra setup required. Combined with [Automations](https://capawesome.io/cloud/automations/index.md) and [notification integrations](https://capawesome.io/cloud/integrations/index.md), you can set up a fully hands-off distribution workflow where builds are triggered on push and your QA team gets notified in Slack or Discord when a new build is ready to install. Check out our [blog post](https://capawesome.io/blog/how-to-distribute-ios-and-android-apps-to-testers/index.md) for a detailed walkthrough of how to distribute test builds to your team. ### App Transfer You can now transfer apps between organizations directly from the Cloud Console. This is useful when you need to move an app to a different team or consolidate projects under a single organization. All associated data — builds, channels, certificates, and destinations — moves with the app. ### Default Environments Apps now support a default environment that is automatically applied to new builds when no environment is explicitly selected. This reduces repetitive configuration when you always build with the same environment and helps prevent accidental builds without the correct environment variables. You can set the default environment in the app settings in the [Cloud Console](https://console.cloud.capawesome.io). ### Web Certificate Support for Cloud Builds Web certificates in PEM format were previously only supported for manually uploaded build artifacts. You can now also use them with web builds created by the Capawesome Cloud build runners, so your Live Update bundles are automatically signed during the build process. Check out the [Web Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/web/index.md) documentation to learn more. ### Usage Limit Notifications Usage limit notifications previously only went to the organization owner and only when the limit was fully reached. Now, both owners and admins can receive notifications, and alerts are sent at 90% as well — giving your team a heads-up before limits are actually hit. You can configure the recipients in the organization settings in the [Cloud Console](https://console.cloud.capawesome.io). ## Plugins ### Android Foreground Service The [Android Foreground Service](https://capawesome.io/plugins/android-foreground-service/index.md) plugin now supports a `notificationTapped` event listener that fires when a user taps on the foreground service notification: ``` import { ForegroundService } from '@capawesome-team/capacitor-android-foreground-service'; await ForegroundService.addListener('notificationTapped', (event) => { console.log('Notification tapped:', event.notificationId); }); ``` This listener is available on Android. ### Apple Sign-In We are excited to announce the initial release of the [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) plugin! This plugin lets you add Apple Sign-In to your Capacitor app on Android, iOS, and Web: ``` import { AppleSignIn } from '@capawesome/capacitor-apple-sign-in'; const result = await AppleSignIn.signIn({ scopes: ['EMAIL', 'FULL_NAME'], }); console.log('ID Token:', result.idToken); ``` Check out the [documentation](https://capawesome.io/plugins/apple-sign-in/index.md) to learn more about getting started with the Apple Sign-In plugin. ### Audio Player The [Audio Player](https://capawesome.io/plugins/audio-player/index.md) plugin adds a `deactivateAudioSession` option to the [`stop(...)`](https://capawesome.io/plugins/audio-player/#stop) method. Set it to `false` when you plan to call `play()` again shortly after stopping, which avoids audio session interruption issues: ``` import { AudioPlayer } from '@capawesome/capacitor-audio-player'; await AudioPlayer.stop({ deactivateAudioSession: false }); ``` This option is available on Android and iOS and defaults to `true`. ### Firebase #### Analytics The [Analytics](https://capawesome.io/plugins/firebase/analytics/index.md) plugin adds a new [`logTransaction(...)`](https://capawesome.io/plugins/firebase/analytics/#logtransaction) method for logging StoreKit 2 in-app purchase transactions to Firebase Analytics on iOS: ``` import { FirebaseAnalytics } from '@capacitor-firebase/analytics'; await FirebaseAnalytics.logTransaction({ transactionId: '123456789', }); ``` Pass the `Transaction.id` from StoreKit 2 as a string — if you're using the [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin, this is available on the transaction object. This method requires iOS 15.0 or later. On Android, in-app purchase tracking is handled automatically by Firebase. Additionally, a new SPM package trait lets you disable IDFA collection on iOS by switching from `FirebaseAnalytics` to `FirebaseAnalyticsCore`. Check out our blog post on [SPM Package Traits in Capacitor](https://capawesome.io/blog/how-to-use-spm-package-traits-in-capacitor/index.md) for details on how to configure this. #### Cloud Firestore The [Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin received three new features. ##### Timestamp, GeoPoint, and FieldValue Support You can now use Firestore's native data types directly. The plugin automatically serializes and deserializes `Timestamp`, `GeoPoint`, and `FieldValue` across all methods: ``` import { FirebaseFirestore, Timestamp, GeoPoint, FieldValue } from '@capacitor-firebase/firestore'; await FirebaseFirestore.setDocument({ reference: 'posts/abc', data: { createdAt: Timestamp.now(), location: new GeoPoint(48.1351, 11.5820), views: FieldValue.increment(1), }, }); ``` ##### Offline Persistence New [`enablePersistence(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#enablepersistence) and [`disablePersistence(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#disablepersistence) methods give you explicit control over Firestore's offline cache. Both must be called before any other Firestore method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; await FirebaseFirestore.enablePersistence({ cacheSizeBytes: 50 * 1024 * 1024, }); ``` ##### Named Database Support A new `databaseId` configuration option lets you connect to a specific Firestore database instead of the default one. This is available on Android and iOS. #### Cloud Messaging The [Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin adds the `apnsTokenReceived` event listener that fires on iOS when the native APNs device token is received: ``` import { FirebaseMessaging } from '@capacitor-firebase/messaging'; await FirebaseMessaging.addListener('apnsTokenReceived', (event) => { console.log('APNs token:', event.token); }); ``` This is useful when you need the raw APNs token for third-party push services. The event is buffered and delivered as soon as a listener is registered. #### Cloud Storage The [Cloud Storage](https://capawesome.io/plugins/firebase/cloud-storage/index.md) plugin adds a [`downloadFile(...)`](https://capawesome.io/plugins/firebase/cloud-storage/#downloadfile) method for downloading files directly to the device filesystem with progress tracking: ``` import { FirebaseStorage } from '@capacitor-firebase/storage'; await FirebaseStorage.downloadFile( { path: 'images/mountains.png', uri: 'file:///path/to/local/mountains.png' }, (event, error) => { if (event?.completed) { console.log('Download complete!'); } }, ); ``` This method is available on Android, iOS, and Web. ### Live Update The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin now supports native channel configuration, allowing you to set a default channel directly in your native project. This is especially useful for versioned channels where the channel is tied to the native build version: ``` // Android: build.gradle android { defaultConfig { resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode } } ``` ``` CapawesomeLiveUpdateDefaultChannel production-$(CURRENT_PROJECT_VERSION) ``` Additionally, a new [`fetchChannels(...)`](https://capawesome.io/plugins/live-update/#fetchchannels) method lets you retrieve available public channels from Capawesome Cloud, which is useful for building channel selectors in development or QA builds. ### NFC The [NFC](https://capawesome.io/plugins/nfc/index.md) plugin now implements the [`isEnabled(...)`](https://capawesome.io/plugins/nfc/#isenabled) method on iOS and Web. On iOS, this reflects whether NFC reading is available on the device. On Web, it checks for Web NFC API support: ``` import { Nfc } from '@capawesome/capacitor-nfc'; const { isEnabled } = await Nfc.isEnabled(); console.log('NFC available:', isEnabled); ``` This method is now available on Android, iOS, and Web. ### PostHog The [PostHog](https://capawesome.io/plugins/posthog/index.md) plugin adds new configuration options. You can now set `apiHost` and `uiHost` to point to your own PostHog instance or reverse proxy, and `captureApplicationLifecycleEvents` to control whether lifecycle events are tracked: ``` import { Posthog } from '@capawesome/capacitor-posthog'; await Posthog.setup({ apiKey: 'phc_YOUR_KEY', apiHost: 'https://eu.i.posthog.com', captureApplicationLifecycleEvents: false, }); ``` The `apiHost` and `uiHost` options are available on Android, iOS, and Web. The `captureApplicationLifecycleEvents` option is available on Android and iOS. ### SQLite The [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin now supports enabling [SQLCipher](https://www.zetetic.net/sqlcipher/) encryption via SPM package traits on iOS. Instead of manually linking SQLCipher, you can enable it directly in your Capacitor config: ``` { "experimental": { "ios": { "spm": { "swiftToolsVersion": "6.1", "packageTraits": { "@capawesome-team/capacitor-sqlite": ["SQLCipher"] } } } } } ``` This requires Xcode 16.3 or later. Check out our blog post on [SPM Package Traits in Capacitor](https://capawesome.io/blog/how-to-use-spm-package-traits-in-capacitor/index.md) for more details. ## Try Capawesome Cloud If you're looking for a powerful cloud platform to build, deploy, and manage your Capacitor apps, check out [Capawesome Cloud](https://capawesome.io/cloud/index.md). It provides native builds, live updates, app store publishing, and more — all in one place. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion March was a big month for Capawesome. On the Cloud side, iOS OTA distribution and app transfers make it easier to test and manage your apps, while the Plugin updates bring native Firestore data types, a new Apple Sign-In plugin, and several quality-of-life improvements across the board. If you have any questions or need help, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And don't forget to subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news and updates. # Capawesome Cloud - The Complete Alternative to Ionic Appflow With Ionic Appflow scheduled to discontinue on December 31, 2027, development teams using Capacitor need a reliable alternative. [Capawesome Cloud](https://cloud.capawesome.io/) has emerged as the complete solution, offering Live Updates, Native Builds, and App Store Publishing in one platform. Built specifically for Capacitor and Ionic apps, Capawesome Cloud delivers faster builds, better developer experience, and significantly lower costs than traditional CI/CD platforms. In this post, we'll explore why Capawesome Cloud is the ideal migration path for teams currently using Ionic Appflow or looking for a comprehensive mobile app deployment platform. Ready to experience the difference? [Start your free trial today](https://cloud.capawesome.io) and see how Capawesome Cloud can transform your mobile app development workflow. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## A Complete Platform for Capacitor Apps Capawesome Cloud provides everything development teams need to build, update, and deploy Capacitor apps—all in one platform. ### Live Updates Ship critical bug fixes, new features, and content updates instantly without waiting for app store approval. [Capawesome Cloud Live Updates](https://capawesome.io/cloud/live-updates/index.md) provides a powerful, feature-rich solution for real-time app updates. **Key Capabilities:** - **Advanced Plugin API**: The [Capacitor Live Update plugin](https://capawesome.io/plugins/live-update/index.md) offers extensive control with methods for bundle management, custom device IDs, version tracking, and more—significantly more capabilities than basic Live Update implementations - **Automatic Rollbacks**: Built-in protection through the [`ready()`](https://capawesome.io/plugins/live-update/#ready) method ensures your app automatically reverts to the previous version if an update fails, keeping your users' experience smooth - **Delta Updates**: Only download changed files instead of entire bundles, reducing update size and download time ([learn more](https://capawesome.io/cloud/live-updates/advanced/delta-updates/index.md)) - **Gradual Rollouts**: Deploy updates to a percentage of users first, then gradually increase rollout based on feedback and monitoring ([learn more](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md)) - **Code Signing**: Sign bundles with private keys to ensure authenticity and integrity ([learn more](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md)) - **Channel Management**: Organize updates into channels for different user segments, testing groups, or deployment stages - **Self-Hosting Options**: Host bundles on your own infrastructure while using Capawesome Cloud for management ([learn more](https://capawesome.io/cloud/live-updates/advanced/self-hosting/index.md)) - **EU-Based Hosting**: Keep data within the European Union for GDPR compliance ([learn more](https://capawesome.io/cloud/live-updates/advanced/privacy/index.md)) - **Git Integration**: Automatically trigger Live Update deployments from GitHub, GitLab, Bitbucket, or Azure DevOps commits ### Native Builds Build native iOS and Android apps in the cloud without maintaining complex CI/CD infrastructure. [Capawesome Cloud Native Builds](https://capawesome.io/cloud/native-builds/index.md) delivers production-ready binaries with optimized performance. **Key Capabilities:** - **Blazing Fast Builds**: Builds are 3-5x faster than traditional CI/CD platforms, running on M4 instances with 4 CPU cores and 14 GB RAM by default - **Optimized Build Stacks**: Pre-configured with latest Node.js, Java, and Xcode versions on macOS 15—no more dependency conflicts or version mismatches - **Universal Git Integration**: Connect any Git provider including GitHub, GitLab, Bitbucket, Azure DevOps, and self-hosted/enterprise instances - **Complete Signing Support**: Securely manage Android keystores and iOS certificates for all build types (Development, Ad Hoc, App Store, Enterprise) - **Simple Configuration**: Use `capawesome.config.json` instead of complex YAML files—supports monorepos, custom commands, and multiple package managers - **Encrypted Secrets**: Store API keys, tokens, and credentials securely with encryption at rest and in transit - **Detailed Job Logs**: Comprehensive, easy-to-read logs make debugging straightforward - **Isolated Environments**: Every build runs in its own isolated VM for security and consistency - **Build from Anywhere**: Use the CLI to trigger builds from any machine—no macOS, Xcode, or Android Studio required ### App Store Publishing Automate submissions to TestFlight, Apple App Store, and Google Play Store. [Capawesome Cloud App Store Publishing](https://capawesome.io/cloud/app-store-publishing/index.md) eliminates manual upload workflows. **Key Capabilities:** - **Automatic Submissions**: Deploy directly to app stores with a single click after successful builds - **Multiple Tracks**: Support for Internal, Alpha, Beta, and Production tracks on Google Play - **TestFlight Integration**: iOS builds automatically upload to TestFlight for testing - **One-Time Setup**: Configure store destinations once, then automate deployments - **Secure Credentials**: App Store Connect and Google Play credentials stored with enterprise-grade security - **Deployment Tracking**: Monitor all submissions and releases in one centralized dashboard ## Performance & Cost Efficiency Capawesome Cloud is built for performance, delivering results that matter to your development workflow and bottom line. ### Build Speed Native Builds on Capawesome Cloud are **3-5x faster** than traditional CI/CD platforms. Running on M4 instances with 4 CPU cores and 14 GB RAM (configurable up to 14 cores and 64 GB RAM), our optimized build environments specifically designed for Capacitor apps deliver: - **iOS Builds**: Average 2-5 minutes - **Android Builds**: Average 2-5 minutes (also using macOS runners for consistency) Real-world example: DHBW VS App reduced build times from 7 minutes on GitHub Actions to just 2 minutes on Capawesome Cloud—a **3.5x improvement**. ### Cost Comparison Here's how Capawesome Cloud pricing compares to other platforms for 600 build minutes: | Platform | Monthly Cost | Hardware | Includes Live Updates | | ---------------- | ------------ | -------- | --------------------- | | Capawesome Cloud | $29 | macOS M4 | Yes | | GitHub Actions | $48 | macOS M1 | No | | Codemagic | $57 | macOS M2 | No | | Bitrise | $99 | macOS M2 | No | **Bottom line**: Capawesome Cloud offers the latest hardware (M4), fastest builds, and includes Live Updates—all at the lowest price point. ### Real Customer Savings **snapAddy BusinessCards** migrated from GitHub Actions and achieved: - Build times: 17 minutes → 5 minutes (3.4x faster) - Monthly costs: ~$1,200 → $299 (75% reduction) - Additional benefits: Simplified certificate management, integrated Live Updates ## Security & Compliance Security is foundational to everything we build at Capawesome Cloud. **Enterprise-Grade Protection:** - **Code Signing**: Sign Live Update bundles with private keys to ensure authenticity and prevent tampering - **Encrypted Secrets**: All signing certificates, API keys, and credentials encrypted at rest and in transit - **Isolated Build Environments**: Every build runs in its own VM with no shared resources between customers - **SOC 2 Compliance**: Committed to SOC 2 certification with regular third-party security audits - **Zero-Trust Architecture**: Security built into every layer of the platform **Open Source Transparency:** The [Capacitor Live Update plugin](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/live-update) is completely open source, allowing anyone to audit the code and verify what data is collected and transmitted. **Privacy by Design:** - **Minimal Data Collection**: Only collect information required to deliver Live Updates - **EU-Based Hosting**: Option for European data residency to ensure compliance with GDPR and keep connection data within the EU ([learn more](https://capawesome.io/cloud/live-updates/advanced/privacy/index.md)) - **Data Sovereignty**: Choose where your data is hosted and processed ## Developer Experience Capawesome Cloud prioritizes developer experience at every level, making mobile app development more accessible and enjoyable. **Simple Configuration:** Forget complex YAML files. Capawesome Cloud uses a straightforward `capawesome.config.json` file that's easy to read, write, and maintain. Configuration is intuitive and documented, with support for: - Monorepo projects - Custom build commands - Multiple package managers (npm, Yarn, pnpm) - Environment variables and secrets - Flexible build customization **Comprehensive Documentation:** Every feature is thoroughly documented with guides, examples, and best practices. Whether you're setting up your first build or implementing advanced rollout strategies, you'll find clear, actionable documentation. **Powerful CLI:** The [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) enables automation and integration with existing workflows: - Trigger builds from any machine without macOS or Android Studio - Automatically download artifacts with `--apk` or `--ipa` flags - Integrate seamlessly into existing CI/CD pipelines - Manage Live Updates, builds, and deployments programmatically **Multi-Platform Console:** Access the [Capawesome Cloud Console](https://console.cloud.capawesome.io) from any device—desktop, tablet, or mobile. Monitor builds, manage deployments, and track analytics wherever you are. ## Migrating from Ionic Appflow With Ionic Appflow discontinuing on December 31, 2027, now is the ideal time to plan your migration to Capawesome Cloud. **Why Migrate Now:** - **Feature Parity**: Capawesome Cloud now offers all essential features—Live Updates, Native Builds, and App Store Publishing - **Better Performance**: 3-5x faster builds mean quicker iterations and faster releases - **Lower Costs**: Significantly reduced monthly costs with transparent, predictable pricing - **Active Development**: Capawesome Cloud is actively developed with new features and improvements shipping regularly - **Smooth Transition**: Ample time to migrate before Appflow's end-of-life date **Migration Resources:** Getting started with Capawesome Cloud is straightforward. Our documentation covers: - [Setting up Live Updates](https://capawesome.io/cloud/live-updates/setup/index.md) - [Configuring Native Builds](https://capawesome.io/cloud/native-builds/setup/index.md) - [Automating App Store Publishing](https://capawesome.io/cloud/app-store-publishing/index.md) - [Git Integration guides](https://capawesome.io/cloud/integrations/index.md) Need help with your migration? [Schedule a demo](https://cal.com/team/capawesome/cloud-demo) with our team to discuss your specific requirements. ## Pricing Capawesome Cloud offers transparent, affordable pricing that scales with your needs. **Live Updates + Native Builds Pricing:** | Plan | Price | Live Updates MAU | Build Minutes | Storage | | ------------ | ---------- | ---------------- | ------------- | --------- | | Free | $0 | 100 | 0 | 100 MB | | Starter | $9/month | 1,000 | 200 | 1 GB | | Professional | $29/month | 10,000 | 600 | 5 GB | | Team | $99/month | 100,000 | 2,000 | 10 GB | | Business | $299/month | 1,000,000 | 5,400 | 50 GB | | Enterprise | Custom | Unlimited | Custom | Unlimited | **Key Advantages:** - **All-in-One**: Live Updates, Native Builds, and App Store Publishing in one platform - **No Hidden Fees**: Transparent pricing with no surprise charges - **Flexible Plans**: Choose the plan that fits your needs or get a custom quote - **Better Value**: Up to 10x more cost-effective than comparable platforms per monthly active user Since Capawesome Cloud builds are 3-5x faster than traditional CI/CD platforms, your build minutes go much further. For example, the Professional plan's 600 build minutes typically translates to 120+ builds per month. [View Complete Pricing](https://cloud.capawesome.io/pricing) ## Get Started Today Capawesome Cloud is the complete, production-ready alternative to Ionic Appflow for Capacitor apps. With Live Updates, Native Builds, and App Store Publishing in one platform, you get everything you need to ship high-quality mobile apps faster and more affordably. **What You Get:** - 3-5x faster builds on M4 hardware - Advanced Live Updates with delta updates, rollouts, and automatic rollbacks - Automated app store submissions - Enterprise-grade security and compliance - EU-based hosting options - Exceptional developer experience - Comprehensive documentation and support **Ready to experience the difference?** Start with our free tier—no credit card required. Deploy your first Live Update and trigger your first build in minutes. [Create Free Account](https://console.cloud.capawesome.io) [Schedule a Demo](https://cal.com/team/capawesome/cloud-demo) Questions? Visit our [documentation](https://capawesome.io/cloud/index.md), join our [Discord community](https://discord.gg/VCXxSVjefW), or [contact our team](mailto:sales@capawesome.io). # Alternative to the Capacitor Community Contacts plugin Need a robust contacts management solution for your Capacitor app? While the Capacitor Community Contacts plugin provides basic contact functionality for iOS and Android, developers seeking enhanced features, web support, and professional maintenance should consider the [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin. This modern alternative offers comprehensive contact management with cross-platform compatibility and enterprise-grade reliability. ## Introduction The Capacitor Community Contacts plugin has served as a foundational solution for developers requiring native contact access in their Capacitor applications. It provides essential functionality for retrieving, creating, and deleting contacts across iOS and Android platforms. However, as applications become more sophisticated and development teams require more comprehensive solutions, the limitations of community-maintained plugins become apparent. The [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin addresses these limitations by providing a professionally maintained, feature-rich alternative that extends beyond basic contact management. With support for web platforms, advanced filtering, contact groups, and modern API design, it represents the next evolution in Capacitor contact management solutions. ## Differences to Capawesome Contacts The [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin offers several advantages over the Capacitor Community Contacts plugin: **Cross-Platform Support**: Unlike the community plugin that only supports iOS and Android, Capawesome Contacts also includes web support, allowing users to pick contacts in web applications. **Enhanced API Design**: The plugin features a modern, promise-based API with comprehensive TypeScript definitions, making development more intuitive and less error-prone. **Professional Maintenance**: As part of the Capawesome ecosystem, the plugin receives regular updates, security patches, and compatibility improvements with new Capacitor versions. **Advanced Features**: Beyond basic CRUD operations, the plugin supports contact groups, photo management, pagination, filtering, and native contact picker integration. **Enterprise Support**: Professional support and consulting services are available for teams requiring assistance with implementation or customization. ## Migration from Capacitor Community Contacts Migrating from the Capacitor Community Contacts plugin involves updating your installation and adapting your API calls to the new plugin structure. ### Installation Begin by removing the existing Capacitor Community Contacts dependency and installing the Capawesome alternative. To install the [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/contacts/#installation) section in the plugin documentation. ### Create a contact Contact creation patterns are streamlined in the Capawesome plugin with advanced type definitions for email addresses, phone numbers, and more. **Capacitor Community Contacts:** ``` import { Contacts } from '@capacitor-community/contacts'; const createContact = async () => { const result = await Contacts.createContact({ contact: { name: { given: 'John', family: 'Doe' }, phones: [{ type: 'mobile', number: '+1234567890' }], emails: [{ type: 'work', address: 'john.doe@example.com' }] } }); return result.contactId; }; ``` **Capawesome Contacts:** ``` import { Contacts, EmailAddressType, PhoneNumberType } from '@capawesome-team/capacitor-contacts'; const createContact = async () => { await Contacts.createContact({ contact: { givenName: 'John', familyName: 'Doe', phoneNumbers: [{ value: '+1234567890', type: PhoneNumberType.Mobile }], emailAddresses: [{ value: 'john.doe@example.com', type: EmailAddressType.Work }] } }); }; ``` ### Retrieve a contact The Capawesome Contacts plugin offers a lot more options for retrieving contacts, including filtering and pagination. The API is designed to be more intuitive and efficient: **Capacitor Community Contacts:** ``` import { Contacts } from '@capacitor-community/contacts'; const getContact = async (contactId: string) => { const { contact } = await Contacts.getContact({ contactId }); return contact; }; ``` **Capawesome Contacts:** ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const getContacts = async () => { const { contacts } = await Contacts.getContacts({ fields: ['id', 'givenName', 'familyName', 'phoneNumbers', 'emailAddresses'], limit: 100, // Limit the number of contacts retrieved offset: 0, // Offset for pagination }); return contacts; }; const getContactById = async (contactId: string) => { const { contact } = await Contacts.getContactById({ id: contactId }); return contact; }; ``` ### Update a contact Contact updates are not directly supported in the Capacitor Community Contacts plugin, requiring a delete and recreate pattern. This has the disadvantage of losing any existing contact IDs or associations. The Capawesome plugin simplifies this with a single method for updating contacts: **Capacitor Community Contacts:** ``` import { Contacts } from '@capacitor-community/contacts'; const updateContact = async (contactId: string) => { // First, delete the existing contact await Contacts.deleteContact({ contactId }); // Then, create a new contact with the updated information await Contacts.createContact({ contact: { name: { given: 'Jane', family: 'Smith' } } }); }; ``` **Capawesome Contacts:** ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const updateContact = async (contactId: string) => { await Contacts.updateContactById({ id: contactId, contact: { givenName: 'Jane', familyName: 'Smith' } }); }; ``` ### Delete a contact Contact deletion is straightforward in both plugins, making it easy to remove contacts when needed: **Capacitor Community Contacts:** ``` import { Contacts } from '@capacitor-community/contacts'; const deleteContact = async (contactId: string) => { await Contacts.deleteContact({ contactId }); }; ``` **Capawesome Contacts:** ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const deleteContact = async (contactId: string) => { await Contacts.deleteContactById({ id: contactId }); }; ``` ### Pick a contact The contact picker functionality is enhanced in the Capawesome plugin, allowing for more flexible selection options: **Capacitor Community Contacts:** ``` import { Contacts } from '@capacitor-community/contacts'; const pickContact = async () => { const { contact } = await Contacts.pickContact(); return contact; }; ``` **Capawesome Contacts:** ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const pickContacts = async () => { const { contacts } = await Contacts.pickContacts({ fields: ['id', 'givenName', 'familyName', 'phoneNumbers', 'emailAddresses'], multiple: false }); return contacts[0]; }; ``` ## Conclusion Transitioning to the [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin offers immediate improvements in functionality, reliability, and scalability. With enhanced API design, cross-platform support, and professional maintenance, it ensures robust contact management as your application grows. The migration is simple, with most features mapping directly to improved equivalents, while advanced capabilities like web support and contact groups prepare your app for future needs. To stay updated with the latest updates, features, and news about Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you need assistance with migrating from the Capacitor Community Contacts plugin or implementing the [Capawesome Contacts](https://capawesome.io/plugins/contacts/index.md) plugin, the Capawesome team is available to help you transition smoothly to this reliable alternative. Just [contact us](mailto:support@capawesome.io) to get started. # Alternative to the Capacitor Community SQLite plugin Looking for a robust **Capacitor SQLite** solution for your Capacitor app? While the Capacitor Community SQLite plugin has been a popular choice for many developers, there's now a compelling alternative that addresses common pain points and provides enhanced features. The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin offers a modern, enterprise-grade SQLite solution with improved security, performance, and developer experience. ## Introduction SQLite databases are a cornerstone of many mobile and web applications, providing a lightweight and efficient way to store structured data. The Capacitor Community SQLite plugin has been widely used in the Capacitor ecosystem, but it comes with certain limitations that can impact application performance, security, and compliance. The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin emerges as a modern solution designed specifically for enterprise-grade applications, offering a range of features that enhance data management capabilities while addressing the shortcomings of the Capacitor Community SQLite plugin. ## 5 reasons to consider alternatives to the Capacitor Community SQLite plugin Before exploring alternatives, let’s take a closer look at 5 key drawbacks of the Capacitor Community SQLite plugin that have led teams to seek other solutions—such as the Capawesome SQLite plugin. ### 1. Encryption Export Regulations When you submit your app to app stores, you upload your app to servers in specific countries (e.g., the US). If your app uses encryption, you may need to comply with export regulations that require you to declare the type of encryption used. The Capacitor Community SQLite plugin uses the [SQLCipher](https://www.zetetic.net/sqlcipher/) library, which is [subject to these regulations](https://discuss.zetetic.net/t/export-requirements-for-applications-using-sqlcipher/47). The problem here is that the Capacitor Community SQLite plugin uses the SQLCipher library even if you do not use the encryption features, which means you have to declare the use of encryption in your app as soon as you use the plugin. Disclaimer We aren’t attorneys or export control experts. This information is not intended as legal advice. Use it at your own risk and consult with a legal expert if you have concerns about compliance. ### 2. Performance The Capacitor Community SQLite plugin has been known to have performance issues, especially with larger databases or complex queries. These performance bottlenecks become particularly noticeable in applications that require real-time data synchronization or handle frequent concurrent database operations. ### 3. Swift Package Manager Modern iOS development increasingly relies on Swift Package Manager (SPM) for dependency management. Unfortunately, the Capacitor Community SQLite plugin does not support SPM, which can complicate integration with other Swift-based libraries and frameworks. This limitation becomes especially problematic for teams working with modern iOS frameworks that have moved away from CocoaPods in favor of SPM's more streamlined approach. ### 4. Web Support There are different approaches to SQLite support in web applications. The most modern approach is to use [WebAssembly](https://en.wikipedia.org/wiki/WebAssembly) (Wasm) to run SQLite in the browser and store the database in the Origin Private File System (OPFS) of the browser. This approach is also the only one that is officially associated with the [SQLite project](https://sqlite.org/). However, the Capacitor Community SQLite plugin does not support this approach and instead uses `sql.js` and `localforage` to store the database in IndexedDB. This complicates the setup, creates additional dependencies, and can lead to performance issues compared to the official approach. ### 5. Maintenance and Support The Capacitor Community SQLite plugin is maintained by the community, which allows the plugin to be free and open-source but can lead to slower updates and less reliable support. While this is not a problem for many developers, those building enterprise applications may require more robust support and maintenance options. ## Differences to the Capacitor SQLite plugin The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin offers 6 key advantages over the Capacitor Community alternative: 1. **Simplified API**: More intuitive and streamlined API for database operations, making it easier to implement complex data management features. 1. **Optional Encryption**: Encryption is optional and only enabled when needed, avoiding unnecessary compliance burdens. 1. **WebAssembly Support**: Native WebAssembly support for web applications, providing better performance and functionality. 1. **Swift Package Manager Support**: Full support for SPM, making it easier to integrate with modern iOS projects. 1. **Enhanced Performance**: Optimized for larger databases and complex queries, ensuring smooth performance even under heavy loads. 1. **Professional Support**: Direct support from the Capawesome team for all Capawesome Insiders, ensuring timely updates and assistance. ## Migration from Capacitor Community SQLite plugin Migrating from the Capacitor Community SQLite plugin to the [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin involves updating your installation, adapting your database opening logic, and adjusting your SQL execution patterns. Let's see how to do it step by step. ### Installation First, remove the existing plugin and install the Capawesome alternative. To install the [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the SQLite capacitor plugin documentation. ### Opening a database The database opening process differs between the two plugins. Here's how to migrate your database opening logic: **Capacitor Community SQLite plugin:** ``` import { CapacitorSQLite, SQLiteConnection } from '@capacitor-community/sqlite'; const open = async () => { await CapacitorSQLite.addUpgradeStatement({ database: 'db', upgrade: [ { toVersion: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)' ] } ] }); await CapacitorSQLite.open({ database: 'db', readonly: false, }); }; ``` **Capawesome SQLite plugin:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const open = async () => { const { databaseId } = await Sqlite.open({ readOnly: false, path: 'db.sqlite3', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT, age INTEGER)' ] } ] }); }; ``` ### Executing SQL statements SQL execution syntax is simplified in the Capawesome SQLite plugin: **Capacitor Community SQLite plugin:** ``` import { CapacitorSQLite } from '@capacitor-community/sqlite'; const execute = async (databaseName: string) => { await CapacitorSQLite.run({ database: databaseName, statement: 'INSERT INTO users (name, age) VALUES (?, ?);', values: ['John Doe', 30] }); }; ``` **Capawesome SQLite plugin:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const execute = async (databaseId: string) => { await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, age) VALUES (?, ?);', values: ['John Doe', 30] }); }; ``` ### Querying data Data querying follows a similar simplification pattern: **Capacitor Community SQLite plugin:** ``` import { CapacitorSQLite } from '@capacitor-community/sqlite'; const query = async (databaseName: string) => { const result = await CapacitorSQLite.query({ database: databaseName, statement: 'SELECT * FROM users WHERE age > ?;', values: [25] }); console.log(result.values); }; ``` **Capawesome SQLite plugin:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const query = async (databaseId: string) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT * FROM users WHERE age > ?;', values: [25] }); console.log(result.values); }; ``` ## FAQ ##### Can I open already existing databases with the Capawesome SQLite plugin? Yes, you can open existing databases (e.g., created with the Capacitor Community SQLite plugin) using the Capawesome SQLite plugin. Just provide the path to the existing database file when calling the `open(...)` method. If the database file does not exist, it will be created automatically. ##### Does the Capawesome SQLite plugin provide the same functionality as the Capacitor Community SQLite plugin? Yes, at least in terms of core functionality. The Capacitor Community SQLite plugin offers a number of advanced features such as biometrics and encryption key management. However, these features are not required by most applications, so the Capawesome team has decided to move these features to separate plugins (e.g. [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) and [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md)). This allows you to use the Capawesome SQLite plugin without the overhead of these features if you don't need them. ## Conclusion The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin provides a modern, robust alternative to the Capacitor Community SQLite plugin, addressing key issues such as encryption compliance, performance, and developer experience. For the full plugin documentation, see the [API Reference](https://capawesome.io/plugins/sqlite/#api). **Related reading:** - [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) - [Announcing the Capacitor SQLite Plugin](https://capawesome.io/blog/announcing-the-capacitor-sqlite-plugin/index.md) - [Encrypting SQLite databases](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md) - [Key-Value Storage with the SQLite plugin](https://capawesome.io/blog/key-value-storage-made-simple-with-the-sqlite-plugin/index.md) **Stay updated:** [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) · [X (formerly Twitter)](https://x.com/capawesomeio) **Need help?** If you have any questions or need assistance with the Capacitor SQLite Plugin, feel free to [reach out](mailto:support@capawesome.io) to the Capawesome team. We're here to help you implement powerful data management features in your Ionic applications. # Alternative to the Ionic Auth Connect Plugin Need SSO authentication in your Capacitor app? With [Ionic discontinuing](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products) their commercial Auth Connect plugin, many developers are searching for a reliable replacement. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin provides a modern, production-ready alternative that supports OAuth 2.0 and OpenID Connect with PKCE across Android, iOS, and Web. ## Introduction Ionic Auth Connect has been a popular choice for adding OAuth and OpenID Connect authentication to Capacitor apps. It handled login flows, token management, and session handling across platforms. However, following Ionic's acquisition by OutSystems and the decision to wind down their commercial products, developers need to find an alternative that keeps their apps running and their auth flows secure. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin fills this gap. It implements the OAuth 2.0 Authorization Code flow with PKCE, supports automatic OpenID Connect discovery, and works with any compliant provider — including Auth0, Azure AD, Amazon Cognito, Okta, OneLogin, and Google. ## Feature Comparison Here's how the two plugins stack up: | Feature | Ionic Auth Connect | Capawesome OAuth | | ----------------------------------- | ------------------ | ------------------------------------------------------------------------------------------ | | OAuth 2.0 Authorization Code + PKCE | Yes | Yes | | OpenID Connect support | Yes | Yes | | Automatic endpoint discovery | Yes | Yes | | Token refresh | Yes | Yes | | ID token decoding | Yes | Yes | | Access token expiration check | Yes | Yes | | Multi-provider support | Yes | Yes | | Android support | Yes | Yes | | iOS support | Yes | Yes | | Web support | Yes | Yes | | Secure token storage | Separate plugin | Works with [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) | | Actively maintained | No (discontinued) | Yes | The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin covers the core authentication functionality most teams need, with a clean API and active maintenance. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill ionic-enterprise-sdk-migration` and use the following prompt with your preferred AI coding assistant: ``` Use the `ionic-enterprise-sdk-migration` skill from `capawesome-team/skills` to help me migrate from Ionic Auth Connect to the Capawesome OAuth plugin. ``` ## Migration from Auth Connect Migrating from Ionic Auth Connect to the Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin is straightforward. The following sections walk through each common operation with side-by-side code examples. ### Installation Begin by removing the existing Auth Connect dependency and installing the Capawesome alternative, if you haven't already. To install the Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. ### Setup Auth Connect requires an explicit setup step before use. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin doesn't need any upfront configuration — you pass all required options directly to each method call. **Ionic Auth Connect:** ``` import { AuthConnect, ProviderOptions } from '@ionic-enterprise/auth'; const setup = async () => { await AuthConnect.setup({ platform: 'capacitor', logLevel: 'DEBUG', ios: { webView: 'private' }, web: { uiMode: 'popup', authFlow: 'PKCE' }, }); }; ``` **Capawesome OAuth:** ``` // No setup needed — ready to use immediately ``` ### Logging in Both plugins initiate an OAuth flow, but the API differs. With the Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin, you pass the provider configuration directly to the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method. **Ionic Auth Connect:** ``` import { AuthConnect, Auth0Provider } from '@ionic-enterprise/auth'; const login = async () => { const provider = new Auth0Provider(); const result = await AuthConnect.login(provider, { audience: 'https://api.example.com', clientId: 'YOUR_CLIENT_ID', discoveryUrl: 'https://example.auth0.com/.well-known/openid-configuration', redirectUri: 'com.example.app://callback', scope: 'openid profile email offline_access', logoutUrl: 'com.example.app://logout', }); return result; }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://example.auth0.com', clientId: 'YOUR_CLIENT_ID', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], additionalParameters: { audience: 'https://api.example.com' }, }); return result; }; ``` A few key differences to note: - **Provider configuration**: Auth Connect uses provider-specific classes (e.g., `Auth0Provider`). The Capawesome plugin uses a single, universal API for all providers. - **Discovery**: Auth Connect requires the full discovery URL. The Capawesome plugin only needs the issuer URL and resolves the discovery document automatically. - **Scopes**: Auth Connect takes a space-delimited string, while the Capawesome plugin takes an array. ### Handling the redirect callback On the web platform, Auth Connect provides a `handleLoginCallback` method. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin has an equivalent [`handleRedirectCallback()`](https://capawesome.io/plugins/oauth/#handleredirectcallback) method. **Ionic Auth Connect:** ``` import { AuthConnect } from '@ionic-enterprise/auth'; const handleCallback = async () => { const result = await AuthConnect.handleLoginCallback(window.location.href); return result; }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const handleCallback = async () => { const result = await Oauth.handleRedirectCallback(); return result; }; ``` This step is only required on the web. On Android and iOS, the redirect is handled natively. ### Refreshing tokens Token refresh follows a similar pattern. Auth Connect uses a combined `AuthResult` object, while the Capawesome plugin takes individual parameters. **Ionic Auth Connect:** ``` import { AuthConnect, Auth0Provider } from '@ionic-enterprise/auth'; const refreshSession = async (authResult) => { const provider = new Auth0Provider(); const newResult = await AuthConnect.refreshSession(provider, authResult); return newResult; }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const refreshToken = async (refreshToken: string) => { const result = await Oauth.refreshToken({ issuerUrl: 'https://example.auth0.com', clientId: 'YOUR_CLIENT_ID', refreshToken, }); return result; }; ``` ### Checking token state Both plugins provide utility methods for checking token availability and expiration. **Ionic Auth Connect:** ``` import { AuthConnect } from '@ionic-enterprise/auth'; const checkTokenState = async (authResult) => { const isExpired = await AuthConnect.isAccessTokenExpired(authResult); const isAvailable = await AuthConnect.isAccessTokenAvailable(authResult); const isRefreshAvailable = await AuthConnect.isRefreshTokenAvailable(authResult); }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const checkTokenState = async ( accessToken: string, accessTokenExpirationDate: number, refreshToken: string ) => { const { isExpired } = await Oauth.isAccessTokenExpired({ accessTokenExpirationDate, }); const { isAvailable } = await Oauth.isAccessTokenAvailable({ accessToken, }); const { isAvailable: isRefreshAvailable } = await Oauth.isRefreshTokenAvailable({ refreshToken, }); }; ``` The main difference is that Auth Connect operates on the `AuthResult` object, while the Capawesome plugin takes individual values. This gives you more flexibility in how you store and manage your tokens. ### Decoding the ID token Both plugins can decode JWT ID tokens locally. **Ionic Auth Connect:** ``` import { AuthConnect } from '@ionic-enterprise/auth'; const decodeToken = async (authResult) => { const decoded = await AuthConnect.decodeToken('id', authResult); return decoded; }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const decodeIdToken = async (idToken: string) => { const { header, payload } = await Oauth.decodeIdToken({ token: idToken, }); return payload; }; ``` ### Logging out Both plugins support ending the user's session through the provider's end-session endpoint. **Ionic Auth Connect:** ``` import { AuthConnect, Auth0Provider } from '@ionic-enterprise/auth'; const logout = async (authResult) => { const provider = new Auth0Provider(); await AuthConnect.logout(provider, authResult); }; ``` **Capawesome OAuth:** ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const logout = async (idToken: string) => { await Oauth.logout({ issuerUrl: 'https://example.auth0.com', idToken, postLogoutRedirectUrl: 'com.example.app://oauth/logout', }); }; ``` ## Need Help Migrating? If you'd rather not handle the migration yourself, the Capawesome team can take care of it for you. Whether you're dealing with a straightforward auth setup or a more complex multi-provider configuration, we offer dedicated migration services to get you up and running with minimal downtime and effort on your end. [Book a Free Consultation](https://cal.com/team/capawesome/ionic-appflow-migration) ## Conclusion The discontinuation of Ionic Auth Connect doesn't have to leave your app without a solid authentication solution. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin provides a comprehensive alternative that covers the core OAuth 2.0 and OpenID Connect functionality you need — with PKCE, automatic discovery, token refresh, and multi-provider support across all platforms. The migration is straightforward, and the plugin's clean API makes it easy to integrate into existing projects. Combined with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin for encrypted token storage, you get a complete authentication solution that's actively maintained and stays current with the latest Capacitor versions. To stay updated with the latest updates, features, and news about Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you need assistance with migrating from Ionic Auth Connect or implementing the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin, the Capawesome team is available to help you transition smoothly to this reliable alternative. Just [contact us](mailto:support@capawesome.io) to get started. # Alternative to the Ionic Identity Vault Plugin Looking for a way to protect sensitive data and authenticate users in your Capacitor app? With [Ionic discontinuing](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products) their commercial Identity Vault plugin, developers need reliable alternatives for **biometric authentication** (Face ID, fingerprint) and secure session management. The [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin and [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin from Capawesome provide a modern alternative that covers the core functionality of Identity Vault. For a complete walkthrough on the Biometrcis plugin make sure to read [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md). ## Introduction Ionic Identity Vault combined biometric authentication, encrypted storage, and session management into a single plugin. It allowed developers to store tokens and credentials securely, lock and unlock a vault using Face ID or fingerprint, and automatically clear sensitive data after inactivity. Following Ionic's decision to phase out their commercial products, developers need to find a replacement. The good news is that you can replicate the key features of Identity Vault by combining the [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin from Capawesome. Together, they cover biometric authentication, encrypted key-value storage, and can be used to build session management logic tailored to your app. ## Feature Comparison Here's a side-by-side look at how Identity Vault features map to the Capawesome plugins: | Feature | Identity Vault | Capawesome | | ---------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------- | | Biometric authentication | `Vault.unlock()` | [`Biometrics.authenticate(...)`](https://capawesome.io/plugins/biometrics/#authenticate) | | Store values | `Vault.setValue(...)` | [`SecurePreferences.set(...)`](https://capawesome.io/plugins/secure-preferences/#set) | | Retrieve values | `Vault.getValue(...)` | [`SecurePreferences.get(...)`](https://capawesome.io/plugins/secure-preferences/#get) | | Remove values | `Vault.removeValue(...)` | [`SecurePreferences.remove(...)`](https://capawesome.io/plugins/secure-preferences/#remove) | | List keys | `Vault.getKeys()` | [`SecurePreferences.keys()`](https://capawesome.io/plugins/secure-preferences/#keys) | | Clear all data | `Vault.clear()` | [`SecurePreferences.clear()`](https://capawesome.io/plugins/secure-preferences/#clear) | | Check biometric availability | Device API | [`Biometrics.isAvailable()`](https://capawesome.io/plugins/biometrics/#isavailable) | | Check biometric enrollment | Device API | [`Biometrics.isEnrolled()`](https://capawesome.io/plugins/biometrics/#isenrolled) | | Device credential fallback | Vault config | [`authenticate(...)`](https://capawesome.io/plugins/biometrics/#authenticate) with `allowDeviceCredential` option | | Auto-lock on timeout | Built-in | Application logic | | Custom passcode | Built-in vault type | Application logic | | Lock/unlock events | `onLock` / `onUnlock` | Application logic | While Identity Vault bundles everything into a single class, the Capawesome approach gives you more flexibility by separating biometric authentication from storage. This makes it easier to use each feature independently and adapt the behavior to your specific needs. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill ionic-enterprise-sdk-migration` and use the following prompt with your preferred AI coding assistant: ``` Use the `ionic-enterprise-sdk-migration` skill from `capawesome-team/skills` to help me migrate from Ionic Identity Vault to Capawesome Biometrics and Secure Preferences. ``` ## Migration from Identity Vault Migrating from Identity Vault involves replacing vault operations with the corresponding Capawesome plugin methods. The following sections walk you through the most common scenarios. ### Installation Begin by removing the existing Identity Vault dependency and installing the Capawesome alternatives. To install the [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/biometrics/#installation) section in the plugin documentation. To install the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/secure-preferences/#installation) section in the plugin documentation. ### Biometric Authentication Identity Vault uses `Vault.unlock()` to trigger biometric authentication. With Capawesome, you use the [Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin's [`authenticate(...)`](https://capawesome.io/plugins/biometrics/#authenticate) method directly. **Identity Vault:** ``` import { Vault, DeviceSecurityType, VaultType } from '@ionic-enterprise/identity-vault'; const vault = new Vault({ key: 'com.example.vault', type: VaultType.DeviceSecurity, deviceSecurityType: DeviceSecurityType.Both, lockAfterBackgrounded: 2000, }); const unlock = async () => { await vault.unlock(); }; ``` **Capawesome Biometrics:** ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { await Biometrics.authenticate({ title: 'Authenticate', subtitle: 'Verify your identity to continue', allowDeviceCredential: true, }); }; ``` The [`authenticate(...)`](https://capawesome.io/plugins/biometrics/#authenticate) method supports customizable prompts via `title`, `subtitle`, and `cancelButtonText` options. Setting `allowDeviceCredential` to `true` lets users fall back to their device PIN or password if biometrics are unavailable. If authentication fails, you can handle the error and decide whether to keep the session locked or clear sensitive data. ### Storing Values Identity Vault's `setValue(...)` stores data inside the encrypted vault. With Capawesome, you use the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, which encrypts data using the platform's native secure storage (Android Keystore with AES-256 encryption on Android, Keychain on iOS). **Identity Vault:** ``` import { Vault } from '@ionic-enterprise/identity-vault'; const storeToken = async (vault: Vault) => { await vault.setValue('session_token', 'eyJhbGciOiJIUzI1NiIs...'); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const storeToken = async () => { await SecurePreferences.set({ key: 'session_token', value: 'eyJhbGciOiJIUzI1NiIs...', }); }; ``` ### Retrieving Values **Identity Vault:** ``` import { Vault } from '@ionic-enterprise/identity-vault'; const getToken = async (vault: Vault) => { const token = await vault.getValue('session_token'); return token; }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const getToken = async () => { const { value } = await SecurePreferences.get({ key: 'session_token' }); return value; }; ``` ### Removing Values **Identity Vault:** ``` import { Vault } from '@ionic-enterprise/identity-vault'; const removeToken = async (vault: Vault) => { await vault.removeValue('session_token'); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const removeToken = async () => { await SecurePreferences.remove({ key: 'session_token' }); }; ``` ### Clearing All Data **Identity Vault:** ``` import { Vault } from '@ionic-enterprise/identity-vault'; const clearVault = async (vault: Vault) => { await vault.clear(); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const clearAll = async () => { await SecurePreferences.clear(); }; ``` ### Session Management One of Identity Vault's built-in features is automatic session locking after a period of inactivity or when the app goes to the background. With Capawesome, you can build the same behavior using Capacitor's [App](https://capacitorjs.com/docs/apis/app) plugin combined with the [Biometrics](https://capawesome.io/plugins/biometrics/index.md) and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugins. Here's an example of how to implement auto-lock when the app is backgrounded: ``` import { App } from '@capacitor/app'; import { Biometrics } from '@capawesome-team/capacitor-biometrics'; import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; let locked = false; App.addListener('appStateChange', async ({ isActive }) => { if (!isActive) { locked = true; } if (isActive && locked) { try { await Biometrics.authenticate({ title: 'Welcome back', subtitle: 'Authenticate to unlock', allowDeviceCredential: true, }); locked = false; } catch (error) { // Authentication failed - keep locked or sign out } } }); ``` This gives you full control over when and how to lock the session, including custom timeout logic or clearing stored data on failed authentication. ## Need Help Migrating? If you'd rather not handle the migration yourself, the Capawesome team can take care of it for you. Whether you're dealing with a straightforward swap or a more complex setup with custom session management, we offer dedicated migration services to get you up and running with minimal downtime and effort on your end. [Book a Free Consultation](https://cal.com/team/capawesome/ionic-appflow-migration) ## Conclusion The discontinuation of Ionic Identity Vault doesn't have to disrupt your workflow. The [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin provide a solid alternative for biometric authentication, encrypted storage, and session management. For the full plugin documentation, see the [API Reference](https://capawesome.io/plugins/biometrics/#api). **Related reading:** - [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md) - [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/index.md) - [Announcing the Capacitor Biometrics Plugin](https://capawesome.io/blog/announcing-the-capacitor-biometrics-plugin/index.md) **Stay updated:** To stay updated with the latest updates, features, and news about Capawesome, Capacitor, and the Ionic ecosystem subscribe to our [Capawesome newsletter](https://cloud.capawesome.io/newsletter). **Need help migrating from Identity Vault?** [Contact us](mailto:support@capawesome.io) to get started. # Alternative to the Ionic Secure Storage Plugin Looking for a secure storage solution for your Capacitor app? With [Ionic discontinuing](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products) their commercial Secure Storage plugin, developers need reliable alternatives for encrypted data storage. The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin provide modern, secure solutions that address the functionality gap left by Ionic Secure Storage. ## Introduction Ionic Secure Storage has been a go-to solution for developers requiring encrypted local storage in their Capacitor applications. However, following Ionic's acquisition by OutSystems and their announcement to phase out commercial products, developers must find alternative solutions for secure data storage. The plugin offered both key-value storage and SQLite database functionality with 256-bit AES encryption, making it essential for applications handling sensitive data. The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin emerge as comprehensive alternatives, providing enterprise-grade security features while maintaining the functionality developers relied on with Ionic Secure Storage. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill ionic-enterprise-sdk-migration` and use the following prompt with your preferred AI coding assistant: ``` Use the `ionic-enterprise-sdk-migration` skill from `capawesome-team/skills` to help me migrate from Ionic Secure Storage to Capawesome alternatives. ``` ## Migration from Ionic Secure Storage Migrating from Ionic Secure Storage requires choosing the appropriate replacement based on your storage needs: key-value storage or SQLite database functionality. ### Key-Value-Store For applications using Ionic Secure Storage's key-value functionality, migrate to the [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. #### Installation Begin by removing the existing Ionic Secure Storage dependency and installing the Capawesome alternative, if you haven't already. To install the [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/secure-preferences/#installation) section in the plugin documentation. #### Create a store Unlike Ionic Secure Storage, the Capawesome Secure Preferences plugin doesn't require explicit store creation. The secure storage is automatically available after installation and the encryption key is managed by the plugin itself, ensuring a seamless experience. **Ionic Secure Storage:** ``` import { KeyValueStorage } from '@ionic-enterprise/secure-storage'; const createStore = async () => { await KeyValueStorage.create('my-secret-key'); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; // No store creation needed - ready to use immediately ``` #### Set a value **Ionic Secure Storage:** ``` import { KeyValueStorage } from '@ionic-enterprise/secure-storage'; const setValue = async () => { await KeyValueStorage.set('username', 'john_doe'); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const setValue = async () => { await SecurePreferences.set({ key: 'username', value: 'john_doe' }); }; ``` #### Get a value **Ionic Secure Storage:** ``` import { KeyValueStorage } from '@ionic-enterprise/secure-storage'; const getValue = async () => { const value = await KeyValueStorage.get('username'); return value; }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const getValue = async () => { const { value } = await SecurePreferences.get({ key: 'username' }); return value; }; ``` #### Remove a value **Ionic Secure Storage:** ``` import { KeyValueStorage } from '@ionic-enterprise/secure-storage'; const removeValue = async () => { await KeyValueStorage.remove('username'); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const removeValue = async () => { await SecurePreferences.remove({ key: 'username' }); }; ``` #### Clear the store **Ionic Secure Storage:** ``` import { KeyValueStorage } from '@ionic-enterprise/secure-storage'; const clearStore = async () => { await KeyValueStorage.clear(); }; ``` **Capawesome Secure Preferences:** ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const clearStore = async () => { await SecurePreferences.clear(); }; ``` ### SQLite For applications using Ionic Secure Storage's SQLite functionality, migrate to the [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin. #### Installation Begin by removing the existing Ionic Secure Storage dependency and installing the Capawesome alternative, if you haven't already. To install the [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. #### Opening a database Database initialization differs between the two solutions. Here's how to adapt your database setup: **Ionic Secure Storage:** ``` import { SQLite } from '@ionic-enterprise/secure-storage'; const openDatabase = async () => { const db = await SQLite.create({ name: 'database.db', location: 'default', }); await db.executeSql('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)', []); return db; }; ``` **Capawesome SQLite:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const openDatabase = async () => { const { databaseId } = await Sqlite.open({ path: 'database.db', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)' ] } ] }); return databaseId; }; ``` #### Executing SQL statements SQL execution patterns are streamlined in the Capawesome SQLite plugin: **Ionic Secure Storage:** ``` import { SQLiteObject } from '@ionic-enterprise/secure-storage'; const insertUser = async (db: SQLiteObject, name: string, email: string) => { await db.executeSql( 'INSERT INTO users (name, email) VALUES (?, ?)', [name, email] ); }; ``` **Capawesome SQLite:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const insertUser = async (databaseId: string, name: string, email: string) => { await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: [name, email] }); }; ``` #### Querying data Data retrieval follows similar simplification patterns: **Ionic Secure Storage:** ``` import { SQLiteObject } from '@ionic-enterprise/secure-storage'; const getUsers = async (db: SQLiteObject) => { return new Promise((resolve) => { db.transaction(tx => { tx.executeSql('SELECT * FROM users WHERE name LIKE ?', ['%John%'], (tx, result) => { resolve(result.rows); }); }); }); }; ``` **Capawesome SQLite:** ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const getUsers = async (databaseId: string) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT * FROM users WHERE name LIKE ?', values: ['%John%'] }); return result.values; }; ``` ## Need Help Migrating? If you'd rather not handle the migration yourself, the Capawesome team can take care of it for you. Whether you're dealing with a straightforward key-value store swap or a more complex SQLite setup, we offer dedicated migration services to get you up and running with minimal downtime and effort on your end. [Book a Free Consultation](https://cal.com/team/capawesome/ionic-appflow-migration) ## Conclusion The discontinuation of Ionic Secure Storage doesn't have to disrupt your development workflow. The [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin provide comprehensive alternatives that not only replace the functionality of Ionic Secure Storage but enhance it with modern architecture, better performance, and professional support. By migrating to these Capawesome plugins, you gain access to actively maintained solutions that stay current with the latest Capacitor versions and platform updates, ensuring your applications remain secure and performant. To stay updated with the latest updates, features, and news about Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you need assistance with migrating from Ionic Secure Storage or implementing the [Capawesome SQLite](https://capawesome.io/plugins/sqlite/index.md) or [Capawesome Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugins, the Capawesome team is available to help you transition smoothly to this reliable alternative. Just [contact us](mailto:support@capawesome.io) to get started. # Alternatives to the Discontinued Ionic Enterprise Plugins Ionic has [discontinued all of its commercial products](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products), including Auth Connect, Identity Vault, and Secure Storage. If your Capacitor app relies on any of these plugins, you need a migration path. Capawesome offers production-ready, actively maintained alternatives for all three — so you can keep building without interruption. ## Introduction In early 2026, Ionic [announced](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products) that it has discontinued new customer sales and maintenance for all commercial products and services. While the open source projects — Ionic Framework and Capacitor — remain unaffected, three widely used enterprise plugins are being phased out: - **Auth Connect** — OAuth 2.0 and OpenID Connect authentication - **Identity Vault** — Biometric authentication and encrypted session management - **Secure Storage** — Encrypted key-value storage and SQLite databases For teams that depend on these plugins, the clock is ticking. The good news: Capawesome has built drop-in alternatives that cover the same core functionality, are actively maintained, and work with the latest Capacitor versions. Let's look at each one. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill ionic-enterprise-sdk-migration` and use the following prompt with your preferred AI coding assistant: ``` Use the `ionic-enterprise-sdk-migration` skill from `capawesome-team/skills` to help me migrate from Ionic Enterprise plugins to Capawesome alternatives. ``` ## Auth Connect → OAuth Plugin Ionic Auth Connect handled OAuth 2.0 and OpenID Connect flows, token management, and multi-provider support. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin replaces this with a clean, universal API that works with any compliant provider — including Auth0, Azure AD, Amazon Cognito, Okta, OneLogin, and Google. Key capabilities: - OAuth 2.0 Authorization Code flow with PKCE - Automatic OpenID Connect endpoint discovery - Token refresh, expiration checks, and ID token decoding - Full support for Android, iOS, and Web The migration is straightforward since both plugins cover the same authentication scenarios. For a detailed walkthrough with side-by-side code examples, check out the dedicated guide: [Alternative to the Ionic Auth Connect Plugin](https://capawesome.io/blog/alternative-to-ionic-auth-connect-plugin/index.md) ## Identity Vault → Biometrics + Secure Preferences Plugins Ionic Identity Vault bundled biometric authentication, encrypted storage, and session management into a single plugin. With Capawesome, you get this same functionality by combining two plugins: the [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin for Face ID/fingerprint authentication and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) for encrypted key-value storage. For a complete walkthrough on the Biometrcis plugin make sure to read [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md). Key capabilities: - Biometric authentication with device credential fallback (Face ID, fingerprint, PIN) - Encrypted storage using platform-native security (Android Keystore, iOS Keychain) - Full control over session locking, timeouts, and unlock behavior Separating authentication from storage gives you more flexibility to tailor each feature to your app's needs. For the full migration guide with code examples: [Alternative to the Ionic Identity Vault Plugin](https://capawesome.io/blog/alternative-to-ionic-identity-vault-plugin/index.md) ## Secure Storage → SQLite + Secure Preferences Plugins Ionic Secure Storage offered both encrypted key-value storage and encrypted SQLite database access. Capawesome covers both use cases with dedicated plugins: [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) for key-value data and [SQLite](https://capawesome.io/plugins/sqlite/index.md) for relational data. Key capabilities: - Encrypted key-value storage with a simple get/set API (Secure Preferences) - Full SQLite database support with schema versioning (SQLite) - Platform-native encryption on Android and iOS Depending on your use case, you may need one or both plugins. The migration guide covers both paths in detail: [Alternative to the Ionic Secure Storage Plugin](https://capawesome.io/blog/alternative-to-ionic-secure-storage-plugin/index.md) ## Migration Steps Regardless of which Ionic plugin you're migrating from, the high-level process is the same: 1. **Identify your dependencies** — Determine which Ionic Enterprise plugins your app uses and which features you rely on. 1. **Install the Capawesome alternatives** — Follow the installation instructions in each plugin's documentation. 1. **Replace imports and API calls** — Swap out the Ionic plugin calls with the corresponding Capawesome methods. The individual migration guides linked above provide side-by-side code examples for every common operation. 1. **Test on all platforms** — Verify that authentication flows, data storage, and biometric prompts work correctly on Android, iOS, and Web. 1. **Remove the old dependencies** — Once everything is working, uninstall the Ionic Enterprise packages and clean up any leftover configuration. ## Need Help Migrating? If you'd rather not handle the migration yourself, the Capawesome team can take care of it for you. Whether you're replacing a single plugin or migrating away from the entire Ionic Enterprise stack, we offer dedicated migration services to get you up and running with minimal downtime and effort on your end. [Book a Free Consultation](https://cal.com/team/capawesome/ionic-appflow-migration) ## Conclusion The discontinuation of Ionic's commercial plugins doesn't have to derail your project. Capawesome provides actively maintained, production-ready alternatives for Auth Connect, Identity Vault, and Secure Storage — all built for Capacitor and designed to make migration as smooth as possible. To stay updated with the latest updates, features, and news about Capawesome, Capacitor, and the Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you need assistance with migrating from any of the Ionic Enterprise plugins, the Capawesome team is available to help you transition smoothly. Just [contact us](mailto:support@capawesome.io) to get started. # Announcing Capawesome Cloud Automations We're excited to announce **Capawesome Cloud Automations**, a new feature that automatically triggers builds whenever you push to a branch or create a tag. No CLI, no external CI/CD pipeline, no manual steps — just connect your Git repository, set up an automation, and every matching Git event kicks off a build. Configure it once and let Capawesome Cloud handle the rest. \[ \](/assets/videos/posts/announcing-capawesome-cloud-automations/05f0e923-8ac3-4297-bb60-f4ad403851bf.mp4) ## Why Automations? Until now, triggering a build on Capawesome Cloud required a manual step — either clicking "Build from Git" in the Console or running a command via the CLI. Many teams worked around this by setting up external CI/CD pipelines (GitHub Actions, GitLab CI, etc.) that called the Capawesome Cloud API on every push. While this worked, it introduced extra complexity: **another pipeline to maintain, another set of credentials to manage, and duplicate costs** for CI/CD minutes you were already paying for with Capawesome Cloud. Automations remove all of that. Everything happens inside Capawesome Cloud — no external tooling, no CLI, no YAML files. Just a Git repository and Capawesome Cloud. This makes the entire setup **less error-prone** and easier to manage, especially for teams where not every member has a local development environment configured. ## How It Works Setting up an automation takes less than a minute: 1. **Navigate to Automations**: Open the Automations page of your app in the Capawesome Cloud Console. 1. **Create an Automation**: Give it a name, select a platform (Android, iOS, or Web), and choose a trigger type — **Branch** or **Tag**. 1. **Set a Trigger Pattern**: Specify which branch or tag should trigger the build, for example `main` or `v*`. 1. **Configure Build Settings** (optional): Attach a signing certificate, environment, store destination, or live update channel. 1. **Push Your Code**: That's it. The next matching push or tag triggers a build automatically. Once a build is triggered, you'll receive a **notification via email** and can track the build progress directly in the Console — just like any other build. [Get Started](https://cloud.capawesome.io) ## Key Features ### Branch and Tag Triggers Manage all your automations in one place Configure automations to trigger on **branch pushes** or **tag creation**. Use branch triggers for continuous integration workflows (e.g., build on every push to `main`) and tag triggers for release workflows (e.g., build when a version tag like `v1.0.0` is created). ### Pattern Matching Target specific branches or tags using **name patterns**. For example, trigger builds only for tags starting with `v` or only for pushes to `release/*` branches. This gives you fine-grained control over which Git events actually start a build. ### Commit Message Filtering Optionally filter triggers based on **commit message content**. This lets you skip builds for non-code changes or only build when a specific keyword is present in the commit message. ### Full Build Configuration Each automation can include the full set of build settings — **build type, build stack, signing certificate, environment variables, store destination, and live update channel**. This means you can set up a complete pipeline from Git push to app store submission or live update deployment, all without leaving Capawesome Cloud. ### Automatic Webhook Setup Set up webhooks manually when needed When you create an automation, Capawesome Cloud **automatically registers a webhook** on your connected Git repository. No manual configuration needed in most cases. If automatic registration isn't possible (e.g., for self-hosted providers), you can set up the webhook manually using the values provided in the Setup dialog. [Learn more about Webhooks](https://capawesome.io/cloud/automations/webhooks/index.md) ### Enable and Disable **Toggle automations on or off** without deleting them. This is useful for temporarily pausing builds during maintenance or when you want to keep a configuration for later use without it triggering on every push. ## Getting Started Head over to the [Automations documentation](https://capawesome.io/cloud/automations/index.md) to learn how to set up your first automation. If you already have a Git repository connected and an app configured in Capawesome Cloud, you can be up and running in under a minute. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Final Thoughts Automations bring true CI/CD to Capawesome Cloud without the need for external tools or duplicate infrastructure. Push your code, and Capawesome Cloud takes care of the build — including signing, publishing, and live update deployment if configured. It's the simplest way to keep your Capacitor app builds in sync with your Git workflow. If you have questions or feedback, join our [Discord community](https://discord.gg/VCXxSVjefW) — we'd love to hear how you're using Automations. And if you want to stay up to date on new features and announcements, [subscribe to our newsletter](https://cloud.capawesome.io/newsletter). For more on cloud builds, check out our [Web Builds announcement](https://capawesome.io/blog/announcing-capawesome-cloud-web-builds/index.md). # Announcing Capawesome Cloud Native Builds We're thrilled to announce the launch of [Capawesome Cloud Native Builds](https://cloud.capawesome.io/), a powerful new feature that brings native iOS and Android app building to the cloud. Inspired by Ionic Appflow and Expo Application Services (EAS), we've built a production-ready solution specifically for Capacitor and Ionic apps with first-class support for [Live Updates](https://cloud.capawesome.io/live-updates/), modern architecture for improved performance, and seamless integration with your development workflow. Say goodbye to complex CI/CD configurations and expensive build minutes — Native Builds is here to make your mobile app development faster, more reliable, and more affordable. \[ \](/assets/videos/posts/announcing-capawesome-cloud-native-builds/c83a9b6f-6ecc-4028-92a6-9c5400fd04d3.mp4) ## Built from the Ground Up for Capacitor Unlike generic CI/CD platforms, Capawesome Cloud Native Builds was **designed specifically for Capacitor and Ionic applications**. We've built the entire platform from scratch with a focus on delivering the perfect CI/CD experience for mobile app developers. **No third-party CI/CD platforms, no workarounds**—just a modern architecture optimized for building and deploying Capacitor apps at scale. ## How It Works Getting started with Native Builds is straightforward: 1. **Connect Your Git Repository**: Link your GitHub, GitLab, Bitbucket, or Azure DevOps repository (including self-hosted instances) 1. **Configure Signing Certificates and Store Destinations**: Upload your signing certificates and optionally configure automatic app store submissions 1. **Trigger Builds and App Submissions**: Start builds manually or automatically via Git integration, then monitor progress with detailed job logs through our web console (accessible from any device, including mobile) [Get Started](https://cloud.capawesome.io) ## Key Features ### Native Builds Native Builds provides **everything you need to build production-ready iOS and Android apps** in the cloud. No more maintaining your own CI/CD runners or struggling with complex YAML configurations. Just connect your repository, configure your build settings, and let Capawesome Cloud handle the rest. Monitor your builds in real-time - **Optimized Build Stacks**: Pre-configured with the latest stable versions of Node.js, Java, and Xcode running on macOS 15 with M4 instances. No more build errors from missing dependencies or incompatible versions. - **Universal Git Integration**: Connect GitHub, GitLab, Bitbucket, or Azure DevOps—including self-hosted and enterprise instances. Trigger builds automatically or manually. - **Complete Signing & Provisioning**: Securely manage Android keystores and iOS certificates. Support for all iOS build types (Simulator, Development, Ad Hoc, App Store with automatic TestFlight upload, and Enterprise) and Android build types. - **Advanced Configuration**: Simple `capawesome.config.json` file with support for monorepos, custom build commands, and various package managers (npm, Yarn, pnpm). - **Easy Debugging**: Comprehensive job logs make troubleshooting straightforward. AI-powered debugging assistance coming soon. [Learn more about Native Builds](https://capawesome.io/cloud/native-builds/index.md) ### App Store Publishing Submit your apps directly to **TestFlight, App Store, and Google Play Store** tracks with ease. Just configure your store destinations once, and every successful build can be deployed with just a single click. Track all deployments in one place - **Automatic Submissions**: Submit builds directly to TestFlight, App Store, and Google Play Store tracks without manual uploads. - **One-Time Setup**: Configure your store destinations once, then let every successful build deploy automatically. - **Centralized Deployment Tracking**: Monitor all app store submissions and deployments in one place with full visibility into your release history. - **Seamless Integration**: Works perfectly with Native Builds to create a complete CI/CD pipeline from code to app stores. [Learn more about App Store Publishing](https://capawesome.io/cloud/app-store-publishing/index.md) ### Command Line Interface Build iOS and Android apps from any machine—**no macOS, Xcode, or Android Studio required**. Our CLI offers powerful automation capabilities that let you trigger builds and deployments directly from the command line, whether you're on Windows, Linux, or macOS. This removes the traditional barriers to mobile app development and speeds up the entire process, making it easier than ever to create awesome mobile apps. - **Build from Anywhere**: Use `npx @capawesome/cli apps:builds:create` to start builds from any machine, regardless of your operating system. Perfect for teams working across different platforms or developers who don't have access to a Mac. - **Automated Artifact Downloads**: Add the `--apk` or `--ipa` flag to automatically download build artifacts directly after the build finishes, streamlining your local development workflow without needing Android Studio or Xcode installed. - **Easy CI/CD Integration**: Integrate seamlessly into your existing CI/CD pipelines or automation scripts, giving you flexibility in how you manage your build and deployment workflow. [Learn more about CLI](https://capawesome.io/cloud/cli/index.md) ## Developer Experience We've designed Native Builds with **developer experience as a top priority**. Forget about complex YAML configuration files—our simple `capawesome.config.json` keeps your build configuration readable and maintainable. The detailed, readable job logs make debugging a breeze, and with AI-powered debugging assistance coming soon, resolving build issues will be even faster. **Best of all, you don't need to maintain your own CI/CD runners** or worry about infrastructure management. ## Performance That Matters Performance is where Capawesome Cloud Native Builds truly shines. Our runner images are optimized specifically for Capacitor and Ionic projects, running on M4 instances with 4 CPU cores, 14 GB RAM, and 50 GB storage by default. You can choose runner configurations up to 14 CPU cores, 64 GB RAM, and 250 GB storage for more demanding builds. Not only iOS builds but also **Android builds use macOS runners** to ensure both the fastest builds and a consistent build environment. Compare this to GitHub Actions, which uses M1 instances with just 3 CPU cores, 7 GB RAM, and 14 GB storage by default. What does this mean for you? Builds that are **3-5x faster** than traditional CI/CD platforms. Faster builds mean quicker iterations, faster releases, and happier developers. Plus, with our optimized build environments, you'll spend less time fighting build errors caused by missing dependencies or incompatible versions. ### Cost Comparison Let's talk numbers. Here's how our pricing compares to other popular CI/CD platforms: | Platform | 600 Build Minutes | Notes | | ---------------- | ----------------- | --------------------------------------------- | | Capawesome Cloud | $29/month | macOS M4, Over-the-Air (OTA) Updates included | | GitHub Actions | $48/month | macOS M1 | | Codemagic | $57/month | macOS M2 | | Bitrise | $99/month | macOS M2 | With Capawesome Cloud, you get more for less: **600 build minutes for $29/month**, and that includes Over-the-Air (OTA) updates. Not only are we more affordable, but **we're also using the latest M4 machines** while other platforms use older M1 or M2 hardware. Since our builds are 3-5x faster, your 600 build minutes go much further—expect around 120+ builds per month. That's significant cost savings combined with superior performance. ## Security & Compliance **Security is at the core of everything we build.** All signing certificates and secrets are encrypted at rest and in transit—handled the same way as in GitHub Actions, Ionic Appflow, or Expo Application Services. Build environments are completely isolated, with no shared runners between customers. We take data privacy seriously and are **committed to SOC 2 compliance**, with regular security audits to ensure your apps and data remain secure. ## Real Results from Real Customers We've helped several customers migrate from GitHub Actions and other CI/CD platforms to Capawesome Cloud Native Builds over the past few weeks. **Here's what they've experienced:** ### snapAddy BusinessCards [snapAddy](https://www.snapaddy.com/en/) provides AI-powered software solutions that automate the capture and management of business contact data for CRM systems. Their mobile app helps sales teams automatically capture and sync contact information on the go. **Before:** - 17 minutes per build on GitHub Actions - ~$1,100/month on GitHub Actions - $100/month for Capawesome Cloud Live Updates - **Total: ~$1,200/month** **After:** - 5 minutes per build on Capawesome Cloud - $299/month on Capawesome Cloud (Faster Builds + More Build Minutes included + Live Updates) - **Total: $299/month** **Savings: ~$900/month (75% reduction)** ### DHBW VS App The [Baden-Württemberg Cooperative State University](https://www.dhbw.de/english/home) (DHBW) is one of Germany's largest universities with over 33,000 students, combining academic studies with practical work experience. Their DHBW VS app serves the Villingen-Schwenningen campus community with essential student services and information. **Before:** - 7 minutes per build on GitHub Actions - $29/month for Capawesome Cloud Live Updates - Complicated signing certificate management via private GitHub repository **After:** - 2 minutes per build on Capawesome Cloud - $29/month for Capawesome Cloud (Faster Builds + Live Updates) - Easy signing certificate management via Capawesome Cloud Console **Result: 3x faster builds with simplified workflow at the same price** ## Pricing Capawesome Cloud Native Builds **starts at just $9/month** for 1,000 Live Updates and 200 build minutes. That's approximately 40 builds per month, considering that builds on Capawesome Cloud are 3-5x faster than traditional CI/CD platforms. For larger teams and enterprises, our **$299/month business plan** includes 1,000,000 Live Updates and 5,400 build minutes. Need more? We offer completely custom plans tailored to your needs. Remember: when you switch to Capawesome Cloud, **you're not just getting faster builds—you're also getting Capacitor Live Updates included in the price**. That's a complete mobile app deployment solution in one platform. [View Full Pricing](https://cloud.capawesome.io/pricing/) ## Limited Time: Launch Week Offer For **everyone subscribing this week**, we're offering a **20% discount for the first 12 months** on all plans. With yearly billing, that brings the starting price down to about **$7/month** for your first year. Lock in your savings today! Use the discount code **`LAUNCHWEEK20`** on the checkout page to claim your launch week discount. [Claim Your Launch Week Discount](https://console.cloud.capawesome.io/organizations/_/billing) Don't miss out on future updates and special offers—[sign up for our newsletter](https://cloud.capawesome.io/newsletter). ## Frequently Asked Questions **Is my source code permanently stored on Capawesome Cloud servers?** No, we do not permanently store your source code on our servers. Your repository is only cloned at build time into a temporary, isolated virtual machine. Once the build job is finished, the VM and all its contents are immediately destroyed. This works the exact same way as any other CI/CD platform like GitHub Actions, GitLab CI, or Jenkins. Additionally, no person ever has access to those files at build time—the build process is fully automated and isolated. **Can I use custom Gradle or Xcode configurations?** Yes! Native Builds supports custom build configurations through your project's existing Gradle and Xcode files. You can also use environment variables to customize build behavior. **How are signing certificates stored securely?** All signing certificates and secrets are encrypted at rest and in transit. They're stored in isolated, secure storage and are never exposed in build logs or accessible to other customers. **Can I trigger builds from my own CI/CD pipeline?** Yes! You can use our [CLI](https://capawesome.io/cloud/cli/index.md) or [API](https://capawesome.io/cloud/api/index.md) to trigger builds programmatically from your existing CI/CD workflows. **What happens if a build fails?** You'll receive detailed job logs that help identify the issue quickly. Our optimized build stacks minimize common build failures, and AI-powered debugging assistance is coming soon to help resolve issues even faster. **Can I customize the build environment?** Absolutely. Use reserved environment variables like `NODE_VERSION`, `JAVA_VERSION`, and `XCODE_VERSION` to select specific tool versions. Advanced users can also work with our team to create custom runner images. **How does Native Builds integrate with Live Updates?** Native Builds is designed to work seamlessly with [Capacitor Live Updates](https://capawesome.io/cloud/live-updates/index.md). Both features are part of the same platform, making it easy to manage your entire mobile app deployment workflow in one place. Direct integration for automatic Live Update deployment after successful builds is coming soon. **Is there a free tier available?** Yes, Capawesome Cloud offers a free tier for Live Updates. Build minutes are not included in the free tier to ensure the best possible performance and shortest queue times for paying customers. You'll need a paid plan to access Native Builds. **Do you offer unlimited build minutes?** Yes, we offer unlimited build minutes in custom billing plans. Contact us at [sales@capawesome.io](mailto:sales@capawesome.io) for more information about custom plans tailored to your team's need. **Do you offer custom billing plans?** Yes, we offer custom billing plans tailored to your needs. If you only need build minutes or only live updates, we can create a more affordable plan for you. Contact us at [sales@capawesome.io](mailto:sales@capawesome.io) to discuss your requirements. **Can I get even larger build machines?** Yes, we offer very large build machines with up to 14 CPU cores, 64 GB RAM, and 250 GB storage on request for custom billing plans. Contact us at [sales@capawesome.io](mailto:sales@capawesome.io) for more information. ## We Want Your Feedback Are we missing a feature you need? Have suggestions for improving Native Builds? **We'd love to hear from you!** Join our [Discord community](https://discord.gg/VCXxSVjefW) to share your feedback and feature requests. ## Schedule a Demo Want to see Native Builds in action or discuss how it can fit into your team's workflow? **Our team is here to help.** Schedule a demo with our sales team to learn more about how Capawesome Cloud can accelerate your mobile app development. [Schedule a Demo](https://cal.com/team/capawesome/cloud-demo) ______________________________________________________________________ Ready to build faster, save money, and simplify your mobile app deployment? [Get started with Capawesome Cloud Native Builds today](https://cloud.capawesome.io) and join the teams already building better apps with less hassle. # Announcing Capawesome Cloud Web Builds We're excited to announce **Capawesome Cloud Web Builds**, a powerful new feature that brings web bundle creation directly to the cloud. Previously, creating bundles for Live Updates required setting up a local development environment and using the Capawesome CLI. Now, you can **build and deploy Live Updates directly from the Capawesome Cloud Console**—from any device, without any local tools. We've fully integrated bundles into our native build and deployment system, making it faster and easier than ever to get your updates live. From Git commit to live update in seconds, no local setup required. \[ \](/assets/videos/posts/announcing-capawesome-cloud-web-builds/f96f6d22-df16-409e-86d6-e5d3e743f1b1.mp4) Create and deploy web builds directly from the console ## From Local Bundles to Cloud Builds Until now, creating bundles for Live Updates meant maintaining a local development environment—installing dependencies, configuring environment variables, and running CLI commands. While this worked, it created friction: **only developers with properly configured environments could create bundles**, local misconfigurations could lead to invalid builds, and every bundle had to be uploaded manually. With **Web Builds**, we've changed all that. We've integrated the entire bundle creation process into the same **build and deployment system** that powers our Native Builds. This means you can now trigger web builds directly from the Capawesome Cloud Console, using the same Git integration, environment variables, and team collaboration features you already use for native app builds. ## How It Works Creating web builds in the cloud is straightforward: 1. **Select Your Git Commit**: Choose any commit from your connected repository 1. **Configure Build Settings**: Use stored environment variables and secrets from your Capawesome Cloud project 1. **Trigger the Build**: Start the web build with a single click 1. **Deploy to Channels**: Deploy the same build to one or multiple channels instantly 1. **Monitor and Rollback**: View deployment history and roll back to previous builds if needed The entire process happens in the cloud, so **any team member can create and deploy web builds** from any device—including mobile devices. ## Key Benefits ### Build from Anywhere Create web builds directly from the **Capawesome Cloud Console** without local development environments. No need to install Node.js, configure package managers, or set up build scripts locally. Just select a Git commit and click build—whether you're at your desk or on the go. ### Consistent Builds Use **environment variables and secrets** already stored in Capawesome Cloud for reliable, consistent builds every time. No more failed builds caused by missing or misconfigured local environment variables. The same configuration that powers your native builds now powers your web builds. ### Team Collaboration Enable **all team members**—QA engineers, project managers, designers—to create and deploy web builds without needing developer setups or command-line knowledge. Democratize the deployment process and empower your entire team to ship updates faster. ### Multiple Deployments per Build Deploy the **same build artifact to multiple channels** without rebuilding or reuploading. Want to test a build in staging before promoting it to production? Just deploy the same build to both channels. This saves build time and ensures you're deploying exactly what you tested. ### Deployment History \[ \](/assets/videos/posts/announcing-capawesome-cloud-web-builds/f80edc3a-7120-44e7-ab0f-c411317bc489.mp4) Easily roll back to previous builds with deployment history View **complete deployment history** for each channel and easily **roll back to previous builds** if an issue arises. No more scrambling to figure out which bundle was deployed last or manually reverting to an older version. Just select a previous build and redeploy with one click. ## Important Changes As part of this release, we've made several important changes to how bundles and channels work in Capawesome Cloud. These changes ensure a seamless transition to the new build and deployment system while improving management and removing previous limitations. ### Bundles Are Now Build Artifacts All existing bundles have been migrated to the new **build and deployment system**. For each bundle in your project, we've automatically created a corresponding build and deployment entry. This ensures **complete backward compatibility**—your Live Update SDK will continue working exactly as before. The term "bundles" will continue to be used in the **Live Update SDK** to refer to build artifacts, maintaining consistency with your existing code and documentation. ### Build Minutes Required Creating web builds in the cloud requires **build minutes** included in your plan. If your current plan doesn't include build minutes, you'll need to upgrade or continue using the **Capawesome CLI** to create bundles locally. For teams already using Native Builds, web builds are included—no changes needed. ### Channels Are Mandatory Manage all your deployment channels in one place Channels are now **required** to deploy Live Updates. Previously, you could create bundles without assigning them to a channel. To ensure backward compatibility, we've automatically assigned all bundles without channels to a **default channel called "default-20250118"**. If your Live Update SDK requests a bundle **without specifying a channel**, it will automatically receive updates from this default channel. You can rename or delete this channel as needed—just make sure to update your SDK configuration if you make changes. ### One Active Build per Channel Each channel now has **one active web build**. This simplifies management significantly—no more juggling bundle limits, no more deciding which bundles to keep or delete. When you deploy a new build to a channel, it becomes the active build for that channel. ### No More Storage Limits Inactive build artifacts are **automatically deleted** after your plan's data retention period. This means you no longer need to manually set expiration dates or channels to clean up old bundles. The system handles cleanup for you, and you only keep what you need. ## Current Limitations While we're excited to launch Web Builds, there are a few limitations to be aware of: - **Manifest Type Support**: Currently, our build system only supports creating bundles with manifest type **`zip`**. Support for artifact type **`manifest`** is coming in the next few weeks. In the meantime, you can continue using the Capawesome CLI to create manifest-type bundles locally. We're actively working on removing these limitations and will announce updates as soon as they're available. ## What's Next Over the **next few days**, we'll be updating our documentation to reflect all the changes introduced with Web Builds. You'll find updated guides, examples, and best practices for using the new build and deployment system. We'd love to hear your feedback on this new feature! **What do you think?** Are there additional features or improvements that would make Web Builds even better for your workflow? Join our [Discord community](https://discord.gg/VCXxSVjefW) to share your thoughts, ask questions, and help us shape the future of Capawesome Cloud. ______________________________________________________________________ Ready to build and deploy Live Updates faster than ever? [Get started with Capawesome Cloud Web Builds today](https://cloud.capawesome.io) and experience the simplicity of cloud-based deployment. # Announcing Open Source AI Agent Skills for Capacitor AI coding agents are becoming an essential part of how developers build apps. But even the best agents struggle with framework-specific tasks like upgrading between Capacitor versions, configuring native plugins, or setting up cloud builds — there's just too much platform-specific knowledge involved. That's why we're releasing **7 open source agent skills** that teach AI assistants how to handle the most common Capacitor tasks, from plugin setup to full platform upgrades. ## What Are Agent Skills? Agent skills are structured sets of procedural instructions and reference documentation that AI coding agents can consume to guide developers through complex tasks. Think of them as specialized knowledge packs — instead of an AI agent improvising based on general training data, a skill gives it step-by-step procedures, platform-specific details, and error handling for a specific domain. Our skills are built on the [skills.sh](https://skills.sh/) framework and follow the open [agentskills.io](https://agentskills.io/) spec. They work with popular AI coding tools like [Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://www.cursor.com/), [Windsurf](https://windsurf.com/), [GitHub Copilot](https://github.com/features/copilot), and others. Once installed, you just prompt your agent and the skill takes over — detecting your project setup, walking through each step, and handling platform differences automatically. ## How Do Agent Skills Work? Without agent skills, adding domain-specific context often means feeding your agent large amounts of documentation upfront — thousands of tokens before it even starts working. With agent skills, the agent only receives a short description of each skill at first. When a relevant task comes up, the agent activates the skill and reads the reference documentation incrementally, pulling in only what it needs to complete the task. This approach is called **progressive disclosure**. For example, our `capacitor-plugins` skill covers over 160 plugins, but the agent doesn't load all 160 reference files at once. It identifies which plugin you need, loads just that plugin's reference, and walks you through the setup. The result is lower token consumption, lower costs, and more accurate output. Skills also delegate to each other when tasks overlap. The `ionic-appflow-migration` skill, for instance, hands off the Capawesome Cloud setup to the `capawesome-cloud` skill rather than duplicating those instructions. This keeps each skill focused and maintainable. ## Available Skills We're launching with 7 skills that cover the most common tasks Capacitor developers face. Here's what's included: ### Capacitor Plugins The `capacitor-plugins` skill helps you install, configure, and use **over 160 Capacitor plugins** from official, Capawesome, community, Firebase, and MLKit sources. Each plugin has its own reference file with installation steps, platform-specific configuration, and usage examples. The agent auto-detects your project's platforms and build tools, then walks you through everything one step at a time. ### Capacitor Push Notifications Setting up push notifications involves Firebase configuration, platform-specific setup for Android and iOS, permission handling, and token management. The `capacitor-push-notifications` skill covers the entire flow end to end — from creating a Firebase project to sending your first test notification. ### Capawesome Cloud The `capawesome-cloud` skill covers three core workflows: **native builds**, **live updates**, and **app store publishing**. Whether you're setting up cloud builds for the first time, configuring over-the-air updates with the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin, or automating submissions to the Apple App Store or Google Play Store — this skill handles it. ### Capacitor App Upgrades Need to update your Capacitor app from version 4 to 8? The `capacitor-app-upgrades` skill detects your current version, checks prerequisites (Node.js, Xcode, Android Studio), and walks you through each major version jump sequentially. It tries the automated upgrade path first and falls back to manual steps if needed. ### Capacitor Plugin Upgrades Similar to app upgrades, the `capacitor-plugin-upgrades` skill handles upgrading **Capacitor plugins** (libraries, not apps) between major versions. It covers Android SDK targets, Gradle configuration, Java/Kotlin versions, iOS deployment targets, and all the plugin-specific build considerations. There's also a `capacitor-plugin-spm-support` skill for [adding Swift Package Manager support to existing plugins](https://capawesome.io/blog/how-to-migrate-a-capacitor-plugin-to-spm/index.md). ### Ionic Appflow Migration The `ionic-appflow-migration` skill is a comprehensive guide for teams moving from Ionic Appflow to [Capawesome Cloud](https://capawesome.io/cloud/overview.md). It detects which Appflow features you're using (live updates, native builds, app store publishing), then migrates each one — including SDK replacement, API call mapping, and CI/CD pipeline updates. This skill delegates to the `capawesome-cloud` skill for the Capawesome Cloud setup, so you get the full setup experience without duplicated instructions. ### Ionic Enterprise SDK Migration With the discontinuation of Ionic Enterprise SDK plugins, the `ionic-enterprise-sdk-migration` skill maps each Ionic plugin to its Capawesome alternative — Auth Connect to [OAuth](https://capawesome.io/plugins/oauth/index.md), Identity Vault to [Biometrics](https://capawesome.io/plugins/biometrics/index.md) and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md), and Secure Storage to [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) or [SQLite](https://capawesome.io/plugins/sqlite/index.md). It handles the migration plugin by plugin, preserving existing behavior along the way. ## Skills, Docs, and CLI — How They Fit Together If you're already using [Capawesome Cloud](https://capawesome.io/cloud/overview.md) and the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md), you might wonder how agent skills fit into the picture. Think of it as the relationship between expertise and capability: - **Agent skills** teach your AI agent *how* to perform Capacitor and Capawesome tasks — the right steps, the right order, the platform-specific details. - **Capawesome CLI** gives the agent (and you) the *ability* to execute those tasks — creating builds, uploading live updates, managing certificates. - **Documentation** remains the complete reference for humans who want to understand every option and edge case in depth. Skills and the CLI are complementary. A skill might instruct the agent to run a CLI command like `npx @capawesome/cli apps:builds:create`, and the agent executes it. We recommend using both together for the best experience. ## Getting Started Install all skills with a single command: ``` npx skills add capawesome-team/skills ``` Then prompt your AI agent to use a specific skill. For example: ``` Use the `capawesome-cloud` skill to help me set up Capacitor Live Updates in my project. ``` ``` Use the `capacitor-plugins` skill to add the NFC plugin to my app. ``` ``` Use the `capacitor-app-upgrades` skill to update my app to Capacitor 8. ``` The agent takes it from there — detecting your project, asking the right questions, and guiding you through each step. The skills are fully open source and available on [GitHub](https://github.com/capawesome-team/skills). Contributions are welcome. [Star Us on GitHub](https://github.com/capawesome-team/skills) ## Final Thoughts These agent skills represent a new way to make Capacitor development more accessible. Instead of reading through documentation and figuring out platform-specific details yourself, you can let an AI agent handle the heavy lifting while you stay in control of every decision. We're starting with 7 skills and plan to expand the collection based on community feedback and the most common tasks developers face. For the full list of skills and more details, check out the [GitHub repository](https://github.com/capawesome-team/skills). If you have questions or want to suggest new skills, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And don't forget to [subscribe to the newsletter](https://cloud.capawesome.io/newsletter) to stay updated on future releases. # Announcing the Capacitor Audio Recorder Plugin Today we are excited to announce our brand new [Capacitor Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin. This plugin enables recording audio in your Capacitor app and provides cross-platform support for Android, iOS and web. It's available to all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's take a quick look at the [API](https://capawesome.io/plugins/audio-recorder/#api) and how you can use the plugin to record audio. ## Installation To install the Capacitor Audio Recorder plugin, please refer to the [Installation](https://capawesome.io/plugins/audio-recorder/#installation) section in the plugin documentation. ## Usage The plugin is very easy to use. Let's take a look at the basic usage of the plugin. ### Start recording You can immediately start recording audio with the [`startRecording()`](https://capawesome.io/plugins/audio-recorder/#startrecording) method: ``` import { AudioRecorder } from "@capawesome-team/capacitor-audio-recorder"; const startRecording = async () => { await AudioRecorder.startRecording(); }; ``` There are no additional parameters required. ### Pause recording You can pause the recording at any time with the [`pauseRecording()`](https://capawesome.io/plugins/audio-recorder/#pauserecording) method: ``` import { AudioRecorder } from "@capawesome-team/capacitor-audio-recorder"; const pauseRecording = async () => { await AudioRecorder.pauseRecording(); }; ``` ### Resume recording After pausing the recording, you can resume it with the [`resumeRecording()`](https://capawesome.io/plugins/audio-recorder/#resumerecording) method: ``` import { AudioRecorder } from "@capawesome-team/capacitor-audio-recorder"; const resumeRecording = async () => { await AudioRecorder.resumeRecording(); }; ``` ### Stop recording Finally, you can stop the recording with the [`stopRecording()`](https://capawesome.io/plugins/audio-recorder/#stoprecording) method: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; import { NativeAudio } from '@capacitor-community/native-audio'; const stopRecording = async () => { // Stop recording and get the audio blob or URI const { blob, uri } = await AudioRecorder.stopRecording(); // Play the audio if (blob) { // Only available on Web const audio = new Audio(); audio.src = URL.createObjectURL(blob); audio.play(); } else if (uri) { // Only available on Android and iOS await NativeAudio.preload({ assetId: 'recording', assetPath: uri, isUrl: true, }); await NativeAudio.play({ assetId: 'recording' }); } }; ``` This method returns a `blob` or `uri` depending on the platform. The `blob` is only available on web, while the `uri` is only available on Android and iOS. You can then upload the audio file to your server or play it back in your app. ### Get recording status Additionally, you can check the recording status at any time with the [`getRecordingStatus()`](https://capawesome.io/plugins/audio-recorder/#getrecordingstatus) method: ``` import { AudioRecorder } from "@capawesome-team/capacitor-audio-recorder"; const getRecordingStatus = async () => { const { status } = await AudioRecorder.getRecordingStatus(); console.log("Recording status:", status); }; ``` This method returns the current recording status, which can be either `INACTIVE`, `RECORDING` or `PAUSED`. ## Conclusion We hope you are as excited as we are about the new [Capacitor Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin. Be sure to check out the [API Reference](https://capawesome.io/plugins/audio-recorder/#api) to see what else you can do with this plugin. If you are missing any features, just [create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Make sure you follow us on [X](https://x.com/capawesomeio) so you don't miss any future updates. # Announcing the Capacitor Barometer Plugin We're pleased to introduce the [Capacitor Barometer](https://capawesome.io/plugins/barometer/index.md) plugin, bringing atmospheric pressure sensing capabilities to your Capacitor applications. This plugin enables precise barometric measurements in hectopascals (hPa) with support for real-time updates and sensor availability detection across Android and iOS platforms. The plugin is now available for all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Whether you're building weather applications, altitude tracking systems, or environmental monitoring tools, this plugin provides the foundation for atmospheric pressure data integration in your cross-platform apps. ## Installation To install the Capacitor Barometer plugin, please refer to the [Installation](https://capawesome.io/plugins/barometer/#installation) section in the plugin documentation. ## Usage The Capacitor Barometer plugin provides a comprehensive API for atmospheric pressure monitoring with both single measurements and continuous updates. Let's explore the core functionality that makes this plugin essential for environmental sensing applications. ### Checking sensor availability Before requesting measurements, verify that the device has a barometer sensor using the [`isAvailable()`](https://capawesome.io/plugins/barometer/#isavailable) method: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const checkBarometerSupport = async () => { const { isAvailable } = await Barometer.isAvailable(); if (isAvailable) { console.log('Barometer sensor is available on this device'); // Proceed with barometer operations } else { console.log('Barometer sensor is not available'); // Handle gracefully or show alternative UI } }; ``` This check ensures your app gracefully handles devices without barometer sensors and provides appropriate user feedback. ### Managing permissions Handle sensor permissions appropriately using the permission methods provided by the plugin: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const handleBarometerPermissions = async () => { // Check current permission status const permissions = await Barometer.checkPermissions(); if (permissions.sensors !== 'granted') { // Request permission if not already granted const result = await Barometer.requestPermissions(); if (result.sensors === 'granted') { console.log('Sensor permission granted'); // Proceed with barometer operations } else { console.log('Sensor permission denied'); // Handle permission denial } } }; ``` This ensures your app has the necessary permissions to access the barometer sensor, enhancing user trust and compliance with platform guidelines. ### Getting a single measurement Use the [`getMeasurement()`](https://capawesome.io/plugins/barometer/#getmeasurement) method to retrieve the current atmospheric pressure reading from the device's barometer sensor: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const getCurrentPressure = async () => { try { const { measurement } = await Barometer.getMeasurement(); console.log('Current pressure:', measurement.pressure, 'hPa'); // Optional: Calculate relative altitude if available if (measurement.relativeAltitude !== undefined) { console.log('Relative altitude:', measurement.relativeAltitude, 'm'); } } catch (error) { console.error('Failed to get barometer measurement:', error); } }; ``` The measurement object contains the pressure value in hectopascals, providing you with accurate atmospheric pressure data for your application's needs. ### Real-time measurement updates For applications requiring continuous atmospheric pressure monitoring, use the [`startMeasurementUpdates()`](https://capawesome.io/plugins/barometer/#startmeasurementupdates) method to receive real-time pressure readings: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const startContinuousMonitoring = async () => { // Listen for measurement updates Barometer.addListener('measurementReceived', (event) => { console.log('New pressure reading:', event.measurement.pressure, 'hPa'); }); // Start receiving measurement updates await Barometer.startMeasurementUpdates(); }; const stopContinuousMonitoring = async () => { // Stop measurement updates to conserve battery await Barometer.stopMeasurementUpdates(); // Remove all listeners Barometer.removeAllListeners(); }; ``` Remember to call [`stopMeasurementUpdates()`](https://capawesome.io/plugins/barometer/#stopmeasurementupdates) when continuous monitoring is no longer needed to preserve device battery life. ## Conclusion The [Capacitor Barometer](https://capawesome.io/plugins/barometer/index.md) plugin delivers reliable atmospheric pressure sensing for environmental monitoring, weather applications, and altitude tracking systems. With its intuitive API and cross-platform compatibility, you can easily integrate barometric measurements into your Capacitor applications. Explore the complete [API Reference](https://capawesome.io/plugins/barometer/#api) to discover additional features and configuration options. Have ideas for enhancements [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) for the latest updates and announcements. # Announcing the Capacitor Biometrics Plugin Today we are excited to announce our [Capacitor Biometrics plugin](https://capawesome.io/plugins/biometrics/index.md). It lets you request **biometric authentication** (Face ID, fingerprint) and device credentials (PIN, pattern, password) on Android and iOS through a single API. The plugin is available to all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's take a quick look at the [API](https://capawesome.io/plugins/biometrics/#api) and how to use the plugin in your Capacitor app. ## Bonus: Video Tutorial and Demo App We created a **step-by-step video tutorial** that walks through installing the plugin, configuring iOS Face ID permissions, organizing your biometrics logic in a small helper module, triggering authentication from the app UI, and handling success and error states. - **[Capacitor Biometrics Demo App](https://github.com/capawesome-team/capacitor-biometrics-demo)** — A minimal, framework-agnostic demo (vanilla JavaScript and Capacitor) with a complete biometric authentication flow using Face ID or fingerprint. ## Installation To install the Capacitor Biometrics plugin, please refer to the [Installation](https://capawesome.io/plugins/biometrics/#installation) section in the plugin documentation. ## Usage Let's take a look at the basic usage of the plugin. You can find the complete API reference in the [API](https://capawesome.io/plugins/biometrics/#api) section of the documentation. The plugin supports both **biometric authentication** and **device credential authentication**. Biometric authentication uses the device's biometric capabilities, such as fingerprint or face recognition, while device credential authentication uses the device's lock screen PIN, pattern, or password. ### Biometric Authentication The most important method of the plugin is [`authenticate()`](https://capawesome.io/plugins/biometrics/#authenticate), which allows you to request biometric authentication from the user. You can customize the authentication prompt with various options, such as the title, subtitle, and button text: ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { try { await Biometrics.authenticate({ title: 'Authentication Required', subtitle: 'Please authenticate to continue', cancelButtonText: 'Cancel', iosFallbackButtonText: 'Use Passcode', }); } catch (error) { if (error.code === ErrorCode.USER_CANCELED) { console.log('User canceled the authentication.'); } else if (error.code === ErrorCode.NOT_ENROLLED) { console.log('No biometric authentication enrolled.'); } else if (error.code === ErrorCode.NOT_AVAILABLE) { console.log('Biometric authentication not available.'); } else { console.log('Another error occurred:', error); } } }; ``` The method returns a promise that resolves when the user successfully authenticates or rejects with an error if the user cancels the authentication or if there is an error during the authentication process. You can handle the error using the `catch` block, where you can check the error code to determine the reason for the failure. The error codes are defined in the `ErrorCode` enum. In most cases, you will also want to use the [`isEnrolled()`](https://capawesome.io/plugins/biometrics/#isenrolled) method to check whether the device supports biometric authentication at all and whether the user has set it up: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const isEnrolled = async () => { const result = await Biometrics.isEnrolled(); if (result.isEnrolled) { console.log('Biometric authentication is enrolled.'); } else { console.log('No biometric authentication enrolled.'); } }; ``` It's recommended to call this method before calling [`authenticate()`](https://capawesome.io/plugins/biometrics/#authenticate) to ensure that the user has set up biometric authentication on their device. ### Device Credential Authentication In addition to biometric authentication, the plugin also supports device credential authentication using the [`authenticate()`](https://capawesome.io/plugins/biometrics/#authenticate) method. For this, you need to set the `allowDeviceCredential` option to `true` when calling the method: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { await Biometrics.authenticate({ allowDeviceCredential: true, }); }; ``` This will show the device's lock screen PIN, pattern, or password prompt if biometric authentication is not available or not enrolled. You can also use the [`hasDeviceCredential()`](https://capawesome.io/plugins/biometrics/#hasdevicecredential) method to check whether the device supports device credential authentication: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const hasDeviceCredential = async () => { const { hasDeviceCredential } = await Biometrics.hasDeviceCredential(); return hasDeviceCredential; }; ``` ## Conclusion We hope you find the [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin useful. Check the [API Reference](https://capawesome.io/plugins/biometrics/#api) for the full plugin documentation. **Related reading:** - [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md) - [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/index.md) - [Alternative to the Ionic Identity Vault plugin](https://capawesome.io/blog/alternative-to-ionic-identity-vault-plugin/index.md). **Missing a feature?** [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). # Announcing the Capacitor Bluetooth Low Energy Plugin Today we are excited to announce our brand new [Capacitor Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin. This plugin enables interaction with Bluetooth Low Energy (BLE) devices in the central and peripheral role and provides cross-platform support for Android and iOS. The project is now available for all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's take a quick look at the [API](https://capawesome.io/plugins/bluetooth-low-energy/#api) and how you can use the pugin to communicate with BLE devices. ## Installation To install the Capacitor Bluetooth Low Energy plugin, please refer to the [Installation](https://capawesome.io/plugins/bluetooth-low-energy/#installation) section in the plugin documentation. ## Usage Let's take a look at the basic usage of the plugin. The plugin supports two roles: - **Central role**: The app acts as a central device that can connect to and communicate with peripheral devices. - **Peripheral role**: The app acts as a peripheral device that can advertise its services and accept connections from central devices. The plugin supports both roles on Android and iOS. The following sections will show you how to use the plugin in both roles. ### Central role The central role allows you to connect to and communicate with BLE devices. #### Initialize the plugin First, you need to initialize the plugin and request the necessary permissions: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; import { Capacitor } from "@capacitor/core"; const initialize = async () => { if (Capacitor.getPlatform() === "ios") { await BluetoothLowEnergy.initialize({ mode: "central", }); } else { await BluetoothLowEnergy.requestPermissions(); } }; ``` In this context, there are differences between Android and iOS. On **iOS**, you need to call the [`initialize`](https://capawesome.io/plugins/bluetooth-low-energy/#initialize) method to initialize the plugin every time the app starts. On **Android**, you need to call the [`requestPermissions`](https://capawesome.io/plugins/bluetooth-low-energy/#requestpermissions) method to request the necessary permissions. #### Scan for devices Before you can connect to a device for the first time, you need to scan for devices. For this, you can use the [`startScan`](https://capawesome.io/plugins/bluetooth-low-energy/#startscan): ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const startScan = async () => { await BluetoothLowEnergy.addListener("deviceScanned", (event) => { console.log("Device scanned", event.device); }); await BluetoothLowEnergy.startScan(); }; const stopScan = async () => { await BluetoothLowEnergy.stopScan(); }; ``` Every time a device is found, the `deviceScanned` event is emitted. You can now display the found devices to your users and let them select the device they want to connect to. As soon as the user has selected a device, you should stop the scan with [`stopScan`](https://capawesome.io/plugins/bluetooth-low-energy/#stopscan). #### Connect to a device To connect to a device, you can use the [`connect`](https://capawesome.io/plugins/bluetooth-low-energy/#connect) method: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const connect = async (deviceId: string) => { await BluetoothLowEnergy.connect({ deviceId }); }; ``` You just need to pass the `deviceId` of the device you want to connect to. The `deviceId` is the address of the device (e.g. `00:11:22:33:AA:BB`) and is usually provided by the `deviceScanned` event. Reconnect to a device You don't need to scan for devices every time you want to connect to a device. You can simply save the `deviceId` of the device and use it to reconnect to the device later. #### Communicate with a device Before you can communicate with a device, you need to discover the services and characteristics of the device. For this, you can use the [`discoverServices`](https://capawesome.io/plugins/bluetooth-low-energy/#discoverservices) method: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const discoverServices = async () => { await BluetoothLowEnergy.discoverServices(); }; ``` Use the [`getServices`](https://capawesome.io/plugins/bluetooth-low-energy/#getservices) method to get a list of all services, characteristics, and descriptors of the device: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const getServices = async () => { const { services } = await BluetoothLowEnergy.getServices(); console.log("Services: ", services); }; ``` Now you can read, write, and subscribe to characteristics and descriptors using the following methods: - [`readCharacteristic`](https://capawesome.io/plugins/bluetooth-low-energy/#readcharacteristic) - [`writeCharacteristic`](https://capawesome.io/plugins/bluetooth-low-energy/#writecharacteristic) - [`startCharacteristicNotifications`](https://capawesome.io/plugins/bluetooth-low-energy/#startcharacteristicnotifications) - [`stopCharacteristicNotifications`](https://capawesome.io/plugins/bluetooth-low-energy/#stopcharacteristicnotifications) - [`readDescriptor`](https://capawesome.io/plugins/bluetooth-low-energy/#readdescriptor) - [`writeDescriptor`](https://capawesome.io/plugins/bluetooth-low-energy/#writedescriptor) This is an example of how to read a characteristic value: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const readCharacteristic = async (characteristicId: string) => { const { value } = await BluetoothLowEnergy.readCharacteristic({ characteristicId, }); console.log("Value: ", value); // e.g. [1, 2, 3, 4] }; ``` Values are exchanged as byte arrays. You can convert them to a hex string using the [`convertBytesToHex`](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/docs/utils/README.md) method: ``` import { BluetoothLowEnergyUtils } from "@capawesome-team/capacitor-bluetooth-low-energy"; const convertBytesToHex = async (bytes: number[]) => { const { hex } = await BluetoothLowEnergyUtils.convertBytesToHex({ bytes }); console.log("Hex: ", hex); // e.g. "01020304" }; ``` #### Disconnect from a device To disconnect from a device, you simply need to call the [`disconnect`](https://capawesome.io/plugins/bluetooth-low-energy/#disconnect) method: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const disconnect = async () => { await BluetoothLowEnergy.disconnect(); }; ``` ### Peripheral role The peripheral role allows you to advertise your app as a BLE device and accept connections from central devices. #### Initialize the plugin The initialization process is the same as in the central role. You need to call the [`initialize`](https://capawesome.io/plugins/bluetooth-low-energy/#initialize) method on iOS and the [`requestPermissions`](https://capawesome.io/plugins/bluetooth-low-energy/#requestpermissions) method on Android. ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const initialize = async () => { if (Capacitor.getPlatform() === "ios") { await BluetoothLowEnergy.initialize({ mode: "peripheral", }); } else { await BluetoothLowEnergy.requestPermissions(); } }; ``` #### Start advertising To start advertising your app as a BLE device, you can use the [`startAdvertising`](https://capawesome.io/plugins/bluetooth-low-energy/#startadvertising) method: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const startAdvertising = async () => { await BluetoothLowEnergy.startAdvertising({ name: "CapBLE", services: [ { id: "12345678-1234-1234-1234-1234567890AB", characteristics: [ { id: "87654321-4321-4321-4321-BA0987654321", descriptors: [], permissions: { read: true, write: true, }, properties: { indicate: true, notify: true, read: true, write: true, }, }, ], }, ], }); }; ``` The `startAdvertising` method allows you to specify the name of the peripheral and the services it provides. The services can include characteristics with various properties and permissions. #### Communicate with a device After starting advertising, devices can connect to your app. You can listen to the `deviceConnected` event to get notified when a device connects to your app: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const addListener = async () => { await BluetoothLowEnergy.addListener("deviceConnected", (event) => { console.log("Device connected", event.deviceId); }); }; ``` You can also listen to the `deviceDisconnected` event to get notified when a device disconnects from your app: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const addListener = async () => { await BluetoothLowEnergy.addListener("deviceDisconnected", (event) => { console.log("Device disconnected", event.deviceId); }); }; ``` Read requests from devices are handled automatically by the plugin. You can use the [`setCharacteristicValue`](https://capawesome.io/plugins/bluetooth-low-energy/#setcharacteristicvalue) method to set or update the value of a characteristic: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const setCharacteristicValue = async () => { await BluetoothLowEnergy.setCharacteristicValue({ characteristicId: "87654321-4321-4321-4321-BA0987654321", serviceId: "12345678-1234-1234-1234-1234567890AB", value: [1, 2, 3, 4], // Value byte array }); }; ``` If a device wants to write a value to a characteristic, the plugin will emit the `characteristicWriteRequest` event. You can listen to this event and respond to the write request using the [`setCharacteristicValue`](https://capawesome.io/plugins/bluetooth-low-energy/#setcharacteristicvalue) method: ``` import { BluetoothLowEnergy } from "@capawesome-team/capacitor-bluetooth-low-energy"; const addListener = async () => { await BluetoothLowEnergy.addListener( "characteristicWriteRequest", (event) => { console.log("Characteristic write request", event); // Respond to the write request void BluetoothLowEnergy.setCharacteristicValue({ characteristicId: event.characteristicId, serviceId: event.serviceId, value: event.value, }); } ); }; ``` ## Closing Thoughts We hope you are as excited as we are about the new [Capacitor Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin. Be sure to check out the [API Reference](https://capawesome.io/plugins/bluetooth-low-energy/#api) to see what else you can do with this plugin. If you are missing any features, just [create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Make sure you follow us on [X](https://twitter.com/capawesomeio) so you don't miss any future updates. # Announcing the Capacitor Contacts Plugin Today we are excited to announce our brand new [Capacitor Contacts](https://capawesome.io/plugins/contacts/index.md) plugin. This plugin allows you to access the device's contacts and provides cross-platform support for Android, iOS, and Web. The project is now available for all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's take a quick look at the [API](https://capawesome.io/plugins/contacts/#api) and how you can use the plugin to retrieve and manage contacts. ## Installation To install the Capacitor Contacts plugin, please refer to the [Installation](https://capawesome.io/plugins/contacts/#installation) section in the plugin documentation. ## Usage Let's take a look at the basic usage of the plugin. You can find the complete API reference in the [API](https://capawesome.io/plugins/contacts/#api) section of the documentation. ### Create a contact First, let's create a new contact. You can use the [`createContact(...)`](https://capawesome.io/plugins/contacts/#createcontact) method to create a new contact: ``` import { Contacts, EmailAddressType, PhoneNumberType, PostalAddressType } from "@capawesome-team/capacitor-contacts"; const createContact = async () => { const createContact = async () => { return Contacts.createContact({ contact: { givenName: 'John', familyName: 'Doe', emailAddresses: [ { value: 'mail@example.com', type: EmailAddressType.Home, isPrimary: true } ], phoneNumbers: [ { value: '1234567890', type: PhoneNumberType.Mobile, isPrimary: true } ], postalAddresses: [ { street: '123 Main St', city: 'Springfield', state: 'IL', postalCode: '62701', country: 'USA', type: PostalAddressType.Home, isPrimary: true } ] } }); }; ``` This method takes a `Contact` object as a parameter, which contains the contact's information such as name, email addresses, phone numbers, and postal addresses. ### Retrieve contacts You can retrieve contacts from the device's address book using the [`getContacts(...)`](https://capawesome.io/plugins/contacts/#getcontacts) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getContacts = async () => { const { contacts } = await Contacts.getContacts({ fields: ['givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses'], }); return contacts; }; ``` This method returns an array of contacts, each containing the requested fields. You can specify which fields you want to retrieve using the `fields` parameter. This is useful to limit the amount of data returned and improve performance. There is also a `getContactById(...)` method that allows you to retrieve a contact by its ID: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getContactById = async (contactId: string) => { const { contact } = await Contacts.getContactById({ id: contactId }); return contact; }; ``` This method takes the contact ID as a parameter and returns the contact with the specified ID. This is useful if you want to retrieve a specific contact without having to search for it in the list of all contacts. ### Update a contact To update an existing contact, you can use the [`updateContact(...)`](https://capawesome.io/plugins/contacts/#updatecontact) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const updateContact = async (contactId: string) => { await Contacts.updateContact({ id: 12, contact: { givenName: 'John', familyName: 'Doe' } }); }; ``` This method takes the contact ID and a `Contact` object as parameters. You can update the contact's information such as name, email addresses, phone numbers, and postal addresses. This is useful if you want to modify an existing contact's information. All contact fields are required All contact fields are required to be provided, even if they are not updated. Fields that are not provided will be removed from the contact. This bevavior will be changed in v8.0.0 of the plugin. From then on, only the fields that are provided will be updated, and the other fields will remain unchanged. If you want to remove a field, you can set it to `null`. ### Delete a contact You can delete a contact using the [`deleteContact(...)`](https://capawesome.io/plugins/contacts/#deletecontact) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const deleteContact = async (contactId: string) => { await Contacts.deleteContact({ id: contactId }); }; ``` This method takes the contact ID as a parameter and deletes the contact with the specified ID. This is useful if you want to remove a contact from the device's address book. ### Pick a contact You can let the user pick a contact from the device's address book using the [`pickContact(...)`](https://capawesome.io/plugins/contacts/#pickcontact) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const pickContact = async () => { const { contact } = await Contacts.pickContact(); return contact; }; ``` This method opens the device's contact picker and returns the selected contact. This way, you can let the user select a contact while respecting their privacy and without having to implement your own contact picker UI. ### Accounts On Android, you can retrieve the accounts associated with the device using the [`getAccounts(...)`](https://capawesome.io/plugins/contacts/#getaccounts) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getAccounts = async () => { const { accounts } = await Contacts.getAccounts(); return accounts; }; ``` You can then create a contact and associate it with an account using the `account` property of the `Contact` object: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createContact = async () => { return Contacts.createContact({ contact: { account: { name: 'john@doe.tld', type: 'com.google' }, givenName: 'John', familyName: 'Doe' }, }); }; ``` ### Groups On iOS, you can retrieve the groups associated with the device using the [`getGroups(...)`](https://capawesome.io/plugins/contacts/#getgroups) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const getGroups = async () => { const { groups } = await Contacts.getGroups(); return groups; }; ``` Just like with accounts, you can create a contact and associate it with one (or more) group(s) using the `groupIds` property of the `Contact` object: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createContact = async () => { return Contacts.createContact({ contact: { groupIds: ['904DE809-D144-4562-8552-DFEB91F0E4BD:ABGroup'], givenName: 'John', familyName: 'Doe' }, }); }; ``` You can even create a new group using the [`createGroup(...)`](https://capawesome.io/plugins/contacts/#creategroup) method: ``` import { Contacts } from "@capawesome-team/capacitor-contacts"; const createGroup = async () => { return Contacts.createGroup({ group: { name: 'Friends' } }); }; ``` ## Conclusion We hope you are as excited as we are about the new [Capacitor Contacts](https://capawesome.io/plugins/contacts/index.md) plugin. Be sure to check out the [API Reference](https://capawesome.io/plugins/contacts/#api) to see what else you can do with this plugin. If you are missing any features, just [create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Make sure you follow us on [X](https://x.com/capawesomeio) so you don't miss any future updates. # Announcing the Capacitor Firebase Cloud Firestore Plugin Today we are excited to announce the release of the [Capacitor Firebase Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin, sponsored by [AppScreens](https://appscreens.com/?_locale=en&utm_source=capawesome&utm_medium=referral&utm_campaign=capawesome&gclid=capawesome). This plugin allows you to use the Android and iOS SDKs for [Firebase Cloud Firestore](https://firebase.google.com/docs/firestore) in your Capacitor project. Until now, it was necessary to use the Firebase JavaScript SDK on Android and iOS as well to use Cloud Firestore. However, this had some drawbacks, such as the need for additional authentication of users in the web layer and degraded performance. The Capacitor Firebase Cloud Firestore plugin addresses these drawbacks by providing a native implementation for Android and iOS. Demo Let's take a quick look at the [Capacitor Firebase Cloud Firestore API](https://capawesome.io/plugins/firebase/cloud-firestore/#api) and how you can add, get and delete data from your database. ## Installation Run the following commands to install the plugin: ``` npm install @capacitor-firebase/firestore npx cap sync ``` You also need to [add Firebase to your project](https://github.com/capawesome-team/capacitor-firebase/blob/main/docs/firebase-setup.md) if you haven't already. ## Usage If you are new to Cloud Firestore, we recommend that you first read the [Understand Cloud Firestore](https://firebase.google.com/docs/firestore) section so that you are familiar with the basics. Let's see the plugin in action. ### Add data There are several ways to write data to Cloud Firestore. One way is to add a new document to a collection with an automatically generated document identifier using the [addDocument(...)](https://capawesome.io/plugins/firebase/cloud-firestore/#adddocument) method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const addDocument = async () => { const { reference } = await FirebaseFirestore.addDocument({ reference: 'users', data: { first: 'Alan', last: 'Turing', born: 1912 }, }); return reference.id; }; ``` In this case we add a new document with the data `{ first: 'Alan', last: 'Turing', born: 1912 }` to the collection `users`. Another way is to set the content of a document within a collection by explicitly specifying a document identifier using the [setDocument(...)](https://capawesome.io/plugins/firebase/cloud-firestore/#setdocument) method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const setDocument = async () => { await FirebaseFirestore.setDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', data: { first: 'Alan', last: 'Turing', born: 1912 }, merge: true, }); }; ``` Use `{ merge: boolean }` to specify whether the newly provided data should overwrite the content of the document or be merged with the existing document. ### Get data To retrieve a single document from a collection, you can use the [`getDocument(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#getdocument) method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const getDocument = async () => { const { snapshot } = await FirebaseFirestore.getDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', }); return snapshot; }; ``` You just need to pass the document reference as a string, with path components separated by a forward slash (`/`). To retrieve multiple documents from a collection, you can use the [`getCollection(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#getcollection) method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const getCollection = async () => { const { snapshots } = await FirebaseFirestore.getCollection({ reference: 'users', compositeFilter: { type: 'and', queryConstraints: [ { type: 'where', fieldPath: 'born', opStr: '==', value: 1912, }, ], }, queryConstraints: [ { type: 'orderBy', fieldPath: 'born', directionStr: 'desc', }, { type: 'limit', limit: 10, }, ], }); return snapshots; }; ``` This method allows you to apply filters and sorting to the query. It is recommended that you specify the `type` property first, so that TypeScript will list the remaining properties for you. ### Delete data To delete a document from a collection, you can use the [`deleteDocument(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#deletedocument) method: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const deleteDocument = async () => { await FirebaseFirestore.deleteDocument({ reference: 'users/Aorq09lkt1ynbR7xhTUx', }); }; ``` Again you just need to pass the document reference as a string, with path components separated by a forward slash (`/`). ### Get real-time updates If you want to get real-time updates when documents change, you can use the [`addDocumentSnapshotListener(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#adddocumentsnapshotlistener) and [`addCollectionSnapshotListener(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#addcollectionsnapshotlistener) methods: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const addDocumentSnapshotListener = async () => { const callbackId = await FirebaseFirestore.addDocumentSnapshotListener( { reference: 'users/Aorq09lkt1ynbR7xhTUx', }, (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; const addCollectionSnapshotListener = async () => { const callbackId = await FirebaseFirestore.addCollectionSnapshotListener( { reference: 'users', }, (event, error) => { if (error) { console.error(error); } else { console.log(event); } } ); return callbackId; }; ``` The callback function will be called every time the document or collection changes. To remove the listener, you have to call the [`removeSnapshotListener(...)`](https://capawesome.io/plugins/firebase/cloud-firestore/#removesnapshotlistener) or [`removeAllListeners()`](https://capawesome.io/plugins/firebase/cloud-firestore/#removealllisteners) methods: ``` import { FirebaseFirestore } from '@capacitor-firebase/firestore'; const removeSnapshotListener = async (callbackId: string) => { await FirebaseFirestore.removeSnapshotListener({ callbackId, }); }; const removeAllListeners = async () => { await FirebaseFirestore.removeAllListeners(); }; ``` ## Limitations Currently, there are still a few limitations that you need to be aware of: 1. **Data types**: The supported data types are those that can be represented in JSON such as numbers, strings, booleans, arrays, and objects. Something that is not currently supported is, for example, the JavaScript `Date` object. However, you can just pass the `Date` as a `string` by using the `toISOString()` method: ``` // Create a string representation of the current date based on ISO 8601 const dateString = new Date().toISOString(); // Create a new date object from the string const date = new Date(dateString); ``` 1. **Field values**: Firestore supports various field values such as `FieldValue.delete()`, `FieldValue.increment()` or `FieldValue.serverTimestamp()`. However, these will not be supported until the next release (see [capacitor-firebase/issues/443](https://github.com/capawesome-team/capacitor-firebase/issues/443)). ## Closing Thoughts Be sure to check out our [API Reference](https://capawesome.io/plugins/firebase/cloud-firestore/#api) to see what else you can do with this plugin. Also feel free to check out our base sponsor [AppScreens](https://appscreens.com/?_locale=en&utm_source=capawesome&utm_medium=referral&utm_campaign=capawesome&gclid=capawesome) - a dedicated screenshot mockup generator for app developers. # Announcing the Capacitor Live Update Plugin One of the biggest advantages of Capacitor over other runtimes is the ability to deliver updates in real-time without having to resubmit your app to the app stores, so-called Over-the-Air (OTA) updates. For this reason, we are very excited to introduce you today to our brand new [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. In this blog post, you will learn everything about the new plugin and how you can implement it in your Capacitor app. Deprecated This guide is deprecated. Please refer to the [Getting Started](https://capawesome.io/cloud/live-updates/setup/index.md) guide for the most up-to-date instructions and best practices. ## How it works Live updates are a powerful feature that allows you to deliver small bug fixes and updates to your Capacitor app in real-time. For this, it is important to understand the different layers of a Capacitor app and how they interact with each other. Capacitor App Layers As you can see in the image above, a Capacitor app basically consists of a web layer and a native layer. The web layer consists of the HTML, CSS, and JavaScript files that are loaded into the web view. The native layer consists of the native code that is compiled into the app. Limitation Live updates only allow you to update the web layer of your app. So as long as you only make changes to your web application, you can deploy live updates without having to resubmit your app to the app stores. As soon as you make a change to the native code, such as adding a plugin, you must resubmit your app to the app stores. ## Installation First you need to install the package and sync your Capacitor project: ``` npm install @capawesome/capacitor-live-update npx cap sync ``` ## Usage To distribute live updates to your users, we recommend using [Capawesome Cloud](https://capawesome.io/cloud/live-updates/index.md). Capawesome Cloud provides a complete solution for managing and distributing your bundles with advanced features like [Channels](https://capawesome.io/cloud/live-updates/channels/index.md), [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md), and [Analytics](https://capawesome.io/cloud/index.md). ### Getting Started with Capawesome Cloud Getting Started Guide For the most up-to-date instructions and best practices, check out our comprehensive [Getting Started guide](https://capawesome.io/cloud/live-updates/setup/index.md). It includes the latest features like automatic update strategies and streamlined setup steps. To get started, you need to create an account on the [Capawesome Cloud Console](https://console.cloud.capawesome.io). After you have created an account, we recommend that you install the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) to manage your apps and bundles from the command line. ``` npm install -g @capawesome/cli ``` First, you need to log in to Capawesome Cloud: ``` npx @capawesome/cli login ``` Then you can create a new app: ``` npx @capawesome/cli apps:create --name "My App" ``` After you have created an app, you need to configure the [Capacitor Live Update](https://capawesome.io/plugins/live-update/#configuration) plugin. To do this, simply add the ID of the app you have just created to the Capacitor configuration file: capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000" } } } ``` Sync your Capacitor project to apply the changes: ``` npx cap sync ``` Next, you can upload a new bundle to the Capawesome Cloud. For this, you simply need to specify the path to the bundle (e.g. `www` or `dist`) and the ID of the app (see previous step): ``` npx @capawesome/cli apps:liveupdates:upload --app-id --path www ``` Upload to a channel We recommend that you also specify a [Channel](https://capawesome.io/cloud/live-updates/channels/index.md) when uploading the bundle: ``` npx @capawesome/cli apps:liveupdates:upload --app-id --path www --channel production-1.0.0 ``` This way you can easily [restrict live updates to native versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/index.md) of your app and prevent incompatible updates. The Capawesome CLI then automatically creates a zip archive of the bundle and uploads it to the Capawesome Cloud where it becomes immediately available for download. All that's left to do is to call the [`sync()`](https://capawesome.io/plugins/live-update/#sync) method in your app to check for new updates and apply them if necessary. ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { const result = await LiveUpdate.sync(); if (result.nextBundleId) { await LiveUpdate.reload(); } }; ``` The new bundle becomes available only after restarting the app. Therefore, if you want to apply the new bundle immediately, you should call the [`reload()`](https://capawesome.io/plugins/live-update/#reload) method to reload the web view. Again, make sure to call the [`ready()`](https://capawesome.io/plugins/live-update/#ready) method as soon as the the app is ready to prevent a rollback: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const ready = async () => { await LiveUpdate.ready(); }; ``` ## Testing When testing the plugin, you must make sure that you do not use the [Live Reload](https://ionicframework.com/docs/cli/livereload) option, as in this case a development server is used to load the bundle. Therefore, simply start your app without the live reload option, for example with the following command: ``` npx cap run android ``` As soon as you have installed a live update, the app will use the live update bundle and no longer the default bundle. So if you make local changes to your app and execute `npx cap run`, for example, these changes will apply to the default bundle, which is not currently in use. You then have three options to get back to the default bundle: 1. **Reset**: Call the [`reset()`](#reset) method to reset the app to the default bundle. 1. **Reinstall**: Reinstall the app to use the default bundle. 1. **Update**: Increase the `versionCode`/`CFBundleVersion` so that the plugin automatically performs a reset. However, this is only a problem during development. It is not a problem in production, as the `versionCode`/`CFBundleVersion` is always incremented during a native update and the plugin automatically resets to the default bundle. ## Closing Thoughts We hope you are as excited as we are about the new [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. We are already working on new features and improvements, such as automatic update modes and partial updates. Be sure to check out the [API Reference](https://capawesome.io/plugins/live-update/#api) to see what else you can do with this plugin. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-plugins/discussions/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Make sure you follow us on [X](https://twitter.com/capawesomeio) so you don't miss any future updates. # Announcing the Capacitor ML Kit Barcode Scanning Plugin Today we are very excited to introduce you to the brand new [Capacitor ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin. This plugin is part of the new [Capacitor ML Kit](https://github.com/capawesome-team/capacitor-mlkit) project by Capawesome, which aims to bring the powerful [ML Kit SDKs](https://developers.google.com/ml-kit)[1](#fn:1) to Capacitor. The plugin allows you to scan and decode various types of barcodes, including QR codes[2](#fn:2) and UPC codes. For a complete list of supported barcodes, see [BarcodeFormat](https://capawesome.io/plugins/mlkit/barcode-scanning/#barcodeformat). The scanning is done directly on the device and does not require a network connection. The plugin supports Android and iOS, and it allows multiple barcodes to be scanned at once. It also has torch and autofocus support, and an optional ready-to-use interface without the need for webview customizations. Demo App Let's take a quick look at the [Barcode Scanning Plugin API](https://capawesome.io/plugins/mlkit/barcode-scanning/#api) and how you can scan and decode barcodes. ## Installation First you need to install the package and sync your Capacitor project: ``` npm install @capacitor-mlkit/barcode-scanning npx cap sync ``` ### Android On Android, this plugin requires the following permissions be added to your `AndroidManifest.xml` (usually `android/app/src/main/AndroidManifest.xml`) before or after the `application` tag: ``` ``` You also need to add the following meta data **in** the `application` tag in your `AndroidManifest.xml`: ``` ``` ### iOS On iOS, add the `NSCameraUsageDescription` key to the `Info.plist` file (usually `ios/App/App/Info.plist`), which tells the user why the app needs to use the camera: ``` NSCameraUsageDescription The app enables the scanning of various barcodes. ``` If you also use `@capacitor-firebase/*` dependencies in your project, then implement [this workaround](https://github.com/capawesome-team/capacitor-mlkit/issues/23#issuecomment-1470739611) to avoid conflict with the Cocoapods dependencies. ## Usage Let's see the plugin in action. ### Request permissions In order to be able to scan barcodes, we first need the camera permissions. We can easily request them via the plugin: ``` import { BarcodeScanner } from "@capacitor-mlkit/barcode-scanning"; const requestPermissions = async () => { await BarcodeScanner.requestPermissions(); }; ``` In addition, you can use the method `isSupported()` to check whether the device has a camera: ``` import { BarcodeScanner } from "@capacitor-mlkit/barcode-scanning"; const isSupported = async () => { await BarcodeScanner.isSupported(); }; ``` ### Scan barcode with ready-to-use interface Now that you have requested the permissions, you can scan your first barcode. To make the first scan as easy as possible and not require any WebView customization, you use the[`scan()`](https://capawesome.io/plugins/mlkit/barcode-scanning/#scan) method, which provides a ready-to-use interface. By choosing a barcode format, we can improve the speed of the barcode scanner. In this example we are only looking for QR codes[2](#fn:2) and return the `rawValue` of the first QR code[2](#fn:2) found: ``` import { BarcodeScanner, BarcodeFormat, } from "@capacitor-mlkit/barcode-scanning"; const scan = async () => { const { barcodes } = await BarcodeScanner.scan({ formats: [BarcodeFormat.QrCode], }); return barcodes[0].rawValue; }; ``` ### Scan barcode with WebView customizations If you want to design the user interface yourself or scan several barcodes in a row, you need the methods [`startScan(...)`](https://capawesome.io/plugins/mlkit/barcode-scanning/#startscan) and [`stopScan()`](https://capawesome.io/plugins/mlkit/barcode-scanning/#stopscan). The camera is visible behind the WebView during scanning. However, this means that you have to hide all elements that should not be visible. In this case we set a class `barcode-scanning-active`, which then contains certain CSS rules (see below) for our app. You also need to add a [`barcodeScanned`](https://capawesome.io/plugins/mlkit/barcode-scanning/#addlistenerbarcodescanned) listener so that you are notified of detected barcodes. ``` import { BarcodeScanner } from "@capawesome-team/capacitor-barcode-scanner"; const startScan = async () => { // Hide all elements in the WebView document.querySelector("body")?.classList.add("barcode-scanning-active"); // Add the `barcodeScanned` listener const listener = await BarcodeScanner.addListener( "barcodeScanned", async (result) => { // Print the found barcode to the console console.log(result.barcode); }, ); // Start the barcode scanner await BarcodeScanner.startScan(); }; const stopScan = async () => { // Make all elements in the WebView visible again document.querySelector("body")?.classList.add("barcode-scanning-active"); // Remove all listeners await BarcodeScanner.removeAllListeners(); // Stop the barcode scanner await BarcodeScanner.stopScan(); }; ``` An example of the CSS class `barcode-scanning-active` **with** Ionic could be: ``` // Hide all elements body.barcode-scanning-active { visibility: hidden; --background: transparent; --ion-background-color: transparent; } // Show only the barcode scanner modal .barcode-scanning-modal { visibility: visible; } @media (prefers-color-scheme: dark) { .barcode-scanning-modal { --background: transparent; --ion-background-color: transparent; } } ``` An example of the CSS class `barcode-scanning-active` **without** Ionic could be: ``` // Hide all elements body.barcode-scanning-active { visibility: hidden; } // Show only the barcode scanner modal .barcode-scanning-modal { visibility: visible; } ``` Tip If you can't see the camera view, make sure **all elements** in the DOM are not visible or have a transparent background to debug the issue. ### Read barcode from image Last but not least, you have the option of scanning barcodes from an image you have already taken. All you need is the file path to the image. You can get the file path, for example, if the user selects an image using the [File Picker Plugin](https://capawesome.io/plugins/file-picker/index.md). The file path is passed to the method [`readBarcodesFromImage(...)`](https://capawesome.io/plugins/mlkit/barcode-scanning/#readbarcodesfromimage), which then returns the detected barcodes: ``` import { BarcodeScanner, BarcodeFormat, } from "@capacitor-mlkit/barcode-scanning"; import { FilePicker } from "@capawesome/capacitor-file-picker"; const pickImage = async () => { const { files } = await FilePicker.pickImages({ multiple: true, }); return files[0]; }; const scan = async () => { const pickedImage = await pickImage(); const { barcodes } = await BarcodeScanner.readBarcodesFromImage({ formats: [BarcodeFormat.QrCode], path: pickedImage.path, }); return barcodes[0].rawValue; }; ``` ## Demo App Feel free to download our [demo app](https://github.com/robingenz/capacitor-mlkit-plugin-demo) to see the plugin in action: 1. Clone the repository: ``` git clone https://github.com/robingenz/capacitor-mlkit-plugin-demo.git ``` 1. Change to the root directory: ``` cd capacitor-mlkit-plugin-demo ``` 1. Install all dependencies: ``` npm i ``` 1. Prepare and launch the Android app: ``` npx ionic cap sync android npx ionic cap run android ``` 1. Prepare and launch the iOS app: ``` npx ionic cap sync ios npx ionic cap run ios ``` ## Closing Thoughts Be sure to check out our [API Reference](https://capawesome.io/plugins/mlkit/barcode-scanning/#api) to see what else you can do with this plugin. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-mlkit/discussions/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-mlkit). Make sure you follow us on [X](https://twitter.com/capawesomeio) so you don't miss any future updates. A big thank you to all the [sponsors](https://github.com/sponsors/capawesome-team) who make these projects possible! ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") 1. QR Code is a registered trademark of DENSO WAVE INCORPORATED. [↩](#fnref:2 "Jump back to footnote 2 in the text")[↩](#fnref2:2 "Jump back to footnote 2 in the text")[↩](#fnref3:2 "Jump back to footnote 2 in the text") # Announcing the Capacitor NFC Plugin Today we are happy to introduce the brand new [Capacitor NFC](https://capawesome.io/plugins/nfc/index.md) plugin from Capawesome, sponsored by [NFC21](https://nfc21.de/?_locale=en). This plugin enables interaction with Near Field Communication (NFC) tags and provides cross-platform support for Android, iOS, and Web. The project is available as Sponsorware on [GitHub](https://github.com/capawesome-team/capacitor-plugins). Let's take a quick look at the [Capacitor NFC API](https://capawesome.io/plugins/nfc/#api) and how you can read and write on passive NFC tags and stickers. For this we will use the NTAG 215 from this [NFC Starter Kit](https://www.nfc-tag-shop.de/en/NFC-Starter-Kit-Medium-12-pieces/68250). ## Installation To install the Capacitor NFC plugin, please refer to the [Installation](https://capawesome.io/plugins/nfc/#installation) section in the plugin documentation. ## Usage Now let's finally start and see the plugin in action. ### Read NFC tags Reading NFC tags is quite simple: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const read = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.stopScanSession(); resolve(event.nfcTag); }); Nfc.startScanSession(); }); }; ``` First you have to add the `nfcTagScanned` listener. This listener is called when an NFC tag is scanned as well as when an NFC tag opens your app. As soon as the listener is active, you can start a new scan session with [`Nfc.startScanSession(...)`](https://capawesome.io/plugins/nfc/#startscansession). During this session the operating system is looking for NFC tags. Once you are done, end the session with [`Nfc.stopScanSession(...)`](https://capawesome.io/plugins/nfc/#stopscansession). ### Write NFC tags An NFC tag can contain different types of data in different formats such as **NDEF**. NDEF means **NFC Data Exchange Format** and defines in which format data is stored on NFC tags and in which way it can be read. Here we create a simple NDEF text record using [`NfcUtils`](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/docs/utils/README.md), a utility class with various helper functions: ``` import { NfcUtils } from "@capawesome-team/capacitor-nfc"; const createNdefTextRecord = () => { const utils = new NfcUtils(); const { record } = utils.createNdefTextRecord({ text: "Capacitor NFC Plugin", }); return record; }; ``` This record can now be written to an NFC tag. A NFC tag may be written to at the moment it is scanned. That means we have to add the `nfcTagScanned` listener again. ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const write = async () => { return new Promise((resolve) => { const record = createNdefTextRecord(); Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.write({ message: { records: [record] } }); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; ``` Now we can call [`Nfc.write(...)`](https://capawesome.io/plugins/nfc/#write) and write the record to the NFC tag while the tag is being scanned. ### Make NFC tags read-only It is possible to make NFC tags permanently read-only using the `makeReadOnly` method: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const makeReadOnly = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.makeReadOnly(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; ``` Warning This is a **one-way** operation and cannot be undone. Once an NFC tag has been made read-only, it can no longer be written to. ### Send custom commands to NFC tags And finally, we can send custom commands to the NFC tag. Which NFC tag supports which commands can be found in the respective specification of the tag. The specification for NTAG 215 can be found [here](https://www.nxp.com/docs/en/data-sheet/NTAG213_215_216.pdf). Note The codes in the specifications are often in hex format, but the plugin needs them as byte array. The [`convertHexToBytes(...)`](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/docs/utils/README.md#converthextobytes) method can help you with this. In the following example we read the signature of the tag: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const readSignature = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { const { response } = await Nfc.transceive({ techType: NfcTagTechType.NfcA, data: [60, 0], }); await Nfc.stopScanSession(); resolve(response); }); Nfc.startScanSession(); }); }; ``` For this, we send the command (`[60, 0]`) to the tag using the [`Nfc.transceive(...)`](https://capawesome.io/plugins/nfc/#transceive) method and receive the signature as a byte array. ## Closing Thoughts Be sure to check out our [API Reference](https://capawesome.io/plugins/nfc/#api) to see what else you can do with this plugin. Also feel free to take a look at our [Demo App](https://github.com/capawesome-team/capacitor-nfc-demo) which shows this plugin in action. # Announcing the OAuth Plugin for Capacitor Authentication is a critical part of most applications, and getting it right can be challenging. With [Ionic's recent decision to discontinue new customer sales of its commercial products](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products), including Auth Connect, many teams building Capacitor apps are now looking for a reliable alternative. That's why we built the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin — a production-ready solution for integrating OAuth 2.0 and OpenID Connect authentication into your Capacitor applications across Android, iOS, and web. The plugin is now available for all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's explore the [API](https://capawesome.io/plugins/oauth/#api) and key features that make this plugin a great fit for your next project. ## Installation To install the Capacitor OAuth plugin, please refer to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. ## Usage The [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin works with any OAuth 2.0 or OpenID Connect provider, including Auth0, Azure AD, Amazon Cognito, Okta, and OneLogin. It implements the Authorization Code flow with PKCE out of the box, following current security best practices. Let's walk through the most common use cases. ### Logging in Start the OAuth flow with the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method. You can either provide an `issuerUrl` for automatic OpenID Connect discovery, or pass the `authorizationEndpoint` and `tokenEndpoint` directly: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const login = async () => { const result = await Oauth.login({ issuerUrl: "https://accounts.google.com", clientId: "YOUR_CLIENT_ID", redirectUrl: "com.example.app://oauth/callback", scopes: ["openid", "profile", "email", "offline_access"], }); console.log("Access token:", result.accessToken); console.log("ID token:", result.idToken); console.log("Refresh token:", result.refreshToken); }; ``` The plugin handles the entire authorization code exchange with PKCE behind the scenes. On Android and iOS, it uses the system browser for a secure authentication experience. On the web, the user is redirected to the provider's authorization page. ### Handling the redirect callback On the web platform, you need to handle the redirect callback after the user is redirected back from the provider. Call [`handleRedirectCallback()`](https://capawesome.io/plugins/oauth/#handleredirectcallback) on page load to complete the token exchange: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; import { Capacitor } from "@capacitor/core"; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== "web") { return; } const result = await Oauth.handleRedirectCallback(); console.log("Access token:", result.accessToken); }; ``` This step is only required on the web. On Android and iOS, the redirect is handled natively. ### Storing tokens securely The [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin is designed to work seamlessly with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, so you can store tokens in encrypted storage right after login: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; import { SecurePreferences } from "@capawesome-team/capacitor-secure-preferences"; const login = async () => { const result = await Oauth.login({ issuerUrl: "https://accounts.google.com", clientId: "YOUR_CLIENT_ID", redirectUrl: "com.example.app://oauth/callback", scopes: ["openid", "profile", "email", "offline_access"], }); await SecurePreferences.set({ key: "tokens", value: JSON.stringify(result), }); }; ``` ### Refreshing the access token Access tokens expire. Use the [`refreshToken(...)`](https://capawesome.io/plugins/oauth/#refreshtoken) method to get a new one without requiring the user to log in again: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const refreshToken = async () => { const result = await Oauth.refreshToken({ issuerUrl: "https://accounts.google.com", clientId: "YOUR_CLIENT_ID", refreshToken: "YOUR_REFRESH_TOKEN", }); console.log("New access token:", result.accessToken); }; ``` ### Decoding the ID token If you need to access the user's profile information from the ID token, use the [`decodeIdToken(...)`](https://capawesome.io/plugins/oauth/#decodeidtoken) method: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const decodeIdToken = async () => { const result = await Oauth.decodeIdToken({ token: "YOUR_ID_TOKEN", }); console.log("Subject:", result.payload.sub); console.log("Email:", result.payload.email); console.log("Name:", result.payload.name); }; ``` This decodes the JWT token locally without sending it to a server. Note that this does not verify the token signature — for server-side validation, you should verify the token on your backend. ### Logging out End the user's session with the [`logout(...)`](https://capawesome.io/plugins/oauth/#logout) method, which calls the provider's end-session endpoint: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const logout = async () => { await Oauth.logout({ issuerUrl: "https://accounts.google.com", idToken: "YOUR_ID_TOKEN", postLogoutRedirectUrl: "com.example.app://oauth/logout", }); }; ``` ### Checking token state The plugin also provides utility methods to check the state of your tokens without additional logic on your side: ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const checkTokenState = async ( accessToken: string, accessTokenExpirationDate: number, refreshToken: string ) => { const { isAvailable } = await Oauth.isAccessTokenAvailable({ accessToken, }); const { isExpired } = await Oauth.isAccessTokenExpired({ accessTokenExpirationDate, }); const { isAvailable: isRefreshAvailable } = await Oauth.isRefreshTokenAvailable({ refreshToken, }); console.log("Access token available:", isAvailable); console.log("Access token expired:", isExpired); console.log("Refresh token available:", isRefreshAvailable); }; ``` ## FAQ ##### How does this compare to Ionic Auth Connect? The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin covers the core authentication functionality that most teams need: OAuth 2.0 and OpenID Connect flows with PKCE, token refresh, and multi-provider support across all platforms. With Ionic [discontinuing sales of its commercial products](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products), including Auth Connect, the Capawesome OAuth plugin provides a maintained and actively supported alternative for Capacitor applications. ##### Which providers are supported? The plugin works with any OAuth 2.0 or OpenID Connect compliant provider. This includes Auth0, Azure AD (Microsoft Entra ID), Amazon Cognito, Okta, OneLogin, Google, and any other provider that follows the standard. ##### Is this plugin a fork of another plugin? No. The Capawesome [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin was built from the ground up with a focus on security, reliability, and ease of use. We implemented the PKCE flow natively on Android and iOS, and designed the API to be consistent across all platforms. The web implementation uses the standard redirect flow for maximum compatibility. ##### Can I store tokens securely? Yes. The plugin is designed to work with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, which provides encrypted key-value storage on Android and iOS. This lets you persist tokens between app sessions without exposing them in plain text. ## Conclusion The [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin gives you a straightforward way to add OAuth 2.0 and OpenID Connect authentication to your Capacitor apps. It handles the complexity of PKCE flows, provider discovery, and token management so you can focus on building your application. Explore the complete [API Reference](https://capawesome.io/plugins/oauth/#api) to see all available methods and options. Have suggestions for new features? [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) and subscribe to our [newsletter](https://cloud.capawesome.io/newsletter/) for the latest updates and announcements. # Announcing the Capacitor Secure Preferences Plugin Today we are excited to announce our brand new [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. This plugin is a drop-in replacement for the official [Capacitor Preferences](https://capacitorjs.com/docs/apis/preferences) plugin and allows you to securely store key/value pairs such as passwords, tokens or other sensitive information. The plugin provides cross-platform support for Android, iOS and Web and is now available to all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's take a quick look at the [API](https://capawesome.io/plugins/secure-preferences/#api) and how to use the plugin in your Capacitor app. ## Installation To install the Capacitor Secure Preferences plugin, please refer to the [Installation](https://capawesome.io/plugins/secure-preferences/#installation) section in the plugin documentation. ## Usage Let's take a look at the basic usage of the plugin. You can find the complete API reference in the [API](https://capawesome.io/plugins/secure-preferences/#api) section of the documentation. ### Set a value You can set a value using the [`set`](https://capawesome.io/plugins/secure-preferences/#set) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const set = async () => { await SecurePreferences.set({ key: 'password', value: '123456', }); }; ``` The stored value can be any string. To store other types or more complex objects, you can use \[`JSON.stringify()`\](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ JSON/stringify) to convert them to a string before storing them and [`JSON.parse()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse) to convert them back to their original form after retrieving them. No encryption on Web On **Android** and **iOS**, the value will be encrypted and stored securely on the device. On **Web**, the value will be stored unencrypted in `localStorage` since the web platform does not provide a secure storage solution. This is for development purposes only and should not be used in production. ### Get a value You can get a value using the [`get`](https://capawesome.io/plugins/secure-preferences/#get) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const get = async () => { const { value } = await SecurePreferences.get({ key: 'password' }); console.log(value); // 123456 }; ``` This will decrypt the value and return it as a string. ### Remove a value To remove a value, you can use the [`remove`](https://capawesome.io/plugins/secure-preferences/#remove) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const remove = async () => { await SecurePreferences.remove({ key: 'password' }); }; ``` ### Clear all values If you want to clear all values, simply call the [`clear`](https://capawesome.io/plugins/secure-preferences/#clear) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const clear = async () => { await SecurePreferences.clear(); }; ``` ### Get all keys And finally, if you want to get all stored keys, you can use the [`keys`](https://capawesome.io/plugins/secure-preferences/#keys) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const getAllKeys = async () => { const { keys } = await SecurePreferences.keys(); console.log(keys); // ['password'] }; ``` ## Conclusion We hope you are as excited as we are about the new [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. Be sure to check out the [API Reference](https://capawesome.io/plugins/secure-preferences/#api) to see what else you can do with this plugin. If you are missing any features, just [create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in the [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Make sure you follow us on [X](https://x.com/capawesomeio) so you don't miss any future updates. # Announcing the SQLite Plugin for Capacitor We are thrilled to announce the launch of our comprehensive [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md), the go-to **Capacitor SQLite** solution for Android, iOS, and web. This powerful database solution brings enterprise-grade SQLite functionality to your Capacitor applications with support for encryption, transactions, schema migrations, and seamless cross-platform compatibility. The plugin is now available for all Capawesome [Insiders](https://capawesome.io/insiders/index.md). Let's explore the [API](https://capawesome.io/plugins/sqlite/#api) and key features that make this plugin a must-have for modern app development. ## Installation To install the Capacitor SQLite plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Usage The Capacitor SQLite plugin offers a complete database management solution with intuitive APIs for all your data operations. Let's walk through the essential features that make this plugin indispensable for modern app development. ### Opening a database Start by opening a database connection with the [`open(...)`](https://capawesome.io/plugins/sqlite/#open) method. This method supports both file-based and in-memory databases, with optional encryption and automatic schema migrations: ``` import { Sqlite } from "@capawesome-team/capacitor-sqlite"; const openDatabase = async () => { const { databaseId } = await Sqlite.open({ encryptionKey: 'your-secret-key', path: 'db.sqlite3', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE)', 'CREATE TABLE posts (id INTEGER PRIMARY KEY, user_id INTEGER, title TEXT, content TEXT)' ] } ], version: 1 }); console.log('Database opened with ID:', databaseId); return databaseId; }; ``` If no database file exists, it will be created automatically. The `encryptionKey` parameter enables database encryption, ensuring your data remains secure. The `upgradeStatements` parameter enables seamless database migrations, automatically applying schema changes when your app updates. If you omit the `path` option, the plugin will create an in-memory database, which is extremely useful for testing or temporary data storage. ### Executing SQL statements Execute data modification operations like `INSERT`, `UPDATE`, and `DELETE` using the [`execute(...)`](https://capawesome.io/plugins/sqlite/#execute) method. This method supports parameterized queries to prevent SQL injection attacks and ensure data integrity: ``` import { Sqlite } from "@capawesome-team/capacitor-sqlite"; const insertUser = async (databaseId: string) => { const { rowId } = await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: ['John Doe', 'john.doe@example.com'] }); console.log('Inserted user with ID: ', rowId); }; const updateUser = async (databaseId: string, userId: number) => { const { changes } = await Sqlite.execute({ databaseId, statement: 'UPDATE users SET email = ? WHERE id = ?', values: ['john.updated@example.com', userId] }); console.log('Updated user, number of rows affected: ', changes); }; ``` For multiple operations, leverage transaction support to ensure data consistency: ``` const performTransaction = async (databaseId: string) => { // Begin a transaction await Sqlite.beginTransaction({ databaseId }); // Perform multiple operations within the transaction await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: ['Alice Smith', 'alice@example.com'] }); await Sqlite.execute({ databaseId, statement: 'INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)', values: [1, 'My First Post', 'Hello world!'] }); // Commit the transaction await Sqlite.commitTransaction({ databaseId }); }; ``` You can also roll back transactions using the [`rollbackTransaction(...)`](https://capawesome.io/plugins/sqlite/#rollbacktransaction) method if an error occurs during the transaction. ### Querying data To retrieve data, use the [`query(...)`](https://capawesome.io/plugins/sqlite/#query) method. This method supports complex SQL queries, including joins and aggregations, and returns results in a structured format: ``` import { Sqlite } from "@capawesome-team/capacitor-sqlite"; const getUsers = async (databaseId: string) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT id, name, email FROM users WHERE name LIKE ?', values: ['%John%'] }); console.log('Found users:', result.values); // Output: [{ id: 1, name: 'John Doe', email: 'john.doe@example.com' }] return result.values; }; const getUserWithPosts = async (databaseId: string, userId: number) => { const result = await Sqlite.query({ databaseId, statement: ` SELECT u.name, u.email, p.title, p.content FROM users u LEFT JOIN posts p ON u.id = p.user_id WHERE u.id = ? `, values: [userId] }); return result.values; }; ``` ### Closing the database As soon as you are done with the database operations, it is a good practice to close the database connection using the [`close(...)`](https://capawesome.io/plugins/sqlite/#close) method. This helps free up resources and ensures data integrity: ``` import { Sqlite } from "@capawesome-team/capacitor-sqlite"; const closeDatabase = async (databaseId: string) => { await Sqlite.close({ databaseId }); console.log('Database connection closed'); }; ``` ## FAQ ##### Is the Capawesome SQLite plugin a fork of another plugin? No, it is NOT a fork. The Capawesome SQLite plugin was developed from scratch over a period of 2 months, with a focus on performance, reliability, and ease of use. It is designed to be fully compatible with the Capacitor ecosystem while providing additional features and optimizations. ##### How does the Capawesome SQLite plugin compare to other SQLite plugins? Most other SQLite plugins are maintained by the community under an open-source license. While this makes it possible to offer the plugin free of charge, it does not ensure that the plugin receives regular updates and support. This can be a significant drawback, especially for commercial applications that require ongoing maintenance, feature enhancements and top-notch support. With the end of Ionic Secure Storage, there is now a need for a high-quality solution. ##### Is the Capawesome SQLite plugin compatible with the Capacitor Community SQLite plugin? Yes, the Capawesome SQLite plugin is designed to be compatible with the Capacitor Community SQLite plugin. This means you can easily migrate your existing applications to the Capawesome SQLite plugin without significant code changes. We will provide a migration guide to help you transition smoothly. ## Conclusion The [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md) delivers a comprehensive database solution that scales with your application's needs. From simple data storage to complex relational operations with encryption and migrations, it provides the foundation for robust data management in Capacitor apps. **Related reading:** - [API Reference](https://capawesome.io/plugins/sqlite/#api) — Explore the complete plugin documentation for advanced features like read-only databases, custom encryption, and platform-specific optimizations - For hands-on API usage: [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) - If you're migrating from the community plugin check [Alternative to the Capacitor Community SQLite plugin](https://capawesome.io/blog/alternative-to-capacitor-community-sqlite-plugin/index.md) - [Encrypting SQLite databases](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md) - [Key-Value Storage with the SQLite plugin](https://capawesome.io/blog/key-value-storage-made-simple-with-the-sqlite-plugin/index.md) **Missing a feature?** [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Join the Capawesome [Discord](https://discord.gg/VCXxSVjefW) server for questions and subscribe to the Capawesome [newsletter](https://cloud.capawesome.io/newsletter) to stay updated. # Announcing the Capawesome NPM Registry Today we are very excited to announce the brand new Capawesome npm registry. This registry is part of Capawesome's Insiders program and aims to make it much easier for sponsors to access our [Sponsorware](https://capawesome.io/insiders/index.md). ## Problems Until now, the process for sponsors to gain access to our Sponsorware was as follows: 1. **Become a sponsor**: Become a sponsor via GitHub Sponsors, Open Collective or Polar. 1. **Join the GitHub repository**: Receive an invitation to a private GitHub repository. 1. **Create a PAT**: Create a PAT (Personal Access Token) for GitHub and configure npm This process was not only unnecessarily complex, but also error-prone and time-consuming. We received 100s of requests from sponsors who had problems creating the PAT with the correct permissions or who did not receive an invitation to the GitHub repository. Furthermore, GitHub unfortunately provides us with few statistics on the use of the packages, making it difficult for us to measure the popularity and success of our Sponsorware. In addition, the large number of contributors in the Sponsorware repository makes it practically impossible to change the GitHub plan, as we have to pay per contributor in the team plan, which would be very costly. ## Solution For these reasons, we have decided to create our own npm registry that is tailored to our needs. This registry is available exclusively for sponsors. The process for gaining access to the Sponsorware is now much simpler: 1. **Become a sponsor**: Become a sponsor via [Polar](https://polar.sh/capawesome-team). 1. **Copy the license key**: Copy the license key and configure npm. Since the license keys are generated by Polar, this npm registry is currently only available to sponsors who are sponsoring via Polar. More information on accessing the Sponsorware via the Capawesome npm registry can be found in the [Getting Started](https://capawesome.io/insiders/getting-started/index.md) guide. ## FAQ ### What happens to the GitHub repository? Don't worry! The GitHub repository will continue to exist and receive updates. So you don't need to migrate to the new npm registry if you don't want to. However, new sponsors will no longer receive an invitation. ### Which packages are available in the Capawesome npm registry? All Sponsorware packages are available in the Capawesome npm registry. The packages are the same as in the GitHub repository. ### How can i migrate to the Capawesome npm registry? If you are already a sponsor on Polar and want to migrate to the new npm registry, you can follow these steps: 1. Remove the packages installed from the GitHub repository: ``` npm rm @capawesome-team/... ``` 1. Configure the Capawesome npm registry: ``` npm config set @capawesome-team:registry https://npm.registry.capawesome.io npm config set //npm.registry.capawesome.io/:_authToken ``` **Attention**: Replace `` with the license key you received by Polar. 1. Install the packages from the Capawesome npm registry: ``` npm install @capawesome-team/... ``` That's it! You are now using the Capawesome npm registry. Make sure to also update your CI/CD configuration if you are using one. ## Conclusion We believe that the new Capawesome npm registry will make it much easier for sponsors to access our Sponsorware. We are excited to see how this change will affect the use of our packages and are looking forward to your feedback. You have questions? Feel free to reach out to [support@capawesome.io](mailto:support@capawesome.io) and we will be happy to help you. Also, make sure you follow us on [X](https://twitter.com/capawesomeio) so you don't miss any future updates. # Capacitor Live Update Plugin 7.3.0 Release We are excited to announce version 7.3.0 of the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin! This release introduces two powerful features that make managing over-the-air (OTA) updates even easier: automatic update strategies and rollback protection. These enhancements reduce the amount of code you need to write while providing a more robust update experience for your users. ## Automatic Update Strategies One of the most requested features has been the ability to handle updates automatically without writing boilerplate code in every app. Version 7.3.0 introduces the [`autoUpdateStrategy`](https://capawesome.io/plugins/live-update/#configuration) configuration option, which **automatically manages the entire update lifecycle** for you. By setting `autoUpdateStrategy` to `background` in your Capacitor configuration file, the plugin will automatically check for updates at app startup and when the app resumes (with a minimum 15-minute interval between checks), download them in the background, and apply them on the next app launch: ``` import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", autoUpdateStrategy: 'background', defaultChannel: 'production-5' } } }; export default config; ``` This is equivalent to manually calling the [`sync()`](https://capawesome.io/plugins/live-update/#sync) method at app startup and resume, but without requiring any code. It provides the perfect balance between keeping your app up-to-date and preserving device resources like battery and data transfer. You can optionally specify a `defaultChannel` to control which channel to pull updates from, making it easy to implement versioned channels for different app versions. For apps that need to prompt users to apply updates immediately, you can combine the background strategy with the `nextBundleSet` event listener: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const initializeApp = async () => { LiveUpdate.addListener('nextBundleSet', async () => { const shouldReload = confirm('A new update is available. Would you like to install it now?'); if (shouldReload) { await LiveUpdate.reload(); } }); }; ``` The `nextBundleSet` event fires whenever a new bundle is set as the next bundle, allowing you to provide users with the option to apply updates without restarting the app. ## Automatic Rollback Protection While the plugin has always supported automatic rollbacks for invalid bundles, version 7.3.0 introduces [`autoBlockRolledBackBundles`](https://capawesome.io/plugins/live-update/#configuration) to **prevent rollback loops**. While this was already possible using a custom implementation, it is now fully automated. When enabled, this option automatically blocks bundles that caused a rollback and skips them in future sync operations. This is particularly useful when you accidentally deploy a broken bundle—instead of users getting stuck in a loop where the broken bundle is repeatedly downloaded and rolled back, the plugin will automatically block it and move on to the next available bundle: ``` import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", autoBlockRolledBackBundles: true, autoUpdateStrategy: 'background', readyTimeout: 10000 } } }; export default config; ``` The `readyTimeout` option specifies the maximum time (in milliseconds) to wait for your app to signal it's ready before rolling back to the built-in bundle. Make sure to call the [`ready()`](https://capawesome.io/plugins/live-update/#ready) method early in your app's lifecycle to prevent automatic rollbacks: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; const initializeApp = async () => { await LiveUpdate.ready(); }; ``` Together, these options provide a robust safety net that protects your users from problematic updates while keeping your app running smoothly. ## Getting Started To take advantage of these new features, update to version 7.3.0: ``` npm install @capawesome/capacitor-live-update@^7.3.0 npx cap sync ``` For more information on implementing different update strategies, check out our [Update Strategies](https://capawesome.io/cloud/live-updates/guides/update-strategies/index.md) guide and [Best Practices](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) documentation. We hope these new features make it even easier to deliver seamless updates to your Capacitor apps. If you have any questions or feedback, please [create a discussion](https://github.com/capawesome-team/capacitor-plugins/discussions/new/choose) in our GitHub repository. Feel free to [subscribe to our newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest releases and features! # Capawesome CLI 4.0.0 Release We are excited to announce version 4.0.0 of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md)! This major release brings a host of new features and improvements to enhance your development experience, including new commands for managing live updates, channel control, and web build support. This release also includes some breaking changes that you should be aware of before upgrading. ## Breaking Changes Before upgrading, please review the following breaking changes: ### Minimum Node.js Version The minimum required Node.js version has been updated to **Node.js 20**. This ensures better performance, security, and access to the latest JavaScript features. Please update your Node.js installation before upgrading to CLI 4.0.0. ### Renamed Commands Several commands have been renamed to better reflect their purpose and align with the new build and deployment system: | Old Command | New Command | | --------------------- | -------------------------------------------------------- | | `apps:bundles:create` | `apps:liveupdates:upload` or `apps:liveupdates:register` | | `manifests:generate` | `apps:liveupdates:generatemanifest` | The `apps:bundles:create` command has been split into two separate commands: - [`apps:liveupdates:upload`](https://capawesome.io/cloud/cli/#appsliveupdatesupload): Upload a locally built bundle and deploy it to a channel - [`apps:liveupdates:register`](https://capawesome.io/cloud/cli/#appsliveupdatesregister): Register a self-hosted bundle URL and deploy it to a channel This separation provides clearer intent and better flexibility for different deployment workflows. ## New Features ### Live Update Rollback The new [`apps:liveupdates:rollback`](https://capawesome.io/cloud/cli/#appsliveupdatesrollback) command allows you to quickly roll back the active build in a channel to a previous deployment: ``` npx @capawesome/cli apps:liveupdates:rollback \ --app-id your-app-id \ --channel production \ --steps 1 ``` The `--steps` option specifies how many deployments to go back (1-5), making it easy to revert to a known working state when issues arise. See our [Rollbacks](https://capawesome.io/cloud/live-updates/advanced/rollbacks/index.md) guide for more details. ### Live Update Rollout Control the rollout percentage of your live updates with the new [`apps:liveupdates:rollout`](https://capawesome.io/cloud/cli/#appsliveupdatesrollout) command: ``` npx @capawesome/cli apps:liveupdates:rollout \ --app-id your-app-id \ --channel production \ --percentage 25 ``` This enables gradual rollouts, allowing you to deploy updates to a subset of users before rolling out to everyone. For more information on implementing gradual rollouts, see our [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md) guide. ### Channel Pausing and Resuming Two new commands give you more control over your deployment channels: - [`apps:channels:pause`](https://capawesome.io/cloud/cli/#appschannelspause): Temporarily pause a channel to stop delivering updates - [`apps:channels:resume`](https://capawesome.io/cloud/cli/#appschannelsresume): Resume a paused channel to continue delivering updates ``` # Pause a channel to investigate issues npx @capawesome/cli apps:channels:pause \ --app-id your-app-id \ --channel production # Resume a paused channel after resolving issues npx @capawesome/cli apps:channels:resume \ --app-id your-app-id \ --channel production ``` Pausing channels is useful during maintenance windows or when you need to temporarily halt updates without removing the channel entirely. ### Protected Channels The [`apps:channels:create`](https://capawesome.io/cloud/cli/#appschannelscreate) and [`apps:channels:update`](https://capawesome.io/cloud/cli/#appschannelsupdate) commands now support an optional `--protected` flag: ``` npx @capawesome/cli apps:channels:create \ --app-id your-app-id \ --name production \ --protected ``` [Protected channels](https://capawesome.io/cloud/live-updates/channels/#protected-channels) provide an additional layer of security by preventing accidental modifications or deletions. ### Web Build Support The [`apps:builds:create`](https://capawesome.io/cloud/cli/#appsbuildscreate) command now supports web builds with the `--platform web` option: ``` npx @capawesome/cli apps:builds:create \ --app-id your-app-id \ --platform web \ --git-ref main ``` This allows you to create web builds directly from the CLI, which can then be deployed as live updates. For more information, see our blog post on [Capawesome Cloud Web Builds](https://capawesome.io/blog/announcing-capawesome-cloud-web-builds/index.md). ### Skip Confirmation Prompts A new `--yes` (or `-y`) flag has been added to commands that require confirmation prompts: ``` npx @capawesome/cli apps:delete --app-id your-app-id --yes ``` This is particularly useful for CI/CD pipelines and automated scripts where interactive prompts are not possible. ## Upgrading To upgrade to version 4.0.0, run: ``` npm install -g @capawesome/cli@latest ``` Make sure you have Node.js 20 or later installed before upgrading. ## Migration Guide If you're upgrading from a previous version, here's what you need to update: 1. **Update Node.js**: Ensure you're running Node.js 20 or later 1. **Update command names**: Replace `apps:bundles:create` with `apps:liveupdates:upload` or `apps:liveupdates:register` 1. **Update manifest command**: Replace `manifests:generate` with `apps:liveupdates:generatemanifest` ## Feedback We hope these new features and improvements make your development workflow even more efficient. If you have any questions or feedback, please [create a discussion](https://github.com/capawesome-team/cli/discussions/new/choose) in our GitHub repository. Feel free to [subscribe to our newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest releases and features! # Capawesome CLI 4.5.0 Release Version 4.5.0 of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) is here, and it's packed with new features that have been added since the [4.0.0 release](https://capawesome.io/blog/capawesome-cli-4-0-0-release/index.md). The highlights include full command line management of signing certificates and app store destinations, a streamlined build workflow, and interactive prompts that make the CLI easier to use than ever. ## Certificate Management You can now create, list, update, and delete signing certificates directly from the command line. Whether you're working with Android keystores, iOS certificates, or Web PEM files for Live Updates, everything can be managed without ever opening the Cloud Console. This is especially useful when setting up new apps or rotating certificates as part of your CI/CD pipeline. For example, to create a new iOS production certificate along with its provisioning profile: ``` npx @capawesome/cli apps:certificates:create \ --app-id your-app-id \ --name "Production Certificate" \ --platform ios \ --type production \ --file /path/to/certificate.p12 \ --password your-password \ --provisioning-profile /path/to/profile.mobileprovision ``` You can also list all certificates for an app to verify your setup or check expiration details: ``` npx @capawesome/cli apps:certificates:list \ --app-id your-app-id ``` The full set of commands includes [`apps:certificates:create`](https://capawesome.io/cloud/cli/#appscertificatescreate), [`apps:certificates:delete`](https://capawesome.io/cloud/cli/#appscertificatesdelete), [`apps:certificates:get`](https://capawesome.io/cloud/cli/#appscertificatesget), [`apps:certificates:list`](https://capawesome.io/cloud/cli/#appscertificateslist), and [`apps:certificates:update`](https://capawesome.io/cloud/cli/#appscertificatesupdate). See the [Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/index.md) documentation for more details on the different certificate types. Since these commands work entirely from the command line, they're also a great fit for AI agents that can set up your signing certificates automatically — no manual interaction with the Cloud Console required. ## Destination Management Similar to certificates, you can now manage app store submission destinations entirely from the CLI. Destinations define where your native builds get submitted — whether that's the Google Play Store or the Apple App Store. Once configured, builds can be automatically submitted to the right store and track after a successful build. To create a new Google Play destination with a service account key: ``` npx @capawesome/cli apps:destinations:create \ --app-id your-app-id \ --name "Google Play Production" \ --platform android \ --android-package-name com.example.app \ --google-play-track production \ --google-service-account-key-file /path/to/service-account.json ``` You can also list all configured destinations to review your submission targets: ``` npx @capawesome/cli apps:destinations:list \ --app-id your-app-id ``` The available commands are [`apps:destinations:create`](https://capawesome.io/cloud/cli/#appsdestinationscreate), [`apps:destinations:delete`](https://capawesome.io/cloud/cli/#appsdestinationsdelete), [`apps:destinations:get`](https://capawesome.io/cloud/cli/#appsdestinationsget), [`apps:destinations:list`](https://capawesome.io/cloud/cli/#appsdestinationslist), and [`apps:destinations:update`](https://capawesome.io/cloud/cli/#appsdestinationsupdate). Check out the [Destinations](https://capawesome.io/cloud/app-store-publishing/destinations/index.md) documentation for platform-specific setup guides. Just like with certificates, these commands open the door for AI agents to fully configure your app store submission pipeline without any human interaction. ## Streamlined Build Workflow The [`apps:builds:create`](https://capawesome.io/cloud/cli/#appsbuildscreate) command now accepts `--channel` and `--destination` options, allowing you to build and deploy **in a single step**. Previously, you had to create a build first and then manually trigger a deployment as a separate step. For web builds, you can now specify a live update channel to deploy to right after the build completes: ``` npx @capawesome/cli apps:builds:create \ --app-id your-app-id \ --platform web \ --git-ref main \ --channel production ``` For native builds, you can specify a destination to automatically submit the build to the app store: ``` npx @capawesome/cli apps:builds:create \ --app-id your-app-id \ --platform ios \ --type app-store \ --git-ref main \ --destination "App Store Production" ``` This is especially useful in CI/CD pipelines where you want to keep your build scripts concise. ## Interactive Prompts Starting with v4.2.0, the CLI now **prompts you to log in automatically** when you run any authenticated command without an active session. No more getting an unhelpful error message — the CLI just asks for your credentials right away. This small quality-of-life improvement removes a common friction point, especially for developers who switch between multiple projects or machines. ## Device Channel Management Sometimes you need direct control over which Live Update channel a specific device uses — whether you're debugging an issue on a test device, delivering a targeted hotfix to a customer, or simply validating a new release before it goes live. With forced channel assignments, added in v4.4.0, you can override the SDK-selected channel on any device directly from the CLI: - [`apps:devices:forcechannel`](https://capawesome.io/cloud/cli/#appsdevicesforcechannel): Force a specific device to use a particular channel - [`apps:devices:unforcechannel`](https://capawesome.io/cloud/cli/#appsdevicesunforcechannel): Remove the forced channel to return to normal SDK behavior ``` npx @capawesome/cli apps:devices:forcechannel \ --app-id your-app-id \ --device-id device-id \ --channel staging ``` When a channel is forced, the channel configured by the Live Update SDK is completely bypassed — no app code changes or new builds required. Everything is controlled server-side through Capawesome Cloud. Read our dedicated blog post on [Forced Channel Assignments](https://capawesome.io/blog/capawesome-cloud-forced-channel-assignments/index.md) for more details. ## Other Improvements Here are some additional changes across v4.1.0–v4.5.0: - **Set native versions** (v4.1.0): The new `apps:liveupdates:setnativeversions` command lets you configure which native app versions are compatible with a live update channel. - **Multipart upload progress** (v4.3.0): Large bundle uploads now show part-level progress, so you can see exactly how far along the upload is. - **Dev version detection** (v4.5.0): The update checker now detects development versions and notifies you accordingly. - **Bug fixes**: Fixed certificate selection for web builds, improved signing key file handling, OpenSSL 3.x compatibility, and more. ## Upgrading To upgrade to the latest version, run: ``` npm install -g @capawesome/cli@latest ``` ## Try Capawesome Cloud If you haven't tried Capawesome Cloud yet, now is a great time to get started. Manage your builds, live updates, and app store submissions — all from the command line. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Final Thoughts Capawesome CLI 4.5.0 rounds out the v4.x series with full command line coverage for certificates and destinations, making it possible to automate your entire build and deployment pipeline without touching the Cloud Console. Combined with interactive prompts and the streamlined build workflow, the CLI is now more powerful and easier to use than ever. For more details on the 4.0.0 release and its breaking changes, check out the [Capawesome CLI 4.0.0 Release](https://capawesome.io/blog/capawesome-cli-4-0-0-release/index.md) blog post. If you have any questions, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And don't forget to [subscribe to our newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest releases. # Capawesome CLI 4.7.0 Release Version 4.7.0 of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) is now available. Building on the [4.5.0 release](https://capawesome.io/blog/capawesome-cli-4-5-0-release/index.md), this update brings a unified command for creating live updates, new app management commands for linking repositories and transferring apps, ad hoc environment variables for builds, and source map detection to keep your production bundles clean. ## Unified Live Update Command The new [`apps:liveupdates:create`](https://capawesome.io/cloud/cli/#appsliveupdatescreate) command builds your web bundle on a Capawesome Cloud Runner and deploys it to one or more channels — all in a single command. Unlike local bundling and uploading, the build runs in a managed cloud environment, giving you consistent and reproducible builds every time. It also consolidates the previous two-step workflow of `apps:builds:create` and `apps:deployments:create` into one: ``` npx @capawesome/cli apps:liveupdates:create \ --app-id your-app-id \ --git-ref main \ --channel production ``` You can deploy to multiple channels at once by specifying `--channel` more than once: ``` npx @capawesome/cli apps:liveupdates:create \ --app-id your-app-id \ --git-ref main \ --channel staging \ --channel production ``` The command also supports gradual rollouts with `--rollout-percentage`, native version constraints, and custom properties — giving you full control over each live update deployment in a single command. In addition to `--git-ref`, you can use `--path` to upload local source files or `--url` to build from a remote ZIP file. ## App Linking Two new commands let you connect and disconnect git repositories to your apps directly from the CLI: - [`apps:link`](https://capawesome.io/cloud/cli/#appslink): Connect a git repository to an app - [`apps:unlink`](https://capawesome.io/cloud/cli/#appsunlink): Disconnect a git repository from an app The `apps:link` command automatically detects repository information from your local git remote, so all you need to do is run: ``` npx @capawesome/cli apps:link \ --app-id your-app-id ``` This is particularly useful when setting up new projects or configuring CI/CD pipelines — the CLI figures out the git provider, owner, and repository name for you. ## App Transfer The new [`apps:transfer`](https://capawesome.io/cloud/cli/#appstransfer) command lets you move an app from one organization to another without having to recreate it: ``` npx @capawesome/cli apps:transfer \ --app-id your-app-id \ --organization-id target-org-id ``` This comes in handy when reorganizing your workspace or handing off a project to another team. ## Ad Hoc Environment Variables The [`apps:builds:create`](https://capawesome.io/cloud/cli/#appsbuildscreate) and [`apps:liveupdates:create`](https://capawesome.io/cloud/cli/#appsliveupdatescreate) commands now support ad hoc environment variables that are passed directly to the build without having to configure them in the Cloud Console first: ``` npx @capawesome/cli apps:builds:create \ --app-id your-app-id \ --platform android \ --git-ref main \ --variable "API_URL=https://api.example.com" \ --variable "DEBUG=false" ``` You can also load variables from a file using `--variable-file`: ``` npx @capawesome/cli apps:builds:create \ --app-id your-app-id \ --platform ios \ --git-ref main \ --variable-file .env.production ``` This makes it easy to customize builds on the fly — especially in CI/CD pipelines where you want to inject environment-specific values without persisting them in the Cloud Console. ## Source Map Detection The CLI now warns you when source maps are detected in your live update bundles. Source maps can unintentionally expose your source code to end users, so catching them before deployment helps keep your production builds secure. This check runs automatically during [`apps:liveupdates:bundle`](https://capawesome.io/cloud/cli/#appsliveupdatesbundle), [`apps:liveupdates:upload`](https://capawesome.io/cloud/cli/#appsliveupdatesupload), and [`apps:liveupdates:generatemanifest`](https://capawesome.io/cloud/cli/#appsliveupdatesgeneratemanifest) — no extra configuration needed. ## Other Improvements Here are some additional changes in v4.6.0–v4.7.0: - **Batch device operations**: The [`apps:devices:forcechannel`](https://capawesome.io/cloud/cli/#appsdevicesforcechannel) and [`apps:devices:unforcechannel`](https://capawesome.io/cloud/cli/#appsdevicesunforcechannel) commands now accept multiple `--device-id` flags, so you can update several devices at once. - **Certificate type filtering**: The [`apps:certificates:get`](https://capawesome.io/cloud/cli/#appscertificatesget) and [`apps:certificates:delete`](https://capawesome.io/cloud/cli/#appscertificatesdelete) commands now support a `--type` option for more targeted certificate selection. - **Bug fixes**: Improved error messages for invalid private keys, fixed login token handling with extra whitespace, and better status reporting for canceled builds. ## Upgrading To upgrade to the latest version, run: ``` npm install -g @capawesome/cli@latest ``` ## Try Capawesome Cloud If you haven't tried Capawesome Cloud yet, now is a great time to get started. Manage your builds, live updates, and app store submissions — all from the command line. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Final Thoughts Capawesome CLI 4.7.0 makes live update workflows significantly simpler with the new unified `apps:liveupdates:create` command, and gives you more flexibility with ad hoc environment variables and app management features like linking and transferring. The source map detection adds an extra layer of safety to your deployment process. For more details on previous releases, check out the [Capawesome CLI 4.5.0 Release](https://capawesome.io/blog/capawesome-cli-4-5-0-release/index.md) blog post. If you have any questions, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And don't forget to [subscribe to our newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest releases. # Channel Pausing for Capawesome Cloud Live Updates Take control of your Live Update deployments with the new channel pausing feature. Temporarily suspend updates for specific channels and resume them when you're ready—all with a single click or command. ## What's New You can now pause and resume channels directly from the [Capawesome Cloud Console](https://console.cloud.capawesome.io/apps/_/channels) or via the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). When a channel is paused, devices will stop receiving new Live Updates from that channel until you resume it. This feature is perfect for: - **Temporary holds**: Pause updates during critical business periods or maintenance windows - **Testing control**: Stop production deployments while testing new bundles - **Incident response**: Quickly halt problematic updates without deleting the channel ## Using the CLI Pause a channel to stop Live Updates: ``` npx @capawesome/cli apps:channels:pause ``` Resume a channel to restore Live Updates: ``` npx @capawesome/cli apps:channels:resume ``` Paused channels remain fully configured and retain all their bundles—you're just temporarily stopping the distribution of updates to devices. ## Get Started The channel pausing feature is available now for all Capawesome Cloud users. Visit the [Channels](https://console.cloud.capawesome.io/apps/_/channels) page in your console or update to the latest version of the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) to start using it today. If you need more granular control at the device level, check out [Forced Channel Assignments](https://capawesome.io/blog/capawesome-cloud-forced-channel-assignments/index.md) to override the channel for individual devices. # Channel Surfing with Capacitor Live Updates Need to deliver beta builds to testers, target updates by app version, or A/B test features — all without a new app store release? Channel surfing with the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin lets you switch between update channels on the fly and deliver different app experiences to different user segments. ## What is Channel Surfing? Channel surfing is the ability to dynamically switch between Live Update channels at runtime. Instead of hardcoding a single channel name in your app, you can programmatically change which channel your app receives updates from based on user preferences, app version, or other criteria. This feature unlocks powerful use cases: - **Beta Testing**: Let users opt-in to beta features by switching to a `beta` channel - **Version-Specific Updates**: Automatically target the right channel based on your app's native version code - **A/B Testing**: Deliver different experiences to different user segments - **Rollback Strategy**: Quickly switch users back to a stable channel if issues arise ## Enabling Channel Surfing Channel surfing works out of the box with the Capacitor Live Update plugin. Simply use the [`setChannel(...)`](https://capawesome.io/plugins/live-update/#setchannel) method to switch channels, or pass the channel name directly to the [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) or [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) methods. ### Set a Persistent Channel Use `setChannel(...)` to persistently change the channel for all future update checks: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const switchToBeta = async () => { await LiveUpdate.setChannel({ channel: 'beta' }); // All future sync() calls will use the 'beta' channel await LiveUpdate.sync(); }; ``` ### Use a Channel for a Single Update Alternatively, specify the channel directly when checking for updates: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const checkBetaUpdates = async () => { const result = await LiveUpdate.sync({ channel: 'beta' }); if (result.nextBundleId) { console.log('Beta update available'); } }; ``` ## Version-Specific Channel Surfing A common pattern is to create channels for each native version code, then automatically switch to the correct channel at runtime. This ensures users only receive updates compatible with their installed app version: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const syncWithVersionChannel = async () => { const { versionCode } = await LiveUpdate.getVersionCode(); await LiveUpdate.sync({ channel: `production-${versionCode}` }); }; ``` This approach is **recommended** for managing binary-compatible updates, as detailed in the [Best Practices](https://capawesome.io/cloud/live-updates/guides/best-practices/#versioned-channels) guide. ## Fetching Available Channels Want to let users choose from available channels? You can fetch a list of all channels for your app using the [`fetchChannels()`](https://capawesome.io/plugins/live-update/#fetchchannels) method: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const fetchChannels = async () => { const { channels } = await LiveUpdate.fetchChannels(); return channels.map(channel => channel.name); }; ``` This returns all channels configured in your Capawesome Cloud app, which you can display to users or use for dynamic channel selection. Please note that the `fetchChannels()` method is only available in the [Live Update SDK](https://capawesome.io/plugins/live-update/index.md) version **8.2.0** or later. Alternatively, you fetch the list of channels from the [Capawesome Cloud API](https://capawesome.io/cloud/api/index.md) or hardcode known channel names in your app. ## Try Channel Surfing Today Ready to deliver the right updates to the right users? Get started with [Capawesome Cloud](https://cloud.capawesome.io/) and the Live Update SDK today. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion Channel surfing gives you fine-grained control over how you deliver updates to your users. Whether you're managing version-specific updates, running beta programs, or implementing A/B tests, it makes it simple to deliver the right experience to the right users. To learn more, check out the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin documentation and the [Best Practices](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) guide. If you're also interested in temporarily pausing updates for specific channels, take a look at the [Channel Pausing](https://capawesome.io/blog/capawesome-cloud-channel-pausing/index.md) blog post. If you need per-device control over channel assignments, check out the [Forced Channel Assignments](https://capawesome.io/blog/capawesome-cloud-forced-channel-assignments/index.md) blog post. If you have any questions, feel free to reach out on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). For the latest updates, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Forced Channel Assignments for Capawesome Cloud Devices Sometimes you need direct control over which Live Update channel a specific device uses. With forced channel assignments, you can now override the SDK-selected channel on any device — whether you're debugging an issue, testing a new release, or managing a customer's device manually. ## What's New You can now force a specific Live Update channel on individual devices directly from the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) or via the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). When a channel is forced on a device, the channel set by the Live Update SDK is ignored and the device receives updates exclusively from the forced channel instead. You can remove the forced channel at any time to return the device to its normal SDK-driven behavior. ## When to Use It Forced channel assignments are useful in several scenarios: - **Debugging and development**: Push a specific device to a `dev` or `beta` channel to test a new bundle before rolling it out to all users. - **Customer support**: Assign a customer's device to a hotfix channel to deliver a targeted fix without affecting other devices. - **Manual management**: Take full control over which channel a device uses, independent of what the app itself selects via the SDK. ## How It Works Normally, the Live Update SDK on each device determines which channel to use — either through the default configuration or by calling [`setChannel(...)`](https://capawesome.io/plugins/live-update/#setchannel) at runtime. When you force a channel on a device, this SDK selection is completely bypassed. The device will only receive updates from the forced channel until you explicitly remove the override. This means you don't need to make any changes to your app code or deploy a new build. Everything is controlled server-side through Capawesome Cloud. ## Using the CLI Force a device to use a specific channel: ``` npx @capawesome/cli apps:devices:forcechannel \ --app-id \ --device-id \ --channel ``` Remove the forced channel to return to normal SDK behavior: ``` npx @capawesome/cli apps:devices:unforcechannel \ --app-id \ --device-id ``` You can find the device ID in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) on the **Devices** page of your app. ## Get Started Forced channel assignments are available now for all Capawesome Cloud users. Check out the [Devices](https://capawesome.io/cloud/live-updates/devices/index.md) documentation for detailed instructions, or try it out in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) right away. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion Forced channel assignments give you precise, per-device control over Live Update delivery — without touching your app code. Whether you're debugging on a test device, helping a customer, or simply want to manage channels manually, this feature makes it straightforward. For more on managing Live Update channels, check out the [Channel Surfing](https://capawesome.io/blog/capawesome-cloud-channel-surfing/index.md) guide. If you have any questions, feel free to reach out on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). For the latest updates, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Announcing the Capawesome Cloud Open Source Program We are excited to announce the launch of the Capawesome Cloud Open Source Program! This program is designed to support open source projects and developers by providing free access to [Capawesome Cloud](https://cloud.capawesome.io), our powerful platform for delivering Over-the-Air (OTA) updates to Capacitor apps. ## Program Benefits If selected, your open source project will receive: - **Capawesome Cloud credits**: Up to $2,000 in credits for 12 months to use Capawesome Cloud. - **Priority support**: Get priority support from the Capawesome team for any issues or questions you may have. - **Promotion**: We will promote your project on our website and social media channels, helping you reach a wider audience. ## Who Should Apply? To be considered for the Capawesome Cloud Open Source Program, your project must meet the following criteria: - The project must be an open source project. - The project must be actively maintained and have a clear roadmap for future development. - The project must show measurable impact or growth potential. If your project meets these criteria, we encourage you to apply! [Apply now](https://forms.gle/3NLWT1Gt9shdGoUY7) ## Frequently Asked Questions ### What happens after 12 months? After 12 months, you can apply for an extension or upgrade to a paid plan. We will review your project and determine if you are eligible for additional credits. ### Can I use the credits for commercial projects? Yes, you can use the credits for commercial projects as long as they are open source. However, we encourage you to consider upgrading to a paid plan if your project generates revenue. ### How do I apply for the program? To apply for the Capawesome Cloud Open Source Program, please fill out the [application form](https://forms.gle/3NLWT1Gt9shdGoUY7). ### How long does it take to get approved? The review process typically takes a few days. Please note that not all requests can be approved, as every use incurs costs. Therefore, the number of accepted requests must be in relation to the number of paying customers. The Capawesome team therefore reserves the right to reject requests without giving reasons. If you have not received a response within 7 days, your request is considered rejected. ### What if I have more questions? If you have any questions about the program, please feel free to reach out to us at [support@capawesome.io](mailto:support@capawesome.io) or join our [Discord community](https://discord.gg/VCXxSVjefW). # Capawesome Cloud Achieves SOC 2 Type 2 Compliance We are thrilled to announce that [Capawesome Cloud](https://cloud.capawesome.io) has successfully achieved SOC 2 Type 2 compliance! This significant milestone demonstrates our unwavering commitment to maintaining the highest security standards and protecting our customers' data. ## What is SOC 2 Type 2 Compliance? SOC 2 (Service Organization Control 2) Type 2 is a comprehensive security framework developed by the American Institute of Certified Public Accountants (AICPA). Unlike SOC 2 Type 1, which evaluates security controls at a single point in time, Type 2 compliance requires continuous monitoring and testing of security controls over an extended period. The SOC 2 Type 2 audit evaluates an organization's systems based on five Trust Service Criteria: - **Security**: Protection against unauthorized access - **Availability**: System and service accessibility as agreed upon - **Processing Integrity**: Complete and accurate system processing - **Confidentiality**: Protection of confidential information - **Privacy**: Collection, use, retention, and disclosure of personal information ## Why This Matters for Our Customers Achieving SOC 2 Type 2 compliance means that Capawesome Cloud has undergone rigorous third-party auditing to verify that our security practices, policies, and procedures meet industry-leading standards. This provides our customers with: - **Enhanced Security Assurance**: Independent validation of our security controls and data protection measures - **Compliance Support**: Helps enterprise customers meet their own compliance requirements and vendor risk management policies - **Trust and Transparency**: Demonstrates our commitment to maintaining the highest standards of data security and operational excellence - **Enterprise Readiness**: Positions Capawesome Cloud as a trusted solution for organizations with strict security requirements ## Our Commitment to Security This achievement reflects months of dedicated effort to strengthen our security posture and implement robust controls across all aspects of our platform. Some key areas covered in our SOC 2 Type 2 compliance include: - **Data Encryption**: End-to-end encryption for data in transit and at rest - **Access Controls**: Multi-factor authentication and role-based access management - **Infrastructure Security**: Secure cloud architecture and network segmentation - **Incident Response**: Comprehensive monitoring and incident response procedures - **Employee Security Training**: Regular security awareness and training programs - **Vendor Management**: Rigorous third-party security assessments ## Looking Forward Achieving SOC 2 Type 2 compliance is not a one-time achievement but an ongoing commitment. We will continue to maintain these high standards through regular audits and continuous improvement of our security practices. For enterprise customers who require a copy of our SOC 2 Type 2 report for their vendor risk assessment processes, please contact our team at [support@capawesome.io](mailto:support@capawesome.io). ## Get Started with Capawesome Cloud Ready to experience enterprise-grade security for your Capacitor app updates? Get started with Capawesome Cloud today and join thousands of developers who trust us to deliver their Over-the-Air updates securely and reliably. [Get Started](https://cloud.capawesome.io) If you have any questions about our SOC 2 Type 2 compliance or security practices, please don't hesitate to reach out to our team at [support@capawesome.io](mailto:support@capawesome.io) or join our [Discord community](https://discord.gg/VCXxSVjefW). # Changes to the Capawesome Insiders program We have decided to make some changes to the [Capawesome Insiders](https://capawesome.io/insiders/index.md) program. In this blog post, we will explain the reasons for these changes and what they mean for you as a sponsor. ## Why are we making these changes? In the past, we released new plugins exclusively for our sponsors first, our so-called **Sponsorware**. Each of these plugins was tied to a funding goal that had to be reached in order for the plugin to be released to the public. We have already been able to fund and release several plugins in this way: 1. [Android Battery Optimization](https://capawesome.io/plugins/android-battery-optimization/index.md) 1. [Android Foreground Service](https://capawesome.io/plugins/android-foreground-service/index.md) 1. [Datetime Picker](https://capawesome.io/plugins/datetime-picker/index.md) 1. [File Opener](https://capawesome.io/plugins/file-opener/index.md) While we are very grateful for the support of our sponsors, we have noticed that this approach has some disadvantages: 1. **No stable funding**: After each goal reached, there has been a drop in funding, which has made it difficult for us to secure long-term funding for the development of new sponsorware. Since Capawesome is no longer a side project for us, we need to ensure that we have a stable source of income to continue developing and maintaining our open-source projects. 1. **Quality decline**: We have realized that it's simply not possible to maintain the same quality for our sponsorware plugins after releasing them to the public. This is because we have to focus on new sponsorware plugins to keep the program attractive for our sponsors. This has led to a decline in the quality of our open-source plugins, which is not acceptable to us. 1. **Few contributions from the community**: This meant that the release of the plugins had almost no added value for us, while the maintenance effort steadily increased as more developers now had access to the plugins. ## What are the changes? We have therefore decided to revise the Capawesome Insiders program. From now on, there will no longer be any funding goals and we will instead either release plugins immediately or make them permanently available exclusively for sponsors, depending on the maintenance effort and complexity. This way, we can not only guarantee the quality of our plugins, but also ensure stable funding. # CI/CD for Capacitor Apps: Choosing the Right Approach If you're still building and deploying your Capacitor app by hand, you're wasting time and shipping slower than you need to. CI/CD automates the repetitive parts — building, signing, and deploying — so you can focus on writing code. But setting it up for mobile apps isn't as straightforward as for web apps. Native builds, code signing, and app store submissions add layers of complexity that catch many teams off guard. In this post, we'll walk through what a CI/CD pipeline for Capacitor apps looks like, how to set one up yourself, and when it makes sense to use a managed solution like [Capawesome Cloud](https://cloud.capawesome.io/) instead. ## What Is CI/CD? CI/CD stands for **Continuous Integration** and **Continuous Delivery**. Continuous Integration means automatically building and testing your code every time you push changes. Continuous Delivery takes it further by automating the deployment process so your app can be released at any time with minimal manual effort. For web apps, this is fairly straightforward — push your code, run some tests, deploy to a server. For mobile apps, it's a different story. You need to compile native code for Android and iOS, manage code signing certificates and provisioning profiles, and submit builds to app stores that each have their own requirements. A CI/CD pipeline takes all of this off your plate and runs it automatically every time you push a change. ## What a Capacitor CI/CD Pipeline Looks Like A typical CI/CD pipeline for a Capacitor app follows these stages: 1. **Install dependencies** — Install your Node.js packages and sync web assets to the native projects using `npx cap sync`. 1. **Build native projects** — Compile the Android APK/AAB using Gradle and the iOS IPA using Xcode. 1. **Code signing** — Sign the Android build with your keystore and the iOS build with your distribution certificate and provisioning profile. 1. **Run tests** — Execute unit tests, integration tests, or end-to-end tests to catch issues before they reach your users. 1. **Deploy** — Distribute the signed build to Google Play, the App Store, TestFlight, or deliver a live update directly to devices. Each of these steps introduces its own challenges. iOS builds require a macOS environment (here's [how to build iOS apps without owning a Mac](https://capawesome.io/blog/how-to-build-and-deploy-ios-apps-without-a-mac/index.md)), and managing certificates across a team is notoriously painful. Let's look at the two main approaches to tackle this. ## The DIY Approach: GitHub Actions & Co. Many teams build their own CI/CD pipelines using platforms like GitHub Actions, GitLab CI, or Bitrise. Here's what that typically involves: - **macOS runners for iOS builds** — iOS apps can only be built on macOS. On GitHub Actions, macOS runners are free for open-source projects, but most teams work on closed-source apps where macOS minutes come at a significant premium. On GitLab, you need to provision and maintain your own macOS runners entirely. - **Code signing management** — You'll need to store your Android keystores, iOS certificates, and provisioning profiles as CI secrets and configure your build scripts to use them correctly. - **Build tooling** — Tools like [Fastlane](https://fastlane.tools/) are commonly used to automate the build and signing process, but they add another layer of configuration and maintenance. - **Keeping build tools up to date** — When Apple releases a new version of Xcode or macOS, you need to update your runners or build environments yourself. This often means waiting weeks for runner images to be updated or manually maintaining your own infrastructure. The DIY approach gives you full control over every step of the pipeline. But that control comes at a cost: significant setup time, ongoing maintenance, and the expertise needed to debug build failures that are often specific to the CI environment. ## The Managed Approach: Capawesome Cloud [Capawesome Cloud](https://cloud.capawesome.io/) takes a different approach. Instead of configuring and maintaining your own pipeline, you connect your Git repository and start building — no pipeline scripts or config files required for standard setups. Here's what makes it a strong option for Capacitor teams: **Get started in minutes.** Connect your Git repository from [GitHub](https://github.com/), [GitLab](https://gitlab.com/), [Bitbucket](https://bitbucket.org/), or [Azure DevOps](https://azure.microsoft.com/en-us/products/devops), and trigger your first native build without writing a single line of configuration. Capawesome Cloud detects your project setup and handles the rest. **More affordable than GitHub Actions.** Build minutes on Capawesome Cloud cost less than macOS runners on GitHub Actions. For teams that ship regularly, the savings add up fast. **Always running the latest build tools.** When Apple releases a new macOS or Xcode version, Capawesome Cloud has it available within days — not weeks. No manual upgrades, no waiting for third-party runner images to catch up. **Built and optimized for Capacitor.** Unlike generic CI runners, Capawesome Cloud is purpose-built for Capacitor apps. The build environment is tailored to what Capacitor needs, which means faster builds and fewer unexpected failures. **Live Updates included.** Need to push a hotfix without going through the app store review process? [Live Updates](https://cloud.capawesome.io/live-updates/) let you ship changes to HTML, CSS, and JavaScript instantly — right from the same platform. **Scales with your team.** Whether you're a solo developer or a large team shipping multiple apps, Capawesome Cloud grows with you. Unlimited build minutes are available on request for high-volume teams. ## Which Approach Is Right for You? There's no one-size-fits-all answer. Here's a quick way to think about it: **The DIY approach makes sense when:** - You have complex, custom workflows that require fine-grained control over every step. - Your team has dedicated DevOps expertise to set up and maintain the pipeline. - You're already running a mature CI/CD setup and just need to extend it for mobile. **A managed solution like Capawesome Cloud makes sense when:** - You want to get up and running fast without spending days writing pipeline scripts. - You're looking for affordable native builds without managing your own macOS infrastructure. - You'd rather spend your time building features than debugging CI environments. You don't have to pick one or the other. Many teams use a hybrid approach — running tests and linting in GitHub Actions while offloading native builds and deployments to Capawesome Cloud. ## Get Started Ready to simplify your Capacitor CI/CD workflow? Book a demo to see how Capawesome Cloud can help your team ship faster. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion A CI/CD pipeline isn't optional for teams that want to ship Capacitor apps reliably. Building your own gives you full control but comes with real setup and maintenance costs — especially for iOS. Capawesome Cloud removes the hardest parts by providing optimized native builds, automatic code signing, live updates, and app store publishing out of the box. If you're evaluating your options, check out our post on [Capawesome Cloud as an alternative to Ionic Appflow](https://capawesome.io/blog/alternative-to-appflow/index.md) for a detailed feature comparison. Have questions or want to share your setup? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And to stay updated on new features and guides, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # CI/CD for Capacitor: Common Pitfalls and How to Avoid Them Getting CI/CD to work for a web app is usually painless. Getting it to work for a Capacitor app? That's where things get interesting. Between code signing, native build environments, and app store submission quirks, teams run into the same problems again and again. The good news is that most of these pitfalls are predictable — and avoidable if you know what to watch out for. ## iOS Code Signing Failures This is the number one headache for teams setting up mobile CI/CD. Locally, everything works fine. In CI, the build fails with a cryptic signing error. The root cause is usually one of these: - **Expired certificates** — Distribution certificates have a limited lifetime, and when they expire, your pipeline breaks without warning. - **Mismatched provisioning profiles** — The profile in CI doesn't match the certificate, the bundle ID, or the entitlements your app requires. - **Team members overwriting profiles** — When multiple developers manage signing through Xcode, profiles get regenerated and the ones stored in CI become invalid. **How to avoid it:** First, make sure you understand [how iOS certificates and provisioning profiles work together](https://capawesome.io/blog/ios-certificates-and-provisioning-profiles-explained/index.md). Then automate provisioning profile and certificate management instead of manually uploading files to your CI secrets. Tools like [Fastlane Match](https://docs.fastlane.tools/actions/match/) can help by storing signing assets in a shared Git repo or cloud storage. Better yet, use a platform that handles code signing for you entirely — upload your certificates once and let the platform manage the rest. ## macOS Runner Availability and Cost iOS apps can only be built on macOS. There's no workaround. And getting access to macOS build environments in CI is more expensive and complicated than most teams expect. On **GitHub Actions**, macOS runners are free for public repositories. But most production apps are closed-source, and macOS minutes cost roughly 10x more than Linux minutes. For a team running multiple builds per day, the bill adds up quickly. On **GitLab CI**, there are no hosted macOS runners at all. You need to buy, set up, and maintain your own Mac hardware as a self-hosted runner — including keeping macOS and Xcode updated, managing disk space, and handling concurrent builds. **How to avoid it:** Be strategic about when you trigger iOS builds. Not every push needs a full native build — run native builds only on merges to your main branch or release tags, and use web-only builds for feature branches. If runner costs are a concern, consider a platform with dedicated macOS infrastructure where build minutes are more affordable than GitHub Actions. ## Outdated Build Tools Breaking Builds Apple releases new versions of Xcode and macOS multiple times per year. Google updates the Android Gradle Plugin and build tools on a similar cadence. When your CI environment doesn't keep up, things break. Common symptoms include: - A new Xcode version deprecates a build setting your pipeline relies on. - A CocoaPods update introduces an incompatibility with the Xcode version available on your runner. - An Android Gradle Plugin update requires a newer Java version than what's installed on the runner. The worst part: CI runner images from major providers often lag weeks behind official releases. You might be stuck waiting for an updated image while your builds are broken. **How to avoid it:** Pin your build tool versions explicitly in your pipeline config so updates don't happen unexpectedly. When you do update, test the new versions in a branch first. Alternatively, use a build platform that keeps its environments current within days of new releases — so you can adopt new tools on your schedule without waiting or maintaining images yourself. ## Slow Build Times A full Capacitor CI pipeline — installing dependencies, syncing web assets, building Android, building iOS — can easily take 20 to 30 minutes or more. When builds are slow, developers stop waiting for CI and merge without checking results. The biggest time sinks are usually: - **No dependency caching** — Reinstalling `node_modules`, CocoaPods, and Gradle dependencies from scratch on every run. - **Sequential builds** — Building Android and iOS one after the other instead of in parallel. - **Unnecessary full rebuilds** — Triggering native builds for changes that only affect the web layer. **How to avoid it:** Cache `node_modules`, the Pods directory, and the Gradle cache between builds. Split Android and iOS builds into parallel jobs. Only trigger full native builds when native code actually changes — for web-only changes, consider shipping a [Live Updates](https://cloud.capawesome.io/live-updates/) instead. If you're using a platform optimized for Capacitor, builds tend to be faster out of the box because the environment is pre-configured with everything your project needs. ## Environment Inconsistencies The classic "works on my machine" problem hits especially hard in mobile CI/CD. Your local machine has a specific combination of Node.js, Java, Ruby, CocoaPods, and Xcode versions that all work together. Your CI environment almost certainly has different versions — and the differences cause subtle, hard-to-debug failures. Common examples: - A different Node.js version causes a dependency to install differently or a build script to behave unexpectedly. - A Ruby version mismatch breaks CocoaPods or Fastlane. - Missing or misconfigured environment variables that exist locally but weren't added to CI secrets. **How to avoid it:** Lock your tool versions using `.nvmrc` (Node.js), `Gemfile.lock` (Ruby/CocoaPods), and your pipeline configuration. Document every required environment variable and secret in your project. Test CI config changes in a feature branch before merging to main. Or use a build environment that comes pre-configured with the right tool versions for Capacitor projects. ## App Store Submission Failures Your build succeeds, your artifact looks good — and then the upload to the App Store or Google Play fails. These failures are especially frustrating because they happen at the very end of the pipeline after you've already waited for the full build. Common causes: - **Version or build number conflicts** — Uploading a build with a version number that already exists in the store. - **Missing metadata** — App Store Connect and Google Play Console require specific metadata, screenshots, and compliance information that isn't always validated until upload time. - **Format mismatches** — Uploading an APK when Google Play expects an AAB, or a development build instead of a distribution build. **How to avoid it:** Automate version bumping as part of your pipeline so you never accidentally reuse a build number. Validate your app metadata before attempting the upload. Use the store APIs or dedicated publishing tools to catch errors early. Platforms with built-in [app store publishing](https://capawesome.io/cloud/app-store-publishing/index.md) handle these details for you and surface clear error messages when something is wrong. ## How Capawesome Cloud Avoids These Pitfalls Most of these pitfalls exist because generic CI platforms weren't designed for mobile apps. They give you a blank Linux or macOS VM and leave the rest to you — code signing, build tool management, store submissions, all of it. [Capawesome Cloud](https://cloud.capawesome.io/) is built specifically for Capacitor apps, which means the most common failure points are handled by the platform: - **Code signing** is managed for you — upload your certificates and profiles once, and builds are signed automatically. - **macOS infrastructure** is included — no self-hosted runners, no expensive per-minute billing. - **Build tools stay current** — new Xcode and macOS versions are available within days of release. - **Build environments are optimized** for Capacitor, so builds are faster and less prone to the dependency conflicts that plague generic runners. - **App store publishing** is built in — submit to Google Play and the App Store directly from the platform. If you want a deeper comparison of DIY pipelines versus a managed approach, check out our post on [choosing the right CI/CD approach for Capacitor apps](https://capawesome.io/blog/choosing-the-right-ci-cd-approach-for-capacitor-apps/index.md). ## Get Started Ready to stop debugging your CI pipeline and start shipping? Book a demo to see how Capawesome Cloud can help. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion CI/CD for Capacitor apps doesn't have to be painful. The pitfalls covered in this post — code signing, runner costs, outdated tools, slow builds, environment inconsistencies, and store submission failures — are well-known and avoidable. Whether you fix them in your existing pipeline or switch to a platform that eliminates them entirely, knowing what to watch out for puts you ahead of most teams. For a broader look at your options, read our post on [choosing the right CI/CD approach for Capacitor apps](https://capawesome.io/blog/choosing-the-right-ci-cd-approach-for-capacitor-apps/index.md). Have questions or want to share what's worked for your team? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And to stay updated on new guides and features, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Encrypting SQLite databases in Capacitor Data security is paramount in mobile apps especially when handling sensitive user information. This guide shows how to encrypt SQLite databases in **Capacitor** using the [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md) with 256-bit AES and secure key management via the [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. For full **Capacitor SQLite plugin documentation**, see the [plugin docs](https://capawesome.io/plugins/sqlite/index.md). ## Introduction SQLite databases in mobile applications often contain sensitive user data such as personal information, authentication tokens, or financial records. Without proper encryption, this data remains vulnerable to unauthorized access if a device is compromised. The [Capacitor SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin provides robust 256-bit AES encryption capabilities, ensuring that your database remains secure even if the device falls into the wrong hands. Combined with the [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin for secure key storage, you can implement a comprehensive encryption strategy that protects both your data and the encryption keys used to secure it. ## Installation To implement database encryption in your Capacitor application, you'll need to install and configure both the Capacitor SQLite plugin (with encryption support) and the Capacitor Secure Preferences plugin for secure key management. ### Secure Preferences The Capacitor Secure Preferences plugin provides secure storage for sensitive information like encryption keys using the [Android Keystore](https://developer.android.com/privacy-and-security/keystore) and [iOS Keychain](https://developer.apple.com/documentation/security/keychain-services). To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/secure-preferences/#installation) section in the plugin documentation. ### SQLite The Capacitor SQLite plugin supports encryption through SQLCipher integration. To install the plugin with encryption support, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. **Important**: Make sure to enable SQLCipher support during installation by configuring the platform-specific settings as described in the plugin documentation. ## Usage Let's walk through the essential steps to encrypt a SQLite database in your Capacitor application. ### Generating the encryption key First, you need to generate a secure encryption key. This key will be used to encrypt and decrypt the database. It is crucial to use a strong, unique key for each database instance. You have several options for generating this key: 1. **Generate a random key on the client**: Use a cryptographically secure random number generator to create a 256-bit key. 1. **Generate a random key on the backend**: Generate the key on your backend server and securely transmit it to the client application. 1. **Use a user-provided key**: Allow users to set their own encryption key, but ensure it meets security standards (e.g., 256 bits). As an example, here's how to generate a random key on the client using the Web Crypto API: ``` const generateEncryptionKey = async (): Promise => { // Use a secure random number generator to create a 256-bit key const key = new Uint8Array(32); // 256 bits = 32 bytes window.crypto.getRandomValues(key); return Array.from(key).map(b => b.toString(16).padStart(2, '0')).join(''); }; ``` This function generates a random 256-bit key and returns it as a hexadecimal string. You can call this function when you need to create a new database or change the encryption key. ### Storing the encryption key Next, you need to securely store the encryption key since it will be required every time you open the database. You can use the Capacitor Secure Preferences plugin to store the key securely on the device: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const getEncryptionKeyFromSecurePreferences = async (): Promise => { const { value } = await SecurePreferences.get({ key: 'encryptionKey' }); return value; }; const setEncryptionKeyInSecurePreferences = async (key: string): Promise => { await SecurePreferences.set({ key: 'encryptionKey', value: key }); }; const getEncryptionKey = async (forceNew: boolean = false): Promise => { // Retrieve the encryption key from secure preferences let encryptionKey = await getEncryptionKeyFromSecurePreferences(); if (!encryptionKey || forceNew) { // Generate a new encryption key if it doesn't exist or if forced encryptionKey = await generateEncryptionKey(); // Store the new key securely await setEncryptionKeyInSecurePreferences(encryptionKey); } return encryptionKey; }; ``` The `getEncryptionKey(...)` function retrieves the encryption key from secure preferences, generating a new one if it doesn't exist or if forced. This ensures that your key is always securely stored and easily retrievable when needed. ### Encrypting the database Now that you have a secure encryption key, you can open an encrypted SQLite database using the Capacitor SQLite plugin. For this, you'll use the `open(...)` method with the `encryptionKey` option: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const openEncryptedDatabase = async () => { const encryptionKey = await getEncryptionKey(); const { databaseId } = await Sqlite.open({ encryptionKey, path: 'db.sqlite3' }); return databaseId; }; ``` The `open(...)` method opens the database with the specified encryption key. Please note that it's not yet possible to encrypt an already existing database with the plugin. You must create a new database with the encryption key from the start. As a workaround, you can create a new encrypted database and then copy the data from the old unencrypted database to the new one. ### Changing the encryption key If you need to change the encryption key for an existing database, you can do so using the [`changeEncryptionKey(...)`](https://capawesome.io/plugins/sqlite/#changeencryptionkey) method. This method allows you to update the encryption key while keeping the existing data intact: ``` const changeKey = async (databaseId: number) => { const encryptionKey = await getEncryptionKey(true); await Sqlite.changeEncryptionKey({ databaseId, encryptionKey, }); }; ``` By passing `true` to the `getEncryptionKey(...)` function, you force it to generate a new key. The `changeEncryptionKey(...)` method updates the database with the new key, ensuring that your data remains secure. ## Best Practices ### Use Strong, Unique Encryption Keys Generate cryptographically secure random keys for each database. Avoid using predictable keys based on user passwords or device identifiers. Use platform-specific secure random number generators and ensure keys are at least 256 bits in length. ### Implement Key Rotation Regularly rotate encryption keys to minimize the impact of potential key compromise. Implement a key rotation strategy that can seamlessly migrate data from old keys to new ones without data loss. ### Handle Key Loss Gracefully Design your application to handle scenarios where encryption keys are lost or corrupted. Implement backup strategies and user recovery mechanisms, while ensuring that fallback procedures don't compromise security. ## Conclusion Encrypting SQLite databases in Capacitor with the [Capacitor SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) adds a strong layer of security for sensitive data. By combining the Capacitor SQLite plugin's 256-bit AES encryption with secure key management through the Capacitor Secure Preferences plugin, you can build robust, secure mobile applications that protect user privacy and comply with modern security standards. **Related reading:** - [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) - [Key-Value Storage with the SQLite plugin](https://capawesome.io/blog/key-value-storage-made-simple-with-the-sqlite-plugin/index.md) - [Plugin documentation](https://capawesome.io/plugins/sqlite/#api) If you have any questions or need assistance with Capacitor SQLite database encryption or database security, feel free to reach out to the Capawesome team. We're here to help you implement robust encryption strategies and secure your Ionic applications effectively. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio), and join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) for updates and support. # Exploring the Capacitor Audio Recorder API Audio recording has become a fundamental feature in modern mobile applications, enabling users to capture voice memos, create audio content, and implement voice-driven functionality. With the [Capacitor Audio Recorder](https://capawesome.io/plugins/audio-recorder/index.md) plugin from Capawesome, developers can integrate comprehensive audio recording capabilities into their Ionic and Capacitor applications, providing high-performance recording features across Android, iOS, and Web platforms through a unified API that simplifies the complexity of platform-specific audio implementations. ## Installation To install the Capacitor Audio Recorder plugin, please refer to the [Installation](https://capawesome.io/plugins/audio-recorder/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor Audio Recorder API and how to implement them in your Ionic applications. ### Start Recording To begin recording audio, use the [`startRecording(...)`](https://capawesome.io/plugins/audio-recorder/#startrecording) method. This method initializes the recording session and begins capturing audio from the device's microphone: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const startRecording = async () => { try { await AudioRecorder.startRecording(); console.log('Recording started successfully'); } catch (error) { console.error('Failed to start recording:', error); } }; ``` The recording will begin immediately when this method is called. Make sure to handle any errors that may occur during the recording initialization, such as permission issues or device compatibility problems. ### Pause and Resume Recording The Capacitor Audio Recorder API provides full control over recording sessions with pause and resume functionality. Use the [`pauseRecording(...)`](https://capawesome.io/plugins/audio-recorder/#pauserecording) method to temporarily pause the recording without stopping it completely: ``` const pauseRecording = async () => { try { await AudioRecorder.pauseRecording(); console.log('Recording paused'); } catch (error) { console.error('Failed to pause recording:', error); } }; ``` To resume a paused recording, use the [`resumeRecording(...)`](https://capawesome.io/plugins/audio-recorder/#resumerecording) method: ``` const resumeRecording = async () => { try { await AudioRecorder.resumeRecording(); console.log('Recording resumed'); } catch (error) { console.error('Failed to resume recording:', error); } }; ``` This pause and resume functionality allows users to have more control over their recording sessions without losing previous audio content. ### Stop Recording To complete the recording session and retrieve the recorded audio, use the [`stopRecording(...)`](https://capawesome.io/plugins/audio-recorder/#stoprecording) method: ``` const stopRecording = async () => { try { const result = await AudioRecorder.stopRecording(); console.log('Recording stopped'); console.log('Audio blob:', result.blob); // Only available on web console.log('Audio URI:', result.uri); // Only available on Android and iOS } catch (error) { console.error('Failed to stop recording:', error); } }; ``` The `stopRecording(...)` method returns an object containing both a blob and URI representation of the recorded audio. The blob can be used for web-based operations, while the URI provides a file path for native platform operations. ### Event Handling The Capacitor Audio Recorder API provides event listeners to handle various recording states and errors. Proper event handling is crucial for creating a robust recording experience. #### `recordingError` Event The `recordingError` event is triggered when an error occurs during the recording process. It's important to handle this event to provide appropriate feedback to users: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const addErrorListener = () => { AudioRecorder.addListener('recordingError', (error) => { console.error('Recording error occurred:', error); alert('Recording failed. Please try again.'); }); }; ``` Always implement error handling to ensure users are informed when recording issues occur and can take appropriate action. #### `recordingStopped` Event The `recordingStopped` event is triggered when the recording session ends, either by user action or system interruption: ``` const addStoppedListener = () => { AudioRecorder.addListener('recordingStopped', (result) => { console.log('Recording stopped:', result); }); }; ``` This event is useful for handling recording completion and updating your application's state accordingly. ### Permission Handling Before using any recording functionality, ensure your application has the necessary microphone permissions. The Capacitor Audio Recorder API provides methods to check and request permissions: ``` const checkPermissions = async () => { try { const permissions = await AudioRecorder.checkPermissions(); if (permissions.microphone !== 'granted') { console.log('Microphone permission not granted'); return false; } return true; } catch (error) { console.error('Failed to check permissions:', error); return false; } }; const requestPermissions = async () => { try { const permissions = await AudioRecorder.requestPermissions(); if (permissions.microphone === 'granted') { console.log('Microphone permission granted'); return true; } else { console.log('Microphone permission denied'); return false; } } catch (error) { console.error('Failed to request permissions:', error); return false; } }; ``` Always check for permissions before attempting to record audio, and provide clear feedback to users about why permissions are needed. ## Best Practices When implementing audio recording with the Capacitor Audio Recorder API, consider these best practices: 1. **Handle permissions proactively**: Always check and request microphone permissions before starting recording operations. Use the [`checkPermissions(...)`](https://capawesome.io/plugins/audio-recorder/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/audio-recorder/#requestpermissions) methods to ensure your application has the necessary access. Provide clear explanations to users about why audio permissions are required. 1. **Implement comprehensive error handling**: Use the [`recordingError`](https://capawesome.io/plugins/audio-recorder/#addlistenerrecordingerror) event listener to handle recording failures gracefully. Provide meaningful error messages to users and implement fallback mechanisms when recording fails. This ensures a smooth user experience even when technical issues occur. 1. **Manage recording state properly**: Keep track of recording states (recording, paused, stopped) in your application and provide appropriate UI feedback. Use the [`recordingStopped`](https://capawesome.io/plugins/audio-recorder/#addlistenerrecordingstopped) event to update your interface and handle the completion of recording sessions. This helps users understand the current recording status and available actions. ## Conclusion The Capacitor Audio Recorder Plugin from Capawesome provides a comprehensive solution for integrating audio recording capabilities into Ionic applications. By offering a unified API across multiple platforms, it enables developers to create powerful audio-enabled applications without the complexity of platform-specific implementations. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Audio Recorder Plugin, feel free to reach out to the Capawesome team. We're here to help you implement robust audio recording features in your Ionic applications. # Exploring the Capacitor Barometer API Environmental sensors have become increasingly important in modern mobile applications, enabling developers to create context-aware experiences that respond to real-world conditions. With the [Capacitor Barometer](https://capawesome.io/plugins/barometer/index.md) plugin from Capawesome, developers can integrate precise atmospheric pressure measurements into their Ionic and Capacitor applications, unlocking possibilities for weather monitoring, altitude tracking, and environmental data collection through a streamlined API that delivers accurate barometric readings across Android and iOS platforms. ## Installation To install the Capacitor Barometer plugin, please refer to the [Installation](https://capawesome.io/plugins/barometer/#installation) section in the plugin documentation. ## Usage Let's explore the core functionality of the Capacitor Barometer API and learn how to implement atmospheric pressure monitoring in your applications. ### Checking Sensor Availability Before implementing barometric pressure features, it's essential to verify that the barometer sensor is available on the device. The Capacitor Barometer API provides the [`isAvailable(...)`](https://capawesome.io/plugins/barometer/#isavailable) method for this purpose: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const checkSensorAvailability = async () => { const result = await Barometer.isAvailable(); if (!result.isAvailable) { alert('Barometer sensor is not available on this device.'); return; } console.log('Barometer sensor is ready for use'); }; ``` Always call this method before attempting any barometric operations. If the sensor is not available, you can inform the user or disable barometer-related features in your application. ### Getting Single Measurements To retrieve a single atmospheric pressure reading, use the [`getMeasurement(...)`](https://capawesome.io/plugins/barometer/#getmeasurement) method: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const getCurrentPressure = async () => { try { const result = await Barometer.getMeasurement(); const pressure = result.measurement.pressure; console.log(`Current atmospheric pressure: ${pressure} hPa`); // Display pressure with appropriate formatting document.getElementById('pressure-display').textContent = `${pressure.toFixed(2)} hPa`; } catch (error) { console.error('Failed to get barometer measurement:', error); } }; ``` This method returns the atmospheric pressure in hectopascals (hPa), which is the standard unit for barometric pressure measurements. You can use this data for weather monitoring, altitude estimation, or environmental analysis. ### Continuous Measurement Updates For applications that require real-time pressure monitoring, you can start continuous measurement updates using the [`startMeasurementUpdates(...)`](https://capawesome.io/plugins/barometer/#startmeasurementupdates) method and listen for changes using the [`measurement`](https://capawesome.io/plugins/barometer/#addlistenermeasurement) event: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const startPressureMonitoring = async () => { // Add listener for measurement updates Barometer.addListener('measurement', (event) => { const pressure = event.measurement.pressure; console.log(`New pressure reading: ${pressure} hPa`); }); // Start receiving continuous updates await Barometer.startMeasurementUpdates(); console.log('Started continuous pressure monitoring'); }; ``` Remember to stop measurement updates when they're no longer needed to conserve battery life: ``` const stopPressureMonitoring = async () => { await Barometer.stopMeasurementUpdates(); // Remove all listeners to prevent memory leaks Barometer.removeAllListeners(); console.log('Stopped pressure monitoring'); }; ``` ### Handling Permissions Before accessing the barometer sensor, ensure your application has the necessary permissions using the [`checkPermissions(...)`](https://capawesome.io/plugins/barometer/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/barometer/#requestpermissions) methods: ``` const handleBarometerPermissions = async () => { // Check current permission status const permissionStatus = await Barometer.checkPermissions(); if (permissionStatus.sensors !== 'granted') { // Request permission if not already granted const requestResult = await Barometer.requestPermissions(); if (requestResult.sensors !== 'granted') { alert('Sensor permissions are required for barometer functionality.'); return false; } } return true; }; ``` Always verify permissions before performing barometer operations to ensure a smooth user experience and prevent runtime errors. ## Best Practices When implementing atmospheric pressure monitoring with the Capacitor Barometer API, consider these best practices: 1. **Check sensor availability**: Always check if the barometer sensor is available on the device using the [`isAvailable(...)`](https://capawesome.io/plugins/barometer/#isavailable) method before attempting to access barometric data. This prevents unnecessary errors and enhances user experience by gracefully handling unsupported devices. 1. **Check and request permissions**: Before accessing the barometer sensor, ensure your application has the necessary permissions using the [`checkPermissions(...)`](https://capawesome.io/plugins/barometer/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/barometer/#requestpermissions) methods. This guarantees a smooth user experience and prevents runtime errors related to missing permissions. 1. **Implement proper resource management**: Always stop measurement updates when your application goes into the background or when continuous monitoring is no longer needed. Use the [`stopMeasurementUpdates(...)`](https://capawesome.io/plugins/barometer/#stopmeasurementupdates) method and remove event listeners to prevent battery drain and memory leaks. ## Conclusion The Capacitor Barometer Plugin from Capawesome provides developers with a powerful tool for integrating atmospheric pressure measurements into mobile applications. By offering precise barometric readings through a simple API, it enables the creation of weather monitoring apps, altitude trackers, and environmental sensing applications with minimal complexity. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Barometer Plugin, feel free to reach out to the Capawesome team. We're here to help you harness the power of environmental sensors in your Ionic applications. # Exploring the Capacitor Biometrics API Modern mobile applications increasingly rely on biometric authentication to provide secure and convenient user experiences. With the [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin from Capawesome, developers can integrate powerful biometric authentication capabilities into their Ionic and Capacitor applications, enabling fingerprint, face, and iris recognition across Android, iOS, and Web platforms through a unified API that simplifies the complexity of platform-specific biometric implementations. This guide covers the **Capacitor Biometrics** API with practical examples and best practices. For the full **Capacitor Biometrics plugin documentation**, see the [API Reference](https://capawesome.io/plugins/biometrics/#api). ## Bonus: Video Tutorial and Demo App We created a **step-by-step video tutorial** that walks through installing the plugin, configuring iOS Face ID permissions, organizing your biometrics logic in a small helper module, triggering authentication from the app UI, and handling success and error states. - **[Capacitor Biometrics Demo App](https://github.com/capawesome-team/capacitor-biometrics-demo)** — A minimal, framework-agnostic demo (vanilla JavaScript and Capacitor) with a complete biometric authentication flow using Face ID or fingerprint. The tutorial assumes you already have a Capacitor app; it focuses only on the Biometrics integration. For the full API, see the [plugin documentation](https://capawesome.io/plugins/biometrics/index.md). ## Installation To install the Capacitor Biometrics plugin, please refer to the [Installation](https://capawesome.io/plugins/biometrics/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor Biometrics API and how to implement them in your Ionic applications. ### Checking Availability Before implementing biometric authentication, it's crucial to verify if biometric features are available on the device. The Capacitor Biometrics API provides the [`isAvailable(...)`](https://capawesome.io/plugins/biometrics/#isavailable) method for this purpose: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const isAvailable = async () => { const result = await Biometrics.isAvailable(); if (!result.isAvailable) { alert('Biometric authentication is not available on this device.'); return; } }; ``` Make sure to call this method once before attempting any biometric operations. If no biometric features are available, you can gracefully handle the situation by informing the user or providing alternative authentication methods. ### Checking Enrollment Biometric authentication should not only but supported by the device, but also configured by the user. The [`isEnrolled(...)`](https://capawesome.io/plugins/biometrics/#isenrolled) method allows you to check if the user has enrolled biometric data on their device: ``` const isEnrolled = async () => { const result = await Biometrics.isEnrolled(); if (!result.isEnrolled) { alert('Please enroll biometric data in your device settings to use this feature.'); } }; ``` If the user has not enrolled any biometric data, you can prompt them to do so through their device settings. On Android, you can even use the [`enroll(...)`](https://capawesome.io/plugins/biometrics/#enroll) method to guide users through the enrollment process directly from your app: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const enroll = async () => { await Biometrics.enroll(); }; ``` This will open the device's biometric enrollment screen, allowing users to add their biometric data without leaving your application. ### Authenticating with Biometrics The core functionality of the plugin is provided through the [`authenticate(...)`](https://capawesome.io/plugins/biometrics/#authenticate) method, which triggers the biometric authentication process: ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { // If the user successfully authenticates, the promise resolves. // If the user cancels the authentication or if an error occurs, the promise rejects. try { await Biometrics.authenticate({ title: 'Authentication Required', subtitle: 'Please authenticate to continue', cancelButtonText: 'Cancel', iosFallbackButtonText: 'Use Passcode', }); } catch (error) { if (error.code === ErrorCode.USER_CANCELED) { console.log('User canceled the authentication.'); } else if (error.code === ErrorCode.NOT_ENROLLED) { console.log('No biometric authentication enrolled.'); } else if (error.code === ErrorCode.NOT_AVAILABLE) { console.log('Biometric authentication not available.'); } else { console.log('Another error occurred:', error); } } }; ``` The `authenticate(...)` method accepts various configuration options to customize the authentication prompt, including titles, descriptions, and fallback options. Make sure to handle the promise rejection properly to manage different error scenarios, such as user cancellation, biometric not enrolled, or biometric not available. ### Authenticating with Device Credentials In addition to biometric authentication, the Capacitor Biometrics API supports device credentials (PIN, pattern, or password) as a fallback option. You can enable this feature by setting the `allowDeviceCredential` option to `true` in the `authenticate(...)` method: ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; const authenticate = async () => { await Biometrics.authenticate({ allowDeviceCredential: true, }); }; ``` This allows users to authenticate using their device credentials if biometric recognition fails or is temporarily unavailable. It provides a seamless user experience by ensuring that authentication can still proceed even when biometrics are not an option. Make sure to check if the device supports device credentials before enabling this option. You can do this using the [`hasDeviceCredential(...)`](https://capawesome.io/plugins/biometrics/#hasdevicecredential) method: ``` const checkDeviceCredential = async () => { const result = await Biometrics.hasDeviceCredential(); if (result.hasDeviceCredential) { console.log('Device credential is available as fallback'); } else { console.log('No device credential configured'); } }; ``` If biometrics are not available and the user has not set up a device credential, this means that the device is completely unprotected. In this case, you may want to prohibit the use of your app on this device. ### Canceling Authentication For scenarios where you need to programmatically cancel an ongoing authentication process, use the [`cancelAuthentication(...)`](https://capawesome.io/plugins/biometrics/#cancelauthentication) method: ``` const cancelAuth = async () => { await Biometrics.cancelAuthentication(); }; ``` This method is useful when implementing custom UI flows or handling application state changes that require interrupting the authentication process. ### Error Handling Proper error handling is essential for a robust implementation. For this purpose, the Capacitor Biometrics API provides a set of error codes that help you identify specific issues during the authentication process. Here are some common error codes: - `LOCKOUT`: The device is locked out due to too many failed authentication attempts. - `NOT_ENROLLED`: The user has not enrolled any biometric data on the device. - `SYSTEM_CANCELED`: The system canceled the authentication process, typically due to a timeout or interruption. - `TIMEOUT`: The authentication process timed out before completion. - `UNAVAILABLE`: Biometric authentication is not available on the device. - `USER_CANCELED`: The user canceled the authentication process. All error codes are defined in the `ErrorCode` enum, which you can import from the plugin: ``` import { ErrorCode } from '@capawesome-team/capacitor-biometrics'; ``` ## Best Practices When implementing biometric authentication with the Capacitor Biometrics API, consider these best practices: 1. **Check availability and enrollment**: Always verify that biometrics are available and enrolled before presenting biometric options to users. Use [`isAvailable(...)`](https://capawesome.io/plugins/biometrics/#isavailable) and [`isEnrolled(...)`](https://capawesome.io/plugins/biometrics/#isenrolled) to ensure the feature is ready for use. 1. **Provide fallback options**: Enable device credential fallback using `allowDeviceCredential: true` in your authentication calls. This ensures users can still authenticate even if biometric recognition fails or is temporarily unavailable. 1. **Handle errors gracefully**: Implement comprehensive error handling to manage different authentication scenarios. Provide clear feedback to users about authentication failures and guide them on alternative authentication methods. 1. **Customize authentication prompts**: Use descriptive titles, subtitles, and descriptions in your authentication prompts to help users understand why authentication is required and what actions they can take. 1. **Respect user preferences**: Allow users to disable biometric authentication in your app settings and provide alternative authentication methods for users who prefer not to use biometric features. ## Conclusion The [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin from Capawesome provides a comprehensive solution for integrating biometric authentication into Ionic applications. By offering a unified API across multiple platforms, it enables developers to create secure and user-friendly authentication experiences without the complexity of platform-specific implementations. For the full plugin documentation, see the [API Reference](https://capawesome.io/plugins/biometrics/#api). **Related guides:** - [Announcing the Capacitor Biometrics Plugin](https://capawesome.io/blog/announcing-the-capacitor-biometrics-plugin/index.md) - [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/index.md) - [Alternative to the Ionic Identity Vault plugin](https://capawesome.io/blog/alternative-to-ionic-identity-vault-plugin/index.md) To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). # Exploring the Capacitor Contacts API Modern mobile applications often require seamless access to device contacts for enhanced user experiences, from social features to communication tools. With the [Capacitor Contacts](https://capawesome.io/plugins/contacts/index.md) plugin from Capawesome, developers can integrate comprehensive contact management capabilities into their Ionic and Capacitor applications, enabling users to create, read, update, and delete contacts across Android, iOS, and Web platforms through a unified API that simplifies the complexity of platform-specific contact implementations. ## Installation To install the Capacitor Contacts plugin, please refer to the [Installation](https://capawesome.io/plugins/contacts/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor Contacts API and how to implement them effectively in your Ionic applications. ### Checking Availability Before implementing contact functionality, it's important to verify if contact features are available on the device. The Capacitor Contacts API provides the [`isAvailable(...)`](https://capawesome.io/plugins/contacts/#isavailable) method for this purpose: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const checkAvailability = async () => { const { isAvailable } = await Contacts.isAvailable(); if (isAvailable) { console.log('Contacts API is ready to use!'); } else { console.log('Contacts API is not available on this device.'); } }; ``` This method ensures that the contacts API is accessible before attempting any contact operations. If contacts are not available, you can handle this gracefully by disabling contact-related features or providing alternative functionality. ### Handling Permissions Contact access requires proper permissions to ensure user privacy and security. Use the [`checkPermissions(...)`](https://capawesome.io/plugins/contacts/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/contacts/#requestpermissions) methods to manage contact permissions: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const checkPermissions = async () => { const { readContacts, writeContacts } = await Contacts.checkPermissions(); if (readContacts !== 'granted') { console.log('Contacts can not be read.'); } if (writeContacts !== 'granted') { console.log('Contacts can not be written.'); } }; const requestPermissions = async () => { const { readContacts, writeContacts } = await Contacts.requestPermissions(); if (readContacts !== 'granted') { console.log('Contacts can not be read.'); } if (writeContacts !== 'granted') { console.log('Contacts can not be written.'); } }; ``` Always check and request permissions before performing contact operations to ensure your application has the necessary access rights. ### Creating a Contact Creating new contacts programmatically is straightforward with the [`createContact(...)`](https://capawesome.io/plugins/contacts/#createcontact) method: ``` import { Contacts, EmailAddressType, PhoneNumberType } from '@capawesome-team/capacitor-contacts'; const createContact = async () => { const newContact = { givenName: 'John', familyName: 'Doe', organizationName: 'Capawesome', phoneNumbers: [ { value: '+1-555-123-4567', type: PhoneNumberType.Mobile, }, ], emailAddresses: [ { value: 'john.doe@example.com', type: EmailAddressType.Work, }, ], postalAddresses: [ { street: '123 Main Street', city: 'New York', region: 'NY', postalCode: '10001', country: 'United States', }, ], }; await Contacts.createContact({ contact: newContact }); console.log('Contact created successfully.'); }; ``` The method accepts a comprehensive contact object with various fields including names, phone numbers, email addresses, and postal addresses. This way, you can create rich contact entries that enhance user interaction. ### Retrieving Contacts To retrieve contacts from the device, use the [`getContacts(...)`](https://capawesome.io/plugins/contacts/#getcontacts) method with various filtering and pagination options: ``` const getContacts = async () => { const { contacts } = await Contacts.getContacts({ fields: [ 'id', 'givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses' ], limit: 10, offset: 0 }); return contacts; }; ``` This method allows you to specify which fields to retrieve, enabling efficient data handling and reducing memory usage. You can also implement pagination by adjusting the `limit` and `offset` parameters to load contacts in manageable chunks. There is also a [`getContactById(...)`](https://capawesome.io/plugins/contacts/#getcontactbyid) method to retrieve a single contact by its ID. ### Updating a Contact Existing contacts can be updated using the [`updateContactById(...)`](https://capawesome.io/plugins/contacts/#updatecontactbyid) method. This method requires the contact ID of the contact you want to update, along with the new contact data: ``` import { Contacts, PhoneNumberType } from '@capawesome-team/capacitor-contacts'; const updateContactById = async (contactId: string) => { await Contacts.updateContactById({ id: contactId, contact: { givenName: 'Jane', familyName: 'Smith', organizationName: 'Capawesome Inc.', phoneNumbers: [ { value: '+1-555-987-6543', type: PhoneNumberType.Work, }, ], }, }); console.log('Contact updated successfully'); }; ``` When updating a contact, make sure to provide all fields, even those that have not changed. This is necessary because the update operation replaces the entire contact record. We recommend retrieving the existing contact first to ensure you have all the necessary fields. ### Deleting a Contact To remove a contact from the device, use the [`deleteContactById(...)`](https://capawesome.io/plugins/contacts/#deletecontactbyid) method: ``` const deleteContactById = async (contactId: string) => { await Contacts.deleteContactById({ id: contactId }); console.log('Contact deleted successfully'); }; ``` Be cautious when implementing contact deletion, as this operation is irreversible. Consider adding confirmation dialogs to prevent accidental data loss. ### Picking a Contact For user-driven contact selection, the [`pickContacts(...)`](https://capawesome.io/plugins/contacts/#pickcontacts) method opens the native contact picker: ``` const pickContacts = async () => { const { contacts } = await Contacts.pickContacts({ fields: [ 'id', 'givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses' ], multiple: false }); if (contacts.length > 0) { console.log('Contact selected:', contacts[0]); } else { console.log('No contact selected'); } }; ``` The native contact picker provides a familiar interface for users to select contacts, with options to choose single or multiple contacts. ### Advanced #### Accounts and Groups On **Android**, contacts can be associated with different accounts (like Google, CalDAV, etc.). The Capawesome Contacts plugin allows you to retrieve a list of available accounts: ``` const getAccounts = async () => { const result = await Contacts.getAccounts(); console.log('Available contact accounts:', result.accounts); }; ``` Unfortunately, Android does not offer a way to create new accounts programmatically due to security and privacy restrictions. However, you can use the retrieved accounts to offer users the option to select a specific account when creating or updating contacts. On **iOS**, contacts can be organized into groups. This is also supported by the Capawesome Contacts plugin, allowing you to manage contact groups without user intervention: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const getGroups = async () => { const result = await Contacts.getGroups(); console.log('Available contact groups:', result.groups); result.groups.forEach(group => { console.log(`Group: ${group.name} (ID: ${group.id})`); }); }; const createContactGroup = async () => { const result = await Contacts.createGroup({ group: { name: 'Friends' } }); console.log('Group created with ID:', result.id); }; ``` Unlike Android, iOS allows you to create new groups programmatically, enabling better organization of contacts within your application. #### Photos You can also manage contact photos using the Capawesome Contacts plugin. The plugin allows you to retrieve, set, and delete contact photos, enhancing the visual representation of contacts in your application. Here’s how to retrieve a contact's photo: ``` const getContactWithPhoto = async (contactId: string) => { const { contact } = await Contacts.getContactById({ id: contactId, fields: ['id', 'givenName', 'familyName', 'photo'], }); if (contact?.photo) { console.log('Contact photo as base64:', contact.photo); } else { console.log('No photo available for this contact.'); } }; ``` Contact photos are always handled as base64-encoded strings, making it easy to use them in your application, such as displaying them in user interfaces or uploading them to a server. ## Best Practices When implementing contact management with the Capacitor Contacts API, consider these best practices: 1. **Implement proper permission handling**: Always check and request contact permissions before attempting any contact operations. Provide clear explanations to users about why your app needs contact access, and gracefully handle permission denials by offering alternative functionality or limited features. 1. **Use fields wisely**: When retrieving contacts, specify only the fields you need. This reduces memory usage and improves performance, especially when dealing with large contact lists. Avoid requesting unnecessary fields to keep your application efficient. 1. **Handle updates carefully**: When updating contacts, ensure you retrieve the existing contact first to avoid losing any data. The update operation replaces the entire contact record, so it’s crucial to include all fields, even those that have not changed. ## Conclusion The Capacitor Contacts Plugin from Capawesome provides a comprehensive solution for integrating contact management into Ionic applications. By offering a unified API across multiple platforms, it enables developers to create powerful contact-driven features without the complexity of platform-specific implementations. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Contacts Plugin, feel free to reach out to the Capawesome team. We're here to help you implement seamless contact management in your Ionic applications. # Exploring the Capacitor File Compressor API File compression is a critical feature for modern mobile applications, especially when dealing with image uploads, storage optimization, and bandwidth management. With the [Capacitor File Compressor](https://capawesome.io/plugins/file-compressor/index.md) plugin from Capawesome, developers can seamlessly integrate powerful image compression capabilities into their Ionic and Capacitor applications, reducing file sizes while maintaining acceptable quality levels across Android, iOS, and Web platforms through a unified API that simplifies the complexity of platform-specific compression implementations. ## Installation To install the Capacitor File Compressor plugin, please refer to the [Installation](https://capawesome.io/plugins/file-compressor/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor File Compressor API and how to implement them effectively in your Ionic applications. ### Compressing Images The primary functionality of the plugin is provided through the [`compressImage(...)`](https://capawesome.io/plugins/file-compressor/#compressimage) method, which handles image compression with customizable quality settings: ``` import { FileCompressor } from '@capawesome-team/capacitor-file-compressor'; const compressImage = async (imagePath: string) => { const { path } = await FileCompressor.compressImage({ mimeType: 'image/jpeg', path: imagePath, quality: 0.7, }); return path; }; ``` The `compressImage(...)` method accepts several configuration options to control the compression process, including: - `mimeType`: The output format (image/jpeg, image/png, or image/webp) - `path`: The file path of the image to compress - `quality`: Compression quality from 0.0 (maximum compression) to 1.0 (best quality) For more aggressive compression, you can lower the quality value: ``` const compressImageHeavily = async (imagePath: string) => { const { path } = await FileCompressor.compressImage({ mimeType: 'image/jpeg', path: imagePath, quality: 0.3, // Higher compression, lower quality }); return path; }; ``` You can also specify different output formats based on your needs. For example, converting PNG images to JPEG for better compression: ``` const convertAndCompress = async (pngPath: string) => { const { path } = await FileCompressor.compressImage({ mimeType: 'image/jpeg', // Convert PNG to JPEG path: pngPath, quality: 0.8, }); return path; }; ``` You can also resize images while compressing them by specifying the desired height and/or width: ``` const compressAndResizeImage = async (imagePath: string, inputFormat: string) => { const { path } = await FileCompressor.compressImage({ height: 800, // Resize height to 800 pixels mimeType: 'image/jpeg', path: imagePath, quality: 0.7, width: 600, // Resize width to 600 pixels }); return path; }; ``` ## Best Practices When implementing image compression with the Capacitor File Compressor API, consider these best practices: 1. **Choose appropriate quality levels**: Balance file size reduction with visual quality by testing different quality values for your specific use case. Generally, values between 0.6 and 0.8 provide good compression while maintaining acceptable quality for most applications. 1. **Handle compression errors gracefully**: Implement comprehensive error handling to manage scenarios where compression fails, such as unsupported file formats or corrupted images. Always provide fallback options or user feedback when compression cannot be completed. 1. **Consider format conversion strategically**: Convert PNG images to JPEG format when transparency is not required, as JPEG typically provides better compression ratios. However, preserve PNG format when working with images that require transparency or when dealing with graphics with sharp edges and solid colors. ## Conclusion The Capacitor File Compressor Plugin from Capawesome provides an efficient solution for integrating image compression into Ionic applications. By offering a unified API across multiple platforms, it enables developers to optimize file sizes and improve application performance without the complexity of platform-specific compression implementations. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor File Compressor Plugin, feel free to reach out to the Capawesome team. We're here to help you implement efficient image compression in your Ionic applications. # Exploring the Capacitor Live Update API Modern mobile application development requires agility and the ability to deliver updates quickly to users without the lengthy app store review process. The [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin from Capawesome revolutionizes how developers deploy updates to their Ionic and Capacitor applications, enabling Over-the-Air (OTA) updates that allow instant delivery of new features, bug fixes, and content changes directly to users' devices without requiring them to download a new version from app stores. ## Prerequisites Before diving into the Capacitor Live Update API, ensure you have a [Capawesome Cloud](https://cloud.capawesome.io/) account. This is essential for managing your live updates and deploying them seamlessly to your applications. ## Installation To install the Capacitor Live Update plugin, please refer to the [Installation](https://capawesome.io/plugins/live-update/#installation) section in the plugin documentation. ## Usage Let's explore the core functionality of the Capacitor Live Update API and how to implement seamless OTA updates in your Ionic applications. ### Syncing Updates The primary method for delivering updates to your application is through the [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) method. This method checks for available updates and downloads them if necessary: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const syncUpdate = async () => { const result = await LiveUpdate.sync({ channel: 'production-5' }); if (result.nextBundleId) { console.log('New bundle downloaded:', result.nextBundleId); // Restart the app to apply the update await LiveUpdate.reload(); } else { console.log('No updates available'); } }; ``` The `sync(...)` method is designed to be non-blocking and will only download updates when they are available. It returns information about the next bundle ID that will be applied, allowing you to control when and how updates are implemented in your application. If you just want to check for updates without downloading them, you can use the [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) method: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const checkForUpdates = async () => { const result = await LiveUpdate.fetchLatestBundle({ channel: 'production-5' }); if (result.nextBundleId) { console.log('Latest bundle available:', result.nextBundleId); } else { console.log('No updates available'); } }; ``` ### Managing Update Channels Update channels provide a powerful way to manage different versions of your application for different audiences. You can set the current channel using the [`setChannel(...)`](https://capawesome.io/plugins/live-update/#setchannel) method: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setChannel = async () => { await LiveUpdate.setChannel({ channel: 'production-5'. // Specify the channel name }); }; ``` This allows you to deliver different versions of your app to different user groups, such as beta testers receiving experimental features while production users get stable releases. Alternatively, you can pass the channel name as an option to the `sync(...)` or `fetchLatestBundle(...)` methods: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const syncWithChannel = async (channelName: string) => { const result = await LiveUpdate.sync({ channel: channelName }); // Handle the result as needed }; ``` ### Retrieving Bundle Information To understand what bundles are available and currently active, use the [`getBundle(...)`](https://capawesome.io/plugins/live-update/#getbundle) method: ``` const getCurrentBundle = async () => { const { bundleId } = await LiveUpdate.getCurrentBundle(); console.log('Current bundle ID:', bundleId); return bundleId; }; ``` This method provides detailed information about the currently active bundle, including its ID, version, and status, helping you track which version of your app is running. ### Downloading Updates For more control over the update process, you can manually download updates using the [`downloadBundle(...)`](https://capawesome.io/plugins/live-update/#downloadbundle) method. This is useful if you first want to check for updates using the `fetchLatestBundle(...)` method and then decide when to download them: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const downloadBundle = async (bundleId: string) => { await LiveUpdate.downloadBundle({ bundleId: bundleId }); }; ``` ### Setting Active Bundles Once a bundle is downloaded, you can set it as the next bundle using the [`setNextBundle(...)`](https://capawesome.io/plugins/live-update/#setnextbundle) method. This way, the bundle will be used the next time the app is restarted: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setNextBundle = async (bundleId: string) => { await LiveUpdate.setNextBundle({ bundleId: bundleId }); }; ``` If you want to apply the downloaded bundle immediately, you can use the [`reload(...)`](https://capawesome.io/plugins/live-update/#reload) method: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const applyUpdate = async () => { await LiveUpdate.reload(); }; ``` ### Monitoring Download Progress For large updates, you can monitor download progress using the [`downloadProgress`](https://capawesome.io/plugins/live-update/#addlistenerdownloadprogress) event listener: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const addDownloadProgressListener = () => { LiveUpdate.addListener('downloadProgress', (event) => { console.log(`Download progress: ${event.progress * 100}%`); }); }; ``` This enables you to provide real-time feedback to users about update downloads, enhancing the user experience during the update process. ### Managing Local Bundles You can retrieve a list of all locally stored bundles using the [`getBundles(...)`](https://capawesome.io/plugins/live-update/#getbundles) method: ``` const listLocalBundles = async () => { const bundles = await LiveUpdate.getBundles(); bundles.forEach(bundle => { console.log(`Bundle ID: ${bundle.bundleId}, Status: ${bundle.status}`); }); return bundles; }; ``` To clean up storage space, you can delete unused bundles with the [`deleteBundle(...)`](https://capawesome.io/plugins/live-update/#deletebundle) method: ``` const cleanupOldBundles = async (bundleId: string) => { await LiveUpdate.deleteBundle({ bundleId: bundleId }); console.log(`Bundle ${bundleId} deleted`); }; ``` There is also a configuration option called `autoDeleteBundles` to automatically delete old bundles when a new one is applied. Just set it to `true` in your Capacitor configuration file: ``` { "plugins": { "LiveUpdate": { "autoDeleteBundles": true } } } ``` ## Best Practices When implementing live updates with the Capacitor Live Update API, consider these best practices: 1. **Enable Automatic Rollback**: Set the `readyTimeout` option to automatically roll back to the previous bundle if the new one fails to load within a specified time. This ensures a smooth user experience even if an update has issues. Make sure to call the `ready()` method directly at app startup to notify the plugin that no rollback is needed. 1. **Fetch Updates Efficiently**: Do not call the `sync(...)` or `fetchLatestBundle(...)` methods too frequently. Instead, implement a strategy to check for updates periodically or based on user actions, such as app startup or specific user interactions. The very best way is to notify devices about new updates via silent push notifications. 1. **Ask for User Consent**: Before applying updates, consider prompting users to confirm the update, especially for significant changes. This can help manage user expectations and ensure they are aware of the changes being made. ## Conclusion The Capacitor Live Update Plugin from Capawesome provides a powerful solution for delivering instant updates to Ionic applications without the constraints of traditional app store distribution. By enabling Over-the-Air updates, developers can respond quickly to user feedback, fix critical issues, and deploy new features with unprecedented agility. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Live Update Plugin, feel free to reach out to the Capawesome team. We're here to help you implement seamless OTA updates in your Ionic applications. # Exploring the Capacitor NFC API Near Field Communication (NFC) has become an essential technology for modern mobile applications, enabling seamless interactions between devices and smart objects. With the [Capacitor NFC](https://capawesome.io/plugins/nfc/index.md) plugin from Capawesome, developers can integrate powerful NFC capabilities into their Ionic and Capacitor applications, bridging the gap between web technologies and native device features through a unified NFC API that works seamlessly across multiple platforms, including Android, iOS, and Web. ## Terms and Concepts Before diving into the details of the Capacitor NFC API, let's clarify some key terms and concepts related to NFC technology: - **NFC Tag**: A small, passive device that can store data and communicate with NFC-enabled devices when they are in close proximity (typically within a few centimeters). - **NFC Reader**: A device that can read data from NFC tags and interact with them. This can be a smartphone, tablet, or any other NFC-enabled device. - **Host Card Emulation (HCE)**: A feature that allows an application to emulate an NFC tag, enabling it to interact with NFC readers as if it were a physical NFC tag. This is particularly useful for applications that need to provide NFC functionality without requiring a physical tag. - **NDEF (NFC Data Exchange Format)**: A standardized format for storing data on NFC tags, allowing different devices to read and write data in a consistent manner. ## Installation To install the Capacitor NFC plugin, please refer to the [Installation](https://capawesome.io/plugins/nfc/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor NFC API and how to use them effectively in your Ionic applications. ### Checking Availability Before using any NFC functionality, it's essential to check if NFC is available on the device. The Capacitor NFC API provides the [`isAvailable(...)`](https://capawesome.io/plugins/nfc/#isavailable) method for this purpose: ``` import { NFC } from '@capawesome-team/capacitor-nfc'; const isAvailable = async () => { const { hce, nfc } = await NFC.isAvailable(); if (!nfc) { alert('NFC is not available on this device. Please check your device settings.'); } else if (!hce) { alert('Host Card Emulation (HCE) is not available on this device. Some features may be limited.'); } }; ``` This method checks whether both NFC and Host Card Emulation (HCE) are available on the device. In most cases, you will want to ensure that NFC is available before proceeding with any NFC operations. HCE is an advanced feature that allows your application to [emulate an NFC tag](#emulating-nfc-tags), enabling more complex interactions with NFC readers. If a feature is not available that you need, you should inform the user about the limitation and should not attempt to use that feature in your application. ### Checking Status To ensure that NFC is enabled on the device, you can use the [`isEnabled(...)`](https://capawesome.io/plugins/nfc/#isenabled) method: ``` const isEnabled = async () => { const enabled = await NFC.isEnabled(); if (!enabled) { alert('NFC is disabled. Please enable it in your device settings.'); } }; ``` This method checks if NFC is enabled. If NFC is disabled, you can prompt the user to enable it in their device settings. For this, you can use the [`openSettings(...)`](https://capawesome.io/plugins/nfc/#opensettings) method to direct users to the NFC settings page: ``` import { NFC } from '@capawesome-team/capacitor-nfc'; const openSettings = async () => { await NFC.openSettings(); }; ``` ### Handling Permissions To ensure your application has the necessary permissions to use NFC, you can check and request permissions using the [`checkPermissions(...)`](https://capawesome.io/plugins/nfc/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/nfc/#requestpermissions) methods: ``` const checkPermissions = async () => { const permissions = await NFC.checkPermissions(); if (!permissions.nfc) { alert('NFC permission is required to use this feature.'); } }; const requestPermissions = async () => { const permissions = await NFC.requestPermissions(); if (!permissions.nfc) { alert('NFC permission was denied. Please enable it in your device settings.'); } }; ``` Make sure to call these methods before performing any NFC operations to ensure your application has the necessary permissions. ### Reading NFC Tags To finally read your first NFC tag (including NDEF messages), you need to start a scan session using the [`startScanSession(...)`](https://capawesome.io/plugins/nfc/#startscansession) method. This method allows you to listen for NFC tags using the [`nfcTagScanned`](https://capawesome.io/plugins/nfc/#addlistenernfctagscanned) event. Here's a simple example of how to read an NFC tag: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const read = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.stopScanSession(); resolve(event.nfcTag); }); Nfc.startScanSession(); }); }; ``` As soon as an NFC tag is hold near the device, the `nfcTagScanned` event will be triggered, and you can access the scanned tag data. After reading the tag, it's important to stop the scan session using the [`stopScanSession(...)`](https://capawesome.io/plugins/nfc/#stopscansession) method to free up system resources. ### Writing NDEF Messages To write NDEF messages to an NFC tag, you need to use the [`write(...)`](https://capawesome.io/plugins/nfc/#write) method. But before writing, you need to create an NDEF record. The Capacitor NFC API provides a [`Utils`](https://capawesome.io/plugins/nfc/#utils) class that helps you create various types of NDEF records, such as text, URI, and MIME records. Here's an example of how to create a text record: ``` import { NfcUtils } from "@capawesome-team/capacitor-nfc"; const createNdefTextRecord = () => { const utils = new NfcUtils(); const { record } = utils.createNdefTextRecord({ text: "Capacitor NFC Plugin", }); return record; }; ``` You can then use the `write(...)` method to write this record as part of an NDEF message to an NFC tag: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const write = async () => { return new Promise((resolve) => { const record = createNdefTextRecord(); Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.write({ message: { records: [record] } }); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; ``` As with reading, you have to start a scan session first to listen for NFC tags. When a tag is scanned, the `nfcTagScanned` event will be triggered, and you can write the NDEF message to the tag using the `write(...)` method. ### Making NFC Tags Read-Only To make an NFC tag read-only, you can use the [`makeReadOnly(...)`](https://capawesome.io/plugins/nfc/#makereadonly) method. This method allows you to set the tag to read-only mode, preventing any further modifications to its data: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const makeReadOnly = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.makeReadOnly(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; ``` This method is useful when you want to ensure that the data on an NFC tag cannot be modified after it has been written, providing a level of data integrity and security. Warning This is a **one-way** operation and cannot be undone. Once an NFC tag has been made read-only, it can no longer be written to. ### Clearing NFC Tags To remove all data from an NFC tag, you can use the [`erase(...)`](https://capawesome.io/plugins/nfc/#erase) method. This method allows you to erase all NDEF records from the tag: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const erase = async () => { return new Promise((resolve) => { Nfc.addListener("nfcTagScanned", async (event) => { await Nfc.erase(); await Nfc.stopScanSession(); resolve(); }); Nfc.startScanSession(); }); }; ``` This method is useful when you want to clear the data on an NFC tag, allowing it to be reused for new data at a later time. Warning This operation will remove all data from the NFC tag, including any NDEF records. Use it with caution, as it cannot be undone. ### Advanced The Capacitor NFC API also provides advanced features for more complex NFC operations. Let's explore some of these features. #### Executing Raw Commands Executing raw commands is a common requirement for advanced NFC applications, especially when dealing with specific NFC tag types or protocols. The Capacitor NFC API allows you to execute raw commands using the [`transceive(...)`](https://capawesome.io/plugins/nfc/#transceive) method. You can find a list of supported commands in the technical documentation of the NFC tag you are working with. Here's an example of how to execute a raw command to read a signature from an NFC tag: ``` const readSignature = async () => { return new Promise((resolve) => { Nfc.addListener('nfcTagScanned', async (event) => { if (Capacitor.getPlatform() === 'android') { // 1. Connect to the tag. await Nfc.connect({ techType: NfcTagTechType.NfcA }); // 2. Send one or more commands to the tag and receive the response. const result = await Nfc.transceive({ data: [60, 0] }); // 3. Close the connection to the tag. await Nfc.close(); await Nfc.stopScanSession(); resolve(response); } else { // 1. Send one or more commands to the tag and receive the response. const result = await Nfc.transceive({ techType: NfcTagTechType.NfcA, data: [60, 0] }); await Nfc.stopScanSession(); resolve(response); } }); Nfc.startScanSession(); }); }; ``` Again, you need to start a scan session to listen for NFC tags. When a tag is scanned, the `nfcTagScanned` event will be triggered, and you can execute the raw command using the `transceive(...)` method. The method takes an array of bytes as input and returns the response from the NFC tag. On **Android**, make sure to connect to the tag first using the [`connect(...)`](https://capawesome.io/plugins/nfc/#connect) method. After executing one or more commands, you should close the connection to the tag using the [`close(...)`](https://capawesome.io/plugins/nfc/#close) method. #### Emulating NFC Tags Emulating NFC tags allows your application to act as an NFC tag, enabling interactions with NFC readers. The Capacitor NFC API provides two event listener and one method for this purpose: - [`respond(...)`](https://capawesome.io/plugins/nfc/#respond): This method allows your application to respond to NFC reader requests. - [`addListener('commandReceived', ...)`](https://capawesome.io/plugins/nfc/#addlistenercommandreceived): This event listener is triggered when an NFC reader sends a command to your application. - [`addListener('nfcLinkDeactivated', ...)`](https://capawesome.io/plugins/nfc/#addlistenernfclinkdeactivated): This event listener is triggered when the NFC link is deactivated, such as when the user moves the NFC tag away from the device. Here's an example of how to set up NFC tag emulation: ``` import { Nfc } from '@capawesome-team/capacitor-nfc'; const addListener = async () => { Nfc.addListener('commandReceived', async (event) => { console.log(event.data); await respond({ data: [...] }); }); }; ``` This time you don't need to start a scan session, as the NFC reader will initiate the communication. The `commandReceived` event will be triggered whenever a NFC reader sends an Application Protocol Data Unit (APDU) command to your application. You can respond to these commands using the `respond(...)` method, which allows you to send data back to the NFC reader. Info The NDEF format is not supported for tag emulation. Only raw commands can be sent to NFC readers during emulation. #### Retrieve NFC Antenna Information On Android, you can retrieve information about the NFC antenna using the [`getAntennaInfo(...)`](https://capawesome.io/plugins/nfc/#getantennainfo) method. This method provides details about the NFC antenna, such as the device height and width, and the antenna's position on the device: ``` import { Nfc } from "@capawesome-team/capacitor-nfc"; const getAntennaInfo = async () => { const antennaInfo = await Nfc.getAntennaInfo(); console.log("Device Height:", antennaInfo.deviceHeight); console.log("Device Width:", antennaInfo.deviceWidth); console.log("Is Devive Foldable:", antennaInfo.isDeviceFoldable); console.log("X Position of first antenna:", antennaInfo.availableAntennas[0].locationX); console.log("Y Position of first antenna:", antennaInfo.availableAntennas[0].locationY); }; ``` This method is particularly useful for applications that want to provide visual feedback to users about where to place their NFC tags for optimal scanning performance. By understanding the antenna's position, you can guide users to hold their NFC tags in the right spot for successful interactions. ## Best Practices When working with the Capacitor NFC API, consider these best practices: 1. **Always check NFC availability and status**: Use the [`isAvailable(...)`](https://capawesome.io/plugins/nfc/#isavailable) and [`isEnabled(...)`](https://capawesome.io/plugins/nfc/#isenabled) methods to ensure that NFC is available and enabled on the device before attempting any NFC operations. This helps prevent errors and provides a better user experience. 1. **Handle permissions properly**: Check and request permissions using the [`checkPermissions(...)`](https://capawesome.io/plugins/nfc/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/nfc/#requestpermissions) methods before performing any NFC operations. This ensures that your application has the necessary permissions to access NFC features and prevents unexpected behavior. 1. **Manage scan sessions carefully**: Always stop scan sessions when done to free up system resources using the [`stopScanSession(...)`](https://capawesome.io/plugins/nfc/#stopscansession) method. This is especially important in mobile applications where resource management is crucial for performance and battery life. 1. **Implement error handling**: Use the [`scanSessionError`](https://capawesome.io/plugins/nfc/#addlistenerscansessionerror) event to handle errors that may occur during NFC operations. This allows you to provide feedback to users and handle unexpected situations gracefully. 1. **Optimize for user experience**: Provide clear instructions to users on how to use NFC features in your application. For example, guide them on how to hold their device and NFC tags for optimal scanning performance. Use visual cues or animations to enhance the user experience. ## Conclusion The Capacitor NFC Plugin from Capawesome provides a powerful and flexible solution for integrating NFC capabilities into Ionic applications. By offering a unified API across multiple platforms, it enables developers to create sophisticated NFC-enabled applications without the complexity of platform-specific implementations. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor NFC Plugin, feel free to reach out to the Capawesome team. We're here to help you make the most of NFC technology in your Ionic applications. # Exploring the Capacitor Printer API Printing functionality has become an essential feature for many mobile applications, enabling users to generate physical copies of documents, receipts, reports, and other important content directly from their devices. With the [Capacitor Printer](https://capawesome.io/plugins/printer/index.md) plugin from Capawesome, developers can seamlessly integrate comprehensive printing capabilities into their Ionic and Capacitor applications, supporting various content types including PDFs, images, HTML content, and web pages through a unified API that works consistently across Android and iOS platforms. ## Installation To install the Capacitor Printer plugin, please refer to the [Installation](https://capawesome.io/plugins/printer/#installation) section in the plugin documentation. ## Usage Let's explore the diverse printing capabilities of the Capacitor Printer API and how to implement them effectively in your applications. ### Printing Base64 Content The [`printBase64(...)`](https://capawesome.io/plugins/printer/#printbase64) method allows you to print content that has been encoded in Base64 format. This method is particularly useful when you have binary data that needs to be transmitted or stored as text: ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printBase64Document = async () => { await Printer.printBase64({ data: 'JVBERi0xLjQKJcOkw7zDtsKuCjIgMCBvYmoKPDwvTGVuZ3RoIDMgMCBSL0ZpbHRlci9GbGF0ZURlY29kZT4+CnN0cmVhbQ==', mimeType: 'application/pdf' }); }; ``` However, it's important to note that printing Base64 content is **not recommended** for larger files as it can lead to Out-of-Memory (OOM) errors. The Base64 encoding process increases the file size by approximately 33%, and keeping large encoded strings in memory can quickly exhaust available resources, especially on mobile devices with limited memory. For better performance and reliability, consider using the [`printFile(...)`](https://capawesome.io/plugins/printer/#printfile) method instead, which handles files more efficiently without the memory overhead of Base64 encoding. ### Printing Files The [`printFile(...)`](https://capawesome.io/plugins/printer/#printfile) method provides the most efficient way to print documents stored on the device. This method directly accesses the file system and handles the printing process without requiring additional memory for encoding: ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printStoredDocument = async () => { await Printer.printFile({ path: 'content://documents/my-document.pdf', mimeType: 'application/pdf' }); }; ``` This approach is ideal for printing documents that are already stored on the device, such as downloaded files, generated reports, or cached content. The method supports various file formats including PDFs and images, making it versatile for different printing needs. ### Printing HTML Content The [`printHtml(...)`](https://capawesome.io/plugins/printer/#printhtml) method enables you to print dynamically generated HTML content, which is particularly useful for creating formatted documents, reports, or receipts directly from your application: ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printHtmlReport = async () => { const htmlContent = `

Sales Report

This is a dynamically generated sales report for the current month.

Total Sales: $12,345.67

Orders Processed: 156

`; await Printer.printHtml({ html: htmlContent }); }; ``` This method gives you complete control over the document layout and styling, allowing you to create professional-looking printed materials with custom formatting, styling, and dynamic content generation. ### Printing Web Content The [`printWebView(...)`](https://capawesome.io/plugins/printer/#printwebview) method allows you to print the current content displayed in your application's web view. This is particularly useful for printing web pages, articles, or any content that users are currently viewing: ``` import { Printer } from '@capawesome-team/capacitor-printer'; const printCurrentPage = async () => { await Printer.printWebView({ name: 'Current Page Print Job' }); }; ``` This method captures the current state of the web view and sends it to the printer, maintaining the visual layout and formatting that users see on their screen. The optional `name` parameter allows you to specify a custom name for the print job, which helps users identify the document in their print queue. ## Best Practices When implementing printing functionality with the Capacitor Printer API, consider these best practices: 1. **Optimize for performance**: Always use [`printFile(...)`](https://capawesome.io/plugins/printer/#printfile) instead of [`printBase64(...)`](https://capawesome.io/plugins/printer/#printbase64) for larger documents to avoid memory issues. The file-based approach is more efficient and reduces the risk of Out of Memory errors, especially when dealing with high-resolution images or large PDF documents. 1. **Handle errors gracefully**: Implement comprehensive error handling around all printing operations to manage scenarios such as printer unavailability, connectivity issues, or unsupported file formats. Provide clear feedback to users when printing fails and offer alternative solutions or retry mechanisms. 1. **Provide user feedback**: Display appropriate loading indicators and progress feedback during printing operations, as these processes can take time depending on document size and printer capabilities. Consider showing print preview options when possible to allow users to verify content before printing, and provide confirmation messages when print jobs are successfully submitted. ## Conclusion The Capacitor Printer Plugin from Capawesome provides a comprehensive solution for integrating printing capabilities into Ionic applications. By supporting multiple content types and offering efficient methods for different printing scenarios, it enables developers to create professional printing experiences that meet diverse user needs. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Printer Plugin, feel free to reach out to the Capawesome team. We're here to help you implement robust printing functionality in your Ionic applications. # Exploring the Capacitor Secure Preferences API Securing sensitive data in mobile applications is crucial for protecting user privacy and maintaining trust. With the [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin from Capawesome, developers can implement robust secure storage solutions in their Ionic and Capacitor applications, leveraging native security features like Android Keystore and iOS Keychain to protect sensitive information such as authentication tokens, passwords, and personal data through a unified API that ensures data remains encrypted and secure across all platforms. ## Installation To install the Capacitor Secure Preferences plugin, please refer to the [Installation](https://capawesome.io/plugins/secure-preferences/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor Secure Preferences API and how to implement them in your Ionic applications. ### Storing Values The primary method of the Capacitor Secure Preferences API is to securely store sensitive data. Use the [`set(...)`](https://capawesome.io/plugins/secure-preferences/#set) method to store key-value pairs: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const set = async () => { await SecurePreferences.set({ key: 'token', value: 'value' }); }; ``` The plugin automatically handles encryption and storage using the most secure method available on each platform. On Android, data is encrypted using the Android Keystore, while on iOS, it's stored in the iOS Keychain. ### Retrieving Values To retrieve stored data, use the [`get(...)`](https://capawesome.io/plugins/secure-preferences/#get) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const get = async () => { const { value } = await SecurePreferences.get({ key: 'token', }); console.log(value) }; ``` The `get(...)` method returns an object with a `value` property that contains the stored data, or `null` if no data exists for the specified key. Always handle the case where the requested key doesn't exist to prevent application errors. ### Retrieving Keys To retrieve all keys stored in secure preferences, you can use the `keys()` method. This method returns an array of all keys currently stored, allowing you to manage them as needed. ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const keys = async () => { const { keys } = await SecurePreferences.keys(); console.log(keys) // ['token', 'password', ...] }; ``` Do not use this method to check if a specific key exists, as it returns all keys. Instead, use the `get(...)` method to check for the existence of a specific key and retrieve its value. ### Deleting Values You can delete values from secure storage either one at a time or all at once, depending on your needs. To remove a specific key-value pair, use the [`remove(...)`](https://capawesome.io/plugins/secure-preferences/#remove) method: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const remove = async () => { await SecurePreferences.remove({ key: 'token', }); }; ``` If you want to remove everything stored in secure preferences, you can use the `clear()` method. This method does not take any arguments and deletes all keys and values currently saved in secure storage. It’s useful for scenarios like logging out a user or resetting the app’s secure data. ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const clear = async () => { await SecurePreferences.clear(); }; ``` ## Best Practices When using Capacitor Secure Preferences plugin in your application, consider these best practices: 1. **Store only Sensitive Data**: Secure Preferences is meant to store sensitive or confidential information, such as tokens, passwords or personal identifiers. Avoid cluttering secure storage with large amounts of non-sensitive data. 1. **Handle Missing or Deleted Data**: Users might clear secure storage manually, reinstall the app or switch devices. Therefore, always check if a key exists before using its value and provide fallback flows if data is missing. A fallback flow can be re-prompting the user to log in or re-enter information. 1. **Never Hardcode Sensitive Data or Keys**: While Secure Preferences encrypts values (except for web platform), keys themselves are often stored as plain strings. Avoid giving away meaning in key names and never hardcode secrets or credentials directly into your app's source code. Instead, use generic key names that don't reveal the data's purpose. ## Conclusion The Capacitor Secure Preferences plugin provides a simple yet powerful way to protect sensitive data in your Ionic and Capacitor applications. By following best practices and leveraging platform-native security features, you can keep user data safe and maintain trust in your apps. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Secure Preferences Plugin, feel free to reach out to the Capawesome team. We're here to help you implement secure biometric authentication in your Ionic applications. # Exploring the Capacitor Speech Recognition API Voice interaction has become a cornerstone of modern mobile applications, transforming how users engage with their devices through natural speech commands and dictation. With the [Capacitor Speech Recognition](https://capawesome.io/plugins/speech-recognition/index.md) plugin from Capawesome, developers can seamlessly integrate powerful voice recognition capabilities into their Ionic and Capacitor applications, enabling real-time speech-to-text conversion across Android, iOS, and Web platforms through a unified API that handles the complexities of platform-specific speech recognition implementations. ## Installation To install the Capacitor Speech Recognition plugin, please refer to the [Installation](https://capawesome.io/plugins/speech-recognition/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor Speech Recognition API and how to implement them effectively in your Ionic applications. ### Permission Handling Before implementing speech recognition functionality, it's crucial to ensure your application has the necessary permissions to access the microphone and speech recognition services. The Capacitor Speech Recognition API provides the [`checkPermissions(...)`](https://capawesome.io/plugins/speech-recognition/#checkpermissions) and [`requestPermissions(...)`](https://capawesome.io/plugins/speech-recognition/#requestpermissions) methods for this purpose: ``` import { SpeechRecognition } from '@capawesome-team/capacitor-speech-recognition'; const checkPermissions = async () => { const permissions = await SpeechRecognition.checkPermissions(); if (permissions.speechRecognition !== 'granted' || permissions.microphone !== 'granted') { console.log('Permissions not granted, requesting...'); await requestPermissions(); } }; const requestPermissions = async () => { const permissions = await SpeechRecognition.requestPermissions(); if (permissions.speechRecognition !== 'granted') { alert('Speech recognition permission is required to use this feature.'); } if (permissions.microphone !== 'granted') { alert('Microphone permission is required to capture audio.'); } }; ``` Always verify permissions before starting speech recognition to ensure a smooth user experience and prevent permission-related errors. ### Start Listening To begin capturing and recognizing speech, use the [`startListening(...)`](https://capawesome.io/plugins/speech-recognition/#startlistening) method. This method allows you to configure various options for the recognition session: ``` const startListening = async () => { try { // Add all necessary event listeners SpeechRecognition.addListener('start', () => { console.log('Speech recognition started'); }); SpeechRecognition.addListener('speechStart', () => { console.log('User started speaking'); }); SpeechRecognition.addListener('speechEnd', () => { console.log('User stopped speaking'); }); SpeechRecognition.addListener('partialResult', (event) => { console.log('Partial result:', event.partialResult); }); SpeechRecognition.addListener('result', (event) => { console.log('Final result:', event.result); }); SpeechRecognition.addListener('end', () => { console.log('Speech recognition ended'); }); SpeechRecognition.addListener('error', (event) => { console.error('Speech recognition error:', event.message); }); // Start listening for speech input await SpeechRecognition.startListening({ language: 'en-US', silenceThreshold: 2000, partialResultsEnabled: true, contextualStrings: ['Capacitor', 'Ionic', 'Angular'] }); console.log('Speech recognition started successfully'); } catch (error) { console.error('Failed to start speech recognition:', error); } }; ``` The `startListening(...)` method accepts several configuration options including language selection, silence detection thresholds, and contextual strings that help improve recognition accuracy for domain-specific vocabulary. Make sure to adjust these parameters based on your application's requirements. Also, ensure that you add all necessary event listeners before calling `startListening(...)` to handle various speech recognition events effectively. The following events are available: - **`start`**: Triggered when speech recognition begins - use this to update your UI to show that the system is ready to listen. - **`end`**: Triggered when the recognition session concludes - essential for returning your UI to an idle state. - **`speechStart`**: Fired when the user begins speaking - ideal for providing visual feedback that speech is being detected. - **`speechEnd`**: Called when the user stops speaking - useful for indicating that the system is processing the captured audio. - **`partialResult`**: Provides interim transcription results while the user is speaking - enables real-time text display for better user experience. - **`result`**: Delivers the final transcribed text when recognition completes - this is where you'll process the user's speech input. - **`error`**: Fired when recognition errors occur - critical for handling network issues, permission problems, or recognition failures gracefully. ### Stop Listening To manually end the speech recognition session, use the [`stopListening(...)`](https://capawesome.io/plugins/speech-recognition/#stoplistening) method: ``` const stopListening = async () => { try { await SpeechRecognition.stopListening(); console.log('Speech recognition stopped'); } catch (error) { console.error('Failed to stop speech recognition:', error); } }; ``` The `stopListening(...)` method only needs to be called if you want to manually stop the recognition session. Otherwise, the speech recognition will automatically stop based on the configured timeout or when silence is detected for the specified duration. ## Best Practices When implementing speech recognition with the Capacitor Speech Recognition API, consider these best practices: 1. **Implement comprehensive error handling**: Always handle the `error` event to manage network issues, audio capture problems, and recognition failures gracefully. Provide clear feedback to users about what went wrong and how they can resolve the issue, ensuring your application remains stable even when speech recognition encounters problems. 1. **Optimize silence detection**: Configure the `silenceThreshold` parameter based on your application's use case. For conversational interfaces, use shorter thresholds (1-2 seconds) to maintain responsiveness, while dictation applications may benefit from longer thresholds (5-10 seconds) to accommodate natural pauses in speech. 1. **Provide visual feedback**: Use the various event listeners (`start`, `speechStart`, `speechEnd`, `end`) to update your UI and provide clear visual indicators of the recognition state. Show users when the system is listening, processing, or idle to create an intuitive voice interface that builds user confidence and understanding. ## Conclusion The Capacitor Speech Recognition Plugin from Capawesome provides a comprehensive solution for integrating voice recognition capabilities into Ionic applications. By offering a unified API across multiple platforms, it enables developers to create sophisticated voice-enabled applications without the complexity of platform-specific speech recognition implementations. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with the Capacitor Speech Recognition Plugin, feel free to reach out to the Capawesome team. We're here to help you implement powerful voice recognition features in your Ionic applications. # Exploring the Capacitor SQLite API Modern mobile applications often require robust local data storage solutions to manage user information, cache data, and enable offline functionality. With the [Capacitor SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin from Capawesome, developers can integrate powerful SQLite database management capabilities into their Ionic and Capacitor applications, providing a unified API that simplifies cross-platform database operations while offering advanced features like encryption, migrations, and transaction support across Android, iOS, and Web platforms. This guide walks through the **Capacitor SQLite plugin** API with practical examples and best practices for Ionic and Capacitor apps. ## Installation To install the Capacitor SQLite plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Usage Let's explore the key features of the Capacitor SQLite API and how to implement robust database operations in your Ionic applications. ### Opening a Database Before performing any database operations, you need to open a database connection. The Capacitor SQLite API provides the [`open(...)`](https://capawesome.io/plugins/sqlite/#open) method to establish a connection to your SQLite database: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const openDatabase = async () => { const { databaseId } = await Sqlite.open({ path: 'myapp.sqlite3', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT UNIQUE)', 'CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT, content TEXT, user_id INTEGER, FOREIGN KEY(user_id) REFERENCES users(id))' ] } ] }); console.log('Database opened with ID:', databaseId); return databaseId; }; ``` The `open(...)` method returns a unique database identifier that you'll use for subsequent database operations. The `upgradeStatements` parameter allows you to define database schema migrations that will be executed automatically when the database is first created or when upgrading to newer versions. ### Executing SQL Statements Once you have a database connection, you can execute SQL statements using the [`execute(...)`](https://capawesome.io/plugins/sqlite/#execute) method. This method is ideal for `INSERT`, `UPDATE`, and `DELETE` operations, as well as executing raw SQL commands: ``` const insertUser = async (databaseId: string, name: string, email: string) => { const { rowId } = await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: [name, email] }); console.log('Inserted user with ID:', rowId); }; const updateUser = async (databaseId: string, userId: number, name: string) => { const { changes } = await Sqlite.execute({ databaseId, statement: 'UPDATE users SET name = ? WHERE id = ?', values: [name, userId] }); console.log('Number of rows updated:', changes); }; const deleteUser = async (databaseId: string, userId: number) => { const { changes } = await Sqlite.execute({ databaseId, statement: 'DELETE FROM users WHERE id = ?', values: [userId] }); console.log('Number of rows deleted:', changes); }; ``` Always use parameterized queries with the `values` array to prevent SQL injection attacks. This approach ensures that user input is properly escaped and your database remains secure. ### Querying Data To retrieve data from your database, use the [`query(...)`](https://capawesome.io/plugins/sqlite/#query) method. This method executes SELECT statements and returns the result set: ``` const getAllUsers = async (databaseId: string) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT id, name, email FROM users ORDER BY name' }); console.log('Found', result.values?.length, 'users'); return result.values || []; }; const getUserById = async (databaseId: string, userId: number) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT id, name, email FROM users WHERE id = ?', values: [userId] }); return result.values?.[0] || null; }; const searchUsers = async (databaseId: string, searchTerm: string) => { const result = await Sqlite.query({ databaseId, statement: 'SELECT id, name, email FROM users WHERE name LIKE ? OR email LIKE ?', values: [`%${searchTerm}%`, `%${searchTerm}%`] }); return result.values || []; }; ``` The `query(...)` method returns an object containing a `values` array with the retrieved rows. Each row is represented as an object with column names as keys. ### Closing the Database When you're done with database operations, it's important to close the database connection to free up system resources. Use the [`close(...)`](https://capawesome.io/plugins/sqlite/#close) method: ``` const closeDatabase = async (databaseId: string) => { await Sqlite.close({ databaseId }); console.log('Database connection closed'); }; ``` Always close database connections when your application is shutting down or when you no longer need the database to ensure optimal resource management. ## Advanced The Capacitor SQLite API provides advanced features for more sophisticated database operations and security requirements. ### Encrypting the Database For applications handling sensitive data, the Capacitor SQLite plugin supports 256-bit AES encryption. You can encrypt your database by providing an encryption key when opening it: ``` const openEncryptedDatabase = async () => { const { databaseId } = await Sqlite.open({ path: 'secure.sqlite3', encryptionKey: 'your-secure-encryption-key', upgradeStatements: [ { version: 1, statements: [ 'CREATE TABLE sensitive_data (id INTEGER PRIMARY KEY, data TEXT)' ] } ] }); return databaseId; }; ``` The encryption key should be stored securely using platform-specific secure storage mechanisms. Never hardcode encryption keys in your application code. Consider using the [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin to store encryption keys safely. To change the encryption key for an existing database, you can use the [`changeEncryptionKey(...)`](https://capawesome.io/plugins/sqlite/#changeencryptionkey) method: ``` const changeEncryptionKey = async (databaseId: string, newKey: string) => { await Sqlite.changeEncryptionKey({ databaseId, encryptionKey: newKey }); console.log('Encryption key changed successfully'); }; ``` This method allows you to update the encryption key without needing to recreate the database, ensuring that your data remains secure while allowing for key rotation. ### Transactions For operations that require atomicity, use the [`beginTransaction(...)`](https://capawesome.io/plugins/sqlite/#begintransaction) and [`commitTransaction(...)`](https://capawesome.io/plugins/sqlite/#committransaction) methods to manage transactions effectively: ``` const performTransaction = async (databaseId: string) => { // Begin a transaction await Sqlite.beginTransaction({ databaseId }); // Perform multiple operations within the transaction await Sqlite.execute({ databaseId, statement: 'INSERT INTO users (name, email) VALUES (?, ?)', values: ['Alice Smith', 'alice@example.com'] }); await Sqlite.execute({ databaseId, statement: 'INSERT INTO posts (user_id, title, content) VALUES (?, ?, ?)', values: [1, 'My First Post', 'Hello world!'] }); // Commit the transaction await Sqlite.commitTransaction({ databaseId }); }; ``` This ensures that either all operations succeed or none are applied, maintaining data integrity. If any operation fails, you can roll back the transaction to revert all changes: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; const rollbackTransaction = async (databaseId: string) => { await Sqlite.rollbackTransaction({ databaseId }); }; ``` ### Vacuuming To optimize database performance and reclaim unused space, use the [`vacuum(...)`](https://capawesome.io/plugins/sqlite/#vacuum) method periodically: ``` const optimizeDatabase = async (databaseId: string) => { await Sqlite.vacuum({ databaseId }); console.log('Database optimized'); }; ``` Vacuuming rebuilds the database file, removing deleted data and defragmenting the database. This operation can improve query performance and reduce file size, especially after large amounts of data have been deleted. ## Limitations While the Capacitor SQLite plugin provides comprehensive cross-platform support, there are some platform-specific limitations to consider. ### iOS On iOS, database encryption is only available when using CocoaPods for dependency management. If you're using Swift Package Manager (SPM), encryption features will not be available. ### Web On the Web platform, BLOB data types are not fully supported due to browser restrictions. When targeting the Web platform, consider alternative approaches for handling binary data. ## Best Practices When working with the Capacitor SQLite API, consider these best practices to ensure optimal performance and security: 1. **Use parameterized queries**: Always use the `values` parameter in your SQL statements to prevent SQL injection attacks. Never concatenate user input directly into SQL strings, as this creates security vulnerabilities that can compromise your entire database. 1. **Implement proper error handling**: Wrap database operations in try-catch blocks and provide meaningful error messages to users. Database operations can fail due to various reasons such as disk space, permissions, or constraint violations, so robust error handling is essential. 1. **Manage database connections efficiently**: Open database connections when needed and close them when done to prevent resource leaks. Consider implementing a connection pool pattern for applications with frequent database access to optimize performance while managing resource usage effectively. ## Conclusion The [Capacitor SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin from Capawesome provides a comprehensive solution for integrating robust database management into Ionic applications. By offering a unified API across multiple platforms with advanced features like encryption, transactions, and migrations, it enables developers to create sophisticated data-driven applications without the complexity of platform-specific database implementations. For the full plugin documentation, see the [API Reference](https://capawesome.io/plugins/sqlite/#api). **Related tutorials:** - [Announcing the Capacitor SQLite Plugin](https://capawesome.io/blog/announcing-the-capacitor-sqlite-plugin/index.md) - [Alternative to the Capacitor Community SQLite plugin](https://capawesome.io/blog/alternative-to-capacitor-community-sqlite-plugin/index.md) - [Encrypting SQLite databases](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md) - [Key-Value Storage with the SQLite plugin](https://capawesome.io/blog/key-value-storage-made-simple-with-the-sqlite-plugin/index.md) - For type-safe ORMs, check [TypeORM](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-and-sqlite/index.md), [Drizzle ORM](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/index.md), [Kysely](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/index.md) If you have any questions or need assistance with the Capacitor SQLite Plugin, feel free to [reach out](mailto:support@capawesome.io) to the Capawesome team. We're here to help you implement robust database solutions in your Ionic applications. Subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) and join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) for questions and updates on Ionic and Capacitor. # How Live Updates Are Changing Mobile App Deployment If you've ever pushed a one-line bug fix and then waited days for app store approval, you know the frustration. Traditional mobile releases are slow, manual, and often out of sync with how fast your team actually ships code. Live updates — also known as Over-The-Air (OTA) updates — are changing that by letting Capacitor teams push web layer changes directly to users' devices, no app store submission required. In this post, we'll look at how live updates are reshaping mobile app deployment and why more teams are making the switch. ## The App Store Bottleneck Every mobile developer has been there. You spot a bug in production, fix it in minutes, and then... wait. The app store review process can take anywhere from a few hours to several days. Meanwhile, your users are stuck with a broken experience. This bottleneck creates real problems: - **Slow feedback loops** — You can't iterate quickly when every change takes days to reach users. - **Accumulated risk** — Teams batch changes into larger releases to avoid frequent submissions, which increases the chance of something going wrong. - **Wasted resources** — Managing app store submissions, screenshots, changelogs, and compliance metadata eats into development time. - **User frustration** — Users who encounter bugs have no choice but to wait for the next store update — and even then, they have to manually install it. For teams building with Capacitor, there's a better way. ## What Are Live Updates? A Capacitor app consists of two layers: a **native layer** (Java/Kotlin on Android, Swift on iOS) and a **web layer** (HTML, CSS, and JavaScript loaded in a WebView). Live updates work by replacing the web layer at runtime with a newer version downloaded from a server. Since the web assets aren't compiled into the app binary, they can be swapped out without going through the app store. This means any change to your UI, business logic, styles, or routing can be delivered directly to users — often within minutes of merging a pull request. For a deeper look at the technical details, check out [How Live Updates for Capacitor Work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/index.md). ## How Live Updates Transform Your Deployment Workflow ### Ship Bug Fixes in Minutes When a critical bug hits production, the last thing you want is a multi-day app store review standing between your fix and your users. With live updates, you can build your web assets, publish a new bundle, and have the fix running on devices within minutes. This changes the way teams think about production issues. Instead of hotfix branches, expedited reviews, and crossed fingers, you just push the fix and move on. ### Roll Back Instantly When Things Go Wrong Every deployment carries risk. With traditional app store releases, rolling back a bad update means submitting yet another version and waiting for approval — again. Live updates flip this around. If a new bundle causes problems, you can roll back to the previous working version immediately. Tools like [Capawesome Cloud](https://cloud.capawesome.io/) make this even safer with automatic rollback detection: if your app fails to start after an update, the plugin reverts to the last known good bundle without any manual intervention. You can also block bundles that caused a rollback so they aren't downloaded again. See the [Rollbacks](https://capawesome.io/cloud/live-updates/advanced/rollbacks/index.md) documentation for details. ### Test with Real Users via Gradual Rollouts Shipping an update to 100% of your users on day one is a gamble. Gradual rollouts let you release a new bundle to a small percentage of users first — say 5% or 10% — and monitor for issues before rolling it out to everyone. This gives you a built-in safety net for every release and opens the door to A/B testing different versions of your app with real user data. Learn more about how to set this up in the [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md) documentation. ### Deploy to iOS and Android Simultaneously With traditional releases, you're managing two separate submission pipelines — one for Apple, one for Google — each with their own timelines and quirks. Live updates simplify this to a single publish step. One bundle, both platforms, same moment. This is especially valuable when you need to coordinate a release across platforms or push a time-sensitive fix that can't wait for both stores to approve independently. ### Automate with CI/CD Live updates fit naturally into modern CI/CD pipelines. You can configure your pipeline to automatically build your web assets and publish a new bundle to [Capawesome Cloud](https://cloud.capawesome.io/) on every merge to your main branch. This turns your deployment process into something fully automated: merge a PR, and the update is on its way to users. No manual steps, no app store forms, no waiting. Capawesome Cloud provides integration guides for [GitHub Actions](https://capawesome.io/cloud/live-updates/integrations/github-actions/index.md), [GitLab CI](https://capawesome.io/cloud/live-updates/integrations/gitlab-ci/index.md), [Bitbucket Pipelines](https://capawesome.io/cloud/live-updates/integrations/bitbucket-pipelines/index.md), and [Azure DevOps](https://capawesome.io/cloud/live-updates/integrations/azure-devops/index.md). ### Reduce App Store Submission Overhead App store submissions come with overhead that adds up fast — managing metadata, preparing screenshots, writing changelogs, and dealing with compliance requirements. Every submission you can skip is time your team gets back. With live updates handling your web layer changes, you only need to go through the app store when you make native changes like adding a new plugin or updating native dependencies. For many teams, this cuts the number of store submissions significantly. ## Getting Started If you're building with Capacitor and want to move faster, [Capawesome Cloud](https://cloud.capawesome.io/) provides everything you need for live updates — bundle management, channels, rollouts, rollbacks, code signing, and CI/CD integration. It's a strong [Ionic Appflow alternative](https://capawesome.io/cloud/index.md) with a dedicated focus on the Capacitor ecosystem. The [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin can be set up in minutes. Head over to the [Getting Started guide](https://capawesome.io/cloud/live-updates/setup/index.md) for step-by-step instructions. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Live updates are shifting how mobile teams think about deployment. Instead of batching changes into big releases and waiting for store approval, you can ship continuously, roll back safely, and iterate faster than ever. For Capacitor teams, this means spending less time managing releases and more time building features your users actually want. If you want to go deeper, check out [Exploring the Capacitor Live Update API](https://capawesome.io/blog/exploring-the-capacitor-live-update-api/index.md) for a hands-on walkthrough of the plugin's capabilities. Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) if you have questions or want to connect with the community, and subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # How Live Updates for Capacitor work One of the biggest advantages of Capacitor over other runtimes is the ability to deliver updates in real-time, without having to resubmit the app to the app stores. This feature is referred to as Live Updates or Over-the-Air (OTA) Updates. There already exist several plugins for this, such as our [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin, which can be integrated into your app within minutes. But how do these plugins work in detail? That's exactly what we'll look at in this blog post. ## Concept Live Updates are a powerful feature that allows you to deliver minor bug fixes and updates to your Capacitor app in real time. To understand exactly how Live Updates work, it is important to first understand the different layers of a Capacitor app and how they interact with each other. Capacitor App Layers As you can see from the image above, a Capacitor app consists essentially of a web layer and a native layer. The web layer consists of the HTML, CSS, and JS files that are loaded into the web view. The native layer consists of the native code, such as Java or Swift. Capacitor now allows you to replace the web layer of the app at runtime since those files are not compiled into the app binary. For this purpose, the new HTML, CSS, and JS files only need to be downloaded from a server and stored at a specific location in the app's file system. Let's take a closer look into the mechanics of this process in the following sections. ## Implementation Capacitor provides various interfaces to instruct the WebView which files should be loaded. Here we distinguish between the **current server path** and the **next server path**. The **current server path** is the path to the files that are currently loaded in the WebView. Changing the current server path results in a refresh of the WebView. The files are immediately loaded from the new path and displayed in the WebView. The **next server path**, on the other hand, is the path to the files that should be loaded at the next restart of the app. Changing the next server path does not result in a refresh of the WebView. ### Android First, let's take a look at the implementation for Android. Under Android, the next server path is stored in the [SharedPreferences](https://developer.android.com/reference/android/content/SharedPreferences). To change the server path, the [`serverBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java#L15) preference in the [`CapWebViewSettings`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/plugin/WebView.java#L14) preferences file of the Capacitor WebView needs to be updated: ``` private void setNextCapacitorServerPath(String path) { SharedPreferences.Editor webViewSettingsEditor = getContext().getSharedPreferences("CapWebViewSettings", Activity.MODE_PRIVATE).edit(); webViewSettingsEditor.putString("serverBasePath", path); webViewSettingsEditor.commit(); } ``` The current server path, on the other hand, is set directly via the Capacitor Android Bridge. For this, the [`setServerBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/android/capacitor/src/main/java/com/getcapacitor/Bridge.java#L1374) method must be called, and the WebView must be reloaded: ``` private void setCurrentCapacitorServerPath(String path) { getBridge().setServerBasePath(path); getBridge().reload(); } ``` To reset the server path to the default value, [`public`](https://github.com/ionic-team/capacitor/blob/ceee68a2db363e9d9a638aa4ed8569fd82d1013a/android/capacitor/src/main/java/com/getcapacitor/Bridge.java#L89) must be passed as the path. This is especially useful when you want to implement a fallback mechanism in case the new files cannot be loaded. ### iOS Under iOS, the next server path is stored in the [UserDefaults](https://developer.apple.com/documentation/foundation/userdefaults). For this purpose, Capacitor provides the [`KeyValueStore`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/KeyValueStore.swift) class, which is used to store key-value pairs in the UserDefaults. To change the server path, the value for the key [`serverBasePath`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/Plugins/WebView.swift#L32) must now be updated: ``` private func setNextCapacitorServerPath(path: String) { KeyValueStore.standard["serverBasePath"] = path } ``` It should be noted that only the last path component of the path is relevant. This is because Capacitor under iOS only supports server paths that are located in the [`/Library/NoCloud/ionic_built_snapshots`](https://github.com/ionic-team/capacitor/blob/3c7b3333762ce8cf7b7c279437dada1e2de7fbea/ios/Capacitor/Capacitor/CAPBridgeViewController.swift#L84:L87) directory. A valid path would be, for example, `/Library/NoCloud/ionic_built_snapshots/my-bundle`. The current server path is again set via the Capacitor iOS Bridge. To do this, you only need to call the [`setServerBasePath`](https://github.com/ionic-team/capacitor/blob/e0f299daaa112fa3bcea81ae539983756f1f7304/ios/Capacitor/Capacitor/CAPBridgeViewController.swift#L267) method: ``` private func setCurrentCapacitorServerPath(path: String) { self.bridge.viewController.setServerBasePath(path: path) } ``` Also under iOS, the server path can be reset to the default value by passing [`public`](https://github.com/ionic-team/capacitor/blob/7113a198bb8165f69e046fbb0db5c938402367bf/ios/Capacitor/Capacitor/CAPInstanceDescriptor.m#L17) as the path. ## Security An important aspect of Live Updates is security. After all, you don't want your app to be compromised by an attacker. Therefore, it is crucial that the downloaded files are checked for their **authenticity** and **integrity** - also known as **code signing**. Authenticity means that the files have not been tampered with and come from a trusted source. Integrity means that the files have not been corrupted during the download process. To ensure the authenticity and integrity of the downloaded files, these files must be digitally signed. For this, a private key is used to sign the files, and a public key is used to verify the signature. The private key must be kept secret and must not be shared with anyone. The public key, on the other hand, is stored in the app and used to verify the signature. This is exactly the security mechanism we use in [Capawesome Cloud](https://capawesome.io/cloud/live-updates/index.md) to securely deliver Live Updates. Therefore not even we are capable of manipulating our customers' files, as we do not have access to the private keys. Feel free to check out our documentation on [Code Signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md). ## Compliance Now that we understand how Live Updates for Capacitor work, the question arises whether they are compliant with the guidelines of the app stores. And the answer is **yes**! ### Apple App Store Live Updates are fully compliant with the Apple App Store policies. The [Apple Developer Program License Agreement](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) states that interpreted code may be downloaded to an application as long as it does not change the primary purpose of the application and does not bypass signing, sandbox, or other security features of the OS: > Interpreted code may be downloaded to an Application but only so long as such code: (a) does not change the primary purpose of the Application by providing features or functionality that are inconsistent with the intended and advertised purpose of the Application as submitted to the App Store, (b) does not create a store or storefront for other code or applications, and (c) does not bypass signing, sandbox, or other security features of the OS. So as long as you do not change the primary purpose of your app via Live Updates, they are fully compliant with the Apple App Store policies since they only update the web layer of your app. ### Google Play Store Live Updates are also compliant with the Google Play Policies. The third paragraph of [Device and Network Abuse](https://support.google.com/googleplay/android-developer/answer/9888379/) states that an app distributed via Google Play may not modify, replace, or update itself using any method other than Google Play's update mechanism. However, the same paragraph also states that this restriction does not apply to JavaScript running in a webview or browser: > This restriction does not apply to code that runs in a virtual machine or an interpreter where either provides indirect access to Android APIs (such as JavaScript in a webview or browser). As Live Updates can only update the web layer of your app, they are fully compliant with the Google Play Policies. ## Conclusion Live Updates are a powerful feature that allows you to deliver updates to your Capacitor app in real time. In this blog post, we have looked at how Live Updates work in detail and how they can be implemented in Android and iOS from a plugin developer's perspective. We have also discussed the security and compliance aspects of Live Updates and shown that they are fully compliant with the guidelines of the app stores. Check out our [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin to get started with Live Updates in your app today. If you want to build a complete update strategy that combines live updates with native app store updates, take a look at [The App Update Delivery Guide for Capacitor](https://capawesome.io/blog/the-app-update-delivery-guide-for-capacitor/index.md). If you're wondering how Live Updates compare to using `server.url` in production, read [The Right Way to Update Your Capacitor App Remotely](https://capawesome.io/blog/the-right-way-to-update-your-capacitor-app-remotely/index.md). # How to Build a Heart Rate Monitor with Capacitor Capacitor makes building a cross-platform app with one codebase easier than ever before. Today we will use the Ionic Framework and Capacitor to create a simple heart rate monitor app for Android and iOS in just 15 minutes. For this, we will use the [Capacitor Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin to connect to a heart rate sensor. The heart rate sensor we use in this tutorial is the [Polar H9 Heart Rate Sensor](https://www.polar.com/en/sensors/h9-heart-rate-sensor), but any other Bluetooth heart rate sensor should work as well. You can find the complete app code referenced in this guide on [GitHub](https://github.com/capawesome-team/capacitor-heart-rate-monitor-app). Capacitor Heart Rate Monitor App ## Basics ### Bluetooth Low Energy First, let's talk about Bluetooth Low Energy (BLE) and how it works. BLE is a wireless communication technology that is designed to reduce power consumption while maintaining a similar communication range to classic Bluetooth. BLE is used in many devices, such as heart rate sensors, fitness trackers, smartwatches, and more. #### Centrals and Peripherals BLE devices are divided into two categories: peripherals and centrals. Peripherals are devices that provide data, such as heart rate sensors, while centrals are devices that consume data, such as smartphones. In our case, the heart rate sensor is the peripheral, and the smartphone is the central. #### Services and Characteristics BLE communication is based on the concept of services and characteristics. A service is a collection of characteristics, and a characteristic is a piece of data that can be read, written, or notified. For example, a heart rate sensor has a service that contains a characteristic for the heart rate measurement. Each service and characteristic is represented by a unique UUID (Universally Unique Identifier). The UUIDs are standardized and defined by the Bluetooth SIG (Special Interest Group). ## Prerequisites Before we start, download and install the following tools to ensure an optimal developer experience: - [Node.js](https://nodejs.org/en/download) to install the required dependencies - A code editor for... writing code! **Tip**: Visual Studio Code supports the new [Ionic VS Code Extension](https://ionicframework.com/docs/intro/vscode-extension) - [Android Studio](https://developer.android.com/studio) to build the Android app - [Xcode](https://apps.apple.com/de/app/xcode/id497799835) to build the iOS app (only available on macOS) ## Create a new app To create a new project, we simply use the [Ionic CLI](https://ionicframework.com/docs/cli). For this, first install the CLI globally: ``` npm install -g @ionic/cli ``` Then you can create a new project with the `ionic start` command: ``` npx ionic start heart-rate-monitor-app blank --type=angular --capacitor ``` In this case, the app is called `heart-rate-monitor-app`, the starter template is `blank`, and the project type for the purposes of this guide is Angular. You can also choose Vue or React, for example. Additionally, we enable the Capacitor integration with `--capacitor`. Once everything is ready, you should see this output: ``` Your Ionic app is ready! Follow these next steps: - Go to your new project: cd .\heart-rate-monitor-app - Run ionic serve within the app directory to see your app in the browser - Run ionic capacitor add to add a native iOS or Android project using Capacitor - Generate your app icon and splash screens using cordova-res --skip-config --copy - Explore the Ionic docs for components, tutorials, and more: https://ion.link/docs - Building an enterprise app? Ionic has Enterprise Support and Features: https://ion.link/enterprise-edition ``` ### Add the Android platform If you want to build the app for Android, you need to add the Android platform. For this, first install the `@capacitor/android` package: ``` npm install @capacitor/android ``` Then add the Android platform using the following command: ``` npx cap add android ``` ### Add the iOS platform If you want to build the app for iOS, you need to add the iOS platform. For this, first install the `@capacitor/ios` package: ``` npm install @capacitor/ios ``` Then add the iOS platform using the following command: ``` npx cap add ios ``` ## Implement the Heart Rate Monitor ### Install the Bluetooth Low Energy plugin In order to connect to the heart rate sensor, we need to install the [Capacitor Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin. Please note that the plugin is currently only available to [Insiders](https://capawesome.io/insiders/index.md). See [Getting started with Insiders](https://capawesome.io/insiders/getting-started/?plugin=capacitor-bluetooth-low-energy) and follow the instructions to install the plugin. On **Android**, this plugin requires the following permissions be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` You can read more about Bluetooth permissions in the [Android documentation](https://developer.android.com/develop/connectivity/bluetooth/bt-permissions). You also need to add the following service **inside** the `application` tag in your `AndroidManifest.xml` (usually `android/app/src/main/AndroidManifest.xml`): ``` ``` On **iOS**, add the `NSBluetoothPeripheralUsageDescription` and `NSBluetoothAlwaysUsageDescription` keys to the `Info.plist` file (usually `ios/App/App/Info.plist`), which tells the user why the app needs access to Bluetooth peripherals: ``` NSBluetoothAlwaysUsageDescription The app needs access to Bluetooth peripherals to communicate with Bluetooth devices. ``` The plugin is now ready to use. ### Build the page Let's start with the actual implementation. Since the app already has a blank page called `home`, we can use this page to display the heart rate. The page consists of a title and the current heart rate. For reason of simplicity, there will be no interactive elements. The app will try to establish a connection to the heart rate sensor via BLE **directly after the page is loaded** and display the current heart rate. The following code goes to your `src/app/home/home.page.ts` file: ``` import { Component, OnInit, signal } from '@angular/core'; import { Capacitor } from '@capacitor/core'; import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; import { IonHeader, IonToolbar, IonTitle, IonContent, IonButton } from '@ionic/angular/standalone'; @Component({ selector: 'app-home', templateUrl: 'home.page.html', styleUrls: ['home.page.scss'], standalone: true, imports: [IonHeader, IonToolbar, IonTitle, IonContent, IonButton], }) export class HomePage implements OnInit { public heartRate = signal(undefined); constructor() {} public async ngOnInit() { // 1. Initialize the Bluetooth Low Energy plugin if (Capacitor.getPlatform() === 'ios') { await BluetoothLowEnergy.initialize(); } else { await BluetoothLowEnergy.requestPermissions(); } // 2. Add a listener for the `deviceScanned` event await BluetoothLowEnergy.addListener('deviceScanned', async (event) => { if (event.name?.startsWith('Polar H9')) { // 4. Stop scanning for devices void BluetoothLowEnergy.stopScan(); // 5. Connect to the device await BluetoothLowEnergy.connect({ deviceId: event.id, }); // 6. Discover services await BluetoothLowEnergy.discoverServices({ deviceId: event.id, }); // 7. Add a listener for the `characteristicChanged` event await BluetoothLowEnergy.addListener('characteristicChanged', (event) => { let byteArray = new Uint8Array(event.value); let firstBitValue = byteArray[0] & 0x01; if (firstBitValue === 0) { this.heartRate.set(byteArray[1]); } else { this.heartRate.set((byteArray[1] << 8) | byteArray[2]); } }); // 8. Start notifications for the Heart Rate Measurement characteristic await BluetoothLowEnergy.startCharacteristicNotifications({ deviceId: event.id, serviceId: '0000180D-0000-1000-8000-00805F9B34FB', characteristicId: '00002A37-0000-1000-8000-00805F9B34FB', }); } }); // 3. Start scanning for devices await BluetoothLowEnergy.startScan(); } } ``` Let's break down the code: 1. **Initialize the Bluetooth Low Energy plugin**: First, we need to initialize the plugin. For iOS, we need to call [`initialize()`](https://capawesome.io/plugins/bluetooth-low-energy/#initialize), and for Android, we need to request the necessary permissions with `requestPermissions()`. 1. **Add the `deviceScanned` listener**: We need to add a listener for the [`deviceScanned`](https://capawesome.io/plugins/bluetooth-low-energy/#addlistenerdevicescanned) event before we start scanning for devices. This listener will be called whenever a BLE device is found. 1. **Start scanning for devices**: To start scanning for devices, we call [`startScan()`](https://capawesome.io/plugins/bluetooth-low-energy/#startscan). As soon as a device is found, the [`deviceScanned`](https://capawesome.io/plugins/bluetooth-low-energy/#addlistenerdevicescanned) event is emitted. 1. **Stop scanning for devices**: After we found the heart rate sensor, we stop scanning for devices. This is important to save battery life. 1. **Connect to the device**: To connect to the heart rate sensor, we call [`connect()`](https://capawesome.io/plugins/bluetooth-low-energy/#connect) with the device ID. 1. **Discover services**: After connecting to the device, we need to discover the services. This is necessary to find the service that contains the heart rate measurement characteristic. For this, we call [`discoverServices()`](https://capawesome.io/plugins/bluetooth-low-energy/#discoverservices). 1. **Add the `characteristicChanged` listener**: We need to add a listener for the [`characteristicChanged`](https://capawesome.io/plugins/bluetooth-low-energy/#addlistenercharacteristicchanged) event to receive the heart rate measurements. This event is emitted whenever the heart rate measurement characteristic changes. The value of the characteristic is a byte array that contains the heart rate measurement. Depending on the first bit of the first byte, the heart rate is either a single byte or two bytes. If the heart rate is a single byte, the heart rate is the second byte. If the heart rate is two bytes, the heart rate is the second byte shifted left by 8 bits and combined with the third byte. 1. **Start notifications for the Heart Rate Measurement characteristic**: To receive the heart rate measurements, we need to start notifications for the heart rate measurement characteristic. For this, we call [`startCharacteristicNotifications()`](https://capawesome.io/plugins/bluetooth-low-energy/#startcharacteristicnotifications). The service ID and characteristic ID are standardized and defined by the Bluetooth SIG. The service ID for the heart rate service is `0000180D-0000-1000-8000-00805F9B34FB`, and the characteristic ID for the heart rate measurement is `00002A37-0000-1000-8000-00805F9B34FB`. Next, we need to create the HTML and CSS for the page. As mentioned earlier, we will keep it simple and only display a title and the current heart rate. The following code goes to your `src/app/home/home.page.html` file: ``` Heart Rate Monitor
Current heart rate
{{ heartRate() ?? '--' }}
``` The following code goes to your `src/app/home/home.page.scss` file: ``` ion-content { text-align: center; } .title { font-size: 32px; margin-top: 36px; } .body { font-size: 64px; font-weight: bold; margin-top: 48px; } ``` That's it! Now everything is set up to build and run the app. 🎉 ## Run the app To run your app on Android, use the following command: ``` npx ionic cap run android ``` To run your app on iOS, use the following command: ``` npx ionic cap run ios ``` ## Conclusion In this guide, we learned how to build a simple heart rate monitor app with Capacitor. We used the [Capacitor Bluetooth Low Energy](https://capawesome.io/plugins/bluetooth-low-energy/index.md) plugin to connect to a heart rate sensor and receive the heart rate measurements. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-plugins) in the GitHub repository. Make sure you follow Capawesome on [X](https://twitter.com/capawesomeio) so you don't miss any future updates. # How to Build an Ionic Barcode Scanner with Capacitor Capacitor makes building a cross-platform app with one codebase easier than ever before. In combination with the Ionic Framework, we also have a modern open source mobile UI toolkit. We will use these technologies to create a complete barcode scanner app for Android and iOS in just 15 minutes. Highlights include: - One Angular codebase that runs on Android and iOS using Capacitor. - Barcode Scanning functionality powered by ML Kit, Google’s machine learning SDK for Android and iOS. Find the complete app code referenced in this guide [on GitHub](https://github.com/robingenz/ionic-capacitor-barcode-scanner). Capacitor Barcode Scanner Demo ## Download Required Tools Download and install the following tools to ensure an optimal developer experience: - [Node.js](https://nodejs.org/en/download) to install the required dependencies - A code editor for... writing code! **Tip**: Visual Studio Code supports the new [Ionic VS Code Extension](https://ionicframework.com/docs/intro/vscode-extension) - [Android Studio](https://developer.android.com/studio) to build the Android app - [Xcode](https://apps.apple.com/de/app/xcode/id497799835) to build the iOS app (only available on macOS) ## Create a new App To create a new project, we simply use the Ionic CLI. For this, first install the CLI globally: ``` npm i -g @ionic/cli ``` Then you can create a new project with the `ionic start` command: ``` npx ionic start barcode-scanner blank --type=angular --capacitor ``` In this case, the app is called `barcode-scanner`, the starter template is `blank` and the project type for the purposes of this guide is Angular. You can also choose Vue or React, for example. Additionally, we enable the Capacitor integration with `--capacitor`. Once everything is ready, you should see this output: ``` Your Ionic app is ready! Follow these next steps: - Go to your new project: cd .\barcode-scanner - Run ionic serve within the app directory to see your app in the browser - Run ionic capacitor add to add a native iOS or Android project using Capacitor - Generate your app icon and splash screens using cordova-res --skip-config --copy - Explore the Ionic docs for components, tutorials, and more: https://ion.link/docs - Building an enterprise app? Ionic has Enterprise Support and Features: https://ion.link/enterprise-edition ``` ### Add the Android Platform Now let's add the Android platform. For this, first install the `@capacitor/android` package: ``` npm install @capacitor/android ``` After that you add the platform: ``` npx cap add android ``` ### Add the iOS Platform Install the `@capacitor/ios` package: ``` npm install @capacitor/ios ``` After that you add the platform: ``` npx cap add ios ``` ## Add the Barcode Scanner ### Install the Plugin To use the ML Kit Barcode Scanning SDK in Capacitor, we need to install the [Capacitor ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin: ``` npm install @capacitor-mlkit/barcode-scanning npx ionic cap sync ``` On Android, the SDKs also require the following permissions in the `AndroidManifest.xml` before or after the `application` tag: android/app/src/main/AndroidManifest.xml ``` ``` You also need to add the following meta data in the `application` tag in your `AndroidManifest.xml`: android/app/src/main/AndroidManifest.xml ``` ``` On **iOS**, add the `NSCameraUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why the app needs to use the camera: ios/App/App/Info.plist ``` NSCameraUsageDescription The app enables the scanning of various barcodes. ``` The plugin is now ready to use. ### Build the UI Scanning a barcode is as simple as it gets: You just have to call the [`scan(...)`](https://capawesome.io/plugins/mlkit/barcode-scanning/#scan) method of the plugin and receive the scanned barcode as a result. To request the necessary permissions and to show the user a dialog in case of missing permissions, we will also use the [`requestPermissions()`](https://capawesome.io/plugins/mlkit/barcode-scanning/#requestpermissions) method. The following code goes to your `src/app/home/home.page.ts`: ``` import { Component, OnInit } from "@angular/core"; import { Barcode, BarcodeScanner } from "@capacitor-mlkit/barcode-scanning"; import { AlertController } from "@ionic/angular"; @Component({ selector: "app-home", templateUrl: "home.page.html", styleUrls: ["home.page.scss"], }) export class HomePage implements OnInit { isSupported = false; barcodes: Barcode[] = []; constructor(private alertController: AlertController) {} ngOnInit() { BarcodeScanner.isSupported().then((result) => { this.isSupported = result.supported; }); } async scan(): Promise { const granted = await this.requestPermissions(); if (!granted) { this.presentAlert(); return; } const { barcodes } = await BarcodeScanner.scan(); this.barcodes.push(...barcodes); } async requestPermissions(): Promise { const { camera } = await BarcodeScanner.requestPermissions(); return camera === "granted" || camera === "limited"; } async presentAlert(): Promise { const alert = await this.alertController.create({ header: "Permission denied", message: "Please grant camera permission to use the barcode scanner.", buttons: ["OK"], }); await alert.present(); } } ``` To make the scanning process even faster and to reduce the error rate even further, you could filter on the formats you are looking for (e.g. QR codes[1](#fn:1)) using the `formats` option. However, we leave this up to you. Last but not least, the only thing missing is the template. To keep the app simple, we just list all scanned barcodes with [`ion-list`](https://ionicframework.com/docs/api/list). The scanning process is started via a floating action button in the bottom right corner. For this, change your `src/app/home/home.page.html` to: ``` Barcode Scanner {{ barcode.format }} ``` Now everything is ready for the first launch! 🎉 ## Run the App Run the app and scan your first barcode or QR code[1](#fn:1): ``` # Run the Android platform npx ionic cap run android # Run the iOS platform npx ionic cap run ios ``` That was easy, wasn't it? ## Conclusion Implementing a barcode scanner across multiple platforms can be challenging. However, the Capacitor ML Kit barcode scanning plugin does most of the work for you and the performance of the ML Kit SDK is quite impressive. Only the Web platform is not supported by Google's machine learning SDK. If you also want to support the Web platform, you can just combine this plugin with other libraries like [zxing-js/library](https://github.com/zxing-js/library) or the [Barcode Detection API](https://developer.mozilla.org/en-US/docs/Web/API/Barcode_Detection_API) (still experimental). Be sure to check out the [API Reference](https://github.com/capawesome-team/capacitor-mlkit/tree/main/packages/barcode-scanning#api) to see what else you can do with this plugin. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-mlkit/discussions/new/choose) in the GitHub repository. Make sure you follow [Capawesome](https://twitter.com/capawesomeio) on X so you don't miss any future updates. ______________________________________________________________________ ______________________________________________________________________ 1. `QR Code` is a registered trademark of DENSO WAVE INCORPORATED. [↩](#fnref:1 "Jump back to footnote 1 in the text")[↩](#fnref2:1 "Jump back to footnote 1 in the text") # How to Build and Deploy iOS Apps Without Owning a Mac If you're building a Capacitor app on Windows or Linux, you've probably hit the same wall every cross-platform developer eventually runs into: shipping to the App Store requires macOS. Xcode, `codesign`, and the iOS simulator all live exclusively on Apple hardware, and Apple isn't planning to change that any time soon. The good news is that you don't actually need a Mac on your desk to build, sign, and ship an iOS app. ## Why iOS Development Requires a Mac Apple's entire iOS toolchain — Xcode, the iOS Simulator, `xcodebuild`, `codesign`, and the provisioning system — only runs on macOS. There's no official Linux or Windows version, and there's no supported way to produce a real `.ipa` file outside of an Apple-controlled environment. For a Capacitor or Ionic developer who lives in VS Code on Windows or Ubuntu, that's a hard blocker the moment you want to ship to TestFlight or the App Store. Fortunately, "you need macOS" doesn't have to mean "you need to buy a Mac." There are three realistic ways to get access to a macOS build environment, and they trade off cost, flexibility, and setup effort in very different ways. ## Three Ways to Build iOS Apps Without Owning a Mac ### Option 1: Buy a Mac The most straightforward route is also the most expensive. A Mac mini or MacBook Air gives you a permanent local build machine, full control over your Xcode versions, and the ability to run the iOS Simulator for debugging. The catch is the upfront cost (often well over $1,000 once you factor in storage and RAM), the ongoing maintenance, and the fact that the machine sits idle most of the time. For a solo developer who only occasionally ships an iOS build, that's a lot of hardware to babysit. ### Option 2: Rent a Mac in the Cloud Services like MacStadium and MacinCloud rent out real Mac hardware by the hour, day, or month. You SSH or VNC into a remote macOS instance and use it just like a local Mac. This avoids the upfront hardware cost and lets you scale up to faster machines when you need them — but you're still responsible for everything that happens inside the box: installing Xcode, managing certificates, writing build scripts, and keeping the environment in sync with your project. It's a Mac you don't own, but it's still a Mac you have to manage. ### Option 3: Use a Mobile CI/CD Service The third option is to skip the "raw Mac" entirely and use a purpose-built mobile CI/CD service such as [Capawesome Cloud](https://cloud.capawesome.io/), Codemagic, or Bitrise. Instead of renting a machine, you describe your build in a config file (or click a button in a web UI), and the service spins up a clean, pre-configured macOS environment, runs the build, signs it, and hands you back an `.ipa`. You never have to touch Xcode yourself. There's one trade-off worth knowing about: a CI/CD service is great for producing builds, but it can't replace a local Mac for **interactive debugging**. If you need to step through code in the iOS Simulator or attach a debugger to a physical device, you'll still want a real Mac (owned or rented) for that part of the workflow. For everything else — TestFlight builds, App Store submissions, ad-hoc test builds for your QA team — a CI/CD service is usually the fastest and cheapest path. ## Building iOS Apps with Capawesome Cloud [Capawesome Cloud](https://cloud.capawesome.io/) is a mobile CI/CD service built specifically for Capacitor and Ionic apps. Its [Native Builds](https://cloud.capawesome.io/native-builds/) feature spins up isolated macOS build environments on M4 Pro hardware, handles Xcode, signing, and provisioning for you, and produces ready-to-install iOS artifacts in around two and a half minutes on average. It supports every iOS build type — Simulator, Development, Ad Hoc, App Store, and Enterprise — and integrates with GitHub, GitLab, Bitbucket, and Azure DevOps, including self-hosted instances. You don't need Xcode on your machine. You don't need a Mac on your network. You just need a way to tell Capawesome Cloud to start a build — and there are two of those. ## Two Ways to Trigger an iOS Build ### From the Console The Console is the web-based interface for managing your apps and builds. It's the easiest way to get started, especially if you want to browse logs, monitor progress, or trigger builds from any device — including your phone. The prerequisite is that the Git repository hosting your iOS project is connected to your app in Capawesome Cloud. Once that's done: 1. Open your app in the [Capawesome Cloud Console](https://console.cloud.capawesome.io). 1. Go to the **Builds** page and click **Build from Git**. 1. Pick a Git reference (branch, tag, or commit), select **iOS** as the platform, and choose a build type. 1. Select a signing certificate with a matching provisioning profile. 1. Click **Build**. Trigger an iOS build directly from the Console That's it. You can close the tab and come back later, or watch the live logs stream in. When the build finishes, you'll find the `.ipa` ready for download — or automatically uploaded to TestFlight, if you've set up an [App Store Publishing destination](https://capawesome.io/cloud/app-store-publishing/index.md). ### From the CLI If you'd rather stay in your terminal — or you want to wire builds into your own CI/CD pipeline — the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) can trigger the same builds with a single command. And unlike the Console, the CLI doesn't require a connected Git repository: you can upload a local project directly. Build from a Git reference (when a repository is connected): ``` npx @capawesome/cli apps:builds:create \ --app-id \ --platform ios \ --git-ref ``` Or build directly from a local directory, no Git connection required: ``` npx @capawesome/cli apps:builds:create \ --app-id \ --platform ios \ --path ``` The `--path` option is also handy when you *do* have a repository connected but want to test a quick local change without committing and pushing it first. You get the same cloud-built `.ipa`, just based on your working copy. To grab the build artifact as soon as the build finishes, add the `--ipa` flag (or `--apk` for Android builds). The CLI will download it directly to your machine when the job completes: ``` npx @capawesome/cli apps:builds:create \ --app-id \ --platform ios \ --git-ref main \ --ipa ``` That single command takes a Capacitor project on a Windows or Linux machine, builds it on a real Mac in the cloud, and drops the signed `.ipa` next to your project — no Xcode required. ## See It in Action Here's a short walkthrough of what triggering an iOS build in Capawesome Cloud actually looks like: ## Try It Yourself If you've been putting off shipping your Capacitor app because you don't have a Mac sitting around, this is the easiest way to unblock yourself. Sign up for free, connect your repo (or just upload a folder), and have a signed iOS build in your hands in a few minutes. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Final Thoughts You don't need to buy a Mac, and you don't need to learn how to administer one in the cloud, just to ship a Capacitor app to the App Store. A purpose-built mobile CI/CD service like Capawesome Cloud handles the macOS side for you, leaves you to write your app on whatever OS you prefer, and works equally well from a polished web Console or a single CLI command. The only thing it can't do is replace your local simulator — for everything else, it's hard to beat. If you want to go deeper on what Capawesome Cloud Native Builds can do under the hood, take a look at [Announcing Capawesome Cloud Native Builds](https://capawesome.io/blog/announcing-capawesome-cloud-native-builds/index.md). And if you have questions or want to share what you're building, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) or [subscribe to the Capawesome newsletter](https://cloud.capawesome.io/newsletter) to keep up with what's new. # How to Distribute iOS and Android Apps to Testers Getting a test build onto a tester's phone should be simple — but if you've ever dealt with TestFlight review delays, Firebase profile installations, or setting up OTA manifest files by hand, you know it's anything but. In this guide, we'll walk through how to distribute iOS and Android builds to testers and stakeholders with just a link or QR code, no app store submission required. ## The Problem with Test Build Distribution Distributing test builds is a common task for any mobile development team, yet the process is often more complex than it needs to be. **TestFlight** is Apple's official beta testing tool, but external testing requires a Beta App Review that can take anywhere from a few hours to several days. Internal testing is limited to 100 users who need App Store Connect roles, and every tester must install the TestFlight app first. **Firebase App Distribution** supports both platforms but still relies on ad-hoc distribution for iOS, which means the same 100-device limit and UDID requirements apply. Testers also need to install a Firebase device profile before they can download builds, which adds friction. Platforms like **Diawi** and **Loadly** make it easy to share a quick download link, but they typically lack CI/CD integration, version tracking, and team management features. And with **Microsoft App Center** having shut down in March 2025, many teams are looking for a reliable replacement. What most teams actually want is straightforward: **build the app, share a link, and let the tester install it with a single tap**. ## Over-the-Air Installation with Capawesome Cloud [Capawesome Cloud](https://cloud.capawesome.io/) includes a built-in app installation feature similar to what platforms like Diawi and Loadly offer — but fully integrated into the build pipeline. Every iOS development, ad-hoc, or enterprise build and every Android build displays an **Install App** button that lets testers install the app directly on their device with a single tap. No additional configuration or setup is required, and the feature is available at no extra cost. The process works the same on both platforms: once a build is complete, you share a link or QR code with your tester, and they scan it to install the app. Let's walk through the specifics for each platform. ## Distributing Android Apps Android distribution is the simpler of the two platforms since there's no device registration required. ### Create a Build First, trigger a new Android build in Capawesome Cloud: 1. Navigate to the **Builds** page of your app. 1. Click **Build from Git** and select a Git reference (branch, tag, or commit). 1. Select **Android** as the platform and choose a build type (**Debug** or **Release**). 1. Click **Build** to start the build process. Signing Certificates **Release** builds require a signing certificate (keystore). Refer to the [Android Signing Certificates](https://capawesome.io/cloud/native-builds/certificates/android/index.md) documentation for setup instructions. ### Install the App Once the build completes: 1. Open the build details page in Capawesome Cloud. 1. Click the **Install App** button. 1. Scan the **QR code** with the Android device or tap **Download** if you're already on the device. 1. Open the downloaded APK to start the installation. \[ \](/assets/videos/cloud-install-app-android.mp4) Unknown Sources Android requires you to allow installations from unknown sources before installing an APK from outside the Play Store. See our [Install an APK on an Android Device](https://capawesome.io/cloud/native-builds/guides/install-apk-on-android-device/index.md) guide for device-specific instructions. Google Play Protect Google Play Protect may show a warning about the app being from an unknown developer. You can dismiss this warning to proceed with the installation. ## Distributing iOS Apps iOS distribution requires a few extra steps due to Apple's code signing requirements. The tester's device must be registered in your Apple Developer account and included in the provisioning profile used to sign the build. ### Register the Device Every iOS device has a unique identifier (UDID) that must be registered before it can install a test build. 1. Find the device UDID using the [iOS UDID Finder](https://cloud.capawesome.io/tools/ios-udid-finder/) tool. 1. Register the device in the [Apple Developer Portal](https://developer.apple.com/account/resources) under **Devices**. 1. Update your provisioning profile to include the new device and re-upload it to Capawesome Cloud. Device Limit Apple limits the number of registered devices to 100 per product family (iPhone, iPad, etc.) per membership year. Disabling a device does not free up a slot. For detailed steps, refer to our [Install an IPA on an iOS Device](https://capawesome.io/cloud/native-builds/guides/install-ipa-on-ios-device/index.md) guide. ### Create a Build 1. Navigate to the **Builds** page of your app. 1. Click **Build from Git** and select a Git reference. 1. Select **iOS** as the platform. 1. Select **Development**, **Ad Hoc**, or **Enterprise** as the build type. 1. Select a signing certificate with a provisioning profile that includes the tester's device. 1. Click **Build** to start the build process. ### Install the App Once the build completes: 1. Open the build details page in Capawesome Cloud. 1. Click the **Install App** button. 1. Scan the **QR code** with the iOS device or tap **Download** if you're already on the device. 1. The app will be installed on the device. \[ \](/assets/videos/cloud-install-app-ios.mp4) ## Automating the Process Manually triggering builds every time you want to share a test version gets old fast. With [Automations](https://capawesome.io/cloud/automations/index.md), you can configure Capawesome Cloud to automatically trigger a build whenever you push to a specific branch or create a tag. For example, you could set up an automation that builds a new iOS and Android version on every push to `main` or whenever a tag matching `v*` is created. Combined with [notification integrations](https://capawesome.io/cloud/integrations/index.md), this creates a hands-off distribution workflow: a developer pushes code, Capawesome Cloud builds the app, and your QA team gets notified in Slack, Discord, or Microsoft Teams that a new build is ready to install. No one needs to open the Console, run a CLI command, or ping someone on the team — the build link is right there in the notification. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Distributing test builds to testers doesn't have to involve long review waits or complicated setup. With over-the-air installation, you can go from a finished build to a running app on a tester's device in minutes — on both Android and iOS. If you want to dive deeper into the installation process for each platform, check out our step-by-step guides for [Android](https://capawesome.io/cloud/native-builds/guides/install-apk-on-android-device/index.md) and [iOS](https://capawesome.io/cloud/native-builds/guides/install-ipa-on-ios-device/index.md). If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) or [subscribe](https://cloud.capawesome.io/newsletter) to the Capawesome newsletter to stay updated. # How to Enable or Disable Developer Options on Android If you're developing or testing apps on Android—for example with Android Studio or [Capacitor](https://capacitorjs.com/)—you need **developer options** on your device. This guide shows you how to enable developer options on Android and how to turn them off when you don't need them, with steps for Google Pixel, Samsung, Huawei, and Xiaomi. **Android developer options** are hidden by default to prevent accidental changes that could affect performance or security. Enabling developer options on your Android device is required for USB debugging, running development builds, and testing outside the Play Store. Below we walk through how to enable developer options on Android (and how to disable developer options) for popular device brands. ## Google Pixel Follow these steps to enable Developer Options on Google Pixel devices: 1. Open the **Settings** app and scroll down to **About phone**. 1. Tap on **Build number** multiple times (usually 7 times) quickly. You may be prompted to enter your device PIN or pattern. You’ll see a message: **“You are now a developer!”** 1. Go back to **Settings**, and you’ll find **Developer Options** under **System > Developer options**. 1. Enable **Use developer options**. 1. Now scroll down to the **Debugging** section and toggle **USB debugging** or **Wireless debugging** in order to be able to pair your development device with Android Studio. 1. To disable the developer options, toggle **Use developer options** again. ## Samsung Follow these steps to enable Developer Options on Samsung devices: 1. Open the **Settings** app and scroll down to **About phone**. 1. Tap on **Software information**, then tap **Build number** multiple times (usually 7 times) quickly. You may be prompted to enter your device PIN or pattern. 1. Go back to **Settings**, below **About phone** you will see **Developer options**. Depending on the device, it may appear under Settings > General > Developer options. 1. To disable the Developer options menu, go to **Developer options** and toggle the switch. ## Huawei Follow these steps to enable Developer Options on Huawei devices: 1. Open the **Settings** app and scroll down to **About phone**. 1. Tap **Build number** repeatedly (about 7 times) until you see the message **"You are already a developer"**. 1. Go back to **Settings**, and you’ll find **Developer options** under **System > Developer options**. 1. Then turn on **USB debugging**. 1. To disable debugging, simply toggle the **USB debugging** option again. ## Xiaomi Follow these steps to enable Developer Options on Xiaomi devices: 1. Open the **Settings** app and tap **About phone/tablet**. 1. Tap on **Detailed info and specs** > **OS/MIUI version** several times (usually 7 times). 1. Go back to the main **Settings** screen. 1. Tap **Additional settings**. 1. You’ll now see **Developer options** in the list. 1. To disable, in **Additional settings** toggle the **Developer options** switch to hide the developer options. ## How to Turn Off Developer Options on Android To turn off developer options on your Android device, open **Settings** (or **Additional settings** on Xiaomi), go to **Developer options**, and toggle **Use developer options** off. The menu will be hidden again until you tap the build number to re-enable it. If you're also building for iOS, see our guide on [how to enable or disable developer mode on iPhone](https://capawesome.io/blog/how-to-enable-ios-developer-mode/index.md). ## Conclusion Enabling developer options on Android is the first step to debugging and testing your app on a physical device. Each manufacturer places the option in a slightly different place, but the pattern is the same: tap the build number several times, then turn on USB or wireless debugging as needed. For more device and build tips, see our [troubleshooting guide](https://capawesome.io/blog/troubleshooting-capacitor-android-issues/index.md) for Capacitor on Android. If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) or [subscribe](https://cloud.capawesome.io/newsletter) to the Capawesome newsletter to stay updated. # How to Enable or Disable Developer Mode on iOS If you're developing or testing apps on your iPhone —for example with Xcode or [Capacitor](https://capacitorjs.com/)— you need **developer mode** on your iOS device. This guide shows you how to enable developer mode on iPhone and how to turn off developer mode when you don't need it. **Developer mode on iPhone** (iOS developer mode) was introduced in **iOS 16** as a security measure. It ensures that only users who explicitly enable it can run apps from Xcode or other development tools, reducing the risk of sideloaded apps being installed without your knowledge. Enabling developer mode on your iPhone is required for running development builds and testing outside the App Store. ## Enabling Developer Mode on iOS 1. Open the Settings app, scroll down and navigate to **Privacy & Security > Developer Mode**. 1. Enable the Developer Mode options. Tap **Restart** when prompted. 1. Unlock your phone once the device restart is completed. Then the device shows an alert confirming that you want to enable Developer Mode. This is just to ensure that you are aware of the security risk associated with installing a development-signed software. 1. Tap **Turn On** and Developer Mode is now enabled. ## How to Disable Developer Mode on iOS To turn off developer mode on your iOS (or any iOS device), open **Settings > Privacy & Security > Developer Mode** and toggle developer mode off. After you disable developer mode on iOS, you won't be able to run apps from Xcode on your device until you enable it again. If you're also building for Android, see our guide on [how to enable Developer Options on Android](https://capawesome.io/blog/how-to-enable-android-developer-mode/index.md) for the equivalent setup on that platform. For other iOS build issues, check the [iOS troubleshooting guide for Capacitor](https://capawesome.io/blog/troubleshooting-capacitor-ios-issues/index.md). If you want to understand how iOS code signing works under the hood, read [iOS Certificates and Provisioning Profiles Explained](https://capawesome.io/blog/ios-certificates-and-provisioning-profiles-explained/index.md). If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) or [subscribe to the Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated. # How to Fix Capacitor Plugin Build Errors with AGP 9 If you recently upgraded to Android Gradle Plugin (AGP) 9 in your Capacitor project, you may have noticed that your Android build suddenly fails. AGP 9 introduces a breaking change that affects Capacitor core and many Capacitor plugins. The good news: fixes are already available. ## The Problem Android Studio may suggest upgrading your Android Gradle Plugin dependency from 8.x to 9.0.0. After accepting this upgrade, your build fails with an error like this: ``` A problem occurred evaluating project ':app'. > `getDefaultProguardFile('proguard-android.txt')` is no longer supported since it includes `-dontoptimize`, which prevents R8 from performing many optimizations. Instead use `getDefaultProguardFile('proguard-android-optimize.txt')`, and if needed, temporarily use `-dontoptimize` in a custom keep rule file while fixing breakages. ``` The root cause is that AGP 9 [removed support](https://github.com/ionic-team/capacitor/issues/8314) for the `proguard-android.txt` default ProGuard file. This file was previously used by Capacitor core and many Capacitor plugins. Since AGP 9 now requires `proguard-android-optimize.txt` instead, any plugin still referencing the old file will cause the build to fail. This affects not only your app's `build.gradle`, but also the `build.gradle` files of individual Capacitor plugins that ship their own ProGuard configuration. ## The Fix There are two ways to resolve this issue: ### Option 1: Update Your Dependencies The Capacitor team has already [merged a fix](https://github.com/ionic-team/capacitor/pull/8315) that replaces `proguard-android.txt` with `proguard-android-optimize.txt` in Capacitor core. The same fix has been applied to all official Capacitor plugins. To resolve the issue, update `@capacitor/core`, `@capacitor/android`, and all official Capacitor plugins to their latest versions: ``` npm install @capacitor/core@latest @capacitor/android@latest ``` Make sure to also update any official Capacitor plugins (e.g. `@capacitor/app`, `@capacitor/device`, etc.) to their latest versions. ### Option 2: Opt Out of the New Behavior If you can't update all your dependencies right away, you can opt out of the breaking change by adding the following line to your `android/gradle.properties` file: android/gradle.properties ``` android.r8.proguardAndroidTxt.disallowed=false ``` This restores support for `proguard-android.txt` and allows your project to build with AGP 9 without updating any plugins. Note that this is only a temporary workaround and may be removed in a future AGP version. ## Capawesome Plugins All Capacitor plugins from [Capawesome](https://capawesome.io/) are already compatible with AGP 9. The fix has been included in the latest version of every Capawesome plugin, so simply updating to the latest version is all you need to do. ## FAQ ### How do I fix "proguard-android.txt is no longer supported"? Replace `proguard-android.txt` with `proguard-android-optimize.txt` in your app's `build.gradle` and update all Capacitor plugins to their latest versions. Alternatively, add `android.r8.proguardAndroidTxt.disallowed=false` to your `gradle.properties` as a temporary workaround. ### Is this just a Capacitor issue? No. Any Android project or library that references `proguard-android.txt` will break with AGP 9. Capacitor apps are commonly affected because both Capacitor core and many plugins used this file. ### Do I need to update all my plugins at once? Not necessarily. If you can't update everything immediately, use the `gradle.properties` opt-out flag to unblock your build while you update plugins incrementally. ### Will the opt-out flag work forever? No. The `android.r8.proguardAndroidTxt.disallowed=false` flag is a temporary escape hatch. It may be removed in a future AGP version, so you should plan to update your dependencies. ### Does this affect Capacitor community plugins too? Yes. Any community plugin that references `proguard-android.txt` in its `build.gradle` will need to be updated. Check with the plugin maintainer or submit a PR replacing the file reference. ### Can I stay on AGP 8 to avoid this? Yes, staying on AGP 8.x avoids the issue entirely. However, future versions of Android Studio will eventually require AGP 9, so it's better to address the change sooner rather than later. ## Stay Updated Want to stay informed about Capacitor compatibility updates and new plugin releases? Subscribe to our newsletter: [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion AGP 9 introduces a breaking change by removing support for the `proguard-android.txt` default ProGuard file. This causes build failures in Capacitor apps and plugins that still reference it. The fix is straightforward: update Capacitor core, your plugins, and your app's `build.gradle` to use `proguard-android-optimize.txt` instead. If you have questions or run into other issues, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) to get help from the community. And don't forget to subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay up to date on the latest news and updates. # How to gradually roll out Capacitor Live Updates [Live Updates](https://capawesome.io/cloud/live-updates/index.md) are a powerful feature that allows you to deliver updates to your Capacitor app in real-time without having to resubmit your app to the app stores. In order to ensure a smooth rollout of updates, it is recommended to gradually roll out updates to your users. This way you can minimize potential issues and collect feedback from a smaller group of users before rolling out the update to all users. Deprecated This guide is deprecated. Please refer to the [Rollouts](https://capawesome.io/cloud/live-updates/advanced/rollouts/index.md) guide for the latest information. ## Introduction In this guide, we will show you how to gradually roll out updates to your users using the [Capawesome Cloud](https://capawesome.io/cloud/index.md). First, we will roll out a new bundle to a small percentage of users and then gradually increase the percentage of users that receive the update. For this, we will use the [Capawesome CLI](https://capawesome.io/cloud/cli/index.md). ## Create a bundle To create a new bundle, you need to use the [`apps:liveupdates:upload`](https://capawesome.io/cloud/cli/#appsliveupdatesupload) command of the Capawesome CLI. Once the bundle is created, it is immediately available to all users. To roll out the bundle to only 10% of users, for example, you can use the `--rollout-percentage` option as follows: ``` npx @capawesome/cli apps:liveupdates:upload --rollout-percentage 10 ``` Note that the `--rollout-percentage` option accepts a value between 0 and 100, where 0 means that the bundle is not rolled out to any users and 100 means that the bundle is rolled out to all users. ## Update a bundle To update an existing bundle, you can use the [`apps:liveupdates:rollout`](https://capawesome.io/cloud/cli/#appsliveupdatesrollout) command of the Capawesome CLI. In order to increase the rollout percentage, you can use the `--percentage` option as follows: ``` npx @capawesome/cli apps:liveupdates:rollout --percentage 30 ``` In this example, the bundle is now rolled out to 30% of users. You can always see the current rollout percentage of a bundle in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/). ## Conclusion By gradually rolling out updates to your users, you can minimize potential issues and father valuable feedback from your users. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-plugins/discussions/new/choose) in the GitHub repository. Make sure you follow [Capawesome](https://twitter.com/capawesomeio) on X so you don't miss any future updates. # How to Migrate a Capacitor Plugin to Swift Package Manager Capacitor 8 made Swift Package Manager (SPM) the default dependency manager for iOS. If you maintain a Capacitor plugin that still relies on CocoaPods and Objective-C bridge files, your plugin won't work out of the box in SPM-based projects. This post walks you through what needs to change and three ways to get it done. ## What Changes Are Needed The migration from CocoaPods to SPM touches a handful of files. Here's what's involved at a high level: ### Add a Package.swift Manifest SPM needs a `Package.swift` at the root of your plugin repository. This file defines the package name, supported platforms, dependencies, and targets. A typical setup points the main target at `ios/Plugin` and the test target at `ios/PluginTests`: ``` // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "MyCapacitorPlugin", platforms: [.iOS(.v15)], products: [ .library( name: "MyCapacitorPlugin", targets: ["MyCapacitorPluginTarget"]) ], dependencies: [ .package(url: "https://github.com/ionic-team/capacitor-swift-pm.git", branch: "8.0.0") ], targets: [ .target( name: "MyCapacitorPluginTarget", dependencies: [ .product(name: "Capacitor", package: "capacitor-swift-pm"), .product(name: "Cordova", package: "capacitor-swift-pm") ], path: "ios/Plugin"), .testTarget( name: "MyCapacitorPluginTests", dependencies: ["MyCapacitorPluginTarget"], path: "ios/PluginTests") ] ) ``` If your plugin depends on third-party libraries (like a database driver or a networking library), add them to the `dependencies` array and reference them in your target's dependencies. ### Replace Objective-C Bridge Files with CAPBridgedPlugin Older Capacitor plugins use Objective-C files (typically `Plugin.h` and `Plugin.m`) containing the `CAP_PLUGIN` macro to register plugin methods. SPM doesn't support mixing Objective-C and Swift in the same target, so these files need to go. Instead, your plugin's main Swift class should conform to the `CAPBridgedPlugin` protocol. This means adding three properties: `identifier`, `jsName`, and `pluginMethods`: ``` import Capacitor @objc(MyPlugin) public class MyPlugin: CAPPlugin, CAPBridgedPlugin { public let identifier = "MyPlugin" public let jsName = "MyPlugin" public let pluginMethods: [CAPPluginMethod] = [ CAPPluginMethod(name: "echo", returnType: CAPPluginReturnPromise), CAPPluginMethod(name: "getData", returnType: CAPPluginReturnPromise) ] // ... plugin implementation } ``` The `pluginMethods` array replaces the `CAP_PLUGIN_METHOD` entries from the old `.m` file. Make sure every method your plugin exposes to JavaScript is listed here — any omission means that method won't be callable at runtime. After updating the Swift file, delete the `.h` and `.m` bridge files. ### Update .gitignore Add SPM-related entries to your `.gitignore` to keep build artifacts out of version control: ``` Package.resolved /.build /Packages .swiftpm/configuration/registries.json .swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata .netrc ``` ### Clean Up the Xcode Project File The `ios/Plugin.xcodeproj/project.pbxproj` file still references the deleted Objective-C files. Remove any lines that reference your old `.h` and `.m` files from the `PBXBuildFile`, `PBXFileReference`, `PBXGroup`, `PBXHeadersBuildPhase`, and `PBXSourcesBuildPhase` sections. This step is tedious to do by hand, which is one reason the automated tools described below are helpful. ### Update package.json Add `Package.swift` to the `files` array in your `package.json` so it gets included when you publish to npm: ``` { "files": [ "dist/", "ios/Plugin/", "android/", "Package.swift" ] } ``` You can also add a convenience script for resolving SPM dependencies locally: ``` { "scripts": { "ios:spm:install": "swift package resolve" } } ``` ## Migration Options You don't have to do all of this manually. Here are three approaches, depending on your situation. ### Option 1: Capacitor Plugin Converter The Ionic team maintains [capacitor-plugin-converter](https://github.com/ionic-team/capacitor-plugin-converter), a CLI tool called `cap2spm` that automates the conversion. It reads your `Plugin.m` and `Plugin.h` files, modifies your `Plugin.swift` to add `CAPBridgedPlugin` conformance, and generates a `Package.swift`. Install it via curl: ``` curl -OL https://github.com/ionic-team/capacitor-plugin-converter/releases/latest/download/cap2spm.zip ``` Then run it against your plugin directory: ``` ./cap2spm /path/to/your/plugin ``` A few things to keep in mind: - The tool is still under heavy development. It works for most cases, but make sure you have a clean commit before running it. - The binary is currently unsigned. If you download it from a browser on macOS, you'll need to remove the quarantine attribute first: ``` xattr -d com.apple.quarantine ./cap2spm ``` ### Option 2: Capawesome Skills [Capawesome Skills](https://github.com/capawesome-team/skills) is a collection of AI-powered agent skills for [Claude Code](https://docs.anthropic.com/en/docs/claude-code) and other AI coding agents. It includes a dedicated `capacitor-plugin-spm-support` skill that guides the agent through the full migration: creating `Package.swift`, replacing Objective-C bridge files, updating `.gitignore`, cleaning up the Xcode project file, and updating `package.json`. Install the skills: ``` npx skills add capawesome-team/skills ``` Then prompt your AI agent: ``` Add Swift Package Manager support to my Capacitor plugin. ``` The agent will walk through each step, adapting to your plugin's specific setup and dependencies. ### Option 3: Create a New Plugin from Scratch If your plugin is relatively simple or you're planning a rewrite anyway, you can scaffold a fresh plugin using [create-capacitor-plugin](https://github.com/ionic-team/create-capacitor-plugin): ``` npm init @capacitor/plugin my-plugin ``` New plugins created with this tool already include full SPM support out of the box. Once the scaffold is ready, migrate your existing implementation code into the new project structure. This approach works best for smaller plugins where copying the implementation is less effort than modifying the existing project files. ## Try Capawesome Cloud If you're building Capacitor apps or plugins, [Capawesome Cloud](https://cloud.capawesome.io/) can help you ship faster with cloud-based native builds, over-the-air updates, and automated app store publishing. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Adding SPM support to your Capacitor plugin is becoming essential as the iOS ecosystem moves away from CocoaPods. The changes are well-defined — a `Package.swift` manifest, `CAPBridgedPlugin` conformance, and some cleanup — and you have several tools to help automate the process. Whether you prefer a CLI tool, an AI agent, or a fresh scaffold, you can get your plugin ready for SPM-based projects without too much effort. If you still need CocoaPods for your app project, check out [How to Use CocoaPods Instead of SPM with Capacitor](https://capawesome.io/blog/how-to-use-cocoapods-with-capacitor/index.md) for guidance on when and how to use it as a fallback. If you have questions, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to patch a Capacitor plugin Learn how to apply small changes to a Capacitor plugin without forking or maintaining the entire plugin. This is especially helpful when you need a hotfix for a plugin that isn't yet available in the official version, or when you need to make quick adjustments to accommodate your specific project requirements. In this guide, we'll use the npm package [patch-package](https://github.com/ds300/patch-package) by [David Sheldrick](https://github.com/ds300). > `patch-package` lets app authors instantly make and keep fixes to npm dependencies. It's a vital band-aid for those of us living on the bleeding edge. With `patch-package`, you can make changes to the source code of a plugin and create a patch that will be applied automatically whenever the plugin is installed. This approach gives you the flexibility to maintain customizations without the overhead of maintaining a complete fork. Check the plugin license Before modifying any plugin code, always **review the plugin's license terms**. Some open source licenses, such as GPL, MPL 2.0, and other copyleft licenses, require you to publish your modifications and may impose additional obligations on your project. ## Getting Started First, install `patch-package` as a development dependency: ``` npm install --save-dev patch-package ``` Next, add the following script to your `package.json`: package.json ``` { "scripts": { "postinstall": "patch-package" } } ``` This `postinstall` script ensures that all patches are automatically applied after each installation of npm dependencies, keeping your customizations in sync across your team and CI/CD environments. ## Creating a Patch Follow these steps to create your first patch: 1. **Identify the issue**: Determine what you need to fix or modify. For example, you might need to resolve a compatibility issue with a specific version of a dependency. 1. **Make the necessary changes**: Modify the source code of the plugin directly in your `node_modules` folder. In this example, we'll change the version constraint of the `FirebaseCrashlytics` dependency from `10.8.0` to `>= 10.8.0`: ``` - s.dependency 'FirebaseCrashlytics', '10.8.0' + s.dependency 'FirebaseCrashlytics', '>= 10.8.0' ``` 1. **Generate the patch**: Once you've made your changes, generate the patch file by running: ``` npx patch-package ``` Replace `` with the npm package name of the plugin you want to patch (e.g., `@capacitor-firebase/crashlytics`). This command creates a new `patches` folder in your project root containing a patch file that looks something like this: ``` diff --git a/node_modules/@capacitor-firebase/crashlytics/CapacitorFirebaseCrashlytics.podspec b/node_modules/@capacitor-firebase/crashlytics/CapacitorFirebaseCrashlytics.podspec index b7b17a9..ef91f1c 100644 --- a/node_modules/@capacitor-firebase/crashlytics/CapacitorFirebaseCrashlytics.podspec +++ b/node_modules/@capacitor-firebase/crashlytics/CapacitorFirebaseCrashlytics.podspec @@ -13,7 +13,7 @@ Pod::Spec.new do |s| s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' s.ios.deployment_target = '13.0' s.dependency 'Capacitor' - s.dependency 'FirebaseCrashlytics', '10.8.0' + s.dependency 'FirebaseCrashlytics', '>= 10.8.0' s.swift_version = '5.1' s.static_framework = true end ``` That's it! Commit the patch file to your repository and share it with your team. The patch will be automatically applied whenever anyone runs `npm install`. ## Conclusion `patch-package` is an invaluable tool for quickly applying small changes to your npm dependencies, making it especially useful in Capacitor projects where you need to maintain compatibility or apply urgent fixes. Keep in mind a few best practices: - **Review patches after updates**: When updating a patched dependency, review and update your patches as needed to ensure they still apply correctly. - **Limit patch scope**: Avoid using patches for extensive modifications. For larger changes, consider maintaining a fork instead. - **Contribute upstream**: Always report issues to the plugin maintainer and create a pull request when possible. This helps the entire community and may eliminate the need for your patch in future versions. By following these guidelines, you can effectively use `patch-package` to keep your Capacitor projects running smoothly while contributing to the broader ecosystem. # How to Reduce the Bundle Size of Capacitor Live Updates If you're using Over-The-Air (OTA) live updates to ship web layer changes to your Capacitor or Ionic app, the size of your update bundles directly affects how fast those changes reach your users. Large bundles mean slower downloads, higher data transfer costs, and a worse experience for users on mobile networks. The good news is that a few targeted optimizations can make a dramatic difference — one team we worked with reduced their average update from ~48 MB to ~9 MB. This guide walks through the most effective ways to reduce your live update bundle size. ## Why Bundle Size Matters Every live update your app downloads counts against your users' data plans and your data transfer budget. On a fast Wi-Fi connection, the difference between a 9 MB and a 50 MB download might not feel significant. But on a metered mobile connection — which is the reality for many users — it can mean the difference between a seamless update and one that fails mid-download or drains data allowances. Smaller bundles also mean faster rollouts. When you need to push a critical fix, you want every device updated as quickly as possible. A lean bundle gets there faster and more reliably. ## Remove Source Maps This is often the single biggest win. Source maps are generated by your build tool to help with debugging, but they can be surprisingly large — in some cases making up over 75% of your total bundle. One team we worked with had a 59 MB bundle. After investigating, they found that 45 MB of that — 76% — was source maps alone. By stripping source maps after the build step, they brought the bundle down to around 14 MB of actual web assets. If you're using an error tracking service like [Sentry](https://sentry.io/), you can upload your source maps to the service during the build process and then remove them before creating the live update bundle. This way you keep full error tracking with readable stack traces without shipping source maps to your users. How you remove source maps depends on your build tool. In Vite, for example, you can set `build.sourcemap` to `false` in your config, or you can delete the `.map` files after the build completes but before uploading the bundle. The key is to make sure source maps never end up in the bundle that gets delivered to devices. ## Choose the Right Artifact Type When uploading a live update bundle to [Capawesome Cloud](https://cloud.capawesome.io/), you can choose between two artifact types: `zip` and `manifest`. With `zip`, your entire bundle is compressed into a single archive and downloaded as one file. With `manifest`, files are uploaded individually and compared against what's already on the device — only changed files are downloaded. This is known as a [delta update](https://capawesome.io/cloud/live-updates/advanced/delta-updates/index.md). Delta updates sound great in theory, but they're **only beneficial if your app contains large files that rarely change** between deployments. The reality is that most modern build tools — including Vite, Nuxt, Angular CLI, and Webpack — generate content-hashed filenames for JavaScript chunks. That means every build produces filenames like `chunk-3a7f2b.js` instead of `chunk.js`. When filenames change on every build, the delta comparison sees every file as new and downloads everything individually — without the benefit of compression. The team mentioned earlier experienced exactly this. Their Vite/Nuxt build generated content-hashed filenames, so the manifest artifact type was effectively downloading all files individually without compression. Switching to `zip` brought their bundle from ~20 MB (after removing source maps) down to ~9 MB thanks to compression. **When to use `zip`:** Most apps should use `zip`. It's the default for a reason — compression is effective, and content-hashed filenames make delta updates ineffective for typical web bundles. **When to use `manifest`:** If your app includes large static assets (images, fonts, data files) that don't change between updates and your filenames remain stable across builds, manifest with delta updates can save data transfer by only downloading what changed. You can specify the artifact type when uploading: ``` npx @capawesome/cli apps:liveupdates:upload --artifact-type zip ``` ## Remove Unnecessary Static Assets Your web build output likely includes images, fonts, videos, or other static assets that get bundled alongside your JavaScript and CSS. Every one of these files adds to your live update bundle size — even if they haven't changed since the last update. Ask yourself: does every asset need to be in the bundle? Assets that aren't needed at initial load time — hero images, promotional banners, downloadable resources — can be loaded on demand from your server or a CDN instead. This keeps them out of the live update bundle entirely. For example, if your app includes a set of tutorial images that are only shown during onboarding, hosting those on a CDN and loading them at runtime removes them from every update bundle your users download. ## Tree Shake Unused Code Tree shaking is the process of eliminating dead code — modules or functions that are imported but never actually used — from your production bundles. Most modern bundlers support tree shaking out of the box, but it doesn't always work perfectly without some attention. A few things to check: - **Make sure you're building in production mode.** Development builds typically skip tree shaking and other optimizations. - **Check for side effects.** Libraries that declare `"sideEffects": false` in their `package.json` are easier for bundlers to tree shake. If you maintain internal packages, add this field where appropriate. - **Audit your bundle.** Tools like [rollup-plugin-visualizer](https://github.com/nicolo-ribaudo/rollup-plugin-visualizer) or [webpack-bundle-analyzer](https://github.com/nicolo-ribaudo/webpack-bundle-analyzer) give you a visual breakdown of what's in your bundle. You might be surprised to find large libraries pulled in by a single import. Even small wins here add up. Removing a few unused dependencies or switching to lighter alternatives can shave megabytes off your bundle. ## Minify JavaScript and CSS Minification removes whitespace, shortens variable names, and applies other transformations to reduce file size without changing behavior. Most build tools enable minification by default for production builds, but it's worth verifying that it's actually happening. If you're using Vite, minification is enabled by default when you run `vite build`. Webpack enables it in production mode via TerserPlugin. If you've customized your build configuration, double-check that minification hasn't been accidentally disabled. CSS minification is equally important. Tools like [cssnano](https://cssnano.github.io/cssnano/) or the built-in CSS minification in your bundler can reduce stylesheet sizes significantly by removing unused rules, shortening color codes, and collapsing redundant properties. ## Get Started Ready to ship smaller, faster live updates? Try Capawesome Cloud and start optimizing your bundle delivery today. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Reducing your live update bundle size doesn't require a complete rework of your build pipeline. Start with the highest-impact changes — removing source maps and choosing the right artifact type — and then look at static assets, tree shaking, and minification for additional gains. The team we highlighted went from ~48 MB per update to ~9 MB with just the first two optimizations. For more details on how live updates work under the hood, check out our post on [how live updates for Capacitor work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/index.md). You can also read the [delta updates documentation](https://capawesome.io/cloud/live-updates/advanced/delta-updates/index.md) to learn more about when manifest-based updates make sense for your app. If you have questions or want to share your own optimization results, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And to stay updated on new guides and features, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to restrict Capacitor Live Updates to Native Versions [Live Updates](https://capawesome.io/cloud/live-updates/index.md) are a powerful feature that allows you to deliver updates to your Capacitor app in real-time without having to resubmit your app to the app stores. However, it is important to make sure that only [Binary Compatible Changes](https://capawesome.io/cloud/live-updates/faq/#what-are-binary-compatible-changes) are delivered to your users to prevent incompatible updates. In this guide, you will learn how to restrict live updates to specific native versions. Deprecated This guide is deprecated. Please refer to the [Best Practices](https://capawesome.io/cloud/live-updates/guides/best-practices/index.md) guide for the latest information. ## Introduction The [Capawesome Cloud](https://capawesome.io/cloud/index.md) offers two different ways to restrict live updates to specific native versions: 1. [Versioned Bundles](#versioned-bundles) 1. [Versioned Channels](#versioned-channels) Let's take a closer look at both options. ## Versioned Bundles Versioned bundles allow you to restrict live updates to specific native versions by defining a range of version codes for each platform. Version Code The version code (named [`versionCode`](https://developer.android.com/studio/publish/versioning) on Android and [`CFBundleVersion`](https://developer.apple.com/documentation/bundleresources/information_property_list/cfbundleversion) on iOS) is the internal version number of your app. It is used to determine whether one version is more recent than another and must be incremented each time you release a new version of your app. To create a versioned build, you only need to specify the minimum and maximum version codes for each platform: - **Minimum**: The native binary must have at least this version code to be compatible with the bundle. - **Maximum**: The native binary must have at most this version code to be compatible with the bundle. - **Equivalent**: If the native binary has this exact version code, do **NOT** download the bundle, because they are equal. For this, you can use the following [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) command: ``` npx @capawesome/cli apps:liveupdates:upload --android-min 10 --android-max 12 --android-eq 11 --ios-min 10 --ios-max 12 --ios-eq 11 ``` ## Versioned Channels Versioned channels allow you to restrict live updates to specific native versions by defining a channel for each version code. Channel A Channel allows you to distribute different versions of your app to different groups of users. To create a versioned channel, you can use the following [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) command: ``` npx @capawesome/cli apps:channels:create --name production-10 ``` In this example, we created a channel named `production-10` for the version code `10`. To upload a bundle to a specific channel, you can use the following [Capawesome CLI](https://capawesome.io/cloud/cli/index.md) command: ``` npx @capawesome/cli apps:liveupdates:upload --channel production-10 ``` Finally, we need to set the correct channel in the app to ensure that only compatible bundles are downloaded: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { // Get the version code of the native app const { versionCode } = await LiveUpdate.getVersionCode(); // Select the channel based on the version code await LiveUpdate.setChannel({ channel: `production-${versionCode}` }); // Automatically download and set the latest compatible bundle await LiveUpdate.sync(); }; ``` ## Conclusion By following the steps in this guide, you can ensure that only compatible updates are delivered to your users and prevent any issues caused by incompatible changes. While [Versioned Builds](#versioned-builds) seems to be the more straightforward approach, [Versioned Channels](#versioned-channels) are the recommended way to restrict live updates to specific native versions because they are easier to manage and therefore less error-prone. If you have any questions, just [create a discussion](https://github.com/capawesome-team/capacitor-plugins/discussions/new/choose) in the GitHub repository. Make sure you follow [Capawesome](https://twitter.com/capawesomeio) on X so you don't miss any future updates. # How to Securely Store Credentials with Capacitor A common requirement for mobile apps is secure storage of credentials (passwords, tokens, API keys) and user authentication via **biometrics** (fingerprint, Face ID). With Ionic's Identity Vault and Secure Storage [no longer maintained](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products), you can achieve the same with the [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin and [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md). For a complete walkthrough on the Biometrcis plugin make sure to read [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md). ## Introduction In this guide, we will show you how to securely store credentials and authenticate users using the Capacitor Biometrics and Secure Preferences plugins. This solution is cross-platform and works on both Android and iOS. ## Installation First, you need to install these two plugins in your Capacitor app. ### Biometrics Refer to [Getting Started with Insiders](https://capawesome.io/insiders/getting-started/?plugin=capacitor-biometrics) and follow the instructions to install the plugin. After installation, follow the platform-specific instructions in the [Android](https://capawesome.io/plugins/biometrics/#android) and [iOS](https://capawesome.io/plugins/biometrics/#ios) sections. ### Secure Preferences Refer to [Getting Started with Insiders](https://capawesome.io/insiders/getting-started/?plugin=capacitor-secure-preferences) and follow the instructions to install the plugin. After installation, follow the platform-specific instructions in the [Android](https://capawesome.io/plugins/secure-preferences/#android) section. ## Usage The following example demonstrates how to use the plugins to securely store credentials and authenticate users with biometrics. ### Storing Credentials First, we need to implement the functionality for storing credentials. The example below shows how to sign in a user with a username and password and securely store the credentials: ``` import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const signInWithCredentials = async (username: string, password: string) => { const isValid = validateCredentials(username, password); if (isValid) { await storeCredentials(username, password); return true; } else { return false; } }; const validateCredentials = (username: string, password: string): boolean => { // Validate the credentials (e.g. perform a network request to your backend). return username === 'user' && password === 'password'; }; const storeCredentials = async (username: string, password: string) => { // Recommended: Prompt the user for biometric authentication before storing the credentials. await SecurePreferences.set({ key: 'credentials', value: JSON.stringify({ username, password }), }); }; ``` The `signInWithCredentials(...)` function first validates the credentials and then securely stores them using the [`set(...)`](https://capawesome.io/plugins/secure-preferences/#set) method of the Secure Preferences plugin. The credentials are stored as a JSON string because the Secure Preferences plugin only accepts strings as values. The `validateCredentials()` function is a placeholder for your own logic to validate the credentials, such as making a network request to your backend. You should replace it with your own implementation. ### Retrieving Credentials Next, we need to implement the functionality for retrieving credentials and authenticating the user with biometrics. The `signInWithBiometrics(...)` function first checks if credentials are stored and then authenticates the user with biometrics using the Biometrics plugin: ``` import { Biometrics, ErrorCode } from '@capawesome-team/capacitor-biometrics'; import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences'; const signInWithBiometrics = async () => { const areCredentialsStored = await areCredentialsStored(); if (!areCredentialsStored) { return false; } try { await Biometrics.authenticate(); } catch (error) { return false; } const credentials = await retrieveCredentials(); return signInWithCredentials(credentials.username, credentials.password); }; const areCredentialsStored = async () => { const { value } = await SecurePreferences.get({ key: 'credentials' }); return !!value; }; const retrieveCredentials = async () => { const { value } = await SecurePreferences.get({ key: 'credentials' }); return JSON.parse(value); }; ``` If biometric authentication is successful, the credentials are retrieved from the Secure Preferences plugin using the [`get(...)`](https://capawesome.io/plugins/secure-preferences/#get) method and passed to the `signInWithCredentials(...)` function to sign in the user. If authentication fails, the function simply returns `false`. ## Conclusion This guide showed how to store credentials and authenticate users with the [Capacitor Biometrics](https://capawesome.io/plugins/biometrics/index.md) plugin and [Capacitor Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md). Please note that this is a basic example — adapt it to your requirements and consider prompting for biometric authentication before storing credentials. For the full plugin documentation, see the [API Reference](https://capawesome.io/plugins/biometrics/#api). **Additional resources:** - [Capacitor Biometrics Demo App](https://github.com/capawesome-team/capacitor-biometrics-demo) - [Step-by-step video](https://www.youtube.com/watch?v=ixUvTX6n7x8) **Related reading:** - [Exploring the Capacitor Biometrics API](https://capawesome.io/blog/exploring-the-capacitor-biometrics-api/index.md) - [Announcing the Capacitor Biometrics Plugin](https://capawesome.io/blog/announcing-the-capacitor-biometrics-plugin/index.md) - [Alternative to the Ionic Identity Vault plugin](https://capawesome.io/blog/alternative-to-ionic-identity-vault-plugin/index.md) Follow [Capawesome on X](https://twitter.com/capawesomeio) and subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated. # How to Sign In with Apple Using Capacitor Sign in with Apple lets users authenticate with their Apple ID, offering a privacy-focused alternative to other social logins. Apple requires apps that offer third-party sign-in to also support Sign in with Apple. The [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) plugin provides a straightforward way to integrate Apple Sign-In into your Ionic or Capacitor app on Android, iOS, and web. This guide walks you through setting up the required credentials, configuring the plugin for each platform, and implementing the sign-in flow. ## Prerequisites Before you begin, make sure you have the following: - An **Apple Developer Program** membership with access to the [Apple Developer Portal](https://developer.apple.com/). - A **Capacitor app** with the [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/apple-sign-in/#installation) section in the plugin documentation. ## Setting Up Apple Developer Credentials ### Creating an App ID If you haven't already, you need to create an App ID with the "Sign in with Apple" capability enabled: 1. Go to the [Apple Developer Portal](https://developer.apple.com/) and navigate to **Certificates, Identifiers & Profiles** > **Identifiers**. 1. Click the **+** button to create a new identifier and select **App IDs**. 1. Select **App** as the type and click **Continue**. 1. Enter a **Description** (e.g. `My Capacitor App`) and a **Bundle ID** that matches your iOS app (e.g. `com.example.app`). 1. Scroll down to the **Capabilities** section, check **Sign in with Apple**, and click **Continue**. 1. Review the details and click **Register**. ### Creating a Service ID (Android and Web) On Android and web, Apple Sign-In uses a web-based OAuth flow. You need a Service ID to identify your app in this flow: 1. In the Apple Developer Portal, navigate to **Certificates, Identifiers & Profiles** > **Identifiers**. 1. Click the **+** button, select **Services IDs**, and click **Continue**. 1. Enter a **Description** (e.g. `My Capacitor App - Web`) and an **Identifier** (e.g. `com.example.app.web`). This identifier will be used as the `clientId` when initializing the plugin. 1. Click **Continue**, then **Register**. 1. Click on the newly created Service ID and check **Sign in with Apple**. 1. Click **Configure** next to Sign in with Apple. 1. Under **Domains and Subdomains**, add the domain where your app is hosted (e.g. `example.com`). Enter just the domain without a protocol or trailing slash. 1. Under **Return URLs**, add the redirect URL that the plugin will use after authentication (e.g. `https://example.com/callback`). The URL must use the `https://` scheme. 1. Click **Save**, then **Continue**, and **Save** again. ## Configuring Your Capacitor App ### Android No additional configuration is required on Android beyond installing the plugin. ### iOS On iOS, you need to add the "Sign in with Apple" capability in Xcode: 1. Open your project in Xcode by running `npx cap open ios`. 1. Select your app target and navigate to the **Signing & Capabilities** tab. 1. Click **+ Capability** and search for **Sign in with Apple**. 1. Add the capability. Xcode will automatically update your entitlements file. ### Web No additional configuration is required on Web beyond installing the plugin. ## Implementing the Sign-In Flow ### Initializing the Plugin On Android and web, you need to initialize the plugin before calling any other method. Use [`initialize(...)`](https://capawesome.io/plugins/apple-sign-in/#initialize) and pass the **Service ID** you created earlier as the `clientId`: ``` import { AppleSignIn } from '@capawesome/capacitor-apple-sign-in'; const initialize = async () => { await AppleSignIn.initialize({ clientId: 'com.example.app.web', // Your Service ID from the Apple Developer Portal }); }; ``` On iOS, initialization is not required because the plugin uses the native Sign in with Apple framework directly. ### Signing In Use the [`signIn(...)`](https://capawesome.io/plugins/apple-sign-in/#signin) method to start the Apple Sign-In flow. You can request scopes for the user's name and email: ``` import { AppleSignIn, SignInScope } from '@capawesome/capacitor-apple-sign-in'; const signIn = async () => { const result = await AppleSignIn.signIn({ redirectUrl: 'https://example.com/callback', // Only required on Android and Web scopes: [SignInScope.Email, SignInScope.Name], }); console.log('Identity token:', result.identityToken); console.log('Authorization code:', result.authorizationCode); console.log('User ID:', result.userId); console.log('Email:', result.email); console.log('Given name:', result.givenName); console.log('Family name:', result.familyName); }; ``` Warning Apple only returns the user's name and email on the **first** sign-in. Subsequent sign-ins will not include this information, so make sure to store it on your backend after the initial authentication. ### Handling Errors If the user cancels the sign-in flow, the plugin throws an error with the code `SIGN_IN_CANCELED`. You can handle this to distinguish between user cancellation and actual errors: ``` import { AppleSignIn, SignInScope } from '@capawesome/capacitor-apple-sign-in'; const signIn = async () => { try { const result = await AppleSignIn.signIn({ scopes: [SignInScope.Email, SignInScope.Name], }); console.log('Identity token:', result.identityToken); } catch (error: any) { if (error.code === 'SIGN_IN_CANCELED') { console.log('User canceled sign-in'); } else { console.error('Sign-in failed:', error); } } }; ``` ## Bonus: Verifying the Identity Token on the Backend The `identityToken` returned by [`signIn(...)`](https://capawesome.io/plugins/apple-sign-in/#signin) is a JSON Web Token (JWT) signed by Apple. You should always verify it on your backend before trusting the information. This ensures the token was actually issued by Apple and hasn't been tampered with. To verify the token, fetch Apple's public keys from `https://appleid.apple.com/auth/keys` and validate the JWT signature and claims. Here's an example using the [jose](https://github.com/panva/jose) library for Node.js: ``` import { createRemoteJWKSet, jwtVerify } from 'jose'; const APPLE_JWKS_URL = new URL('https://appleid.apple.com/auth/keys'); const jwks = createRemoteJWKSet(APPLE_JWKS_URL); const verifyIdentityToken = async (identityToken: string) => { const { payload } = await jwtVerify(identityToken, jwks, { issuer: 'https://appleid.apple.com', audience: 'com.example.app', }); console.log('User ID:', payload.sub); console.log('Email:', payload.email); }; ``` The `audience` should match your app's **Bundle ID** (for tokens from iOS) or your **Service ID** (for tokens from Android and web). If your app uses both, verify against both values. ## Conclusion In this guide, we covered how to set up Apple Sign-In in a Capacitor app using the [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) plugin. From creating App IDs and Service IDs in the Apple Developer Portal to configuring platform-specific settings and implementing the sign-in flow, the plugin handles the complexity across Android, iOS, and web so you can focus on building your app. Explore the complete [API Reference](https://capawesome.io/plugins/apple-sign-in/#api) to see all available methods and options. Have suggestions or questions? [Create an issue](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) and subscribe to our [newsletter](https://cloud.capawesome.io/newsletter/) for the latest updates. # How to Sign In with Auth0 Using Capacitor [Auth0](https://auth0.com/) is one of the most popular identity platforms, offering authentication and authorization as a service. If you're building a cross-platform app with Capacitor, the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin makes it easy to integrate Auth0 using the Authorization Code flow with PKCE. This guide walks you through application setup, sign-in, token management, and fetching user profile information. ## Prerequisites Before you begin, make sure you have the following: - An **Auth0 account**. If you don't have one, you can [sign up for free](https://auth0.com/signup). - A **Capacitor app** with the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. ## Setting Up Auth0 ### Creating an Application First, you need to create an application in the Auth0 Dashboard: 1. Sign in to the [Auth0 Dashboard](https://manage.auth0.com/). 1. Navigate to **Applications** > **Applications** > **Create Application**. 1. Enter a **Name** for your application (e.g. `My Capacitor App`). 1. Select **Native** as the application type and click **Create**. 1. On the **Settings** tab, note the **Domain** and **Client ID**. You will need these later. ### Configuring Callback URLs You need to configure a callback URL for each platform you want to support. In your application's **Settings** tab, add the following URLs to the **Allowed Callback URLs** field (comma-separated). **Android and iOS**: Use a custom scheme based on your app's package name or bundle identifier: ``` com.example.app://oauth/callback ``` **Web**: Use your web app's URL: ``` http://localhost:3000/oauth/callback ``` ### Configuring Logout URLs To support logout, you also need to add the following URLs to the **Allowed Logout URLs** field in your application's **Settings** tab. **Android and iOS**: ``` com.example.app://oauth/logout ``` **Web**: ``` http://localhost:3000/oauth/logout ``` ## Implementing Authentication Throughout the following examples, replace `{domain}` with your Auth0 **Domain** and `{client-id}` with your **Client ID**. ### Signing In Use the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method to start the OAuth flow. The plugin automatically fetches the OpenID Connect discovery document from the issuer URL and handles the PKCE exchange: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://{domain}', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); console.log('Access token:', result.accessToken); console.log('ID token:', result.idToken); console.log('Refresh token:', result.refreshToken); }; ``` Include the `offline_access` scope to receive a refresh token. ### Handling the Redirect Callback (Web) On the web, the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method redirects the user to the Auth0 login page. After authentication, the user is redirected back to your app. You need to call [`handleRedirectCallback()`](https://capawesome.io/plugins/oauth/#handleredirectcallback) on page load to complete the token exchange: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; import { Capacitor } from '@capacitor/core'; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== 'web') { return; } const url = new URL(window.location.href); if (!url.searchParams.has('code')) { return; } const result = await Oauth.handleRedirectCallback(); console.log('Access token:', result.accessToken); }; handleRedirectCallback(); ``` This step is only required on the web. On Android and iOS, the redirect is handled natively. ### Refreshing the Access Token Access tokens expire after a short time. Use the [`refreshToken(...)`](https://capawesome.io/plugins/oauth/#refreshtoken) method to get a new access token without requiring the user to sign in again: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const refreshToken = async () => { const result = await Oauth.refreshToken({ issuerUrl: 'https://{domain}', clientId: '{client-id}', refreshToken: 'YOUR_REFRESH_TOKEN', }); console.log('New access token:', result.accessToken); }; ``` ### Decoding the ID Token Use the [`decodeIdToken(...)`](https://capawesome.io/plugins/oauth/#decodeidtoken) method to read the user's profile claims from the ID token: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const decodeIdToken = async () => { const result = await Oauth.decodeIdToken({ token: 'YOUR_ID_TOKEN', }); console.log('Name:', result.payload.name); console.log('Email:', result.payload.email); }; ``` This decodes the JWT locally without sending it to a server. For server-side validation, you should verify the token on your backend. ### Signing Out End the session with the [`logout(...)`](https://capawesome.io/plugins/oauth/#logout) method: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const logout = async () => { await Oauth.logout({ issuerUrl: 'https://{domain}', idToken: 'YOUR_ID_TOKEN', postLogoutRedirectUrl: 'com.example.app://oauth/logout', }); }; ``` ## Fetching the User Profile To fetch the authenticated user's profile from Auth0, you can call the `/userinfo` endpoint using the access token: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://{domain}', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); const response = await fetch('https://{domain}/userinfo', { headers: { Authorization: `Bearer ${result.accessToken}`, }, }); const user = await response.json(); console.log('Name:', user.name); console.log('Email:', user.email); }; ``` ## Conclusion In this guide, we covered how to set up Auth0 authentication in a Capacitor app using the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin. From application setup and callback configuration to sign-in, token refresh, and fetching the user profile, the plugin handles the complexity of the OAuth flow so you can focus on building your application. Explore the complete [API Reference](https://capawesome.io/plugins/oauth/#api) to see all available methods and options. If you're using Okta instead, check out [How to Sign In with Okta Using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-okta-using-capacitor/index.md). Have suggestions or questions? [Create an issue](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) and subscribe to our [newsletter](https://cloud.capawesome.io/newsletter/) for the latest updates. # How to Sign In with Azure Entra ID Using Capacitor Many enterprise applications rely on [Microsoft Entra ID](https://www.microsoft.com/en-us/security/business/identity-access/microsoft-entra-id) (formerly Azure Active Directory) for identity and access management. If you're building a cross-platform app with Capacitor, the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin makes it straightforward to integrate Entra ID authentication using the Authorization Code flow with PKCE. This guide walks you through app registration, sign-in, token management, and accessing the Microsoft Graph API. ## Prerequisites Before you begin, make sure you have the following: - A **Microsoft Entra ID tenant**. If you don't have one, you can [create a free Azure account](https://azure.microsoft.com/en-us/free/). - A **Capacitor app** with the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. ## Setting Up Azure Entra ID ### Registering Your App First, you need to register your application in the Microsoft Entra admin center: 1. Sign in to the [Microsoft Entra admin center](https://entra.microsoft.com/). 1. Navigate to **Entra ID** > **App registrations** > **New registration**. 1. Enter a **Name** for your application (e.g. `My Capacitor App`). 1. Under **Supported account types**, select the option that fits your use case: - **Single tenant**: Only accounts in your directory. - **Multitenant**: Accounts in any organizational directory. - **Multitenant + personal**: Includes Microsoft personal accounts. 1. Under **Redirect URI**, select the platform and enter a redirect URI (see [Configuring Redirect URIs](#configuring-redirect-uris) below). 1. Click **Register**. 1. On the app overview page, note the **Application (client) ID** and **Directory (tenant) ID**. You will need these later. ### Configuring Redirect URIs You need to register a redirect URI for each platform you want to support. To add multiple redirect URIs, go to **Overview** > **Redirect URIs** and click **Add Redirect URI**. **Android and iOS**: Select **Public client/native (mobile & desktop)** and use a custom scheme based on your app's package name or bundle identifier: ``` com.example.app://oauth/callback ``` **Web**: Select **Single-page application (SPA)** and use your web app's URL: ``` http://localhost:3000/oauth/callback ``` ## Implementing Authentication Throughout the following examples, replace `{tenant-id}` with your **Directory (tenant) ID** and `{client-id}` with your **Application (client) ID**. ### Signing In Use the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method to start the OAuth flow. The plugin automatically fetches the OpenID Connect discovery document from the issuer URL and handles the PKCE exchange: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://login.microsoftonline.com/{tenant-id}/v2.0', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); console.log('Access token:', result.accessToken); console.log('ID token:', result.idToken); console.log('Refresh token:', result.refreshToken); }; ``` Include the `offline_access` scope to receive a refresh token. ### Handling the Redirect Callback (Web) On the web, the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method redirects the user to the Microsoft login page. After authentication, the user is redirected back to your app. You need to call [`handleRedirectCallback()`](https://capawesome.io/plugins/oauth/#handleredirectcallback) on page load to complete the token exchange: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; import { Capacitor } from '@capacitor/core'; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== 'web') { return; } const url = new URL(window.location.href); if (!url.searchParams.has('code')) { return; } const result = await Oauth.handleRedirectCallback(); console.log('Access token:', result.accessToken); }; handleRedirectCallback(); ``` This step is only required on the web. On Android and iOS, the redirect is handled natively. ### Refreshing the Access Token Access tokens expire after a short time. Use the [`refreshToken(...)`](https://capawesome.io/plugins/oauth/#refreshtoken) method to get a new access token without requiring the user to sign in again: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const refreshToken = async () => { const result = await Oauth.refreshToken({ issuerUrl: 'https://login.microsoftonline.com/{tenant-id}/v2.0', clientId: '{client-id}', refreshToken: 'YOUR_REFRESH_TOKEN', }); console.log('New access token:', result.accessToken); }; ``` ### Decoding the ID Token Use the [`decodeIdToken(...)`](https://capawesome.io/plugins/oauth/#decodeidtoken) method to read the user's profile claims from the ID token: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const decodeIdToken = async () => { const result = await Oauth.decodeIdToken({ token: 'YOUR_ID_TOKEN', }); console.log('Name:', result.payload.name); console.log('Email:', result.payload.preferred_username); }; ``` This decodes the JWT locally without sending it to a server. For server-side validation, you should verify the token on your backend. ### Signing Out End the session with the [`logout(...)`](https://capawesome.io/plugins/oauth/#logout) method: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const logout = async () => { await Oauth.logout({ issuerUrl: 'https://login.microsoftonline.com/{tenant-id}/v2.0', idToken: 'YOUR_ID_TOKEN', postLogoutRedirectUrl: 'com.example.app://oauth/logout', }); }; ``` Note that Microsoft Entra ID may show a "You have signed out" page instead of redirecting back to your app. In this case, the user needs to close the browser manually, which results in a `USER_CANCELED` error even though the logout was successful. ## Accessing the Microsoft Graph API To call the [Microsoft Graph API](https://learn.microsoft.com/en-us/graph/overview), request the `User.Read` scope (or other scopes you need) during login and use the access token in your requests: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://login.microsoftonline.com/{tenant-id}/v2.0', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access', 'User.Read'], }); const response = await fetch('https://graph.microsoft.com/v1.0/me', { headers: { Authorization: `Bearer ${result.accessToken}`, }, }); const user = await response.json(); console.log('Display name:', user.displayName); console.log('Email:', user.mail); }; ``` ## Conclusion In this guide, we covered how to set up Microsoft Entra ID authentication in a Capacitor app using the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin. From app registration and redirect URI configuration to sign-in, token refresh, and Microsoft Graph API access, the plugin handles the complexity of the OAuth flow so you can focus on building your application. Explore the complete [API Reference](https://capawesome.io/plugins/oauth/#api) to see all available methods and options. If you're using Okta instead, check out [How to Sign In with Okta Using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-okta-using-capacitor/index.md). Have suggestions or questions? [Create an issue](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) and subscribe to our [newsletter](https://cloud.capawesome.io/newsletter/) for the latest updates. # How to Sign In with Google Using Capacitor Google Sign-In is one of the most widely used authentication methods in mobile and web apps. It lets users sign in with their existing Google account in just a few taps, avoiding the need to create and remember yet another password. Whether you're building with Ionic or another framework, the [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) plugin provides a simple way to integrate Google Sign-In into your Capacitor app on Android, iOS, and web. This guide walks you through creating the required credentials, configuring the plugin for each platform, and implementing the sign-in flow. ## Prerequisites Before you begin, make sure you have the following: - A **Google account** with access to the [Google Cloud Console](https://console.cloud.google.com/). - A **Capacitor app** with the [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/google-sign-in/#installation) section in the plugin documentation. ## Creating Google API Credentials To use Google Sign-In, you need to create a Google Cloud project and set up OAuth client IDs for each platform you want to support. ### Creating a Google Cloud Project 1. Go to the [Google Cloud Console](https://console.cloud.google.com/). 1. Click the project selector in the top navigation bar and select **New Project**. 1. Enter a project name (e.g. `My Capacitor App`) and click **Create**. 1. Make sure the newly created project is selected in the project selector. ### Configuring the OAuth Consent Screen Before creating client IDs, you need to configure the OAuth consent screen. This is the screen users see when they sign in with Google for the first time. 1. In the Google Cloud Console, navigate to **APIs & Services** > **OAuth consent screen**. 1. Select a **User Type**: - **Internal**: Only available if you have a Google Workspace account. Limits sign-in to users within your organization. - **External**: Allows any Google account to sign in. You need to add test users while the app is in testing mode. 1. Click **Create** and fill in the required fields: - **App name**: The name shown to users during sign-in. - **User support email**: An email address users can contact for support. - **Developer contact information**: Your email address for Google to contact you. 1. Under **Scopes**, click **Add or Remove Scopes** and add the following scopes: - `openid` - `email` - `profile` 1. If you selected **External**, add your Google account email under **Test users** so you can test the sign-in flow. 1. Click **Save and Continue** to finish. ### Creating OAuth Client IDs You need to create a separate OAuth client ID for each platform. All client IDs are created under **APIs & Services** > **Credentials** > **Create Credentials** > **OAuth client ID**. #### Web Client ID The web client ID is required for all platforms. On Android, it serves as the server client ID that the Credential Manager API uses to request an ID token. On iOS, it is used as the server client ID to request a server auth code. 1. Select **Web application** as the application type. 1. Enter a name (e.g. `Web Client`). 1. Under **Authorized JavaScript origins**, add the origins where your web app is hosted (e.g. `http://localhost:3000` for local development). 1. Under **Authorized redirect URIs**, add the redirect URIs for your web app (e.g. `http://localhost:3000`). 1. Click **Create** and copy the **Client ID**. You will need this later. #### Android Client ID 1. Select **Android** as the application type. 1. Enter a name (e.g. `Android Client`). 1. Enter the **Package name** of your Android app. You can find this in your `android/app/build.gradle` file as the `applicationId` (e.g. `com.example.app`). 1. Enter the **SHA-1 certificate fingerprint**. To get the SHA-1 fingerprint for your debug keystore, run the following command in your project's `android` directory: ``` ./gradlew signingReport ``` Look for the `SHA1` value under the `debug` variant. Warning For production, you also need to add the SHA-1 fingerprint of your release signing key. If you use Google Play App Signing, you can find the upload key fingerprint in the Google Play Console under **Setup** > **App signing**. 1. Click **Create**. #### iOS Client ID 1. Select **iOS** as the application type. 1. Enter a name (e.g. `iOS Client`). 1. Enter the **Bundle ID** of your iOS app. You can find this in Xcode under your target's **General** tab (e.g. `com.example.app`). 1. Click **Create** and copy the **Client ID**. You will need this later. ## Configuring the Plugin ### Android No additional configuration is required on Android beyond installing the plugin. The plugin uses the AndroidX Credential Manager API and Google Play Services under the hood. ### iOS On iOS, you need to add your iOS client ID and a URL scheme to the `ios/App/App/Info.plist` file. Add the `GIDClientID` key with your iOS client ID: ``` GIDClientID YOUR_IOS_CLIENT_ID ``` Then add the reversed client ID as a URL scheme. This allows Google to redirect back to your app after authentication: ``` CFBundleURLTypes CFBundleURLSchemes com.googleusercontent.apps.YOUR_IOS_CLIENT_ID ``` Replace `YOUR_IOS_CLIENT_ID` with your actual iOS client ID from the Google Cloud Console. The URL scheme is the reversed form of the client ID (e.g. if your client ID is `123456789-abc.apps.googleusercontent.com`, the URL scheme is `com.googleusercontent.apps.123456789-abc`). ## Implementing the Sign-In Flow ### Initializing the Plugin Before calling any other method, you need to initialize the plugin using [`initialize(...)`](https://capawesome.io/plugins/google-sign-in/#initialize). Pass the **web client ID** you created earlier as the `clientId`: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const initialize = async () => { await GoogleSignIn.initialize({ clientId: 'YOUR_WEB_CLIENT_ID', }); }; ``` ### Signing In Use the [`signIn(...)`](https://capawesome.io/plugins/google-sign-in/#signin) method to start the Google Sign-In flow. The method returns a `SignInResult` object containing the user's profile information and an ID token: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const signIn = async () => { const result = await GoogleSignIn.signIn(); console.log('ID token:', result.idToken); console.log('User ID:', result.userId); console.log('Email:', result.email); console.log('Display name:', result.displayName); console.log('Image URL:', result.imageUrl); }; ``` The `idToken` is a JWT that you can send to your backend to verify the user's identity (see [Bonus: Verifying the ID Token on the Backend](#bonus-verifying-the-id-token-on-the-backend) below). ### Handling the Redirect Callback (Web) On the web, the [`signIn(...)`](https://capawesome.io/plugins/google-sign-in/#signin) method redirects the user to Google's authorization page. After the user authenticates, Google redirects them back to your app. You need to call [`handleRedirectCallback()`](https://capawesome.io/plugins/google-sign-in/#handleredirectcallback) on page load to complete the sign-in flow and retrieve the result: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; import { Capacitor } from '@capacitor/core'; const handleRedirectCallback = async () => { // Only handle the redirect callback on the web platform if (Capacitor.getPlatform() !== 'web') { return; } // This will return null if there is no redirect result to handle const result = await GoogleSignIn.handleRedirectCallback(); console.log('ID token:', result.idToken); console.log('User ID:', result.userId); console.log('Email:', result.email); }; ``` Call this method when your app loads after a redirect. On Android and iOS, the sign-in flow is handled natively and [`signIn(...)`](https://capawesome.io/plugins/google-sign-in/#signin) returns the result directly, so this method is only needed on the web. ### Requesting Additional Scopes By default, the plugin only performs authentication and returns an ID token. If you need to access Google APIs on behalf of the user, you can request additional OAuth scopes by passing them during initialization: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const initialize = async () => { await GoogleSignIn.initialize({ clientId: 'YOUR_WEB_CLIENT_ID', scopes: ['https://www.googleapis.com/auth/userinfo.profile'], }); }; const signIn = async () => { const result = await GoogleSignIn.signIn(); console.log('Access token:', result.accessToken); console.log('Server auth code:', result.serverAuthCode); }; ``` When scopes are configured, the sign-in result includes an `accessToken` for making API calls and a `serverAuthCode` (Android and iOS only) that your backend can exchange for long-lived access and refresh tokens. ### Signing Out Use the [`signOut()`](https://capawesome.io/plugins/google-sign-in/#signout) method to sign out the current user: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const signOut = async () => { await GoogleSignIn.signOut(); }; ``` ## Bonus: Verifying the ID Token on the Backend The `idToken` returned by [`signIn(...)`](https://capawesome.io/plugins/google-sign-in/#signin) is a JSON Web Token (JWT) signed by Google. While you can decode it on the client to read the user's profile claims, you should always verify it on your backend before trusting the information. This ensures the token was actually issued by Google and hasn't been tampered with. To verify the token, send it to your backend and validate it against Google's public keys. Here's an example using the [google-auth-library](https://github.com/googleapis/google-auth-library-nodejs) for Node.js: ``` import { OAuth2Client } from 'google-auth-library'; const client = new OAuth2Client('YOUR_WEB_CLIENT_ID'); const verifyIdToken = async (idToken: string) => { const ticket = await client.verifyIdToken({ idToken, audience: 'YOUR_WEB_CLIENT_ID', }); const payload = ticket.getPayload(); console.log('User ID:', payload?.sub); console.log('Email:', payload?.email); console.log('Name:', payload?.name); }; ``` The `audience` parameter should match the client ID that was used to obtain the token. If your app uses different client IDs for different platforms, you can pass all of them as an array. ## Conclusion In this guide, we covered how to set up Google Sign-In in a Capacitor app using the [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) plugin. From creating OAuth credentials in the Google Cloud Console and configuring platform-specific settings to implementing the sign-in flow and verifying tokens on the backend, the plugin handles the complexity across Android, iOS, and web so you can focus on building your app. Explore the complete [API Reference](https://capawesome.io/plugins/google-sign-in/#api) to see all available methods and options. Have suggestions or questions? [Create an issue](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) in our [GitHub repository](https://github.com/capawesome-team/capacitor-plugins). Stay connected with us on [X](https://x.com/capawesomeio) and subscribe to our [newsletter](https://cloud.capawesome.io/newsletter/) for the latest updates. # How to Sign In with Okta Using Capacitor [Okta](https://www.okta.com/) is a widely used identity platform that powers single sign-on (SSO) and user management for thousands of organizations. If you need to add Okta authentication to a Capacitor app, the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin supports the Authorization Code flow with PKCE out of the box. In this guide, you'll learn how to register your app in Okta, implement sign-in and sign-out, manage tokens, and retrieve user profile data on Android, iOS, and web. This is also a great alternative to [Ionic Auth Connect](https://ionic.io/docs/auth-connect/okta) for teams looking for a lightweight, open approach. ## Prerequisites Before getting started, make sure you have: - An **Okta developer account**. You can [sign up for a free Okta developer account](https://developer.okta.com/signup/) if you don't already have one. - A **Capacitor app** with the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin installed. For installation instructions, head over to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. ## Setting Up Okta ### Creating an Application To get started, you need to register a new application in the Okta Admin Console: 1. Sign in to your [Okta Admin Console](https://login.okta.com/). 1. Go to **Applications** > **Applications** and click **Create App Integration**. 1. Select **OIDC - OpenID Connect** as the sign-in method. 1. Choose **Native Application** as the application type and click **Next**. 1. Give your application a **Name** (e.g. `My Capacitor App`). 1. Under **Grant type**, make sure **Authorization Code** and **Refresh Token** are selected. 1. Configure the **Sign-in redirect URIs** and **Sign-out redirect URIs** (see sections below). 1. Under **Assignments**, choose the appropriate controlled access option for your use case. 1. Click **Save**. 1. On the application's **General** tab, note the **Client ID** and your **Okta domain** (e.g. `dev-123456.okta.com`). You'll need both values later. ### Configuring Sign-in Redirect URIs Add a redirect URI for each platform your app supports. You can add multiple URIs in the **Sign-in redirect URIs** section of your application settings. **Android and iOS**: Use a custom scheme based on your app's package name or bundle identifier: ``` com.example.app://oauth/callback ``` **Web**: Use your local development URL: ``` http://localhost:3000/oauth/callback ``` ### Configuring Sign-out Redirect URIs Similarly, configure the **Sign-out redirect URIs** so Okta knows where to send users after they log out. **Android and iOS**: ``` com.example.app://oauth/logout ``` **Web**: ``` http://localhost:3000/oauth/logout ``` ## Implementing Authentication In the examples below, replace `{okta-domain}` with your Okta domain (e.g. `dev-123456.okta.com`) and `{client-id}` with your application's **Client ID**. ### Signing In Kick off the OAuth flow using the [`login(...)`](https://capawesome.io/plugins/oauth/#login) method. The plugin fetches Okta's OpenID Connect discovery document automatically and takes care of the PKCE challenge: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://{okta-domain}/oauth2/default', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); console.log('Access token:', result.accessToken); console.log('ID token:', result.idToken); console.log('Refresh token:', result.refreshToken); }; ``` Adding `offline_access` to the scopes ensures you receive a refresh token. ### Handling the Redirect Callback (Web) On the web, [`login(...)`](https://capawesome.io/plugins/oauth/#login) redirects the user to Okta's hosted login page. Once the user authenticates, Okta redirects them back to your app. Call [`handleRedirectCallback()`](https://capawesome.io/plugins/oauth/#handleredirectcallback) when the page loads to finish the token exchange: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; import { Capacitor } from '@capacitor/core'; const handleRedirectCallback = async () => { if (Capacitor.getPlatform() !== 'web') { return; } const url = new URL(window.location.href); if (!url.searchParams.has('code')) { return; } const result = await Oauth.handleRedirectCallback(); console.log('Access token:', result.accessToken); }; handleRedirectCallback(); ``` On Android and iOS, the redirect is handled natively, so this step only applies to the web. ### Refreshing the Access Token Access tokens are short-lived by design. Use the [`refreshToken(...)`](https://capawesome.io/plugins/oauth/#refreshtoken) method to obtain a fresh access token without prompting the user to sign in again: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const refreshToken = async () => { const result = await Oauth.refreshToken({ issuerUrl: 'https://{okta-domain}/oauth2/default', clientId: '{client-id}', refreshToken: 'YOUR_REFRESH_TOKEN', }); console.log('New access token:', result.accessToken); }; ``` ### Decoding the ID Token Extract user profile claims from the ID token with the [`decodeIdToken(...)`](https://capawesome.io/plugins/oauth/#decodeidtoken) method: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const decodeIdToken = async () => { const result = await Oauth.decodeIdToken({ token: 'YOUR_ID_TOKEN', }); console.log('Name:', result.payload.name); console.log('Email:', result.payload.email); }; ``` The token is decoded locally on the device. If you need server-side verification, validate the JWT on your backend instead. ### Signing Out Terminate the session by calling the [`logout(...)`](https://capawesome.io/plugins/oauth/#logout) method: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const logout = async () => { await Oauth.logout({ issuerUrl: 'https://{okta-domain}/oauth2/default', idToken: 'YOUR_ID_TOKEN', postLogoutRedirectUrl: 'com.example.app://oauth/logout', }); }; ``` ## Fetching the User Profile You can also retrieve the authenticated user's profile directly from Okta's `/userinfo` endpoint using the access token: ``` import { Oauth } from '@capawesome-team/capacitor-oauth'; const login = async () => { const result = await Oauth.login({ issuerUrl: 'https://{okta-domain}/oauth2/default', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email', 'offline_access'], }); const response = await fetch( 'https://{okta-domain}/oauth2/default/v1/userinfo', { headers: { Authorization: `Bearer ${result.accessToken}`, }, }, ); const user = await response.json(); console.log('Name:', user.name); console.log('Email:', user.email); }; ``` ## Try Capawesome Cloud If you're building Capacitor apps, [Capawesome Cloud](https://cloud.capawesome.io/) can help you ship faster with cloud-based native builds, over-the-air updates, and more. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion In this guide, you learned how to integrate Okta authentication into a Capacitor app using the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin. We covered creating an Okta application, configuring redirect URIs, signing users in and out, refreshing tokens, and fetching profile data. The plugin handles the OIDC and PKCE details under the hood, so you can focus on your app. For more details, check out the full [API Reference](https://capawesome.io/plugins/oauth/#api) for all available methods and options. You might also find the [How to Sign In with Azure Entra ID Using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-azure-entra-id-using-capacitor/index.md) guide helpful if you need to support multiple identity providers. If you have questions or run into issues, feel free to [create an issue](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose) on [GitHub](https://github.com/capawesome-team/capacitor-plugins) or join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to Upgrade Your Capacitor App to Capacitor 8 Capacitor 8 ships with Swift Package Manager as the default for iOS, targets Android SDK 36, and requires Node.js 22+. Whether you prefer an automated CLI migration, an AI-assisted upgrade, or full manual control, this guide walks you through all three approaches so you can pick the one that fits your project best. ## Why Upgrade to Capacitor 8 Before diving into the how, here's why the upgrade is worth it: - **Swift Package Manager by default**: New iOS projects use SPM instead of CocoaPods, aligning with Apple's direction for dependency management. CocoaPods [entered maintenance mode](https://blog.cocoapods.org/CocoaPods-Specs-Repo-Rate-Limiting/) in August 2024 and its Specs repository will become read-only in December 2026. - **Android SDK 36**: Targets the latest Android platform with updated Gradle tooling. - **Node.js 22+**: Requires the current LTS version, keeping your toolchain up to date. - **New System Bars plugin**: Replaces the removed `adjustMarginsForEdgeToEdge` config option with a proper core plugin for managing system bar insets via CSS `env` variables. - **Updated platform tooling**: Xcode 26+ and Android Studio Otter (2025.2.1+) are now required. ## What Changes at a Glance Here's a summary of the most impactful breaking changes. For the full list, see the [official upgrade guide](https://capacitorjs.com/docs/updating/8-0). ### iOS - **SPM is the default** for new projects. To use CocoaPods instead, run `npx cap add ios --packagemanager CocoaPods`. - iOS deployment target raised to **15.0**. - Requires **Xcode 26.0+**. ### Android - Minimum SDK raised to **24**, compile and target SDK updated to **36**. - Gradle wrapper updated to **8.14.3**, AGP to **8.13.0**. - `bridge_layout_main.xml` renamed to `capacitor_bridge_layout_main.xml`. - `density` must be added to `configChanges` in `AndroidManifest.xml`. - Updated `variables.gradle` values: ``` minSdkVersion = 24 compileSdkVersion = 36 targetSdkVersion = 36 ``` ### Config - `android.adjustMarginsForEdgeToEdge` has been removed. Use the new System Bars core plugin instead. ### Node.js - **Node.js 22** or greater is required. ## Upgrade Options There are three ways to upgrade — pick the one that best fits your project and workflow. ### Option 1: Upgrade Using the Capacitor CLI The fastest way to upgrade is the built-in `migrate` command. It updates your dependencies, adjusts platform configuration files, and handles most of the heavy lifting automatically. ``` npm i -D @capacitor/cli@latest npx cap migrate ``` The CLI will walk you through the migration interactively — updating `package.json` dependencies, modifying native project files, and prompting you where manual intervention is needed. This works well for most projects. However, if your app has significant native customizations (custom build scripts, manual Gradle modifications, or non-standard project structures), you may need to handle some steps manually after the migration. Check the CLI output carefully for any warnings or skipped steps. ### Option 2: Upgrade Using Capawesome Skills [Capawesome Skills](https://github.com/capawesome-team/skills) are AI agent tools designed to automate development tasks for Capacitor projects. They work with AI coding assistants like Claude Code, Cursor, or GitHub Copilot and provide step-by-step upgrade instructions that are optimized for machine-actionable execution. First, add the skills to your project: ``` npx skills add capawesome-team/skills ``` Then, use the following prompt with your AI coding assistant: ``` Use the `capacitor-app-upgrades` skill from `capawesome-team/skills` to help me upgrade my Capacitor app to Capacitor 8. ``` The skill will attempt the automated upgrade first and fall back to manual steps where needed. It covers both the core Capacitor upgrade and the configuration of 160+ Capacitor plugins, making it a good choice for projects with many dependencies. ### Option 3: Upgrade Manually If you prefer full control over every change, you can follow the [official Capacitor 8 upgrade guide](https://capacitorjs.com/docs/updating/8-0) step by step. Here's a condensed overview of the key steps: 1. Update Node.js to version 22+. 1. Install the latest Capacitor packages: ``` npm i @capacitor/cli@latest @capacitor/core@latest @capacitor/ios@latest @capacitor/android@latest ``` 1. **iOS**: Update Xcode to 26.0+, set the deployment target to 15.0, and update the `Podfile` platform accordingly. 1. **Android**: Update Android Studio to Otter (2025.2.1+), run the AGP Upgrade Assistant, update `variables.gradle` with the new SDK and dependency versions, update the Gradle wrapper to 8.14.3, and add `density` to `configChanges` in `AndroidManifest.xml`. 1. Update all official and third-party Capacitor plugins to their Capacitor 8–compatible versions. 1. Replace any usage of `adjustMarginsForEdgeToEdge` with the new System Bars core plugin. This approach takes more effort but gives you the most visibility into exactly what changes in your project. ## Which Approach Should You Choose? | Approach | Best for | Trade-off | | --------------------- | --------------------------------------------------- | -------------------------------------------------------- | | **Capacitor CLI** | Most projects with standard setups | Fast, but may skip steps in heavily customized projects | | **Capawesome Skills** | Projects with many plugins or native customizations | Handles edge cases well, requires an AI coding assistant | | **Manual** | Teams that want full control over every change | Most effort, but complete visibility | For most teams, starting with the **Capacitor CLI** is the right call. If you run into issues or have a complex project with many plugins, **Capawesome Skills** can help fill in the gaps. The **manual** approach is there when you need it, but it's rarely necessary to do everything by hand. ## Try Capawesome Cloud Building and deploying Capacitor apps is easier with [Capawesome Cloud](https://cloud.capawesome.io/). Get cloud-based native builds, over-the-air live updates, and automated app store publishing — all in one platform. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Capacitor 8 brings meaningful platform updates — SPM as the iOS default, Android SDK 36 targeting, and a cleaner approach to system bar insets. Upgrading doesn't have to be painful: the CLI handles most of the work, Capawesome Skills can assist with complex projects, and the manual route is always available as a fallback. If you're upgrading a Capacitor plugin, check out [How to Upgrade Your Capacitor Plugin to Capacitor 8](https://capawesome.io/blog/how-to-upgrade-your-capacitor-plugin-to-capacitor-8/index.md) for a full walkthrough of the plugin-level migration. If you're also using Capawesome plugins, check out [Updating to Capacitor 8.0](https://capawesome.io/blog/updating-to-capacitor-8/index.md) for a breakdown of plugin-specific breaking changes. If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to Upgrade Your Capacitor Plugin to Capacitor 8 Capacitor 8 makes Swift Package Manager the default on iOS, bumps the Android target to SDK 36, and upgrades to Kotlin 2.2. This guide covers three ways to bring your Capacitor plugin up to date — an automated CLI tool, AI-powered Capawesome Skills, or a fully manual upgrade. ## Why Upgrade to Capacitor 8 Before jumping into the migration steps, here's why the upgrade matters for plugin authors: - **Swift Package Manager by default**: New iOS projects use SPM instead of CocoaPods. CocoaPods [entered maintenance mode](https://blog.cocoapods.org/CocoaPods-Specs-Repo-Rate-Limiting/) in August 2024 and its Specs repository will become read-only in December 2026. Updating your plugin's `Package.swift` ensures compatibility with SPM-based projects. - **Android SDK 36**: Targeting the latest Android platform keeps your plugin compatible with apps that follow Google's target API requirements. - **Kotlin 2.2**: The jump from Kotlin 1.9 to 2.2 is a major upgrade that brings performance improvements and language features, but also includes breaking changes like the removal of the `kotlin-android-extensions` plugin. - **Edge-to-edge support**: Capacitor 8 introduces a new System Bars plugin for managing status and navigation bar insets, replacing the removed `adjustMarginsForEdgeToEdge` config option. - **Updated platform tooling**: Xcode 26+ and Android Studio Otter (2025.2.1+) are now required. ## What Changes at a Glance Here's a summary of the most impactful changes for plugin authors. For the full list, see the [official plugin upgrade guide](https://capacitorjs.com/docs/updating/plugins/8-0). ### Dependencies - Update `@capacitor/core` peer dependency to `>=8.0.0`. - Update `@capacitor/cli`, `@capacitor/core`, `@capacitor/android`, and `@capacitor/ios` dev dependencies to `^8.0.0`. ### Android - Compile and target SDK updated to **36**, minimum SDK raised to **24**. - Gradle wrapper updated to **8.14.3**, Android Gradle Plugin (AGP) to **8.13.0**. - Kotlin updated to **2.2.20**. - **Gradle syntax change**: The space-assignment syntax is deprecated. Properties like `compileSdk 36` must be written as `compileSdk = 36`. - Replace the deprecated `kotlinOptions{}` block with the new `compilerOptions{}` API. - Various AndroidX dependency version bumps (AppCompat, JUnit, Espresso, and others). ### iOS - Deployment target raised to **15.0** in `.podspec` files and `Podfile`. - Update `Package.swift` to specify `.iOS(.v15)` and change the Capacitor SPM dependency from version 7.0.0 to 8.0.0. ## Upgrade Options There are three ways to upgrade — pick the one that best fits your project and workflow. ### Option 1: Upgrade Using the Capacitor CLI The fastest way to upgrade your plugin is the official migration tool. Run the following command from your plugin's root directory: ``` npx @capacitor/plugin-migration-v7-to-v8@latest ``` The tool automatically updates your `package.json` dependencies, adjusts `build.gradle` values, updates the Gradle wrapper, bumps the iOS deployment target, and applies the Kotlin version change. This works well for most plugins with a standard project structure. If your plugin has significant native customizations — custom Gradle configurations, non-standard directory layouts, or complex build scripts — you may need to handle some steps manually after the migration. Check the tool's output for any warnings or skipped steps. ### Option 2: Upgrade Using Capawesome Skills [Capawesome Skills](https://github.com/capawesome-team/skills) are agent-optimized instruction sets designed for AI coding assistants like Claude Code, Cursor, or GitHub Copilot. They provide step-by-step upgrade instructions that handle both the automated migration and manual fallback steps. First, add the skills to your project: ``` npx skills add capawesome-team/skills ``` Then, use the following prompt with your AI coding assistant: ``` Use the `capacitor-plugin-upgrades` skill from `capawesome-team/skills` to help me upgrade my Capacitor plugin to Capacitor 8. ``` The skill will attempt the CLI migration first and fall back to manual steps where needed. It also covers edge cases like adding SPM support, updating Kotlin compiler options, and handling the Gradle syntax changes — making it a good option if the CLI tool doesn't catch everything. ### Option 3: Upgrade Manually If you prefer full control, follow the [official Capacitor 8 plugin upgrade guide](https://capacitorjs.com/docs/updating/plugins/8-0) step by step. Here's a condensed overview: 1. Update `@capacitor/core` peer dependency to `>=8.0.0` and dev dependencies to `^8.0.0`. 1. **Android**: Update `compileSdk`, `targetSdkVersion`, and `minSdkVersion` in `build.gradle`: ``` compileSdk = 36 minSdkVersion = 24 targetSdkVersion = 36 ``` 1. **Android**: Update Kotlin to `2.2.20` and replace `kotlinOptions{}` with `compilerOptions{}`. 1. **Android**: Update the Gradle wrapper to `8.14.3` and AGP to `8.13.0`. 1. **Android**: Switch to the `=` assignment syntax in all Gradle files. 1. **Android**: Update AndroidX dependency versions (only the ones your plugin uses). 1. **iOS**: Raise the deployment target to `15.0` in your `.podspec` file. 1. **iOS**: Update `Package.swift` to target `.iOS(.v15)` and set the Capacitor SPM dependency to `8.0.0`. This approach takes more effort but gives you complete visibility into every change. ## Which Approach Should You Choose? | Approach | Best for | Trade-off | | --------------------- | ------------------------------------------------- | -------------------------------------------------------- | | **Capacitor CLI** | Most plugins with standard project structures | Fast, but may miss steps in heavily customized plugins | | **Capawesome Skills** | Plugins with custom native code or complex setups | Handles edge cases well, requires an AI coding assistant | | **Manual** | Plugin authors who want full control | Most effort, but complete visibility | For most plugins, the **Capacitor CLI** migration tool is the right starting point. If you run into issues or have a complex plugin with custom build configurations, **Capawesome Skills** can fill in the gaps. The **manual** approach is always available when you need full control. ## Try Capawesome Cloud Building and deploying Capacitor apps is easier with [Capawesome Cloud](https://cloud.capawesome.io/). Get cloud-based native builds, over-the-air live updates, and automated app store publishing — all in one platform. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Capacitor 8 brings meaningful updates for plugin authors — SPM as the iOS default, Android SDK 36, Kotlin 2.2, and modernized Gradle syntax. The CLI migration tool handles most of the work, Capawesome Skills can assist with more complex plugins, and the manual route gives you full control when you need it. If you're also upgrading a Capacitor app project, check out [How to Upgrade Your Capacitor App to Capacitor 8](https://capawesome.io/blog/how-to-upgrade-your-capacitor-app-to-capacitor-8/index.md) for a full walkthrough of the app-level migration. If you have questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to Use Better Auth in Ionic and Capacitor Apps [Better Auth](https://www.better-auth.com/) is an open-source, framework-agnostic authentication framework for TypeScript. Whether you're building with Ionic, Angular, React, or Vue, the Better Auth JavaScript SDK works out of the box in your Capacitor app — no special configuration needed. Since Capacitor apps are essentially web apps running in a native WebView, you can use the SDK directly for standard authentication flows. You only need Capacitor plugins when it comes to social login flows that require native functionality, like opening a browser window or using platform-specific sign-in APIs. This guide shows you how to set up mobile authentication with Better Auth in your Capacitor app using the [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md), [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md), and [OAuth](https://capawesome.io/plugins/oauth/index.md) plugins. ## Prerequisites Before you begin, make sure you have the following: - A **Better Auth backend** set up and running. If you don't have one yet, follow the [official installation guide](https://www.better-auth.com/docs/installation) to get started. - A **Capacitor app** ready for development. ## Setting Up the Auth Client First, install the Better Auth client SDK in your Capacitor app: ``` npm install better-auth ``` Then create an auth client instance pointing to your Better Auth backend: ``` import { createAuthClient } from "better-auth/client"; const authClient = createAuthClient({ baseURL: "https://api.example.com", }); ``` That's it. Since your Capacitor app runs in a WebView, the Better Auth client works exactly the same as in any other web application. ## Email and Password Authentication Email and password authentication works without any additional plugins. The Better Auth client handles everything over HTTP, which the WebView supports natively. ### Signing Up Use `authClient.signUp.email()` to create a new account: ``` const { data, error } = await authClient.signUp.email({ name: "John Doe", email: "john.doe@example.com", password: "password1234", image: "https://example.com/image.png", callbackURL: "https://example.com/callback", }); ``` ### Signing In Use `authClient.signIn.email()` to sign in with an existing account: ``` const { data, error } = await authClient.signIn.email({ email: "john.doe@example.com", password: "password1234", }); ``` Both methods return a `{ data, error }` object, making it easy to handle success and failure cases in your UI. ## Social Sign-In with Native Plugins While email and password authentication works directly through the SDK, social login providers like Apple, Google, or Microsoft typically require opening a browser window or using platform-specific APIs for mobile authentication. This is where Capacitor plugins come in. The approach is simple: use a Capacitor plugin to handle the native social login flow and obtain an ID token, then pass that token to Better Auth's `signIn.social()` method. This gives you a native sign-in experience while Better Auth manages your users and sessions on the server. ### Apple Sign-In The [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md) plugin provides native Sign in with Apple on Android, iOS, and web. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/apple-sign-in/#installation) section in the plugin documentation. Use [`signIn(...)`](https://capawesome.io/plugins/apple-sign-in/#signin) to obtain an ID token and pass it to Better Auth: ``` import { AppleSignIn, SignInScope } from "@capawesome/capacitor-apple-sign-in"; const signInWithApple = async () => { // 1. Sign in with Apple using the plugin to get an ID token const { idToken } = await AppleSignIn.signIn({ redirectUrl: 'https://example.com/callback', // Only required on Android and Web scopes: [SignInScope.Email, SignInScope.Name], }); // 2. Pass the ID token to Better Auth to sign in or create the user await authClient.signIn.social({ provider: "apple", idToken: { token: idToken, }, }); }; ``` The plugin handles the native Apple sign-in dialog on iOS and falls back to a web-based flow on Android and web. The returned `idToken` is a JWT that Better Auth verifies server-side to create or sign in the user. ### Google Sign-In The [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md) plugin provides native Google Sign-In on Android, iOS, and web. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/google-sign-in/#installation) section in the plugin documentation. Use [`signIn(...)`](https://capawesome.io/plugins/google-sign-in/#signin) to obtain an ID token and pass it to Better Auth: ``` import { GoogleSignIn } from "@capawesome/capacitor-google-sign-in"; const signInWithGoogle = async () => { // 1. Sign in with Google using the plugin to get an ID token const { idToken } = await GoogleSignIn.signIn({ redirectUrl: 'https://example.com/callback', // Only required on Android and Web scopes: ["profile", "email"], }); // 2. Pass the ID token to Better Auth to sign in or create the user await authClient.signIn.social({ provider: "google", idToken: { token: idToken, }, }); }; ``` On Android, the plugin uses the Credential Manager API for a seamless sign-in experience. On iOS, it uses the Google Sign-In SDK. The returned `idToken` is passed to Better Auth just like with Apple. ### Other Providers with the OAuth Plugin For providers that don't have a dedicated plugin — like Microsoft, LinkedIn, Facebook, or PayPal — you can use the [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin. It supports any OAuth 2.0 / OpenID Connect provider using the Authorization Code flow with PKCE. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/oauth/#installation) section in the plugin documentation. Here's an example of signing in with Microsoft using [`login(...)`](https://capawesome.io/plugins/oauth/#login): ``` import { Oauth } from "@capawesome-team/capacitor-oauth"; const signInWithMicrosoft = async () => { // 1. Use the OAuth plugin to sign in with Microsoft and get an ID token const result = await Oauth.login({ issuerUrl: 'https://login.microsoftonline.com/{tenant-id}/v2.0', clientId: '{client-id}', redirectUrl: 'com.example.app://oauth/callback', scopes: ['openid', 'profile', 'email'], }); // 2. Pass the ID token to Better Auth to sign in or create the user await authClient.signIn.social({ provider: "microsoft", idToken: { token: result.idToken, }, }); }; ``` The [OAuth](https://capawesome.io/plugins/oauth/index.md) plugin handles the browser-based authorization flow and returns an ID token, which you then pass to Better Auth. This same approach works for any provider that Better Auth supports, including LinkedIn, Facebook, PayPal, and [many more](https://www.better-auth.com/docs/authentication/social). ## Stay Updated If you want to stay up to date with the latest news and updates from Capawesome, subscribe to the newsletter: [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion Integrating Better Auth into a Capacitor app is straightforward. Since Capacitor apps run in a WebView, the Better Auth JavaScript SDK works out of the box for authentication flows like email and password. For social sign-in, the [Apple Sign-In](https://capawesome.io/plugins/apple-sign-in/index.md), [Google Sign-In](https://capawesome.io/plugins/google-sign-in/index.md), and [OAuth](https://capawesome.io/plugins/oauth/index.md) plugins handle the native parts and return ID tokens that Better Auth verifies server-side — giving you a seamless authentication experience across Android, iOS, and web. If you want to learn more about OAuth-based authentication in Capacitor apps, check out the guide on [How to Sign In with Auth0 Using Capacitor](https://capawesome.io/blog/how-to-sign-in-with-auth0-using-capacitor/index.md). If you have any questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) and connect with the community. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to Use CocoaPods Instead of SPM with Capacitor Capacitor 8 made Swift Package Manager (SPM) the default dependency manager for new iOS projects. While SPM is the future of iOS dependency management, it still has some rough edges that can block real-world projects. In those cases, CocoaPods remains a solid alternative. This post covers when you might want to stick with CocoaPods and how to set it up. ## Why SPM Is the Default When the Ionic team [announced Capacitor 8](https://ionic.io/blog/announcing-capacitor-8), one of the headline changes was adopting SPM as the default package manager for iOS. This aligns with the broader iOS ecosystem trend: CocoaPods [entered maintenance mode](https://blog.cocoapods.org/CocoaPods-Specs-Repo-Rate-Limiting/) in August 2024, and its Specs repository will become read-only in December 2026. Apple has been pushing SPM as the official replacement, and most first-party frameworks already ship with SPM support. For many projects, SPM works well out of the box. But SPM is still maturing, and there are cases where it falls short. ## When CocoaPods Is the Better Choice Here are two real-world examples where SPM limitations can cause issues in Capacitor projects: ### Package Identity Collisions SPM determines a package's identity based on its path or URL. When Capacitor generates the `Package.swift` for your project, this can lead to identity collisions where two unrelated packages end up with the same identity. Depending on the Xcode version, this may cause warnings or hard build errors. See [capawesome-team/capacitor-firebase#959](https://github.com/capawesome-team/capacitor-firebase/issues/959) for a concrete example. ### Missing Support for Package Traits Swift 6.1 introduced [Package Traits (SE-0450)](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0450-swiftpm-package-traits.md), which allow package authors to define optional, toggleable dependencies. A practical example: the Firebase Analytics SDK offers a variant with IDFA/Ad ID collection support and one without. CocoaPods handled this cleanly via subspecs, and SPM package traits are the intended replacement. However, since Capacitor generates the `Package.swift` on behalf of the developer, there is currently no way to pass traits to plugin dependencies. Until Capacitor adds support for this (tracked in [ionic-team/capacitor#8335](https://github.com/ionic-team/capacitor/issues/8335)), projects that need to toggle SDK features like IDFA support are better off using CocoaPods. ### Other Considerations Beyond these specific issues, there are a few more reasons to prefer CocoaPods: - **Third-party plugin support**: Not all Capacitor plugins have added SPM support yet. If a plugin you depend on only ships a `.podspec`, you need CocoaPods. - **Mixed Objective-C/Swift plugins**: SPM does not allow mixing Objective-C and Swift in the same target. Plugins that rely on this pattern require CocoaPods. - **You cannot mix SPM and CocoaPods**: This is a hard constraint. If even one of your dependencies requires CocoaPods, your entire project needs to use CocoaPods. ## Setting Up CocoaPods Using CocoaPods with Capacitor 8 is straightforward. When adding the iOS platform to your project, pass the `--packagemanager` flag with the value `cocoapods`: ``` npx cap add ios --packagemanager cocoapods ``` That's it. Capacitor will generate the iOS project with a `Podfile` instead of a `Package.swift`, and all plugin dependencies will be resolved through CocoaPods. If you already have an iOS project using SPM and want to switch to CocoaPods, you'll need to remove the existing iOS project first and re-add it: ``` npx cap rm ios npx cap add ios --packagemanager cocoapods ``` Make sure to back up any custom native code before removing the platform. ## Try Capawesome Cloud If you're building Capacitor apps, [Capawesome Cloud](https://cloud.capawesome.io/) can help you ship faster with cloud-based native builds, over-the-air updates, and more. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion SPM is the direction Apple and the Capacitor team are heading, and it's the right default for most new projects. But it's still evolving, and edge cases like package identity collisions and missing package trait support can be real blockers. When you hit one of these issues, CocoaPods is a proven fallback that works reliably with Capacitor 8. If you haven't upgraded yet, check out [How to Upgrade Your Capacitor App to Capacitor 8](https://capawesome.io/blog/how-to-upgrade-your-capacitor-app-to-capacitor-8/index.md) for a full walkthrough of all migration approaches. If you have questions, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # How to Use Drizzle ORM with Capacitor and SQLite If you're building a Capacitor app that needs a local database, you've probably dealt with writing raw SQL strings and mapping results manually. It works, but it's error-prone and doesn't scale well. Drizzle ORM offers a better approach: a lightweight, type-safe ORM that lets you define your schema in TypeScript and write queries that feel like SQL — with full autocompletion and compile-time checks. In this guide, you'll learn how to set up Drizzle ORM with the [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md) using the new `@capawesome/capacitor-sqlite-drizzle` adapter. For the **Capacitor SQLite plugin** itself, see the [plugin documentation](https://capawesome.io/plugins/sqlite/#api). ## What is Drizzle ORM? [Drizzle ORM](https://orm.drizzle.team/) is a TypeScript ORM designed to be lightweight, type-safe, and close to SQL. Unlike heavier ORMs that abstract SQL away entirely, Drizzle uses a query API that mirrors SQL syntax. If you know SQL, you already know most of Drizzle. Here's what makes it a good fit for Capacitor apps: - **Type safety** — Your schema is defined in TypeScript, so queries are checked at compile time. No more runtime surprises from mistyped column names. - **No code generation** — Unlike some ORMs, Drizzle doesn't require a separate code generation step. Your schema files are regular TypeScript. - **SQL-like syntax** — Queries read like SQL, which makes them easy to understand and debug. - **Built-in migrations** — Drizzle Kit can generate and manage SQL migrations from your schema changes automatically. - **Lightweight** — Drizzle has zero runtime dependencies and a small bundle size, which matters for mobile apps. ## Prerequisites Before you begin, make sure you have a Capacitor project with the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Installation Install the Drizzle adapter along with Drizzle ORM: ``` npm install @capawesome/capacitor-sqlite-drizzle drizzle-orm ``` You'll also want to install Drizzle Kit as a dev dependency for schema migrations: ``` npm install -D drizzle-kit ``` ## Setting Up the Database To get started, open a database using the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and pass it to the `drizzle()` function: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; import { drizzle } from '@capawesome/capacitor-sqlite-drizzle'; const { databaseId } = await Sqlite.open({ path: 'my.db' }); const db = drizzle(Sqlite, { databaseId }); ``` The `drizzle()` function accepts the `Sqlite` plugin instance and a configuration object. The `databaseId` is the unique identifier returned by [`open(...)`](https://capawesome.io/plugins/sqlite/#open) and is required to route queries to the correct database. You can also pass additional Drizzle configuration options like `schema` and `logger`: ``` import * as schema from './schema'; const db = drizzle(Sqlite, { databaseId, schema, logger: true }); ``` Passing `schema` enables Drizzle's relational query API (more on that later), and `logger: true` logs all executed SQL statements to the console — useful during development. ## Defining a Schema Drizzle uses a schema-as-code approach. You define your tables as TypeScript objects, which Drizzle uses for type inference and query building. Create a `schema.ts` file in your project: ``` import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core'; export const users = sqliteTable('users', { id: integer('id').primaryKey({ autoIncrement: true }), name: text('name').notNull(), email: text('email').notNull().unique(), createdAt: integer('created_at', { mode: 'timestamp' }) .notNull() .$defaultFn(() => new Date()), }); export const posts = sqliteTable('posts', { id: integer('id').primaryKey({ autoIncrement: true }), title: text('title').notNull(), content: text('content'), authorId: integer('author_id') .notNull() .references(() => users.id), }); ``` A few things to note here: - `sqliteTable` defines a table with its columns and constraints. - Column types like `integer` and `text` map directly to SQLite types. - The `mode: 'timestamp'` option on `createdAt` tells Drizzle to automatically convert between JavaScript `Date` objects and integer timestamps. - `references(() => users.id)` creates a foreign key constraint linking `posts.authorId` to `users.id`. - `$defaultFn` sets a default value at the application level, not in the database. This schema definition serves as the single source of truth for both your TypeScript types and your database structure. ## Running Queries With the schema in place, you can run type-safe CRUD operations. All queries return promises and use the familiar SQL patterns. ### Insert ``` await db.insert(users).values({ name: 'Alice', email: 'alice@example.com', }); ``` ### Select ``` import { eq } from 'drizzle-orm'; // Select all users const allUsers = await db.select().from(users); // Select with a filter const user = await db .select() .from(users) .where(eq(users.email, 'alice@example.com')); ``` ### Update ``` await db .update(users) .set({ name: 'Bob' }) .where(eq(users.id, 1)); ``` ### Delete ``` await db.delete(users).where(eq(users.id, 1)); ``` Every query is fully typed. The `allUsers` variable, for example, is automatically inferred as an array of objects matching the `users` table schema — no manual type annotations needed. ## Relational Queries When you pass a `schema` to the `drizzle()` function, you unlock Drizzle's relational query API. This lets you load related data in a single query without writing manual joins: ``` const usersWithPosts = await db.query.users.findMany({ with: { posts: true }, }); ``` This returns all users along with their associated posts, based on the foreign key relationship defined in the schema. The result is fully typed and nested — each user object includes a `posts` array. You can also use `findFirst` to retrieve a single record: ``` const user = await db.query.users.findFirst({ where: eq(users.id, 1), with: { posts: true }, }); ``` ## Transactions For operations that need to succeed or fail together, use transactions. Drizzle sends `BEGIN`, `COMMIT`, and `ROLLBACK` statements automatically: ``` await db.transaction(async (tx) => { const [user] = await tx .insert(users) .values({ name: 'Alice', email: 'alice@example.com' }) .returning(); await tx .insert(posts) .values({ title: 'Hello World', content: '...', authorId: user.id }); }); ``` If any statement inside the callback throws an error, the entire transaction is rolled back. This is essential for maintaining data integrity, especially when inserting related records across multiple tables. ## Migrations Manually managing database schema changes with raw SQL is tedious and error-prone. Drizzle Kit solves this by generating SQL migration files from your schema changes. The adapter provides a `migrate()` function to apply these migrations at runtime. ### 1. Configure Drizzle Kit Create a `drizzle.config.ts` file in your project root: ``` import { defineConfig } from 'drizzle-kit'; export default defineConfig({ schema: './src/schema.ts', out: './src/drizzle', dialect: 'sqlite', driver: 'expo', }); ``` Note The `driver: 'expo'` setting is required. It tells Drizzle Kit to generate a bundled `migrations.js` file that works in non-Node environments like Capacitor. ### 2. Generate Migrations Whenever you change your schema, run the following command to generate migration files: ``` npx drizzle-kit generate ``` This creates SQL migration files and a `migrations.js` bundle in the output directory (e.g. `./src/drizzle/`). Note The generated `migrations.js` file imports `.sql` files as strings. Depending on your bundler, you may need a plugin to handle this. For Vite-based projects, use [`vite-plugin-plain-text`](https://www.npmjs.com/package/vite-plugin-plain-text). For Babel-based setups, use [`babel-plugin-inline-import`](https://www.npmjs.com/package/babel-plugin-inline-import). ### 3. Apply Migrations Import the generated migrations and apply them when your app starts: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; import { drizzle, migrate } from '@capawesome/capacitor-sqlite-drizzle'; import migrations from './drizzle/migrations'; const { databaseId } = await Sqlite.open({ path: 'my.db' }); const db = drizzle(Sqlite, { databaseId }); await migrate(Sqlite, databaseId, migrations); ``` Each migration runs inside its own transaction. The adapter automatically creates a `__drizzle_migrations` table to track which migrations have already been applied, so calling `migrate()` multiple times is safe — only pending migrations are executed. ## Stay Updated Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion With the `@capawesome/capacitor-sqlite-drizzle` adapter, you can use Drizzle ORM's type-safe queries, schema-as-code approach, and automated migrations in your Capacitor apps. The setup is straightforward: define your schema in TypeScript, generate migrations with Drizzle Kit, and run queries using a familiar SQL-like API — all without sacrificing type safety. **Resources and related readings:** - [Adapter on GitHub](https://github.com/capawesome-team/capacitor-sqlite-drivers/tree/main/packages/drizzle) - [API Reference](https://capawesome.io/plugins/sqlite/#api) - [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) - For decorator-based ORMs, make sure to read [TypeORM](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-and-sqlite/index.md) - For a query-builder approach check [Kysely](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/index.md) Join the Capawesome [Discord](https://discord.gg/VCXxSVjefW) server for questions and subscribe to the Capawesome [newsletter](https://cloud.capawesome.io/newsletter) to stay updated. # How to Use Kysely with Capacitor and SQLite Working with raw SQL in a Capacitor app gets messy fast — queries are just strings, results are untyped, and refactoring a column name means hunting through your entire codebase. Kysely solves this with a type-safe query builder that catches errors at compile time while keeping you close to SQL. In this guide, you'll learn how to set up Kysely with the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin using the new `@capawesome/capacitor-sqlite-kysely` dialect. For the **Capacitor SQLite plugin** API, see the [plugin documentation](https://capawesome.io/plugins/sqlite/#api). ## What is Kysely? [Kysely](https://kysely.dev/) (pronounced "Key-seh-lee") is a type-safe TypeScript SQL query builder. It's not a traditional ORM that hides SQL behind abstract methods — instead, it gives you a fluent API that maps directly to SQL, with full type inference at every step. Here's what makes it a good fit for Capacitor apps: - **Type safety** — Queries are validated against your database types at compile time. If you reference a column that doesn't exist, TypeScript catches it before the code runs. - **SQL-first** — The API mirrors SQL syntax closely. If you know `SELECT`, `WHERE`, `JOIN`, and `INSERT`, you already know how to use Kysely. - **Dialect system** — Kysely uses a pluggable dialect architecture, making it straightforward to integrate with different database backends — including Capacitor SQLite. - **Built-in migrations** — Kysely includes a `Migrator` class that lets you define and run migrations in TypeScript. No external tooling required. - **Lightweight** — Kysely has no runtime dependencies and a small footprint, which keeps your app bundle lean. ## Prerequisites Before you begin, make sure you have a Capacitor project with the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Installation Install the Kysely dialect along with Kysely itself: ``` npm install @capawesome/capacitor-sqlite-kysely kysely ``` ## Setting Up the Database To get started, open a database using the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin and create a Kysely instance with the `CapacitorSqliteDialect`: ``` import { Sqlite } from '@capawesome-team/capacitor-sqlite'; import { Kysely } from 'kysely'; import { CapacitorSqliteDialect } from '@capawesome/capacitor-sqlite-kysely'; const { databaseId } = await Sqlite.open({ path: 'my.db' }); const db = new Kysely({ dialect: new CapacitorSqliteDialect(Sqlite, { databaseId }), }); ``` The `CapacitorSqliteDialect` takes two arguments: the `Sqlite` plugin instance and a configuration object with the `databaseId` returned by [`open(...)`](https://capawesome.io/plugins/sqlite/#open). The `Database` generic parameter is a TypeScript interface that describes your tables — we'll define that next. ## Defining Your Database Types Kysely uses TypeScript interfaces to describe your database schema. This is what powers its type inference — every query you write is checked against these types at compile time. Create a types file for your database: ``` import { Generated } from 'kysely'; interface Database { users: UsersTable; posts: PostsTable; } interface UsersTable { id: Generated; name: string; email: string; } interface PostsTable { id: Generated; title: string; content: string | null; author_id: number; } ``` A few things to note here: - Each key in the `Database` interface corresponds to a table name in your database. - `Generated` marks a column as auto-generated (e.g. an auto-incrementing primary key). Kysely will make this column optional in `INSERT` statements but required in `SELECT` results. - Nullable columns use a union type with `null` (e.g. `string | null`). - These types don't create tables — they only describe the shape of your data for TypeScript's type checker. ## Running Queries With the database types in place, Kysely gives you a fluent, chainable API for building SQL queries. Every query is fully typed based on your `Database` interface. ### Insert ``` await db .insertInto('users') .values({ name: 'Alice', email: 'alice@example.com' }) .execute(); ``` ### Select ``` // Select all users const allUsers = await db.selectFrom('users').selectAll().execute(); // Select with a filter const user = await db .selectFrom('users') .selectAll() .where('email', '=', 'alice@example.com') .executeTakeFirst(); ``` The `executeTakeFirst()` method returns a single result or `undefined`, which is useful when you expect at most one row. ### Update ``` await db .updateTable('users') .set({ name: 'Bob' }) .where('id', '=', 1) .execute(); ``` ### Delete ``` await db .deleteFrom('users') .where('id', '=', 1) .execute(); ``` Every query is validated at compile time. If you mistype a column name or pass the wrong type, TypeScript will flag it immediately. ## Transactions For operations that need to succeed or fail atomically, use transactions. Kysely manages `BEGIN`, `COMMIT`, and `ROLLBACK` automatically: ``` await db.transaction().execute(async (trx) => { await trx .insertInto('users') .values({ name: 'Alice', email: 'alice@example.com' }) .execute(); await trx .insertInto('posts') .values({ title: 'Hello World', content: '...', author_id: 1 }) .execute(); }); ``` If any statement inside the callback throws an error, the entire transaction is rolled back. This is essential for maintaining data consistency when inserting related records across multiple tables. ## Migrations Kysely includes a built-in `Migrator` class for managing database schema changes. Since the `CapacitorSqliteDialect` implements Kysely's standard `Dialect` interface, migrations work out of the box — no extra tooling or bundler plugins needed. Define your migrations as a `MigrationProvider`: ``` import { Kysely, Migrator, MigrationProvider } from 'kysely'; const migrationProvider: MigrationProvider = { async getMigrations() { return { '001_create_users': { async up(db: Kysely) { await db.schema .createTable('users') .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement()) .addColumn('name', 'text', (col) => col.notNull()) .addColumn('email', 'text', (col) => col.notNull().unique()) .execute(); }, }, '002_create_posts': { async up(db: Kysely) { await db.schema .createTable('posts') .addColumn('id', 'integer', (col) => col.primaryKey().autoIncrement()) .addColumn('title', 'text', (col) => col.notNull()) .addColumn('content', 'text') .addColumn('author_id', 'integer', (col) => col.notNull().references('users.id')) .execute(); }, }, }; }, }; ``` Then apply the migrations when your app starts: ``` const migrator = new Migrator({ db, provider: migrationProvider }); const { error, results } = await migrator.migrateToLatest(); if (error) { console.error('Migration failed:', error); } ``` Migrations are written in TypeScript using Kysely's schema builder, which means they benefit from the same type safety and autocompletion as your queries. The `Migrator` tracks applied migrations automatically, so calling `migrateToLatest()` multiple times is safe — only pending migrations are executed. ## Stay Updated Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion With the `@capawesome/capacitor-sqlite-kysely` dialect, you can use Kysely's type-safe query builder and built-in migration system directly in your Capacitor apps. The setup is minimal: define your database types as TypeScript interfaces, create a dialect instance, and start writing queries that are checked at compile time — all while staying close to SQL. **Resources:** - For the full API reference and source code, visit the [Adapter on GitHub](https://github.com/capawesome-team/capacitor-sqlite-drivers/tree/main/packages/kysely) **Related tutorials:** - If you're looking for an alternative approach with schema-as-code and relational queries, check out our guide on [How to Use Drizzle ORM with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/index.md) - For a decorator-based ORM check [TypeORM with Capacitor and SQLite](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-and-sqlite/index.md) If you have questions or feedback, join the [Capawesome Discord](https://discord.gg/VCXxSVjefW) server to connect with the community. And subscribe to the Capawesome [newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # How to Use SPM Package Traits in Capacitor 8 Capacitor 8 now supports Swift Package Manager (SPM) package traits, bringing feature-flag-like capabilities to your iOS plugin dependencies. If you've ever needed to toggle an optional dependency — like enabling SQLCipher encryption for a SQLite plugin — you can now do it directly from your Capacitor config. This was previously only possible with CocoaPods subspecs, and since SPM is the default package manager in Capacitor, this fills an important gap. ## What Are SPM Package Traits? Package traits were introduced in [SE-0450](https://github.com/swiftlang/swift-evolution/blob/main/proposals/0450-swiftpm-package-traits.md), a Swift Evolution proposal that shipped with Swift 6.1. In short, traits act as feature flags for Swift packages. They let package authors: - **Conditionally compile code** using `#if TraitName` directives - **Toggle optional dependencies** so consumers only pull in what they need - **Define default traits** that activate automatically unless explicitly disabled If you're familiar with CocoaPods subspecs or Cargo features in Rust, traits serve a similar purpose. A plugin can declare a set of traits, and the consumer picks which ones to enable. For example, a SQLite package might define a default `SQLite` trait that uses the system-provided SQLite, and an optional `SQLCipher` trait that swaps in an encrypted variant. The consumer decides at build time which variant they want — without the plugin author needing to maintain separate packages. ## Why This Matters for Capacitor When Capacitor moved to SPM as the default package manager (starting with Capacitor 8), plugin authors lost the ability to offer optional dependency variants. CocoaPods had subspecs for this, but SPM had no equivalent — until now. Without trait support, a plugin author who wanted to offer an optional feature like SQLCipher encryption had two choices: bundle the dependency unconditionally (increasing binary size and adding compliance concerns), or maintain a separate plugin package entirely. Neither option was great. With Capacitor CLI 8.3.0+, the generated `Package.swift` can now include trait information, letting you opt in to exactly the features you need. ## How It Works Capacitor introduces two new configuration options under the `experimental` namespace in your `capacitor.config.json`: 1. **`experimental.ios.spm.swiftToolsVersion`** — Sets the `swift-tools-version` in the generated `Package.swift`. Must be `"6.1"` or higher for traits to work. 1. **`experimental.ios.spm.packageTraits`** — Maps plugin package names to arrays of trait names you want to enable. Here's what the configuration looks like in practice: capacitor.config.json ``` { "appId": "com.example.app", "appName": "MyApp", "experimental": { "ios": { "spm": { "swiftToolsVersion": "6.1", "packageTraits": { "@capawesome-team/capacitor-sqlite": ["SQLCipher"] } } } } } ``` After running `npx cap sync`, the Capacitor CLI generates a `CapApp-SPM/Package.swift` that includes `traits: ["SQLCipher"]` in the dependency line for the SQLite plugin and uses `swift-tools-version: 6.1` at the top of the file. ## Real-World Example: The SQLite Plugin The [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin is one of the first Capacitor plugins to use SPM package traits. It offers optional [SQLCipher](https://www.zetetic.net/sqlcipher/) encryption support that you can enable via a trait. The plugin's `Package.swift` defines two traits: Package.swift ``` traits: [ .default(enabledTraits: ["SQLite"]), .trait( name: "SQLite", description: "Uses the system-provided SQLite." ), .trait( name: "SQLCipher", description: "Enables SQLCipher encryption support." ) ] ``` When the `SQLCipher` trait is enabled, the plugin conditionally includes the SQLCipher dependency and sets a compile-time flag: Package.swift ``` targets: [ .target( name: "SqlitePlugin", dependencies: [ // ... .product(name: "SQLCipher", package: "SQLCipher.swift", condition: .when(traits: ["SQLCipher"])) ], swiftSettings: [ .define("CAPAWESOME_INCLUDE_SQLCIPHER", .when(traits: ["SQLCipher"])) ]) ] ``` In the Swift source code, the plugin uses `#if` directives to guard encryption-specific functionality: ``` #if CAPAWESOME_INCLUDE_SQLCIPHER try connection.key(encryptionKey) #else throw CustomError.unavailable #endif ``` This way, apps that don't need encryption get a smaller binary without the SQLCipher dependency, while apps that do need it simply enable the trait in their Capacitor config. ## For Plugin Authors If you maintain a Capacitor plugin and want to add trait support, here's what you need to do in your `Package.swift`: **1. Set the Swift tools version to 6.1:** ``` // swift-tools-version: 6.1 ``` **2. Define your traits:** ``` traits: [ .default(enabledTraits: ["Default"]), .trait(name: "Default", description: "Standard behavior."), .trait(name: "OptionalFeature", description: "Enables an optional feature.") ] ``` **3. Add conditional dependencies:** ``` dependencies: [ .package( url: "https://github.com/example/optional-lib.git", from: "1.0.0" ) ], targets: [ .target( name: "MyPlugin", dependencies: [ .product(name: "OptionalLib", package: "optional-lib", condition: .when(traits: ["OptionalFeature"])) ], swiftSettings: [ .define("INCLUDE_OPTIONAL_FEATURE", .when(traits: ["OptionalFeature"])) ]) ] ``` **4. Guard your code with conditional compilation:** ``` #if INCLUDE_OPTIONAL_FEATURE import OptionalLib #endif ``` Keep in mind that traits should be additive — enabling a trait should add functionality, not remove it. This aligns with semantic versioning expectations and avoids surprises for consumers. ## Requirements and Limitations This feature is currently marked as **experimental** because Capacitor does not yet officially support Swift 6. Here's what you need: | Requirement | Minimum Version | | ------------- | --------------- | | Capacitor CLI | 8.3.0+ | | Xcode | 16.3+ | | Swift | 6.1+ | A few things to keep in mind: - **Compatibility**: Setting `swift-tools-version` to `6.1` can cause issues with dependencies that were built with older Swift versions, particularly those using XCFrameworks. Test your app thoroughly after enabling this setting. - **Experimental namespace**: Both config options live under the `experimental` block, signaling that the API may change in future Capacitor versions. - **Swift 6 timeline**: The Capacitor team has indicated that full Swift 6 support is planned for a future major version (likely Capacitor 9), at which point these settings may move out of the experimental namespace. ## Try Capawesome Cloud Build, deploy, and update your Capacitor apps with ease using Capawesome Cloud — with native builds, live updates, and app store publishing all in one platform. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion SPM package traits bring much-needed parity with CocoaPods for managing optional dependencies in Capacitor plugins. For app developers, it means you can now opt in to features like SQLCipher encryption with a simple config change. For plugin authors, it opens the door to offering flexible, modular plugins without maintaining separate packages. If you want to learn more about encrypting SQLite databases with the SQLCipher trait, check out the guide on [Encrypting SQLite databases in Capacitor](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md). If you have questions or want to share how you're using traits in your plugins, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay up to date on the latest Capacitor news and plugin releases. # How to Use TypeORM with Capacitor and SQLite Many developers already use TypeORM on the backend to manage databases with TypeScript decorators and a familiar repository pattern. The good news: the same approach works in Capacitor apps too. The [Capacitor SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin ships with a built-in `SQLiteConnection` class that plugs directly into TypeORM's `DataSource` — no additional adapter package required. This guide walks you through the complete setup, from defining entities to running queries and managing migrations. For raw API usage, see the [Capacitor SQLite plugin documentation](https://capawesome.io/plugins/sqlite/#api). ## What is TypeORM? [TypeORM](https://typeorm.io/) is one of the most widely used ORMs in the TypeScript ecosystem. It takes a decorator-based approach to database modeling: you define your tables as classes, annotate columns and relationships with decorators, and interact with data through repositories and query builders. Here's why it pairs well with Capacitor apps: - **Decorator-based entities** — Tables are modeled as regular TypeScript classes. Columns, primary keys, and relationships are declared with decorators like `@Column()` and `@ManyToOne()`. - **Repository pattern** — Each entity gets a repository with built-in methods for common operations (`find`, `save`, `remove`), so you rarely need to write SQL. - **Automatic schema sync** — During development, TypeORM can synchronize your database schema with your entity definitions automatically. - **Migration support** — For production, TypeORM provides a migration system to apply schema changes incrementally. - **Wide adoption** — TypeORM has a large community and extensive documentation, which means answers to most questions are a search away. ## Prerequisites Before you begin, make sure you have a Capacitor project with the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Installation Since the TypeORM driver is included in the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin itself, you only need to install TypeORM and its peer dependency: ``` npm install typeorm reflect-metadata ``` TypeORM relies on decorators and metadata reflection, so you need to enable both in your `tsconfig.json`: ``` { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` Finally, import `reflect-metadata` once at the entry point of your app (e.g. `main.ts`), before any other imports: ``` import 'reflect-metadata'; ``` ## Configuring the DataSource TypeORM uses a `DataSource` to manage the database connection. To connect it to the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin, pass an `SQLiteConnection` instance as the `driver`: ``` import { Sqlite, SQLiteConnection } from '@capawesome-team/capacitor-sqlite'; import { DataSource } from 'typeorm'; const AppDataSource = new DataSource({ type: 'capacitor', driver: new SQLiteConnection(Sqlite), database: 'my-app', entities: [], synchronize: true, logging: ['error', 'schema'], migrationsRun: false, }); ``` A few things to note here: - `type: 'capacitor'` tells TypeORM to use its built-in Capacitor driver, which delegates database operations to the provided `driver` instance. - `driver: new SQLiteConnection(Sqlite)` bridges TypeORM to the Capacitor SQLite plugin. The `SQLiteConnection` class handles opening, closing, and routing queries to the correct database. - `database` is the name used to identify the database file. - `synchronize: true` automatically creates and updates tables based on your entities. This is convenient during development but should be disabled in production. - `migrationsRun: false` is required when using the `capacitor` type. To initialize the connection when your app starts: ``` await AppDataSource.initialize(); ``` ## Defining Entities TypeORM models database tables as classes decorated with `@Entity()`. Each property that maps to a column is annotated with a decorator like `@PrimaryGeneratedColumn()` or `@Column()`. ``` import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, OneToMany, CreateDateColumn, } from 'typeorm'; @Entity() export class User { @PrimaryGeneratedColumn() id!: number; @Column('text') name!: string; @Column({ type: 'text', unique: true }) email!: string; @CreateDateColumn() createdAt!: Date; @OneToMany(() => Post, (post) => post.author) posts!: Post[]; } @Entity() export class Post { @PrimaryGeneratedColumn() id!: number; @Column('text') title!: string; @Column({ type: 'text', nullable: true }) content!: string | null; @ManyToOne(() => User, (user) => user.posts) author!: User; } ``` A few things to note: - `@PrimaryGeneratedColumn()` creates an auto-incrementing primary key. - `@Column()` accepts a type string or an options object for constraints like `unique` and `nullable`. - `@CreateDateColumn()` automatically sets the current timestamp when a row is inserted. - `@OneToMany()` and `@ManyToOne()` define the relationship between `User` and `Post`. TypeORM uses these to generate foreign keys and enable eager/lazy loading. Don't forget to register your entities in the `DataSource` configuration: ``` const AppDataSource = new DataSource({ // ... entities: [User, Post], }); ``` ## Working with Repositories TypeORM's repository pattern provides a high-level API for data access. Each entity gets its own repository with built-in methods for the most common operations. ### Insert ``` const userRepo = AppDataSource.getRepository(User); const user = userRepo.create({ name: 'Alice', email: 'alice@example.com', }); await userRepo.save(user); ``` The `create()` method instantiates an entity without persisting it. Calling `save()` writes it to the database and populates the generated `id`. ### Select ``` // Find all users const allUsers = await userRepo.find(); // Find with a condition const user = await userRepo.findOneBy({ email: 'alice@example.com', }); // Find with relations const userWithPosts = await userRepo.findOne({ where: { id: 1 }, relations: { posts: true }, }); ``` ### Update ``` await userRepo.update({ id: 1 }, { name: 'Bob' }); ``` ### Delete ``` await userRepo.delete({ id: 1 }); ``` For more complex queries, TypeORM also offers a `QueryBuilder` that supports joins, subqueries, and aggregations: ``` const users = await userRepo .createQueryBuilder('user') .leftJoinAndSelect('user.posts', 'post') .where('user.name LIKE :name', { name: '%Ali%' }) .getMany(); ``` ## Transactions When multiple operations need to succeed or fail as a unit, wrap them in a transaction: ``` await AppDataSource.transaction(async (manager) => { const user = manager.create(User, { name: 'Alice', email: 'alice@example.com', }); await manager.save(user); const post = manager.create(Post, { title: 'Hello World', content: '...', author: user, }); await manager.save(post); }); ``` If any operation inside the callback throws, the entire transaction is rolled back. The `manager` parameter is a transactional `EntityManager` — use it instead of individual repositories to ensure all operations run within the same transaction. ## Migrations While `synchronize: true` is handy during development, it should not be used in production since it can lead to data loss. Instead, use TypeORM's migration system to apply schema changes incrementally. ### Writing Migrations Create a migration class that implements `up` and `down` methods: ``` import { MigrationInterface, QueryRunner } from 'typeorm'; export class CreateUsers1709000000000 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { await queryRunner.query(` CREATE TABLE IF NOT EXISTS user ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL UNIQUE, createdAt DATETIME DEFAULT (datetime('now')) ) `); } async down(queryRunner: QueryRunner): Promise { await queryRunner.query(`DROP TABLE IF EXISTS user`); } } ``` ### Running Migrations Register your migrations in the `DataSource` and run them at startup: ``` import { CreateUsers1709000000000 } from './migrations/CreateUsers1709000000000'; const AppDataSource = new DataSource({ type: 'capacitor', driver: new SQLiteConnection(Sqlite), database: 'my-app', entities: [User, Post], migrations: [CreateUsers1709000000000], synchronize: false, migrationsRun: false, }); await AppDataSource.initialize(); await AppDataSource.runMigrations(); ``` TypeORM tracks which migrations have been applied in a `migrations` table, so calling `runMigrations()` repeatedly is safe — only pending migrations are executed. ## Stay Updated Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion TypeORM brings its decorator-based entity modeling and repository pattern to Capacitor apps through the built-in `SQLiteConnection` class in the [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md). There's no separate adapter to install — just configure a `DataSource` with `type: 'capacitor'`, define your entities as decorated classes, and use repositories for data access. **Resources:** - [API Reference](https://capawesome.io/plugins/sqlite/#api) - [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) **Related tutorials:** - If you prefer a lighter, SQL-first approach check [Drizzle ORM](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/index.md) or [Kysely](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/index.md) If you have questions or feedback, join the [Capawesome Discord](https://discord.gg/VCXxSVjefW) server to connect with the community. And subscribe to the Capawesome [newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # How Usage is Tracked in Capawesome Cloud Understanding how your application's usage is tracked in Capawesome Cloud is essential for managing your service limits and optimizing your deployment strategy. This comprehensive guide explains the different types of usage metrics and how they're measured. ## Introduction [Capawesome Cloud](https://cloud.capawesome.io) is a comprehensive platform for building, updating, and deploying your Capacitor apps. With [Live Updates](https://cloud.capawesome.io/live-updates/), [Native Builds](https://cloud.capawesome.io/native-builds/), and [App Store Publishing](https://cloud.capawesome.io/app-store-publishing/), the platform provides detailed usage tracking to help you monitor your application's performance and resource consumption. The platform tracks several key metrics that directly impact your service plan and billing, ensuring transparency and predictable costs for your development team. The tracking system is designed to provide accurate measurements while maintaining user privacy and system efficiency. By understanding these metrics, you can better plan your application's growth and optimize resource usage. ## Usage Types Capawesome Cloud tracks several key types of usage that are essential for service delivery and billing purposes. ### Build Minutes Build Minutes measure the cloud compute time used to build your native iOS and Android apps with Native Builds. This metric tracks only the active processing time when a virtual machine is building your application. The tracking mechanism works as follows: - Only the time spent actively building your app on the VM is counted - Queue time (waiting for an available build machine) is not counted towards your limit When your Build Minutes limit is reached, the following occurs: - An email notification is sent to your account - Builds that are currently running will not be canceled (fair overusage from our side) This approach ensures that you're only charged for actual build processing time, not for time spent waiting in the queue, and that in-progress builds are never interrupted when you reach your limit. ### Live Updates Live Updates refer to the number of times an update is downloaded by a device using the [Capawesome Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. Each time a device downloads an update from Capawesome Cloud, it counts as one Live Update. Checking for updates without downloading does not count towards this metric. When your Live Update limit is reached, the following occurs: - An email notification is sent to your account - No more updates can be downloaded by devices ### Monthly Active Users Monthly Active Users (MAU) is an important metric for understanding your application's reach and engagement when using Live Updates. In Capawesome Cloud, a monthly active user is defined as **a unique device that has synced with the Capawesome Cloud in the current month**. The tracking mechanism works through a unique device identification system: - The [Capawesome Live Update](https://capawesome.io/plugins/live-update/index.md) plugin generates a unique device ID for each installation - This device ID remains valid only while the app is installed on the device - Each unique device that syncs with Capawesome Cloud during a calendar month counts as one MAU When your MAU limit is reached, the following occurs: - An email notification is sent to your account - No new devices can receive updates - Existing devices continue to receive updates normally This approach ensures that your active user base can continue using the latest version of your app while preventing overage charges. ### Storage Storage usage refers to the total amount of space consumed by your Live Updates app bundles and related assets stored in Capawesome Cloud. This includes: - App bundles uploaded to the platform - Metadata attached to each bundle Storage tracking is real-time and provides immediate feedback when limits are approached. When your storage limit is reached: - You'll receive a notification when attempting to upload a new bundle - Several options are available to resolve the issue: - Delete older bundles that are no longer needed - Upgrade to a plan with higher storage limits - Enable automatic bundle deletion features to manage storage automatically ### Bandwidth Bandwidth usage measures the total amount of data transferred between Capawesome Cloud and your application users for Live Updates. This includes: - Bundle downloads to user devices - Update synchronization data transfer - API communication overhead Bandwidth is measured over your billing period and helps ensure optimal performance for all users on the platform. The tracking system monitors both inbound and outbound data transfer to provide comprehensive usage metrics. ## Conclusion Capawesome Cloud's usage tracking system provides transparent and accurate measurement of your application's resource consumption across Live Updates, Native Builds, and App Store Publishing. By understanding how Live Updates, Build Minutes, Monthly Active Users, storage, and bandwidth are tracked, you can make informed decisions about your service plan and optimize your application's deployment strategy. The tracking system is designed to be fair and predictable—you're only charged for actual resource consumption, queue time doesn't count against your limits, and in-progress builds are never interrupted. The unique device identification system ensures precise MAU tracking while maintaining user privacy, and the real-time storage and bandwidth monitoring helps prevent service interruptions. With these insights, you can confidently scale your application while staying within your plan limits. For more detailed information about usage limits and plan features, visit the [Capawesome Cloud](https://capawesome.io/cloud/index.md) documentation. # Install Tailwind CSS with Ionic Framework Learn how to install and configure [Tailwind CSS](https://tailwindcss.com/) version 4.0 with the Ionic Framework across Angular, React, and Vue projects. This guide provides step-by-step instructions for integrating Tailwind's utility-first CSS framework into your Ionic applications, enabling you to build modern, responsive interfaces with ease. ## Introduction Tailwind CSS is a utility-first CSS framework that provides low-level utility classes to build custom designs quickly. When combined with the Ionic Framework, it offers developers the flexibility to create highly customized mobile and web applications while maintaining Ionic's native look and feel. Tailwind CSS version 4.0 introduces new features and improvements that make it even more powerful for building modern applications. This guide will walk you through the installation process for Angular, React, and Vue projects using the Ionic Framework. ## Prerequisites Before you begin, ensure you have the following installed on your system: - [Node.js](https://nodejs.org/) (version 16 or higher) and [npm](https://www.npmjs.com/) - [Ionic CLI](https://ionicframework.com/docs/cli) installed globally You can install the Ionic CLI globally using: ``` npm install -g @ionic/cli ``` Next, create a new Ionic project for your chosen framework: ``` npx ionic start my-ionic-app blank --type= cd my-ionic-app ``` ## Installation ### Angular To install Tailwind CSS with your Ionic Angular project, follow these steps: 1. **Install Tailwind CSS and dependencies:** ``` npm install tailwindcss @tailwindcss/postcss postcss ``` 1. **Configure PostCSS:**\ Create a `.postcssrc.json` file in your project root: ``` { "plugins": { "@tailwindcss/postcss": {} } } ``` 1. **Import Tailwind in your styles:**\ Add the following to your `src/global.scss` file: ``` @import "tailwindcss"; ``` 1. **Start the development server:** ``` npx ionic serve ``` ### React For Ionic React projects, the installation process uses Vite: 1. **Install Tailwind CSS:** ``` npm install tailwindcss @tailwindcss/vite ``` 1. **Configure Vite:**\ Update your `vite.config.ts` file to include the Tailwind plugin: ``` + import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ + tailwindcss(), ], }) ``` 1. **Import Tailwind CSS:**\ Add the following to your `src/theme/variables.css` file: ``` @import "tailwindcss"; ``` 1. **Start the development server:** ``` npx ionic serve ``` ### Vue For Ionic Vue projects, the installation process is similar to React: 1. **Install Tailwind CSS:** ``` npm install tailwindcss @tailwindcss/vite ``` 1. **Configure Vite:**\ Update your `vite.config.ts` file to include the Tailwind plugin: ``` + import tailwindcss from '@tailwindcss/vite' export default defineConfig({ plugins: [ + tailwindcss(), ], }) ``` 1. **Import Tailwind CSS:**\ Add the following to your `src/theme/variables.css` file: ``` @import "tailwindcss"; ``` 1. **Start the development server:** ``` npx ionic serve ``` ## Usage Once Tailwind CSS is installed and configured, you can start using utility classes in your Ionic application: ``` Home

Welcome to Ionic

This is a Tailwind CSS styled Ionic app.

``` ## Tip: Platform-Specific Styles You can use Tailwind's variants to apply platform-specific styles. For example, to apply different styles for Android and iOS, you can create custom variants in a global CSS file: ``` @custom-variant android (.md &); @custom-variant ios (.ios &); ``` Then, you can use these variants in your HTML: ```
This div will have a primary background color on iOS and a secondary background color on Android.
``` If you are using Tailwind CSS v3 or earlier, check out the [Tailwind CSS Plugin for Ionic Framework](https://capawesome.io/blog/tailwind-css-plugin-for-ionic-framework/index.md) guide to learn how to create a custom Tailwind CSS plugin. ## Conclusion You've successfully installed and configured Tailwind CSS version 4.0 with the Ionic Framework across Angular, React, and Vue projects. The combination of Ionic's component library and Tailwind's utility classes provides a powerful foundation for building modern, responsive applications. Remember to refer to the [Tailwind CSS documentation](https://tailwindcss.com/docs) for a complete reference of available utility classes and features. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). Happy coding with Ionic and Tailwind CSS! # Introducing Capver: Version Management for Capacitor If you've ever shipped a Capacitor app and had to manually update version numbers in three different files across three different platforms, you know how easy it is for things to get out of sync. [Capver](https://github.com/capawesome-team/capver) is an open-source CLI tool that manages your app versions across iOS, Android, and web in a single command. In this post, we'll walk you through what it does and how it works. ## The Problem Every Capacitor project stores version information in multiple places. iOS uses `CFBundleShortVersionString` and `CFBundleVersion` in `project.pbxproj`, Android uses `versionName` and `versionCode` in `build.gradle`, and web relies on the `version` field in `package.json`. When it's time to release a new version, you have to update all of these files manually — and keep them in sync. Miss one, and you might end up with a version mismatch that causes build failures, app store rejections, or confused users running different versions on different platforms. This problem only gets worse as your release cadence increases. ## Meet Capver [Capver](https://github.com/capawesome-team/capver) is a lightweight CLI tool built specifically for Capacitor projects. It reads and writes version numbers across all platforms so you don't have to touch each file by hand. You can install it via npm: ``` npm install -D @capawesome/capver ``` Once installed, you can check the current version across all platforms with a single command: ``` npx capver get ``` If any platform is out of sync, Capver will let you know right away. ## Key Commands Capver provides a small, focused set of commands that cover the most common versioning tasks: ### Viewing and Setting Versions - **`capver get`** — Displays the current version for each platform and warns you if they're out of sync. - **`capver set `** — Sets a specific version (e.g., `1.2.0`) across iOS, Android, and web at once. - **`capver sync`** — Detects the highest version across all platforms and syncs everything to match it. ### Bumping Versions - **`capver major`** — Bumps the major version (e.g., `1.2.3` → `2.0.0`). - **`capver minor`** — Bumps the minor version (e.g., `1.2.3` → `1.3.0`). - **`capver patch`** — Bumps the patch version (e.g., `1.2.3` → `1.2.4`). - **`capver hotfix`** — Increments the hotfix version on mobile platforms only, without changing the semantic version. ### Debugging - **`capver doctor`** — Prints system information like Node.js version, OS, and CLI version to help with troubleshooting. ## Hotfix Versioning One feature worth highlighting is hotfix support. Sometimes you need to push a quick fix to your mobile apps without bumping the public-facing version number. Capver handles this by incrementing a separate hotfix digit in the mobile build number while leaving the semantic version (`major.minor.patch`) unchanged. This is especially useful when using over-the-air update tools where you want to differentiate between builds without changing what users see in the app store. ## Configurable Build Number Patterns On mobile platforms, Capver generates build numbers from your version using a configurable pattern. The default pattern is `MMmmmpphh`, where each letter represents a digit: - `M` — Major version - `m` — Minor version - `p` — Patch version - `h` — Hotfix version For example, version `1.23.4` with hotfix `5` produces the build number `102300405`. You can customize this pattern in your `package.json`: ``` { "capver": { "pattern": "MMmmmpphh" } } ``` This gives you control over how much space each version component gets, so you can adapt it to your project's versioning needs. ## Integrations Since Capver is a simple CLI, it's easy to integrate into your existing release workflow — whether that's a CI/CD pipeline, a custom script, or a versioning tool. One example is [commit-and-tag-version](https://www.npmjs.com/package/commit-and-tag-version), which handles automatic versioning, changelog generation, and Git tagging based on conventional commits. By adding a `postbump` hook to your `package.json`, Capver automatically syncs all platform versions whenever the version is bumped: ``` { "scripts": { "release": "commit-and-tag-version --commit-all" }, "commit-and-tag-version": { "scripts": { "postbump": "npx @capawesome/capver set $(node -p \"require('./package.json').version\") && git add android/app/build.gradle ios/App/App/Info.plist" } } } ``` With this setup, running `npm run release` bumps the version in `package.json`, generates the changelog, and then Capver updates all platform files automatically — all in a single step. ## Try Capver Capver is open source under the MIT license and available on [npm](https://www.npmjs.com/package/@capawesome/capver) and [GitHub](https://github.com/capawesome-team/capver). Give it a try in your next Capacitor project and let us know what you think. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Final Thoughts Managing app versions across multiple platforms doesn't have to be a manual chore. Capver gives you a single source of truth for your Capacitor project's version, reducing the risk of mismatches and saving time on every release. If you have questions or feedback, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) — we'd love to hear from you. And if you want to stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # iOS Certificates and Provisioning Profiles Explained If you've ever hit a cryptic code signing error in Xcode and spent hours trying to figure out what went wrong, you're not alone. iOS code signing is one of the most confusing parts of iOS development, especially for developers coming from web or Android backgrounds. The good news is that once you understand the pieces and how they fit together, it all starts to make sense. In this guide, you'll learn what certificates and provisioning profiles are, how they work together, and which combinations you need for each type of build. ## Why iOS Code Signing Exists Every app that runs on a physical iOS device must be code signed. This is Apple's way of ensuring that: - **Identity**: The app comes from a known, verified developer. - **Integrity**: The app hasn't been tampered with since it was signed. - **Authorization**: The app is allowed to run on that specific device or be distributed through a specific channel. Unlike Android, where you can generate your own signing key without a central authority, Apple acts as the gatekeeper. You need an Apple Developer account, Apple-issued certificates, and Apple-generated provisioning profiles before your app can leave the simulator. ## The Building Blocks iOS code signing relies on three key pieces working together: certificates, provisioning profiles, and the private key on your Mac. ### Certificates A certificate is a digital document issued by Apple that verifies your identity as a developer. When you create a certificate, your Mac generates a public/private key pair. The private key stays in your Keychain, and the public key is sent to Apple as part of a Certificate Signing Request (CSR). Apple then issues a certificate containing your public key and Apple's own digital signature. There are two main types of certificates: - **Apple Development** — Used for building and running apps on your own test devices during development. This certificate belongs to you as an individual developer. You can have up to two development certificates at a time. - **Apple Distribution** — Used for distributing apps via the App Store, TestFlight, or Ad Hoc. This certificate belongs to the team, not an individual. Only Account Holders or Admins can create it. There's also a third type for companies enrolled in the Apple Developer Enterprise Program: - **iOS Distribution (In-House)** — Used exclusively for distributing proprietary apps to employees within a company. Certificate Naming History Before Xcode 11, Apple used platform-specific certificate names like "iOS Development" and "iOS Distribution." In 2019, Apple unified these into "Apple Development" and "Apple Distribution," which work across iOS, macOS, tvOS, and watchOS. You might still see the old names in older projects, but new certificates use the unified naming. ### Provisioning Profiles A provisioning profile is the glue that ties everything together. It's a file generated by Apple that bundles four pieces of information: - **App ID** — The bundle identifier of your app. - **Certificate(s)** — Which signing certificates are authorized to sign the app. - **Entitlements** — Which capabilities the app is allowed to use (Push Notifications, iCloud, HealthKit, etc.). - **Device UDIDs** — Which specific devices can run the app (only for Development and Ad Hoc profiles). There are four types of provisioning profiles: - **Development** — For running the app on registered test devices from Xcode. - **Ad Hoc** — For distributing the app to a limited list of registered devices outside the App Store (up to 100 per device type per year). - **App Store** — For submitting the app to App Store Connect for TestFlight or public App Store release. - **In-House** — For distributing apps internally to employees (Enterprise Program only). ### How They Work Together When you build your app, Xcode uses your private key to sign the app bundle and embeds the provisioning profile into it. When an iOS device receives the app, it checks three things: 1. Does the signature match a certificate listed in the provisioning profile? 1. Does the app's bundle ID match the App ID in the profile? 1. Is this device in the allowed list (for Development and Ad Hoc builds)? If any of these checks fail, the app won't install or run. This is why mismatching a certificate with the wrong profile type causes those frustrating signing errors. ## Certificate and Build Type Compatibility Matrix Here's the compatibility matrix that shows exactly which certificate and provisioning profile you need for each build type: | Build Type | Required Certificate | Required Profile | Used For | | -------------------------- | --------------------------- | ---------------- | ---------------------------------------------------------------- | | **Simulator** | None | None | Testing on the Xcode Simulator | | **Development** | Apple Development | Development | Running on physical test devices via Xcode | | **Ad Hoc** | Apple Distribution | Ad Hoc | Distributing to registered devices outside the App Store | | **App Store / TestFlight** | Apple Distribution | App Store | Submitting to App Store Connect for TestFlight or public release | | **Enterprise (In-House)** | iOS Distribution (In-House) | In-House | Internal distribution to company employees | The key rule is simple: **certificates and profiles must match**. A Development certificate only works with a Development profile. An Apple Distribution certificate works with Ad Hoc and App Store profiles. Mixing them will result in a code signing error. ## Key Things to Keep in Mind ### TestFlight Uses App Store Signing This trips up a lot of developers. Even though TestFlight is a testing tool, Apple treats TestFlight builds as App Store builds. You need an Apple Distribution certificate and an App Store provisioning profile to upload to App Store Connect. An Ad Hoc profile will not work — the upload will fail with a "Provisioning profile is not an iOS App Store profile" error. ### Certificate Limits Apple enforces limits on how many certificates you can have: - **Development**: Up to 2 per individual developer. - **Distribution**: Up to 3 active iOS Distribution certificates per team (standard program). If you hit the limit, you'll need to revoke an existing certificate before creating a new one. Keep in mind that revoking a certificate invalidates all provisioning profiles that reference it. ### Enterprise Requires a Separate Program The Apple Developer Enterprise Program ($299/year) is separate from the standard Apple Developer Program ($99/year). It's intended exclusively for distributing proprietary apps to a company's employees. Misusing it to distribute apps to the public can result in Apple revoking your certificate, which immediately breaks all deployed apps. Since iOS 18, manually installed enterprise apps (not via MDM) require a device restart to establish trust with the provisioning profile. Apple recommends using an MDM solution for enterprise distribution, which bypasses this restart requirement. ### Automatic Signing and Cloud-Managed Certificates If managing certificates and profiles manually sounds tedious, Xcode offers an "Automatically manage signing" option that handles most of the work for you. Since Xcode 13, Apple also supports cloud-managed certificates for distribution. These are created and rotated automatically — no need to manually manage a local distribution certificate in your Keychain. Cloud signing works when you use the Xcode Organizer archive and distribution workflow. If no local distribution certificate is found, Xcode falls back to cloud-managed signing automatically. ## Common Mistakes to Avoid Here are the most common code signing pitfalls and how to avoid them: - **Mismatching certificate and profile types** — Using a Development certificate with an App Store profile (or vice versa) is the most common cause of signing errors. Always verify that your certificate type matches your profile type. - **Expired or revoked certificates** — When a certificate expires or is revoked, all provisioning profiles that reference it become invalid. Builds that worked yesterday will suddenly fail. - **Missing private keys** — The private key is generated on a specific Mac and stored only in that Mac's Keychain. If you switch machines without exporting the key as a `.p12` file, you won't be able to sign with that certificate anymore. - **Wrong team selected in Xcode** — If you belong to multiple Apple Developer teams, make sure the correct team is selected in your project's Signing & Capabilities tab. Selecting the wrong team is a surprisingly common and hard-to-spot mistake. - **Bundle ID mismatches** — The bundle identifier in your Xcode project must exactly match the App ID in your provisioning profile. Even a small typo will cause a signing failure. ## Try Capawesome Cloud If you're building Capacitor apps, [Capawesome Cloud](https://cloud.capawesome.io/) can take the pain out of iOS code signing. Upload your certificates and provisioning profiles once, and let cloud builds handle the signing for you — no local Keychain management required. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion iOS code signing comes down to three things: certificates prove your identity, provisioning profiles define where and how your app can run, and the two must always match. Once you understand the compatibility rules — Development certificates with Development profiles, Distribution certificates with Ad Hoc or App Store profiles — most signing errors become straightforward to diagnose and fix. If you're setting up a CI/CD pipeline for your Capacitor app and want to avoid common signing issues, check out [Common CI/CD Pitfalls for Capacitor Apps](https://capawesome.io/blog/ci-cd-for-capacitor-common-pitfalls/index.md) for practical tips on automating your builds. If you have questions, feel free to join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). To stay up to date with the latest news, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Key-Value Storage Made Simple with the SQLite Plugin Storing simple key-value data in Ionic and Capacitor apps often means choosing between unreliable browser storage or writing boilerplate SQL. The [Capacitor SQLite plugin](https://capawesome.io/plugins/sqlite/index.md) now includes `SqliteKeyValueStore` — a built-in class that gives you a familiar `get`/`set` API backed by a real SQLite database. No SQL queries, no schema setup, no risk of data loss from WebView storage clearing. In this guide, you'll learn how to use `SqliteKeyValueStore` in your Capacitor apps and when to choose it over the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin. No SQL, no schema; when you need more, use the same **Capacitor SQLite plugin** and the full [plugin documentation](https://capawesome.io/plugins/sqlite/#key-value-store). ## What is `SqliteKeyValueStore`? `SqliteKeyValueStore` is a built-in class shipped with the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin that wraps a SQLite database behind a minimal key-value API. The database is automatically created and managed under the hood — you don't need to define schemas, write migrations, or execute any SQL. Values are stored as strings, so you can use `JSON.stringify` and `JSON.parse` to work with objects. Here's what makes it a great choice for your next project: - **Reliable persistence** — Data lives in a SQLite database file, not in WebView storage that the OS can clear at any time. - **No SQL required** — A simple `get`/`set` interface. No schemas, no migrations, no queries. - **Cross-platform** — Works on Android, iOS, Web, and Electron with a single API. - **Performance** — SQLite is optimized for fast reads and writes, even with larger data sets. - **Scales with your app** — Start with key-value storage today, graduate to full SQL queries later using the same plugin — no migration needed. For more details on the available methods, check out the [Key-Value Store](https://capawesome.io/plugins/sqlite/#key-value-store) section in the plugin documentation. ## SQLite Key Value Store vs. Secure Preferences If you're already familiar with the [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) plugin, you might be wondering when to use which. Both provide key-value storage, but they're designed for different use cases: | | SQLite Key Value Store | Secure Preferences | | ------------------------------- | --------------------------------------- | ----------------------------------------- | | **Best for** | App settings, caching, larger data sets | Sensitive data (tokens, keys, secrets) | | **Encryption** | Optional (requires SQLCipher setup) | Built-in, uses platform keychain/keystore | | **Third-party dependency** | SQLCipher (only if encryption needed) | None | | **Performance with large data** | Optimized (SQLite engine) | Designed for small values | | **SQL upgrade path** | Yes, same plugin | No | Use [Secure Preferences](https://capawesome.io/plugins/secure-preferences/index.md) for sensitive credentials that need encryption by default with zero setup. Use [SQLite Key Value Store](https://capawesome.io/plugins/sqlite/#key-value-store) when you need reliable persistence for larger data sets with SQLite-level performance and an optional upgrade path to full SQL. ## Getting Started To get started with `SqliteKeyValueStore`, you first need to install the [SQLite](https://capawesome.io/plugins/sqlite/index.md) plugin. Please refer to the [Installation](https://capawesome.io/plugins/sqlite/#installation) section in the plugin documentation. ## Usage Once the plugin is installed, you can start using `SqliteKeyValueStore` right away. Let's walk through the most common operations. ### Storing Data First, create a `SqliteKeyValueStore` instance. Then use [`set(...)`](https://capawesome.io/plugins/sqlite/#set) to store simple strings or JSON-serialized objects: ``` import { Sqlite, SqliteKeyValueStore } from '@capawesome-team/capacitor-sqlite'; const store = new SqliteKeyValueStore(Sqlite); // Store a simple string value await store.set({ key: 'language', value: 'en' }); // Store an object as a JSON string const preferences = { theme: 'dark', fontSize: 16, notifications: true }; await store.set({ key: 'preferences', value: JSON.stringify(preferences) }); ``` ### Retrieving Data Use [`get(...)`](https://capawesome.io/plugins/sqlite/#get) to read a value by its key. The returned `value` is `null` if the key doesn't exist, so always check before parsing: ``` const { value } = await store.get({ key: 'preferences' }); if (value) { const preferences = JSON.parse(value); console.log(preferences.theme); // 'dark' } ``` ### Removing Data Use `remove(...)` to delete a single key or `clear()` to wipe all stored data: ``` await store.remove({ key: 'language' }); await store.clear(); ``` ### Listing All Keys Use `keys()` to get a list of all stored keys: ``` const { keys } = await store.keys(); console.log(keys); // ['preferences', 'onboardingComplete', ...] ``` ## Use Cases Here are some common scenarios where `SqliteKeyValueStore` is a great fit: - **User preferences** — Theme, language, font size, or notification settings. Data persists reliably across app restarts without worrying about WebView storage limits. - **Feature flags and onboarding state** — Track which features are enabled or whether the user has completed onboarding. Simple boolean or string values that need to survive app updates. - **Lightweight caching** — Cache API responses or computed results as JSON strings. SQLite performance keeps reads fast even as your cache grows. ## Conclusion `SqliteKeyValueStore` gives you reliable, performant key-value storage with zero SQL boilerplate — and a clear path to full SQL when your app needs it. If you're looking for a simple yet robust way to persist data in your Capacitor app, give it a try. **Resources:** - [Plugin docs](https://capawesome.io/plugins/sqlite/index.md) - [Key-Value Store API](https://capawesome.io/plugins/sqlite/#key-value-store) **Related guides:** - For raw API usage [Exploring the Capacitor SQLite API](https://capawesome.io/blog/exploring-the-capacitor-sqlite-api/index.md) - [Encrypting SQLite databases](https://capawesome.io/blog/encrypting-capacitor-sqlite-database/index.md) - ORMs: [TypeORM](https://capawesome.io/blog/how-to-use-typeorm-with-capacitor-and-sqlite/index.md), [Drizzle ORM](https://capawesome.io/blog/how-to-use-drizzle-orm-with-capacitor-and-sqlite/index.md), [Kysely](https://capawesome.io/blog/how-to-use-kysely-with-capacitor-and-sqlite/index.md) If you have any questions or need help, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) to connect with the community. To stay updated with the latest news about Capawesome, Capacitor, and the Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Live Updates for Nuxt Capacitor Apps with Capawesome Cloud Live Updates, also known as Over-the-Air (OTA) or hot code updates, are a way to push updates to your Android or iOS app without going through the app store review process. This is particularly useful for fixing bugs, adding new features, or making changes to your app without requiring users to download a new version from the app store. For this, we will use the [Capacitor Live Update](https://capawesome.io/plugins/live-update/) plugin from Capawesome in combination with [Capawesome Cloud](https://cloud.capawesome.io/). ## Installation To enable Live Updates in your Capacitor app, you need to install the `@capawesome/capacitor-live-update` plugin: ``` npm install @capawesome/capacitor-live-update ``` After that, you need to sync the changes with your native projects: ``` npx cap sync ``` ## Configuration Next, you need to configure the plugin to work with [Capawesome Cloud](https://cloud.capawesome.io/). ### App ID In order for your app to identify itself to Capawesome Cloud, you need to set the `appId` in your `capacitor.config` file. For this, you need to create an app on the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) and get the App ID. ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000" } } } ``` Replace `00000000-0000-0000-0000-000000000000` with your actual App ID from the Capawesome Cloud Console. After configuring the App ID, sync your Capacitor project again: ``` npx cap sync ``` ## Usage The most basic usage of the Live Update plugin is to call the [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) method when the app starts. This method checks for updates, downloads them if available, and sets them as the next bundle to be applied. You can then call the [`reload()`](https://capawesome.io/plugins/live-update/#reload) method to apply the update immediately. If the [`reload()`](https://capawesome.io/plugins/live-update/#reload) method is not called, the new bundle will be used on the next app start. ``` import { LiveUpdate } from "@capawesome/capacitor-live-update" const sync = async () => { const result = await LiveUpdate.sync() if (result.nextBundleId) { await LiveUpdate.reload() } } ``` ## Publishing updates To publish your first update, you need to [create a bundle](https://capawesome.io/cloud/live-updates/bundles/#create-a-bundle) on Capawesome Cloud. For this, you need a bundle artifact. A bundle artifact is the build output of your web app. In Nuxt, this is the `dist` folder. You can create a bundle artifact by running the following command: ``` npx nuxi generate ``` This will create a `dist` folder with the build output of your web app. You can then upload this folder to Capawesome Cloud using the [Capawesome CLI](https://capawesome.io/cloud/cli/). To install the Capawesome CLI, run the following command: ``` npm i -g @capawesome/cli ``` After installing the Capawesome CLI, you need to log in to your Capawesome Cloud account. Run the following command and follow the instructions: ``` npx capawesome login ``` Once you are logged in, you can create a bundle by running the following command: ``` npx capawesome apps:liveupdates:upload --path dist ``` Congratulations! You have successfully published your first live update. You can now test it by running your app on a device or emulator. The app will check for updates and apply them if available. Feel free to check out the [documentation](https://capawesome.io/plugins/live-update/) of the Live Update plugin to see what else you can do with it. # Migrating from App Center to Capawesome Cloud Microsoft App Center is being retired, leaving many developers looking for alternatives to continue delivering over-the-air updates and building their mobile applications. [Capawesome Cloud](https://cloud.capawesome.io/) provides a powerful, modern solution for Capacitor apps that offers seamless Live Updates and enterprise-grade features. This guide will walk you through migrating your Capacitor app from App Center to Capawesome Cloud, focusing on replacing App Center's CodePush functionality with Capawesome Live Updates while maintaining your existing workflow and improving your app's update delivery system. ## Why Migrate from App Center? With App Center's retirement, you need a reliable alternative that provides: - **Continued Service**: Capawesome Cloud is actively maintained and continuously improved - **Modern Architecture**: Built specifically for Capacitor apps with modern tooling - **Enhanced Features**: Advanced update channels, rollback capabilities, and delta updates - **Better Performance**: Optimized delivery network and faster update mechanisms - **Enterprise Support**: Professional support and SLA options for critical applications - **Open Source**: The Live Update SDK is open-source and MIT licensed Capawesome Cloud offers equivalent functionality to App Center's CodePush with additional benefits like channel management, automatic rollbacks, and better integration with the Capacitor ecosystem. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up Capacitor Live Updates in my project. ``` ## Prerequisites Before starting the migration, ensure you have: 1. **Node.js and npm**: Version 16 or higher 1. **Capacitor CLI**: Install with `npm install -g @capacitor/cli` 1. **Existing Capacitor App**: Your app should already be using Capacitor 1. **Git Repository**: Your code should be in a Git repository 1. **App Center Account**: Access to your current App Center configuration for reference If you're still using Cordova, consider migrating to Capacitor first using the [Capacitor migration guide](https://capacitorjs.com/docs/cordova/migrating-from-cordova-to-capacitor). ## Migration Steps Overview The migration process involves these key steps: 1. Set up Capawesome Cloud account and create your app 1. Install and configure the Capawesome Live Update SDK 1. Configure your app with the necessary settings 1. Replace App Center CodePush implementation 1. Build and deploy your first update 1. Test the migration thoroughly ## Step 1: Setting Up Capawesome Cloud ### Create Your Account 1. Visit the [Capawesome Cloud Console](https://console.cloud.capawesome.io) 1. Sign up for a new account or log in if you already have one 1. Complete the onboarding process ### Create Your App 1. In the Capawesome Cloud Console, click "Create App" 1. Choose a name for your app 1. Copy the App ID of the newly created app via the dropdown; you will need it later ## Step 2: Installing the Capawesome Live Update SDK Remove any existing App Center dependencies first. After that, install the [Capacitor Live Update](https://capawesome.io/plugins/live-update/index.md) plugin from Capawesome: ``` npm install @capawesome/capacitor-live-update npx cap sync ``` ## Step 3: Configuring Your App ### Add Configuration Add the Capawesome Live Update configuration to your `capacitor.config.ts` file: ``` import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { appId: 'com.yourcompany.yourapp', appName: 'Your App Name', webDir: 'dist', plugins: { LiveUpdate: { appId: 'YOUR_CAPAWESOME_APP_ID', // Replace with your App ID from Step 1 }, }, }; export default config; ``` ### Sync Configuration Apply the configuration changes: ``` npx cap sync ``` ## Step 4: Replacing App Center CodePush ### Remove App Center Code If you have existing App Center CodePush code, remove it. ### Implement Capawesome Live Updates Replace your App Center CodePush implementation with Capawesome Live Updates: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; // Basic sync (equivalent to CodePush.sync()) const syncUpdates = async () => { try { const result = await LiveUpdate.sync(); if (result.nextBundleId) { // Update is available and downloaded await LiveUpdate.reload(); } } catch (error) { console.error('Update sync failed:', error); } }; // Check for updates (equivalent to CodePush.checkForUpdate()) const checkForUpdates = async () => { try { const result = await LiveUpdate.fetchLatestBundle(); if (result.bundleId) { console.log('Update available:', result.bundleId); return true; } return false; } catch (error) { console.error('Update check failed:', error); return false; } }; // Manual update download and installation const downloadAndInstallUpdate = async () => { try { const latestBundle = await LiveUpdate.fetchLatestBundle(); if (latestBundle.bundleId) { await LiveUpdate.downloadBundle({ bundleId: latestBundle.bundleId }); await LiveUpdate.setNextBundle({ bundleId: latestBundle.bundleId }); await LiveUpdate.reload(); } } catch (error) { console.error('Update installation failed:', error); } }; ``` ### Update Your App Initialization Add the sync call to your app initialization: ``` // In your main app initialization document.addEventListener('DOMContentLoaded', () => { // Sync updates when app starts syncUpdates(); // Clean up interval when needed window.addEventListener('beforeunload', () => { clearInterval(interval); }); }); ``` ## Step 5: Building and Distributing Updates ### Build Your App Create a production build of your web assets: ``` npm run build ``` ### Install Capawesome CLI Install the Capawesome CLI for managing updates: ``` npm install -g @capawesome/cli@latest ``` ### Create and Upload a Bundle Create and upload your first update bundle: ``` # Create a new bundle from your build npx @capawesome/cli apps:liveupdates:upload --path dist # The CLI will prompt you for: # - App ID (from your Capawesome Cloud Console) # - Channel (e.g., 'production', 'staging') # - Bundle directory (usually 'dist' or 'build') ``` ### Verify Upload Check the Capawesome Cloud Console to verify your bundle was uploaded successfully. You should see your new bundle listed under your app's bundles section. ## Step 6: Testing Your Migration ### Test Update Delivery 1. **Install the app** with the Capawesome Live Update SDK on a test device 1. **Upload a new bundle** with a visible change (e.g., updated text or styling) 1. **Trigger an update** by calling `LiveUpdate.sync()` or restarting the app 1. **Verify the update** is applied correctly ### Test Different Scenarios Test these scenarios to ensure robust functionality: - **Network connectivity issues**: Ensure graceful handling of offline scenarios - **Update failures**: Test rollback functionality - **Channel switching**: Test different update channels if you use them - **App backgrounding**: Ensure updates work when the app is backgrounded ## Best Practices ### Update Strategy - **Test thoroughly**: Always test updates in staging before production - **Gradual rollouts**: Use channels to control update distribution - **Monitor performance**: Track update success rates and performance impact - **Rollback plan**: Have a rollback strategy for problematic updates ### Security - **Validate bundles**: Only deploy tested and validated bundles - **Use HTTPS**: Ensure all update communications are encrypted - **Monitor updates**: Track update installations and failures ### Performance - **Optimize bundle size**: Keep updates small and focused - **Update timing**: Schedule updates during low-usage periods - **Network awareness**: Consider network conditions when updating ## Conclusion Migrating from App Center to Capawesome Cloud provides a modern, reliable solution for delivering over-the-air updates to your Capacitor apps. The [Capawesome Live Update](https://capawesome.io/plugins/live-update/index.md) plugin offers enhanced features, better performance, and ongoing support that App Center can no longer provide. With this migration complete, you now have: - A robust update delivery system - Modern tooling and CLI integration - Professional support options - Enhanced features like channels and rollbacks - An open-source foundation you can trust Your app is now ready to continue delivering seamless updates to your users with improved reliability and performance. # Migrating from Capgo to Capawesome Cloud If you're currently using Capgo for live updates and considering a switch to a more reliable, feature-rich platform, Capawesome Cloud offers a seamless migration path. With superior uptime, faster response times, and more competitive pricing, Capawesome Cloud provides everything you need for robust Over-the-Air updates. ## Why Migrate from Capgo? While Capgo has served as a live update solution for Capacitor apps, there are several compelling reasons to consider migrating to Capawesome Cloud. The platform offers significantly better reliability with 99.99% uptime compared to Capgo's 99.65%, translating to just 10 minutes of downtime versus 7 hours over the past 90 days. Response times are notably faster at 267ms versus 441ms, ensuring your users receive updates more quickly. Beyond performance metrics, Capawesome Cloud provides a more stable platform with fewer bug reports and patch releases, indicating a more mature and tested codebase. The pricing structure is more accessible, starting at $9/month with a free tier available, compared to Capgo's $14/month minimum. You also gain access to unique features like EU hosting options, Git integration, and unlimited updates that aren't available with Capgo. The Capawesome team brings deep expertise in Capacitor plugin development, with official Ionic Developer Experts on staff who understand the intricacies of the Capacitor ecosystem. This expertise translates into better support and more thoughtful feature development that aligns with Capacitor best practices. ## Prerequisites Before starting your migration from Capgo to Capawesome Cloud, make sure you have these requirements in place: - A Capacitor app currently using Capgo for live updates - Node.js version 18 or higher installed on your development machine - Access to your current Capgo configuration and deployment settings - Your app's bundle IDs and channel configurations from Capgo Take a moment to document your current Capgo setup, including any custom update strategies or configurations you've implemented. This will help ensure a smooth transition to Capawesome Cloud. ## Migration Overview The migration process from Capgo to Capawesome Cloud follows a structured approach designed to minimize disruption to your existing users. You'll begin by setting up your Capawesome Cloud account and configuring your app. Then you'll install the Capawesome Live Update SDK, replacing the Capgo plugin in your project. After updating your code to use Capawesome's API methods, you'll configure your desired update strategies. Finally, you'll build and deploy your first update through Capawesome Cloud. Throughout this process, you can maintain your existing update cadence and user experience patterns. The APIs are designed to be familiar, making the code migration straightforward while giving you access to enhanced capabilities. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capawesome-cloud` and use the following prompt with your preferred AI coding assistant: ``` Use the `capawesome-cloud` skill from `capawesome-team/skills` to help me set up Capacitor Live Updates in my project. ``` ## Step 1: Setting Up Capawesome Cloud ### Create Your Account Start by visiting the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) to create your account. The registration process is quick and gives you immediate access to the platform. Unlike Capgo, Capawesome Cloud offers a generous free tier that lets you test all features before committing to a paid plan. Once logged in, you'll find an intuitive dashboard where you can manage your applications, monitor update deployments, and access detailed analytics about your user base and update performance. The interface is designed to be familiar to anyone who has used live update platforms before, while offering enhanced visibility into your deployment pipeline. ### Create Your App In the Capawesome Cloud Console, navigate to the Apps section and click "Create App" to set up your first application. Enter a descriptive name for your app that will help you identify it easily in your dashboard. The system will generate a unique App ID that serves as the primary identifier for your application in all API calls and configurations. You'll want to configure channels that match your existing Capgo setup. If you're using channels like "production", "staging", or "development" in Capgo, you can create equivalent channels in Capawesome Cloud. This ensures a smooth transition without disrupting your existing deployment workflows. ## Step 2: Installing the Capawesome Live Update SDK Remove the Capgo plugin from your project and install the Capawesome Live Update plugin: ``` npm uninstall @capgo/capacitor-updater npm install @capawesome/capacitor-live-update npx cap sync ``` The Capawesome Live Update plugin is built from the ground up for Capacitor, ensuring optimal performance and compatibility. It's fully open-source under the MIT license, giving you transparency into how your updates are handled and the freedom to contribute improvements if needed. ## Step 3: Configuring Your App Replace your Capgo configuration with the Capawesome Cloud settings in your Capacitor configuration file: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", // Your Capawesome App ID defaultChannel: "production", // Optional: Your default channel autoDeleteBundles: true // Optional: Automatically clean up old bundles } } }; export default config; ``` capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000", "defaultChannel": "production", "autoDeleteBundles": true } } } ``` The configuration is intentionally streamlined compared to Capgo, focusing on essential settings while handling advanced behaviors through code. This gives you more flexibility in implementing custom update logic specific to your app's needs. Here's a full comparison of the relevant configuration options between Capgo and Capawesome Cloud: | Capgo | Capawesome Cloud | Notes | | -------------------- | ------------------- | -------------------------------------------------------------- | | `appReadyTimeout` | `readyTimeout` | | | `autoDeletePrevious` | `autoDeleteBundles` | | | `autoSplashscreen` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-capgo-sdk) | | `autoUpdate` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-capgo-sdk) | | `appId` | `appId` | | | `defaultChannel` | `defaultChannel` | | | `directUpdate` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-capgo-sdk) | | `periodCheckDelay` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-capgo-sdk) | | `publicKey` | `publicKey` | | | `resetWhenUpdate` | Not needed | Automatically handled by the SDK | | `responseTimeout` | `httpTimeout` | | | `statsUrl` | Not needed | | | `updateUrl` | `serverDomain` | Same purpose, different value | | `version` | Not needed | Native version code is used by the SDK | ## Step 4: Replacing the Capgo SDK ### API The migration from Capgo's API to Capawesome Cloud's API involves updating your method calls and adjusting some patterns. Here's how to translate the most common operations: #### Get Latest In Capgo, you might check for the latest available bundle using the `getLatest` method. Capawesome Cloud provides this functionality through the [`fetchLatestBundle(...)`](https://capawesome.io/plugins/live-update/#fetchlatestbundle) method: **Capgo**: ``` import { CapacitorUpdater } from '@capgo/capacitor-updater'; const checkForUpdate = async () => { const latest = await CapacitorUpdater.getLatest(); if (latest.url) { console.log('Update available:', latest.version); } }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const checkForUpdate = async () => { const result = await LiveUpdate.fetchLatestBundle(); if (result.downloadUrl) { console.log('Update available:', result.bundleId); } }; ``` #### Download Downloading the update bundle is done using the `download` method in Capgo, while Capawesome Cloud uses the [`downloadBundle(...)`](https://capawesome.io/plugins/live-update/#downloadbundle) method: **Capgo**: ``` import { CapacitorUpdater } from '@capgo/capacitor-updater'; const downloadUpdate = async () => { const latest = await CapacitorUpdater.getLatest(); if (latest.url) { const bundle = await CapacitorUpdater.download({ url: latest.url, version: latest.version }); console.log('Bundle downloaded'); } }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const downloadUpdate = async () => { const result = await LiveUpdate.fetchLatestBundle(); if (result.downloadUrl) { await LiveUpdate.downloadBundle({ bundleId: result.bundleId, url: result.downloadUrl }); console.log('Bundle downloaded'); } }; ``` #### Next To set a specific bundle as the next one to be loaded, you have to use the [`setNextBundle(...)`](https://capawesome.io/plugins/live-update/#setnextbundle) method in Capawesome Cloud. **Capgo**: ``` import { CapacitorUpdater } from '@capgo/capacitor-updater'; const setNextBundle = async () => { await CapacitorUpdater.next({ id: 'bundle-id-123' }); }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setNextBundle = async () => { await LiveUpdate.setNextBundle({ bundleId: 'bundle-id-123' }); }; ``` #### Reload Finally, if you want to apply the downloaded update directly, you have to use the `reload` functionality which works the same way in both platforms. **Capgo**: ``` import { CapacitorUpdater } from '@capgo/capacitor-updater'; const applyUpdate = async () => { await CapacitorUpdater.reload(); }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const applyUpdate = async () => { await LiveUpdate.reload(); }; ``` ### Live Update Strategies Capawesome Cloud supports flexible update strategies that can replicate and enhance your existing Capgo update behaviors: #### Background The background strategy silently downloads updates while your app runs, applying them seamlessly on the next launch: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; // Check for updates on app launch const initializeApp = async () => { await LiveUpdate.sync(); // Your app initialization code }; initializeApp(); // Call once at app startup ``` This approach mirrors Capgo's default behavior and offers additional flexibility in managing updates. #### Always Latest This approach is our recommended strategy for ensuring users always have the latest version of your app in a timely manner: ``` import { App } from '@capacitor/app'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; App.addListener('resume', async () => { const result = await LiveUpdate.sync(); if (result.nextBundleId) { const shouldReload = confirm('A new update is available. Would you like to install it now?'); if (shouldReload) { await LiveUpdate.reload(); } } }); ``` This strategy balances keeping your app updated with respecting user choice, allowing them to defer updates if they're in the middle of something important. #### Force Update The force update strategy ensures users have the latest version **before** they can use the app. This is useful for critical updates: ``` import { SplashScreen } from '@capacitor/splash-screen'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const initializeApp = async () => { const result = await LiveUpdate.sync(); if (result.nextBundleId) { await LiveUpdate.reload(); // Apply the update } else { await SplashScreen.hide(); // Hide the splash screen } }; initializeApp(); // Call once at app startup ``` While this ensures all users are on the latest version, use this strategy judiciously as it can impact the user experience if updates are frequent or large. ## Step 5: Building and Distributing Updates ### Build Your App Generate your web assets using your standard build process: ``` npm run build ``` Ensure your build output directory (typically `www` or `dist`) contains all necessary assets. Capawesome Cloud uses intelligent diffing to create delta updates, so even if your initial bundle is large, subsequent updates will be minimal. ### Install Capawesome CLI Install the Capawesome CLI tool to manage your deployments: ``` npm install -g @capawesome/cli@latest ``` The CLI provides powerful commands for bundle management, including creation, upload, and rollback capabilities. It's designed to integrate seamlessly with CI/CD pipelines, supporting both interactive and automated workflows. ### Deploy a Live Update Create and deploy your first bundle to Capawesome Cloud: ``` npx @capawesome/cli apps:liveupdates:upload --app-id YOUR_APP_ID --channel production --path www ``` This command packages your web assets into a bundle and uploads it to Capawesome Cloud. The bundle becomes immediately available to your users through the channel you specified. You can automate this process in your CI/CD pipeline just as you did with Capgo. ## Migration Timeline A well-planned migration ensures minimal disruption to your users while maintaining continuous update capability throughout the transition. Here's a recommended timeline that balances thoroughness with efficiency: Begin with a pilot phase where you set up Capawesome Cloud and test the integration with a development build of your app. This typically takes 1-2 days and allows you to familiarize yourself with the platform's features and validate that all your update scenarios work correctly. Next, implement the SDK replacement in a feature branch, thoroughly testing each update strategy your app uses. Allocate 3-5 days for this phase, including comprehensive QA testing. Pay special attention to edge cases like network interruptions during updates or app backgrounding during the update process. Roll out the Capawesome-enabled version to a small group of beta users or internal testers. Monitor this group for 5-7 days, tracking metrics like update success rates, download times, and any user-reported issues. This controlled rollout helps identify any unexpected behaviors before full deployment. Finally, release the updated app to all users through your standard app store deployment process. Continue monitoring for the first week after release to ensure stability. Once confirmed, you can safely sunset your Capgo subscription while maintaining full update capabilities through Capawesome Cloud. ## Conclusion Migrating from Capgo to Capawesome Cloud brings immediate benefits in reliability, performance, and cost-effectiveness. The platform's superior uptime, faster response times, and robust feature set ensure your live update infrastructure is built on a solid foundation. With delta updates reducing data transfer and transparent pricing that scales with your app's growth, Capawesome Cloud represents a significant upgrade for your Over-the-Air update strategy. The migration process, while requiring some code changes, is designed to be straightforward and can typically be completed in less than a day of development time. The familiar API patterns and comprehensive documentation make the transition smooth, while the enhanced capabilities provide room for future growth and optimization. For assistance during your migration, the Capawesome team offers dedicated support to ensure your transition is successful. Visit the [Capawesome Cloud documentation](https://capawesome.io/cloud/index.md) for detailed technical guides, explore the [Live Update plugin reference](https://capawesome.io/plugins/live-update/index.md) for API details, or [contact the support team](mailto:support@capawesome.io) for personalized migration assistance. Take control of your app's update infrastructure today with Capawesome Cloud and join the growing community of developers who've made the switch to a more reliable, feature-rich live update platform. # Migrating from Ionic Appflow to Capawesome Cloud With Ionic's recent announcement about discontinuing commercial products including Appflow by December 31, 2027, many teams are looking for alternative solutions for their live update needs. Capawesome Cloud provides the only **drop-in replacement** for Ionic Appflow, making migration straightforward while preserving your existing workflows. ## Why Migrate from Ionic Appflow? Ionic [announced the discontinuation of their commercial products](https://ionic.io/blog/important-announcement-the-future-of-ionics-commercial-products), including Appflow, as part of their transition after being acquired by OutSystems. While existing users have access through December 2027, it's important to plan your migration early. Capawesome Cloud offers several compelling advantages for teams making this transition. You'll benefit from transparent, scalable pricing based on Monthly Active Users, delta updates that significantly reduce data transfer, and a secure zero-trust architecture. The platform is actively maintained with regular updates and enhancements, and the entire migration process typically takes less than an hour to complete. ## Prerequisites Before beginning your migration, ensure you have the following in place: - An existing Capacitor app (version 6 or higher) - Node.js version 18 or higher installed on your development machine - Access to your Ionic Appflow configuration and deployment settings - Your app's build and deployment processes documented Having these prerequisites ready will make the migration process smoother and help you avoid potential roadblocks during the transition. ## Migration Overview The migration from Ionic Appflow to Capawesome Cloud involves five main steps that we'll walk through in detail. First, you'll set up your Capawesome Cloud account and create your app. Next, you'll install the Capawesome Live Update SDK in your project. Then, you'll configure your app with the appropriate settings. After that, you'll replace the Ionic Appflow SDK calls with their Capawesome equivalents. Finally, you'll build and distribute your first update through Capawesome Cloud. Each step is designed to be straightforward, and the entire process maintains compatibility with your existing app structure and deployment workflows. AI-Assisted Migration For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill ionic-appflow-migration` and use the following prompt with your preferred AI coding assistant: ``` Use the `ionic-appflow-migration` skill from `capawesome-team/skills` to help me migrate from Ionic Appflow to Capawesome Cloud. ``` ## Step 1: Setting Up Capawesome Cloud ### Create Your Account Visit the [Capawesome Cloud Console](https://console.cloud.capawesome.io/) and sign up for a new account. The registration process is quick and straightforward. Once you've created your account, you'll have access to Capawesome Cloud, where you can manage your apps, view analytics, and configure your live update settings. The platform offers a free tier that's perfect for testing and small projects, allowing you to explore all features before committing to a paid plan. This gives you the opportunity to validate that Capawesome Cloud meets all your requirements before completing the full migration. ### Create Your App After logging into the Capawesome Cloud Console, click on "Create App" to create a new project within Capawesome Cloud. You'll just need to provide a name for your app. The console will generate a unique ID for your app, which you'll use to configure the Live Update SDK. Take note of this App ID as you'll need it in the configuration step. You can also set up multiple channels for different environments (development, staging, production) at this stage, similar to how you might have configured them in Ionic Appflow. ## Step 2: Installing the Capawesome Live Update SDK Remove the Ionic Live Updates SDK from your project and install the Capawesome Live Update plugin: ``` npm uninstall @capacitor/live-updates npm install @capawesome/capacitor-live-update npx cap sync ``` The Capawesome Live Update plugin is built specifically for Capacitor and provides all the functionality you need for Over-the-Air updates. It's open-source and actively maintained, ensuring you have access to the latest features and improvements. ## Step 3: Configuring Your App Update your [Capacitor Configuration](https://capacitorjs.com/docs/config) file to include the Capawesome Live Update configuration. Replace your Ionic Appflow configuration with the following: capacitor.config.ts ``` import { CapacitorConfig } from "@capacitor/cli"; const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", // Required. Replace with your Capawesome App ID from Step 1 defaultChannel: "production" // Optional. Replace with your desired default channel (if you have created one) } } }; export default config; ``` capacitor.config.json ``` { "plugins": { "LiveUpdate": { "appId": "00000000-0000-0000-0000-000000000000", // Required. Replace with your Capawesome App ID from Step 1 "defaultChannel": "production" // Optional. Replace with your desired default channel (if you have created one) } } } ``` The configuration options are similar to what you're familiar with from Ionic Appflow, making the transition intuitive. The `appId` is the unique identifier you received when creating your app in the Capawesome Cloud Console. The `defaultChannel` determines which update channel your app will check for updates. Here's a full comparison of the relevant configuration options between Ionic Appflow and Capawesome Cloud: | Ionic Appflow | Capawesome Cloud | Notes | | ------------------ | ------------------- | ---------------------------------------------------------------------- | | `appId` | `appId` | Same purpose, different value | | `autoUpdateMethod` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-ionic-appflow-sdk) | | `channel` | `defaultChannel` | | | `enabled` | Not needed | Handled in code, see [Step 4](#step-4-replacing-the-ionic-appflow-sdk) | | `maxVersions` | `autoDeleteBundles` | No longer a number, but a boolean | ## Step 4: Replacing the Ionic Appflow SDK ### API The Capawesome Live Update plugin provides a similar API to the Ionic Appflow SDK, making code migration straightforward. Here's how to update your implementation: #### Sync The `sync` method checks for updates and downloads them if available. The Capawesome Live Update plugin provides the exact same API as the Ionic Appflow SDK, with additional optional features. **Ionic Appflow**: ``` import { LiveUpdates } from '@capacitor/live-updates'; const sync = async () => { await LiveUpdates.sync(); }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { await LiveUpdate.sync(); }; ``` Check out the [`sync(...)`](https://capawesome.io/plugins/live-update/#sync) method documentation of the Capawesome Live Update SDK to learn what extra options are available. #### Reload The `reload` method in Capawesome Cloud works similarly to Ionic Appflow, reloading your app with the latest downloaded bundle: **Ionic Appflow**: ``` import { LiveUpdates } from '@capacitor/live-updates'; const reload = async () => { await LiveUpdates.reload(); }; ``` **Capawesome Cloud**: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const reload = async () => { await LiveUpdate.reload(); }; ``` ### Live Update Strategies Capawesome Cloud supports several update strategies similar to Ionic Appflow. For detailed information, see the [Update Strategies](https://capawesome.io/cloud/live-updates/guides/update-strategies/index.md) guide. #### Background Set the `autoUpdateStrategy` configuration option to `background` to automatically check for updates at app startup and when the app resumes: capacitor.config.ts ``` const config: CapacitorConfig = { plugins: { LiveUpdate: { appId: "00000000-0000-0000-0000-000000000000", autoUpdateStrategy: 'background', defaultChannel: 'production' // Optional } } }; ``` This downloads updates in the background and applies them on the next app launch, requiring no additional code. #### Always Latest Combine the background strategy with the `nextBundleSet` event listener to prompt users to apply updates immediately: ``` import { LiveUpdate } from "@capawesome/capacitor-live-update"; LiveUpdate.addListener('nextBundleSet', async () => { const shouldReload = confirm('A new update is available. Would you like to install it now?'); if (shouldReload) { await LiveUpdate.reload(); } }); ``` #### Force Update To ensure users have the latest version before using the app, configure the splash screen to not auto-hide and implement a force update check: ``` import { SplashScreen } from '@capacitor/splash-screen'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const initializeApp = async () => { const { nextBundleId } = await LiveUpdate.sync(); if (nextBundleId) { await LiveUpdate.reload(); } else { await SplashScreen.hide(); } }; ``` Note that this may impact user experience if updates are large or connections are slow. ## Step 5: Building and Distributing Updates ### Build Your App Build your web assets as you normally would: ``` npm run build ``` Ensure your build process generates the web assets in the correct directory (typically `www` or `dist`). The build output should be identical to what you were creating for Ionic Appflow deployments. ### Install Capawesome CLI Install the Capawesome CLI tool globally to enable bundle creation and deployment: ``` npm install -g @capawesome/cli@latest ``` The CLI provides commands for creating bundles, uploading them to Capawesome Cloud, and managing your deployments. It integrates seamlessly with your existing CI/CD pipelines. ### Deploy a Live Update Create and deploy a new bundle to Capawesome Cloud: ``` npx @capawesome/cli apps:liveupdates:upload --app-id YOUR_APP_ID --channel production --path www ``` This command packages your web assets into a bundle and uploads it to Capawesome Cloud. The bundle becomes immediately available to your users through the channel you specified. You can automate this process in your CI/CD pipeline just as you did with Ionic Appflow. ## Migration Timeline Planning your migration timeline is crucial for a smooth transition. Here's a recommended approach: Start by setting up Capawesome Cloud and testing the integration in a development environment. This initial phase typically takes 1-2 days and allows you to familiarize yourself with the platform without affecting production users. Next, implement the SDK replacement in a feature branch and thoroughly test all update strategies. Plan for 3-5 days for this phase, including time for QA testing and addressing any edge cases specific to your app. Once testing is complete, deploy the updated app to a subset of users for beta testing. Monitor the rollout for 1-2 weeks to ensure everything works as expected. During this period, you can run both Ionic Appflow and Capawesome Cloud in parallel if needed. Finally, complete the full migration by updating all users to the Capawesome Cloud-enabled version of your app. Continue monitoring for another week to ensure stability, then you can safely discontinue your Ionic Appflow usage. Remember that you have until December 31, 2027, to complete the migration from Ionic Appflow, so you can take a measured approach that minimizes risk to your production environment. ## Conclusion Migrating from Ionic Appflow to Capawesome Cloud is a straightforward process that ensures your app's live update infrastructure remains robust and well-supported. The similar APIs and update strategies mean minimal code changes, while the enhanced features like delta updates and transparent pricing provide additional value for your team. Capawesome Cloud is built specifically for Capacitor apps and is actively maintained with regular updates and improvements. The platform's commitment to long-term stability and continuous enhancement makes it an ideal choice for teams seeking a reliable live update solution. If you need assistance during your migration, the Capawesome team provides excellent support and documentation. Visit the [Capawesome Cloud documentation](https://capawesome.io/cloud/index.md) for detailed guides, check out the [Getting Started with Live Updates](https://capawesome.io/cloud/live-updates/setup/index.md) guide for comprehensive setup instructions, or [reach out to their support team](mailto:support@capawesome.io) for personalized assistance with your migration. Start your migration today and ensure your app's update infrastructure is ready for the future. With Capawesome Cloud, you'll have a powerful, secure, and cost-effective solution that grows with your app and your team's needs. # Showcase: Appreciation Jar - Send appreciative messages to your partner In today's fast-paced digital world, meaningful connection between partners can sometimes get lost in the noise. Appreciation Jar, developed by Stanislav Khromov, offers a refreshing solution: a privacy-first gratitude journaling app based on Capacitor designed specifically for couples to share appreciations and positive thoughts in a secure, intimate space. ## Introduction [Appreciation Jar](https://appreciation.place/) transforms the simple act of expressing gratitude into a powerful tool for strengthening relationships. Unlike social media platforms where personal moments become public, this app creates a private sanctuary where couples can focus purely on appreciating each other without external distractions or pressure. The concept is beautifully simple: create a shared virtual appreciation jar with your partner, family members, or close friends, and fill it with positive messages that build stronger emotional connections through regular gratitude practice. What sets Appreciation Jar apart is its unwavering commitment to privacy - no email or phone number required for signup, with all data stored on EU-based servers and complete user ownership without any third-party involvement. ## Features Appreciation Jar prioritizes simplicity and privacy in its feature set. The app offers quick setup that takes less than a minute, allowing users to immediately start sharing appreciative messages with their partners. The cross-platform availability means couples can stay connected whether they're using iOS, Android, or the web version. The privacy-first design stands out in today's data-hungry digital landscape. Users maintain complete control over their data, with servers located in the European Union and absolutely no third-party data sharing. This approach creates a truly secure environment where couples can express their deepest appreciations without concerns about privacy or data misuse. The shared jar concept provides an elegant metaphor for relationship building - each positive message becomes a precious memory stored in a digital container that both partners can revisit whenever they need emotional support or simply want to remember why they appreciate each other. ## Monetization Appreciation Jar follows a unique approach to monetization by currently offering the app completely free to users. With approximately 5,000 total downloads and 500 monthly active users, Stanislav has chosen to focus on creating value and building a loyal user base rather than immediate revenue generation. This strategy reflects the app's core philosophy of putting relationships and user experience first. By removing financial barriers, Appreciation Jar ensures that strengthening relationships through gratitude practice remains accessible to everyone, regardless of their economic situation. ## Development The development story of Appreciation Jar showcases the power of modern cross-platform tools. Stanislav chose Capacitor because learning native iOS and Android development plus maintaining multiple codebases would have been overwhelming for a side project. Among web-to-native options, Capacitor emerged as the mature choice that properly supported both platforms while allowing him to use familiar web technologies. In the end, Appreciation Jar was built using a stack that includes: - Capacitor: Cross-platform framework for building native mobile apps using web technologies - SvelteKit: A modern framework for building fast, interactive web applications - PostgreSQL: A powerful relational database for storing user data securely - CapRover and Docker: Containerization and deployment platform for easy app management - Umami: Privacy-friendly analytics - GlitchTip: Error monitoring - Pushpin: Realtime updates The development process took approximately six months, with the only major challenge being the app-specific requirements like creating developer accounts with Apple and Google, setting up native environments locally, and building app bundles correctly. Stanislav praised Capacitor's documentation, noting it was excellent even for advanced topics like building custom plugins. For Capacitor, the following plugins were utilized: ``` @capacitor/android @capacitor/app @capacitor/assets @capacitor/cli @capacitor/clipboard @capacitor/core @capacitor/ios @capacitor/preferences @capacitor/push-notifications @capacitor/status-bar @capawesome-team/capacitor-printer ``` As you can see, the app leverages only a few essential plugins to keep the codebase lean and focused on core functionality. This approach aligns with the app's philosophy of simplicity and privacy. The [Capawesome Printer](https://capawesome.io/plugins/printer/index.md) plugin is the only unofficial Capacitor plugin used, allowing users to print their appreciation messages directly from the app. ## Q&A **Why did you choose Capacitor for this project?** "I chose Capacitor because learning native iOS and Android development plus maintaining multiple codebases would have been difficult for a side project - I needed a single codebase solution using web technologies I already was familiar with. Among the web-to-native options, Capacitor was the only mature choice that properly supported both platforms." **What do you like most about Capacitor?** "That I can still use my favourite stack for web projects. While there are UI kits like Ionic, I wanted to make a completely custom design for this app and Capacitor made that super simple. It also makes it easy to prototype the web/PWA version first to validate the idea and then add Capacitor at the end." **What are your future plans for the app?** "I'm working on a big update right now that focuses on the realtime components in the app. There will be presence indicators showing if other users are currently in the app (and where), live updating of posted messages (like a chat app) and some new creative ways to stay in touch with each other remotely." **What's your most valuable tip for developers using Capacitor?** "Start with what you already know - web and PWA, and build out your prototype. Once it starts to feel good (or you start needing native features) add Capacitor on top!" ## Conclusion Appreciation Jar represents more than just another messaging app - it's a thoughtfully crafted tool for nurturing relationships in our digital age. By combining the convenience of modern technology with a deep respect for privacy and meaningful connection, Stanislav has created something truly special. For couples looking to deepen their connection or developers interested in building privacy-first applications with Capacitor, Appreciation Jar serves as an inspiring example of what's possible when technology is used to genuinely improve human relationships. *Download Appreciation Jar on the [App Store](https://apps.apple.com/us/app/appreciation-jar/id6447924542) or [Google Play Store](https://play.google.com/store/apps/details?id=place.appreciation.jar) to start saving money on your Costco purchases today.* **You want to showcase your Capacitor app?** Reach out to us at [support@capawesome.io](mailto:support@capawesome.io). We would love to feature your app in our next showcase post! # Showcase: CostPal - Price tracking app for Costco Meet CostPal, a smart price tracking app based on Capacitor that helps Costco shoppers save money by automatically monitoring their purchases for price drops and facilitating refunds through Costco's 30-day price adjustment policy. Developed by Aleksandr Melentiev and Christopher Lee at Gravi Opus Studios, this innovative app has already helped thousands of users save money on their Costco purchases. ## Introduction [CostPal](https://www.costpal.app/?utm_source=capawesome-blog) (formerly known as "Companion for Costco") is a mobile application that solves a common problem faced by Costco shoppers: missing out on potential refunds when prices drop shortly after a purchase. With Costco's generous 30-day price adjustment policy, customers can get refunds when items they recently purchased go on sale, but manually tracking these price changes is time-consuming and often overlooked. The app addresses this challenge by automating the entire process. Users simply upload their Costco receipts, and CostPal monitors their purchases for price drops, sending real-time notifications when savings opportunities arise. With over 50,000 downloads and 10,000 monthly active users, CostPal has established itself as an essential tool for savvy Costco shoppers. To date, CostPal has saved their users over $100,000. ## Features CostPal offers a comprehensive set of features designed to maximize savings for Costco shoppers: **Automated Price Tracking**: The app continuously monitors recent Costco purchases for price drops, eliminating the need for manual price checking. **Real-time Price Adjustment Alerts**: Users receive instant notifications when prices drop on their recent purchases, ensuring they never miss a savings opportunity. **Receipt Upload**: Simply scan or upload Costco receipts to the app, and it automatically extracts purchase information for tracking. **Purchase Analytics**: Gain insights into purchasing habits and track total savings over time. **Price Adjustment Assistance**: The app simplifies the process of claiming refunds by providing guidance on Costco's price adjustment policy. The app's intuitive interface makes it easy for users to manage their receipts and track their savings. As one user noted, "As a busy mom and family of 6, I don't have time to watch prices myself" - CostPal solves this problem perfectly. ## Monetization CostPal follows a freemium model with strategic monetization that aligns with user value: **Free Features**: Basic app download and initial setup are free, allowing users to experience the core functionality. **Price Adjustment Assistant**: The premium feature that provides advanced price adjustment assistance is offered as a paid subscription. **Free Trial**: New users can try the premium features with $5 worth of price adjustment savings at no cost. **Subscription Tiers**: Plans start at $2.99/month or $29.99/year, with alternative payment options ranging from $0.99 to $9.99 (as of July 2025). This pricing strategy ensures that users only pay when they're actively saving money, creating a win-win situation where the app's success is directly tied to user savings. ## Development The development of CostPal showcases the power of modern cross-platform development tools. Aleksandr Melentiev and Christopher Lee chose Capacitor for its ability to deliver a "fast-to-market" solution using a single front-end codebase without worrying about iOS and Android specific development complexities. The app is built using a combination of **technologies** that enable rapid development and deployment across platforms: - Capacitor - Ionic Framework - Vue.js - Firebase (Analytics, Crashlytics, and more) The team has also leveraged a wide range of **Capacitor plugins** to enhance the app's functionality, including: ``` @capacitor-community/admob @capacitor-community/in-app-review @capacitor-firebase/analytics @capacitor-firebase/authentication @capacitor-firebase/crashlytics @capacitor-firebase/messaging @capacitor-firebase/remote-config @capacitor/app @capacitor/camera @capacitor/device @capacitor/filesystem @capacitor/geolocation @capacitor/haptics @capacitor/share @capacitor/splash-screen @capacitor/status-bar @capawesome/capacitor-app-update @capawesome/capacitor-live-update @capawesome/capacitor-screen-orientation @revenuecat/purchases-capacitor capacitor-branch-deep-links ``` ## Q&A **Q: Why did you choose Capacitor for your app development?** A: "Fast-to-market by utilizing single front-end codebase without having to worry about iOS and Android specific development. Probably the easiest framework to get a mobile app up and running fast enough at the moment." **Q: What challenges did you face during development?** A: The main challenge was dealing with outdated plugins in the Capacitor ecosystem. "We have encountered some outdated plugins for Capacitor, from both community-maintained plugins as well as official big company plugins that were not updated to Capacitor v7 for a long time, hindering our development and plans. We had to create our own forks by upgrading them to v7 compatibility." **Q: How satisfied are you with Capacitor?** A: The team rates their satisfaction as 8/10, appreciating Capacitor's ease of use and rapid development capabilities. **Q: What's your most valuable tip for developers using Capacitor?** A: Focus on leveraging the extensive plugin ecosystem while being prepared to maintain or fork plugins when necessary for compatibility with newer Capacitor versions. **Q: What are your future plans for CostPal?** A: The team continues to enhance the app's features and expand its capabilities to provide even more value to Costco shoppers. ## Conclusion CostPal represents an excellent example of how modern cross-platform development tools like Capacitor can be used to solve real-world problems efficiently. By focusing on a specific niche (Costco shoppers) and providing genuine value (automated price tracking and refund assistance), the app has built a loyal user base and sustainable business model. The app's success demonstrates several key principles: - **Focused Problem-Solving**: Addressing a specific pain point for a well-defined audience - **Value-Aligned Monetization**: Pricing that scales with user savings - **Efficient Development**: Using Capacitor to rapidly deploy across platforms - **Community Engagement**: Building tools that genuinely help users save money For Capacitor developers, CostPal showcases the framework's potential for creating successful commercial applications. The extensive use of plugins, from core Capacitor features to specialized third-party solutions, demonstrates how developers can leverage the rich ecosystem to build feature-rich applications. Whether you're a Costco shopper looking to maximize your savings or a developer interested in cross-platform app development, CostPal offers valuable insights into both smart shopping and modern app development practices. *Download CostPal on the [App Store](https://apps.apple.com/app/costco-companion/id6443647697?pt=125523447&ct=capawesome-blog&mt=8) or [Google Play Store](https://play.google.com/store/apps/details?id=app.costcocompanion.dev&utm_source=capawesome-blog&utm_campaign=launch) to start saving money on your Costco purchases today.* **You want to showcase your Capacitor app?** Reach out to us at [support@capawesome.io](mailto:support@capawesome.io). We would love to feature your app in our next showcase post! # Showcase: LEAGUES - The immersive football viewing app LEAGUES is revolutionizing how football fans experience the beautiful game through their innovative mobile app. Built with Capacitor, this app brings the stadium atmosphere directly to your pocket with immersive storytelling and live streaming capabilities. ## Introduction [LEAGUES](https://www.leagues.football/) is a cutting-edge football app developed by LEAGUES GmbH in Stuttgart, Germany, that transforms how fans consume football content. Built with Capacitor, this cross-platform application delivers an immersive experience that makes users feel like they're right in the stadium, even when watching from home. The app focuses on telling football stories through short portrait format videos, highlighting the most important moments of matches with professional production quality. What sets LEAGUES apart is its unique approach to football content delivery - combining live streaming with curated highlights in an easily digestible format. ## Features LEAGUES offers a comprehensive suite of features designed to enhance the football viewing experience. At its core, the app provides live streaming capabilities for Regionalliga Südwest and West matches through their Match Pass subscription service. Users can follow games in real-time with the live audiovisual match ticker, which delivers updates as they happen, complete with goal replays and highlights presented in an engaging "Full-Story" format. The app excels at storytelling through short portrait format videos that showcase key match highlights, making it easy for fans to catch up on the most important moments. When new content becomes available, users receive push notifications to stay connected to the action. Beyond match coverage, LEAGUES features exclusive interviews with players and club officials, providing insider perspectives that bring fans closer to their favorite teams. The user experience is designed for seamless navigation through intuitive swipe gestures, allowing users to effortlessly browse through content. The app's high-performance infinity scrolling system handles multiple video elements smoothly, ensuring optimal playback even with heavy video content. Multi-device support means fans can enjoy the experience whether they're on mobile, tablet, or TV, while the redesigned timeline focuses on goals, highlights, and videos for faster content delivery. ## Monetization LEAGUES operates on a freemium model with their Match Pass subscription service. While the app is free to download and use for basic features, users can upgrade to Match Pass to access live streaming of Regionalliga Südwest matches. Additionally, some features are only accessible to members of specific clubs, creating exclusive content for club communities. The company has chosen not to implement traditional in-app purchases, instead focusing on subscription-based revenue through their streaming service and club-specific access. This model aligns well with their content strategy and provides a sustainable revenue stream while keeping the core app experience free. ## Development LEAGUES leverages a modern technology stack built around web technologies: - **Capacitor** for cross-platform mobile development - **Vue.js 3** with TypeScript for the frontend framework - **Ionic Framework** for UI components - **Tailwind CSS** for styling - **Firebase** for authentication and real-time database - **Vite** for build tooling The team has also leveraged a wide range of Capacitor and Cordova plugins to enhance the app's functionality, including: ``` @awesome-cordova-plugins/core @awesome-cordova-plugins/social-sharing @awesome-cordova-plugins/vibration @capacitor/android @capacitor/app @capacitor/browser @capacitor/clipboard @capacitor/core @capacitor/device @capacitor/filesystem @capacitor/haptics @capacitor/ios @capacitor/keyboard @capacitor/local-notifications @capacitor/network @capacitor/push-notifications @capacitor/share @capacitor/splash-screen @capacitor/status-bar capacitor-blob-writer capacitor-plugin-app-tracking-transparency cordova-plugin-vibration ``` The app was developed by a lean team of just 2 people over 4 years, who also worked on other parts of the product including the website and HLS streaming infrastructure, demonstrating the efficiency that can be achieved with modern web technologies and Capacitor. Peter Wingen, the CPO, leads the development efforts at LEAGUES GmbH, showcasing how small teams can build sophisticated applications when using the right tools and approaches. The development team faced several unique technical challenges during the project. Creating smooth infinity scrolling with multiple video elements required careful optimization, leading the team to implement a video player that is constantly repositioned via `position:absolute` and only loads new videos when needed. This approach ensures smooth performance even with heavy video content, which is crucial for the app's core functionality. Additionally, maintaining a consistent experience across iOS and Android while leveraging native features required careful planning and testing to ensure users get the same high-quality experience regardless of their platform. ## Q&A **Q: Why did you choose Capacitor for LEAGUES?** *Peter Wingen (CPO):* "Capacitor is easy to integrate and significantly enhances the modern web and app development landscape. Our development team works with TypeScript and VueJS – by using Capacitor, we can seamlessly bring existing web technologies to mobile platforms. This saves development effort, reduces technical complexity, and allows us to be productive quickly." **Q: What's your satisfaction level with Capacitor?** *Peter:* "We're very happy with Capacitor - I'd rate it 10/10. The ability to use our existing web development skills while still accessing native mobile features has been game-changing for our small team." **Q: What would you improve about Capacitor?** *Peter:* "I would improve the debugging experience for native functionality, especially when working with custom plugins. While the overall developer experience is great, troubleshooting native issues across platforms can sometimes be time-consuming and less transparent compared to web debugging." **Q: What's your most valuable tip for Capacitor developers?** *Peter:* "Treat your web codebase as the core of your application and use Capacitor primarily as a thin bridge to native features. Keep your business logic and UI within your TypeScript/VueJS app, and isolate native code in well-defined plugins. This approach keeps your app maintainable, testable, and future-proof across platforms." **Q: What are your future plans for LEAGUES?** *Peter:* "We're focusing on adding more interactivity to the app. Our goal is to make the football viewing experience even more engaging and immersive for our users." ## Conclusion LEAGUES exemplifies how Capacitor can empower small teams to build sophisticated, cross-platform applications that compete with native apps. By leveraging web technologies like Vue.js and TypeScript, the team at LEAGUES GmbH has created an app that delivers premium football content to thousands of users across iOS and Android. The app's success demonstrates that with the right approach and technology choices, even a two-person team can build and maintain a feature-rich mobile application that serves a passionate user base. LEAGUES proves that Capacitor isn't just about code sharing - it's about enabling teams to focus on what matters most: creating great user experiences. *Download LEAGUES on the [App Store](https://apps.apple.com/us/app/leagues-football/id1572628190) or [Google Play Store](https://play.google.com/store/apps/details?id=football.leagues.app) to experience the future of football storytelling.* **You want to showcase your Capacitor app?** Reach out to us at [support@capawesome.io](mailto:support@capawesome.io). We would love to feature your app in our next showcase post! # Showcase: MyBodyTutor - A Personalized Nutrition and Weight Loss Coaching App [MyBodyTutor](https://www.mybodytutor.com/) is revolutionizing the weight loss and nutrition coaching industry with a unique approach that prioritizes human connection and personalized accountability. Built with Capacitor, this cross-platform app bridges the gap between knowing what to do and actually doing it consistently, offering clients daily one-on-one coaching support that goes far beyond traditional diet and exercise programs. ## Introduction MyBodyTutor stands out in the crowded health and fitness app market by focusing on what many other apps miss: the psychological and behavioral aspects of sustainable weight loss. Unlike automated or AI-driven solutions, MyBodyTutor pairs each client with a dedicated human coach who provides daily support, accountability, and personalized guidance. The app is built using Capacitor, allowing the MyBodyTutor team to leverage web technologies while delivering a native mobile experience across iOS and Android platforms. This cross-platform approach has enabled them to efficiently serve over 10,000 users while maintaining a lean development team. ## Features The MyBodyTutor app provides a comprehensive suite of features designed to support clients throughout their weight loss journey. Here are some of the key functionalities: - **Daily Coach Communication**: Direct messaging with assigned personal coaches - **Food Tracking**: Log meals and receive personalized feedback - **Workout Logging**: Track exercise activities and progress - **Progress Photos**: Visual tracking of transformation journey - **Inspirational Content**: Access to motivational articles and videos - **Custom Reminders**: Personalized notification system - **Extensive Customization**: Tailored app experience based on individual preferences The app's standout feature is its emphasis on **Mindset, Psychology, and Habits (MPH)** transformation. Rather than providing generic meal plans, coaches create 100% unique plans around each client's food preferences, lifestyle, and psychological relationship with food and exercise. ## Monetization MyBodyTutor operates on a coaching service model rather than traditional in-app purchases. The company focuses on high-value, personalized coaching relationships, serving between 750-1,000 active clients depending on the season. This approach allows them to provide premium, individualized service while maintaining sustainable business operations. The service includes a 100% risk-free, 30-day money-back guarantee, demonstrating confidence in their coaching methodology and commitment to client success. ## Development MyBodyTutor's app is built with a modern, robust technology stack: - **Angular** as the web framework - **Ionic Framework** for UI components - **Capacitor** as the cross-platform layer - **Sentry** for monitoring - **Uploadcare** for media management - **Auth0** for authentication The app leverages several Capacitor plugins to provide native functionality: ``` @capacitor/app @capacitor/camera @capacitor/filesystem @capacitor/haptics @capacitor/keyboard @capacitor/local-notifications @capacitor/network @capacitor/preferences @capacitor/share @capacitor/splash-screen @capacitor/status-bar @capawesome-team/capacitor-datetime-picker @capawesome-team/capacitor-file-compressor @capawesome/capacitor-android-edge-to-edge-support @capawesome/capacitor-file-picker @capawesome/capacitor-live-update @capgo/capacitor-navigation-bar ``` The MyBodyTutor app has been in **continuous development since 2016**, starting with the original Ionic/Angular/Cordova stack and evolving to embrace Capacitor as it matured. The development process has involved thousands of hours of iteration and refinement across multiple versions. The development is primarily handled by CTO Jonathan Bennett, with support from a **small team of freelancers** when needed, typically involving 1-3 people actively working on the app at any given time. ## Q&A **Q: Why did you choose Capacitor over other cross-platform solutions?** **A**: "From what I've seen and worked with, it seems to be the easiest way to get a reliable cross-platform mobile app launched. You can use whichever framework you're already familiar with and gain access to native functions quickly. They've continued to improve it over the years and it's solid." - Jonathan Bennett, CTO **Q: What challenges did you face during development?** **A**: "In the earlier days, the best practices for mobile app development were not as well understood, and we faced some tricky bugs that took some time to figure out. The Ionic/Capacitor ecosystem was not as mature as it is now. We also learned that it's helpful to have in-house developers who can pick up any slack that an agency might have." **Q: What are your future development plans?** **A**: The team plans to focus on UX/UI improvements, more customization options, adding offline support, and implementing charts to help clients better track and understand their progress. **Q: What's your most valuable tip for Capacitor developers?** **A**: "It's worth paying for the paid content out there to get up to speed quickly with using it. Besides that, AI tools such as Claude Code seem to understand Angular and Capacitor pretty well, so you can also use that to help double-check your work and troubleshoot potential issues." **Q: How satisfied are you with Capacitor?** **A**: Jonathan rates his satisfaction as 9 out of 10, noting that "it's not as popular as some options such as React Native, but there is still a helpful community of developers using it that you can learn from." ## Conclusion MyBodyTutor demonstrates the power of combining human-centered design with modern cross-platform technology. By choosing Capacitor, the team has been able to focus on what matters most—delivering exceptional coaching experiences—while efficiently maintaining apps across multiple platforms. The app's success with over 10,000 downloads and consistently high ratings (5.0 stars on the App Store) showcases how the right technology choices can enable a small team to create meaningful impact in people's lives. MyBodyTutor's approach proves that in the health and fitness space, technology should enhance rather than replace human connection. For developers considering Capacitor for their next project, MyBodyTutor serves as an excellent example of how web technologies can be leveraged to create professional, feature-rich mobile applications that compete effectively in demanding markets. *Interested in learning more about MyBodyTutor? Visit their website at [mybodytutor.com](https://www.mybodytutor.com/) or download the app from the [App Store](https://apps.apple.com/us/app/bodytutor/id1503217320) or [Google Play Store](https://play.google.com/store/apps/details?id=com.mybodytutor.app).* **You want to showcase your Capacitor app?** Reach out to us at [support@capawesome.io](mailto:support@capawesome.io). We would love to feature your app in our next showcase post! # Support 16 KB page sizes in Android with Capacitor Google Play is introducing a new compatibility requirement that affects Android developers using Capacitor. Starting November 1st, 2025, all new apps and app updates targeting Android 15+ must support 16 KB page sizes on 64-bit devices. This change aims to improve app performance but requires developers to ensure their apps and dependencies are compatible. ## Understanding 16 KB Page Sizes Android devices are transitioning from 4 KB to 16 KB page sizes to optimize memory performance. This change brings several benefits: - **Lower app launch times**: Applications start faster due to improved memory management - **Reduced power consumption**: More efficient memory operations during app launch - **Faster camera launches**: Camera-intensive applications see performance improvements - **Improved system boot time**: Overall system responsiveness is enhanced However, this transition requires apps using native code (NDK, C/C++, or third-party native libraries) to be recompiled with 16 KB ELF alignment. Apps that don't support 16 KB page sizes may experience crashes or compatibility issues on devices with this configuration. ## Capacitor's Role in Supporting 16 KB Pages Capacitor apps are particularly affected by this requirement because they often rely on native plugins and dependencies that may not yet be compatible with 16 KB page sizes. The Capacitor framework itself is being updated to support this new requirement, but individual plugins may need attention. To ensure your Capacitor app is ready for the 16 KB page size requirement: 1. **Update your development environment**: - Use Android Gradle Plugin 8.5.1 or higher - Update to the latest NDK (r28 or higher) 1. **Test your app compatibility**: - Use Android 15 emulators with 16 KB system images - Test on supported Pixel devices with developer options enabled - Utilize Samsung Remote Test Lab for additional testing 1. **Check plugin compatibility**: Review all Capacitor plugins in your project for 16 KB page size support. You can follow Google's [16 KB page size impact guide](https://developer.android.com/guide/practices/page-sizes#16-kb-impact) to check if your dependencies are compatible. ## Known Issues Several popular Capacitor plugins have encountered compatibility issues with 16 KB page sizes. Here are the most common issues and their current status: ### ML Kit Barcode Scanning The Capacitor [ML Kit Barcode Scanning](https://capawesome.io/plugins/mlkit/barcode-scanning/index.md) plugin experienced compatibility issues due to an outdated `androidx.camera:camera-core` dependency (version 1.1.0). This resulted in Google Play warnings about libraries not supporting 16 KB page sizes. **Status**: The issue is resolved in `androidx.camera:camera-core` version 1.4.0, and the fix will be included in the next major update when Capacitor 8 becomes available. More information can be found [here](https://github.com/capawesome-team/capacitor-mlkit/issues/289). **Solution**: Override the `androidx.camera:camera-core` version in your app's `variables.gradle` file. Example: ``` ext { androidxCameraCoreVersion = '1.4.0' } ``` ### SQLite The [Capacitor Community SQLite](https://github.com/capacitor-community/sqlite) plugin encountered crashes on Android 15 emulators with 16 KB page sizes due to an outdated SQLCipher dependency (`android-database-sqlcipher:4.5.3`). **Status**: The issue is actively being resolved with an upgrade to SQLCipher dependency `net.zetetic:sqlcipher-android:4.6.1@aar`, which supports Android 15 devices with 16 KB page sizes. This upgrade is already available in version `7.0.1` of the plugin. **Solution**: Update the Capacitor Community SQLite plugin to the latest version or switch to the [Capawesome SQLite plugin](https://capawesome.io/plugins/sqlite/index.md), which supports 16 KB page sizes and offers additional features. For full **Capacitor SQLite plugin documentation**, see the [SQLite plugin documentation](https://capawesome.io/plugins/sqlite/#api). Or even better, check the [5 reasons why you should consider alternatives to the Capacitor Community SQLite plugin](https://capawesome.io/blog/alternative-to-capacitor-community-sqlite-plugin/index.md) Cordova SQLite Plugins This issue also affects Cordova SQLite plugins, which may not support 16 KB page sizes. We recommend migrating to an up-to-date Capacitor plugin to ensure compatibility and future-proof your app. ## Conclusion The transition to 16 KB page sizes represents an important step forward for Android app performance, but it requires proactive preparation from Capacitor developers. While the Capacitor ecosystem is actively addressing compatibility issues, developers should: 1. Update their development environment and build tools 1. Test their apps thoroughly using 16 KB page size environments 1. Monitor plugin compatibility and apply available workarounds 1. Stay informed about updates from plugin maintainers By taking these steps before the November 1st, 2025 deadline, you can ensure your Capacitor apps continue to work smoothly on the latest Android devices while benefiting from improved performance characteristics. For the most up-to-date information on plugin compatibility, regularly check the GitHub repositories of your dependencies and consider joining the Capacitor community discussions where compatibility updates are frequently shared. ______________________________________________________________________ **You discovered additional Capacitor plugins with 16 KB page size compatibility issues?** Please send us an email at [support@capawesome.io](mailto:support@capawesome.io). We'll investigate and update this blog post to help the community stay informed about known issues and solutions. # Tailwind CSS Plugin for Ionic Framework Tailwind CSS is a popular utility-first CSS framework that allows developers to build custom designs quickly. In this guide, we will show you how to create a simple Tailwind CSS plugin for the Ionic Framework that adds platform-specific variants for Android and iOS. ## Introduction A common requirement for Ionic apps is to apply different styles based on the platform. For example, you might want to use a different background color for Android and iOS. This can be achieved by creating a custom Tailwind CSS plugin that adds platform-specific variants. ## Creating the Plugin Plugins in Tailwind CSS allow you to extend the framework's functionality by adding custom utilities, components, or variants. In this case, we will create a plugin that adds `ios:` and `android:` variants to target specific platforms. To create the plugin, you need to set up your Tailwind CSS configuration file (`tailwind.config.js`) and define the custom variants. Here’s how you can do it: ``` const plugin = require('tailwindcss/plugin'); module.exports = { content: ['./src/**/*.{html,ts}'], plugins: [ plugin(function ({ addVariant }) { addVariant('android', '.md &'); addVariant('ios', '.ios &'); }) ] }; ``` In this code snippet, we are using the `addVariant` function from Tailwind CSS to create two new variants: `android` and `ios`. The `.md &` selector targets Android devices, while the `.ios &` selector targets iOS devices. This allows us to apply styles conditionally based on the platform. Both, the `md` and `ios` classes are automatically added by Ionic Framework when the app is running on the respective platform. Tailwind CSS v4 and later If you are using Tailwind CSS v4 or later, you have to declare those variants directly in your CSS file since no Tailwind CSS configuration file is used anymore. You can do this by adding the following code to your CSS file: ``` @custom-variant android (.md &); @custom-variant ios (.ios &); ``` ## Usage Now that we have created the plugin, we can use the `ios:` and `android:` variants in our Tailwind CSS classes. For example: ```
This div will have a primary background color on iOS and a secondary background color on Android.
``` ## Conclusion With this simple Tailwind CSS plugin, you can easily apply platform-specific styles in your Ionic applications. This approach allows you to maintain a clean and organized codebase while leveraging the power of Tailwind CSS. To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter/) and follow us on [X (formerly Twitter)](https://x.com/capawesomeio). If you have any questions or need assistance with Capacitor, Ionic Framework, or Tailwind CSS, feel free to reach out to the Capawesome team. We are here to help you build amazing applications with the best tools available. # The App Update Delivery Guide for Capacitor Keeping your users on the latest version of your app isn't just nice to have — it's critical for security, performance, and delivering the best experience. In this guide, you'll learn how to build a complete update delivery strategy for your Capacitor app by combining two plugins: the [App Update](https://capawesome.io/plugins/app-update/index.md) plugin for native app store updates and the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin for Over-The-Air (OTA) updates. ## Understanding the Two Types of Updates Before diving in, it helps to understand the two fundamentally different ways you can update a Capacitor app: 1. **Native App Updates** — Full binary updates distributed through the Google Play Store or Apple App Store. These are required whenever you make changes to native code, add or remove plugins, or update native dependencies. Users typically have to download the new version from the store. 1. **Over-The-Air (OTA) Updates** — Also known as live updates, these let you push changes to the web layer of your app (HTML, CSS, and JavaScript) directly to your users' devices. No app store review, no waiting for users to manually update. The best update strategy uses both: native updates for binary changes, and OTA updates for everything else. This gives you the speed and flexibility of live updates while still being able to ship native changes when needed. ## Checking for Native App Updates The [App Update](https://capawesome.io/plugins/app-update/index.md) plugin lets you check whether a newer version of your app is available in the app store and prompt users to update. This is especially useful for critical updates or when you need users to move off an older native version. To install the plugin, refer to the [Installation](https://capawesome.io/plugins/app-update/#installation) section in the plugin documentation. ### Getting Update Information Use [`getAppUpdateInfo(...)`](https://capawesome.io/plugins/app-update/#getappupdateinfo) to check whether an update is available: ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const checkForUpdate = async () => { const info = await AppUpdate.getAppUpdateInfo(); if (info.updateAvailability === AppUpdateAvailability.UPDATE_AVAILABLE) { // An update is available } }; ``` This method returns details like the current and available version, update priority, and whether immediate or flexible updates are allowed on Android. ### Android: In-App Updates On Android, the App Update plugin supports Google Play's in-app update flows, giving you two options. #### Immediate Updates An immediate update displays a full-screen experience that blocks the app until the update is installed. Use this for critical updates that users shouldn't skip: ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const performImmediateUpdate = async () => { const info = await AppUpdate.getAppUpdateInfo(); if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (info.immediateUpdateAllowed) { await AppUpdate.performImmediateUpdate(); } }; ``` #### Flexible Updates A flexible update downloads the new version in the background while the user continues using the app. Once downloaded, you can prompt the user to restart: ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const startFlexibleUpdate = async () => { const info = await AppUpdate.getAppUpdateInfo(); if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (info.flexibleUpdateAllowed) { await AppUpdate.startFlexibleUpdate(); } }; const completeFlexibleUpdate = async () => { await AppUpdate.completeFlexibleUpdate(); }; ``` You can monitor the download progress using the [`addListener('onFlexibleUpdateStateChange', ...)`](https://capawesome.io/plugins/app-update/#addlisteneronflexibleupdatestatechange) method. ### iOS: Opening the App Store iOS doesn't support in-app update flows like Android. Instead, you can check for an available update and direct users to the App Store using [`openAppStore(...)`](https://capawesome.io/plugins/app-update/#openappstore): ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const promptUpdate = async () => { const info = await AppUpdate.getAppUpdateInfo(); if (info.updateAvailability === AppUpdateAvailability.UPDATE_AVAILABLE) { await AppUpdate.openAppStore(); } }; ``` You might want to show a dialog before opening the App Store to let users know why the update is recommended. ## Delivering OTA Updates with Live Update While native updates handle binary changes, most of your day-to-day updates — bug fixes, UI tweaks, new features in the web layer — can be delivered instantly using the [Live Update](https://capawesome.io/plugins/live-update/index.md) plugin. To install the plugin, refer to the [Installation](https://capawesome.io/plugins/live-update/#installation) section in the plugin documentation. ### What Are Live Updates? Live updates let you push changes to your app's web assets (HTML, CSS, JavaScript) directly to users' devices without going through the app store. When your app launches, it checks for new bundles, downloads them, and applies them on the next restart. Keep in mind that live updates only work for web layer changes. If you add a new Capacitor plugin, update native dependencies, or modify native code, you need a full native update through the store. ### Configuration The Live Update plugin supports a background auto-update strategy that handles everything for you. Configure it in your `capacitor.config.json`: ``` { "plugins": { "LiveUpdate": { "appId": "your-capawesome-cloud-app-id", "autoUpdateStrategy": "background", "readyTimeout": 10000, "autoBlockRolledBackBundles": true } } } ``` Here's what each option does: - **`autoUpdateStrategy`** — Setting this to `"background"` tells the plugin to automatically check for, download, and stage the latest bundle at app startup and whenever the app resumes after more than 15 minutes. The update is applied on the next app restart. - **`readyTimeout`** — Gives your app 10 seconds (10000 ms) to signal that it loaded successfully. If the app doesn't call [`ready()`](https://capawesome.io/plugins/live-update/#ready) within this time, the plugin automatically rolls back to the previous working bundle. This is your safety net against broken updates. - **`autoBlockRolledBackBundles`** — When set to `true`, the plugin automatically blocks bundles that caused a rollback. This prevents the same broken update from being downloaded and applied again. ### Versioned Channels When you release a new native version of your app, the live update bundles from the previous native version might not be compatible. Versioned channels solve this by automatically tying each native version to its own update channel. Instead of setting a static channel in your Capacitor config, configure it natively so it includes your build version: **Android** (`android/app/build.gradle`): ``` android { defaultConfig { resValue "string", "capawesome_live_update_default_channel", "production-" + defaultConfig.versionCode } } ``` **iOS** (`ios/App/App/Info.plist`): ``` CapawesomeLiveUpdateDefaultChannel production-$(CURRENT_PROJECT_VERSION) ``` With this setup, an app with version code `10` automatically receives updates from the `production-10` channel, while version `11` receives updates from `production-11`. This ensures that each native version only gets compatible live updates. ### Signaling App Readiness When using `readyTimeout`, you need to call [`ready()`](https://capawesome.io/plugins/live-update/#ready) early in your app's startup to tell the plugin that the app loaded successfully: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const signalReady = async () => { const result = await LiveUpdate.ready(); if (result.rollback) { console.log('App was rolled back to the default bundle.'); } }; ``` Call this as soon as your app has initialized and is in a working state. If you don't call `ready()` within the configured timeout, the plugin assumes the update is broken and rolls back automatically. ## Putting It All Together With both plugins in place, your update strategy looks like this: 1. **Live updates run automatically in the background.** The `background` strategy handles checking, downloading, and staging new bundles without any additional code. 1. **At app startup, signal readiness.** Call [`ready()`](https://capawesome.io/plugins/live-update/#ready) to confirm the current bundle works and prevent unnecessary rollbacks. 1. **Check for native updates.** Use the App Update plugin to prompt users when a new native version is available in the store. Here's an example of how this comes together in your app's initialization: ``` import { Capacitor } from '@capacitor/core'; import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const initializeApp = async () => { await LiveUpdate.ready(); await checkForNativeUpdate(); }; const checkForNativeUpdate = async () => { const info = await AppUpdate.getAppUpdateInfo(); if (info.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (Capacitor.getPlatform() === 'android') { if (info.immediateUpdateAllowed) { await AppUpdate.performImmediateUpdate(); } } else { await AppUpdate.openAppStore(); } }; ``` With this setup, your web layer updates are delivered silently through live updates, while native updates are handled through in-app prompts. Your users always get the latest version — whether it requires a store download or not. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion By combining the [App Update](https://capawesome.io/plugins/app-update/index.md) and [Live Update](https://capawesome.io/plugins/live-update/index.md) plugins, you can build a solid update delivery strategy that covers every scenario. Use live updates for fast, friction-free web layer changes, and the App Update plugin to nudge users toward new native versions when needed. If you want to learn more about how live updates work under the hood, check out the blog post [How Live Updates for Capacitor Work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/index.md). If you're currently using `server.url` in production and considering Live Updates instead, read [The Right Way to Update Your Capacitor App Remotely](https://capawesome.io/blog/the-right-way-to-update-your-capacitor-app-remotely/index.md). Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) if you have questions or want to connect with the community, and subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # The File Handling Guide for Capacitor Handling files in Capacitor can be a crucial part of your app. Whether you want to read, write or share a file, it is essential to understand the best practices in file handling to avoid potential out of memory (OOM) issues. In this guide, we will explore what you need to consider when dealing with files on Android and iOS and how to ensure efficient and reliable file management. ## Problem ``` Caused by: java.lang.OutOfMemoryError: Failed to allocate a 268431376 byte allocation with 100663296 free bytes and 123MB until OOM, target footprint 239514824, growth limit 268435456 ``` This and similar errors are often caused by inefficient file handling. The most common mistake is to load a file into the WebView as a base64 string or data URL. This can quickly lead to OOM errors, especially when dealing with large files. ## Best Practices Capacitor provides powerful capabilities for working with files in a cross-platform app environment. The following best practices will help you to avoid potential pitfalls and ensure efficient and reliable file management. ### Read a file When reading a file, you should make sure that the file is not loaded into the WebView as a base64 string or data URL. So forget about the [`readFile(...)`](https://capacitorjs.com/docs/apis/filesystem#readfile) method of the Capacitor Filesystem plugin. Instead, use the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) in combination with the [`convertFileSrc(...)`](https://capacitorjs.com/docs/basics/utilities#capacitorconvertfilesrc) method to load the file as a blob: ``` import { Camera, CameraResultType } from "@capacitor/camera"; const getPhotoAsBlob = async () => { // 1. Pick a photo const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri, }); // 2. Convert the path to a webPath const webPath = Capacitor.convertFileSrc(photo.path); // 3. Load the photo as a blob const response = await fetch(webPath); return response.blob(); }; ``` A blob is a file-like object that can be used for further operations. As soon as the in-memory space for blobs is getting full, the blob system will automatically uses the disk.[1](#fn:1) ### Write a file If you do not load any files into the WebView, writing a file via the WebView is usually not necessary. Nevertheless, if you want to write a file via the WebView, you should make sure that the file is not written as a base64 string or data URL. So again, forget about the [`writeFile(...)`](https://capacitorjs.com/docs/apis/filesystem#writefile) method of the Capacitor Filesystem plugin. Instead, use a Capacitor plugin that supports writing a file as a blob. For example, you can use the [Capacitor Blob Writer](https://github.com/diachedelic/capacitor-blob-writer) plugin: ``` import { Directory } from "@capacitor/filesystem"; import write_blob from "capacitor-blob-writer"; const writeBlob = async () => { const blob = new Blob(["Hello world!"], { type: "text/plain" }); await write_blob({ path: "notes/hello.txt", directory: Directory.Data, blob: blob, }); }; ``` Another option is to use the [Capacitor File Chunk](https://github.com/qrclip/capacitor-file-chunk) plugin. Warning Writing a file as a blob via the WebView needs a local HTTP server to be started, which is associated with **potential security risks**. You can find more information in the documentation of the respective plugin. You can now use the path to the selected file to open, share or upload the file with another plugin. ### Display a file To display a file, you can simply convert the native file path to a webPath using the [`convertFileSrc(...)`](https://capacitorjs.com/docs/basics/utilities#capacitorconvertfilesrc) method and then set it as the `src` attribute of an HTML element: ``` import { Camera, CameraResultType } from "@capacitor/camera"; const displayPhoto = async () => { // 1. Pick a photo const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri, }); // 2. Convert the path to a webPath const webPath = Capacitor.convertFileSrc(photo.path); // 3. Display the photo document.getElementById("savedPhoto").src = webPath; }; ``` ``` ``` This way, you don't have to load the file as a base64 string or data URL into the WebView and can avoid potential OOM issues. ### Open a file At some point, you may want to open a file with another app. For example, you may want to open a PDF file with a PDF viewer app. To do this, you can use the [Capacitor File Opener](https://capawesome.io/plugins/file-opener/index.md) plugin: ``` import { FileOpener } from "@capawesome-team/capacitor-file-opener"; const openFile = async () => { await FileOpener.openFile({ path: "file:///path/to/device/file", }); }; ``` Tip On iOS, the [UIDocumentInteractionController](https://developer.apple.com/documentation/uikit/uidocumentinteractioncontroller) is used to preview and open files. If you would rather give the user the option to choose the app to open the file with, you can just use the [Capacitor Share](https://capacitorjs.com/docs/apis/share) plugin to share the file with another app. ### Pick a file When picking a file, it is recommended to use the [Capacitor File Picker](https://capawesome.io/plugins/file-picker/index.md) plugin. This way, you can just get the path to the selected file without the need to load the file into the WebView: ``` import { FilePicker } from "@capawesome/capacitor-file-picker"; const pickFile = async () => { const result = await FilePicker.pickFiles(); return result.files[0].path; }; ``` Of course, you can also use the HTML `` element to pick a file. However, you then have the problem that you first have to write the file to the file system before you can process it with another plugin. ### Upload a file Uploading a file is actually quite simple and does not require any plugins. We just need to [read the file](#read-a-file) as a blob and then upload it via the [fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch): ``` import { Camera, CameraResultType } from "@capacitor/camera"; import { Capacitor } from "@capacitor/core"; const uploadFile = async (path: string) => { // 1. Pick a photo const photo = await Camera.getPhoto({ resultType: CameraResultType.Uri, }); // 2. Load the photo as a blob const response = await fetch(photo.webPath); const blob = response.blob(); // 3. Upload the file const formData = new FormData(); formData.append("file", blob, "file.jpg"); await fetch("https://example.tld/upload", { method: "POST", body: formData, }); }; ``` ### Download a file When downloading a file, you should make sure that the file is not loaded into the WebView as a base64 string or data URL. It is best to use the [Capacitor Filesystem](https://capacitorjs.com/docs/apis/filesystem) plugin to download the file directly to the file system: ``` import { Filesystem, Directory } from "@capacitor/filesystem"; const downloadFile = async () => { await Filesystem.downloadfile({ path: "image.png", url: "https://example.tld/image.png", }); }; ``` ## Conclusion In this guide, we have explored the best practices for handling files in Capacitor. By following these best practices, you can avoid potential pitfalls and ensure efficient and reliable file management. If you have any questions or feedback, feel free to reach out to us. ______________________________________________________________________ 1. [Chrome's Blob Storage System Design](https://chromium.googlesource.com/chromium/src/+/HEAD/storage/browser/blob/README.md) [↩](#fnref:1 "Jump back to footnote 1 in the text") # The Push Notifications Guide for Capacitor Push notifications are one of the most commonly used features for mobile apps. This guide covers **Capacitor push notifications** on Android, iOS, and the web using the [Firebase Cloud Messaging](https://firebase.google.com/docs/cloud-messaging) SDK and the [Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin—whether you're building with Capacitor alone or also using **Ionic**. Capacitor Push Notifications Demo For this we will create a new Ionic Capacitor app. First we will set up our Firebase project and configure the Capacitor App. Then we will add the Firebase Cloud Messaging SDK. Last but not least, we will test the implementation using the Firebase Console and the Firebase Admin SDK. This guide covers everything that is needed. If you have already done certain steps such as creating a Firebase project, just skip those steps. Let's get started! AI-Assisted Setup For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capacitor-push-notifications` and use the following prompt with your preferred AI coding assistant: ``` Use the `capacitor-push-notifications` skill from `capawesome-team/skills` to help me set up push notifications in my Capacitor app. ``` ## Why Firebase? With [Firebase](https://firebase.google.com/), Google offers a great mobile platform that lets you build high-quality apps very quickly and efficiently. Firebase Cloud Messaging is the cross-platform messaging solution that lets you reliably send messages at no cost. Firebase has a large community and SDKs that work well in Capacitor; we also offer a [Cloud Firestore](https://capawesome.io/plugins/firebase/cloud-firestore/index.md) plugin for native database access alongside Cloud Messaging. ## Preparation First, we will create a new app, which we will then configure step-by-step. ### Create a new App To create a new project, we simply use the [Ionic CLI](https://ionicframework.com/docs/cli). For this, first install the CLI globally: ``` npm i -g @ionic/cli ``` After that, create a new project with the `ionic start` command: ``` npx ionic start capacitor-firebase-messaging-demo blank --type=angular --capacitor --package-id=YOUR_PACKAGE_ID ``` Warning Be sure to specify a unique identifier for the `package-id`. It must be in reverse domain name notation, generally representing a domain name that you or your company owns. In this example, `dev.robingenz.capacitorjs.demo.firebasemessaging` will be used as `package-id`. Choose a different identifier for your app. In this case, the app is called `capacitor-firebase-messaging-demo`, the starter template is `blank` and the project type for the purposes of this guide is Angular. You can also choose Vue or React, for example. Additionally, we enable the Capacitor integration with `--capacitor` and set a custom package identifier. Once everything is ready, you should see this output: ``` Your Ionic app is ready! Follow these next steps: - Go to your new project: cd ./capacitor-firebase-messaging-demo - Run ionic serve within the app directory to see your app in the browser - Run ionic capacitor add to add a native iOS or Android project using Capacitor - Generate your app icon and splash screens using cordova-res --skip-config --copy - Explore the Ionic docs for components, tutorials, and more: https://ion.link/docs - Building an enterprise app? Ionic has Enterprise Support and Features: https://ion.link/enterprise-edition ``` #### Add the Android platform Install the `@capacitor/android` package and add the Android platform: ``` npm install @capacitor/android npx cap add android ``` #### Add the iOS platform Install the `@capacitor/ios` package and add the iOS platform: ``` npm install @capacitor/ios npx cap add ios ``` ### Create a Firebase project Before we can set up Firebase Cloud Messaging we need to create a Firebase project. If you already have a project, you can skip this step. Go to the [Firebase console](https://console.firebase.google.com/) and click the **Add project** button. Enter a project name and create the project. ### Install the Firebase Cloud Messaging Plugin We will use the [Capacitor Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin to integrate the Firebase Cloud Messaging SDK into the app. Install the npm package and then synchronize the Capacitor project: ``` npm install @capacitor-firebase/messaging firebase npx ionic cap sync ``` Now let's get started with the configuration of the different platforms. ## Android We will start with Android, as this is the easiest. We will go through all the steps in detail. Just skip the steps you have already done if you are applying the guide to an existing project. ### Add Firebase to your Android App Navigate to the Firebase project you just created. In the center of the [Project overview](https://console.firebase.google.com/project/_/overview) page, click the Android icon to launch the setup workflow. If you've already added an app to your Firebase project, click `Add app` to display the platform options. Enter your Android package name and your app nickname. Click on `Register App`. Now click on `Download google-services.json` and download the `google-services.json` file. Then click twice on `Next` and on `Continue to console`. Move the downloaded file to the `/android/app/` directory of your project. ### Configure the Push Notification Icon If no icon is specified, Android uses the application icon, but the push icon should be white pixels on a transparent background. Since the application icon does not usually look like this, it shows a white square or circle. Therefore, it is recommended to provide a separate icon for push notifications. To do this, add the following line to the `android/app/src/main/AndroidManifest.xml` file **in** the `application` tag: android/app/src/main/AndroidManifest.xml ``` ``` After that you have to add the appropriate icons to the `android/app/src/main/res/mipmap-*` folders. The icon files should have the name as configured in the `AndroidManifest.xml` file. In this example they are called `notification_icon.png`. Tip You can create Android Notification Icons using the [Android Asset Studio](https://romannurik.github.io/AndroidAssetStudio/icons-notification.html) for example. ``` ``` ## iOS Adding push notifications to iOS is unfortunately much more complicated. You also need a paid [Apple Developer account](https://developer.apple.com/). ### Add Firebase to your iOS App Navigate back to the [Firebase project](https://console.firebase.google.com/project/_/overview) you just created. In the center of the project overview page, click the iOS icon to launch the setup workflow. If you've already added an app to your Firebase project, click `Add app` to display the platform options. Enter your Apple bundle ID and your app nickname. Click on `Register App`. Now click on the button `Download GoogleService-Info.plist` and download the `GoogleService-Info.plist` file. After that click three times on `Next` and then on `Continue to console`. Move the downloaded file to the `/ios/App/App/` directory of your project. Now open Xcode by running `npx cap open ios` in your project. Register the file in your Xcode project by dragging it from `/ios/App/App/GoogleService-Info.plist` into the Xcode file explorer into the folder `/App/App`. If prompted, select to add the config file to all targets. Now it should look like this: ### Register the App ID If you have not yet registered your App Identifier with Apple, log in to your [App Store Developer Account](https://developer.apple.com/account/){:target="*blank"} and navigate to [Identifiers](https://developer.apple.com/account/resources/identifiers/list). Add a new App Identifier and make sure that you select the \_Push Notifications* capability. Enter the `appId` from the `capacitor.config.ts` as bundle ID. Click on `Continue` and then on `Register`. Your App Identifier is now registered with Apple. ### Create an APNs certificate or key Next, navigate to the [Keys](https://developer.apple.com/account/resources/authkeys/list) page and click the to add a new key. Enter a name and enable Apple Push Notifications service (APNs). Click on `Continue` and then on `Register`. Next, download your key and write down the key ID. Usually the Key ID is also included in the filename of the downloaded key. After download, you need to upload the key in the [Messaging Tab](https://console.firebase.google.com/project/_/settings/cloudmessaging/) of the Firebase Console. Click the `Upload` button, select your key, enter your Key ID and your Team ID and upload the key to Firebase. You can find the Team ID in your [Membership details](https://developer.apple.com/account#membership-details). ### Add initialization code​ Add the following to your app's `AppDelegate.swift`: ``` func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) { NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error) } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { NotificationCenter.default.post(name: Notification.Name.init("didReceiveRemoteNotification"), object: completionHandler, userInfo: userInfo) } ``` This makes sure that the plugin is notified about new Apple Push Notification service (APNs) device tokens and incoming push notifications. ### Enable Push Notification capabilities Last but not least you have to enable the `Push Notifications` and `Background Modes` capabilities in your Xcode project. To do this, open the project using the [Capacitor CLI](https://capacitorjs.com/docs/cli): ``` npx cap open ios ``` Select the project `App` in the File Explorer, click on the corresponding target, switch to the tab `Signing & Capabilities` and now click on `+ Capabilities`. Search for `Push Notifications` and add the capability by double clicking on it. Also add the `Background Modes` capability and select the `Remote notifications` checkbox. Yay, you've done it. The iOS configuration is now ready. ## Web Now we add push notifications to the web platform. ### Add Firebase to your Web App In the center of the project overview page, click the *Web+* icon to launch the setup workflow. If you've already added an app to your Firebase project, click `Add app` to display the platform options. Enter your app nickname and then click the `Register App` button. You now need to add the Firebase configuration to your app. In our Angular app we add the Firebase configuration to the `src/environments/environment.ts` and `src/environments/environment.prod.ts` files: ``` export const environment = { ..., firebase: { apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_MESSAGING_SENDER_ID", appId: "YOUR_APP_ID" }, }; ``` Replace the placeholders with your configuration values. If you use VueJS or React, you have to store the Firebase configuration in a different place. Next, you need to modify the `src/app/app.component.ts` as follows: ``` import { Component } from "@angular/core"; import { Capacitor } from "@capacitor/core"; import { IonicModule } from "@ionic/angular"; import { environment } from "src/environments/environment"; import { initializeApp } from "firebase/app"; @Component({ selector: "app-root", templateUrl: "app.component.html", styleUrls: ["app.component.scss"], standalone: true, imports: [IonicModule], }) export class AppComponent { constructor() { this.initializeFirebase(); } public async initializeFirebase(): Promise { if (Capacitor.isNativePlatform()) { return; } initializeApp(environment.firebase); } } ``` This makes sure that the Firebase JS SDK is only initialized on the web and not on the native platforms, since on Android and iOS we do not use the Firebase JS SDK but the Firebase Android SDK and the Firebase iOS SDK. These are already initialized using our downloaded configuration files. Click on `Continue to console`. ### Configure Web Credentials with FCM FCM Web uses so-called VAPID keys to authorize send requests to supported web push services. You can either generate a new key pair or import an existing pair. We will generate a new key pair in this example. For information on how to import an existing key pair, see [Import an existing key pair](https://firebase.google.com/docs/cloud-messaging/js/client#import_an_existing_key_pair). Open the [Cloud Messaging Tab](https://console.firebase.google.com/project/_/settings/cloudmessaging/) in your Firebase project, scroll to the `Web configuration` section and click `Generate key pair`. Copy the generated key into the files `src/environments/environment.ts` and `src/environments/environment.prod.ts`. They now look like this (with placeholder): ``` export const environment = { ..., firebase: { apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_MESSAGING_SENDER_ID", appId: "YOUR_APP_ID", vapidKey: "YOUR_VAPID_KEY" }, }; ``` ### Add the Service Worker Next, add a service worker to your project by creating an empty file named `firebase-messaging-sw.js` in the `src/` folder. Add this file to your [Assets configuration](https://angular.io/guide/workspace-config#asset-config): angular.json ``` "assets": [ "src/firebase-messaging-sw.js" ] ``` If you don't want to receive push notifications on the web while your application is in the background, you are now done. If you want to receive push notifications in the background, add the following content to the `firebase-messaging-sw.js` file and replace the placeholders with your Firebase configuration: ``` importScripts( "https://www.gstatic.com/firebasejs/9.7.0/firebase-app-compat.js", ); importScripts( "https://www.gstatic.com/firebasejs/9.7.0/firebase-messaging-compat.js", ); firebase.initializeApp({ apiKey: "YOUR_API_KEY", authDomain: "YOUR_AUTH_DOMAIN", projectId: "YOUR_PROJECT_ID", storageBucket: "YOUR_STORAGE_BUCKET", messagingSenderId: "YOUR_MESSAGING_SENDER_ID", appId: "YOUR_APP_ID", }); const messaging = firebase.messaging(); ``` More information about receiving push messages on the web can be found at [Receive messages in a JavaScript client](https://firebase.google.com/docs/cloud-messaging/js/receive). ## Integrate Push Notifications Now let's see the Firebase messaging plugin in action. For this, let's first take a look at the following APIs of the plugin: - [`requestPermissions()`](https://capawesome.io/plugins/firebase/cloud-messaging/#requestpermissions): Use this method to request permissions to allow push notifications to be displayed. - [`getToken()`](https://capawesome.io/plugins/firebase/cloud-messaging/#gettoken): This method is needed to request an FCM token. An FCM token is unique and always associated with a specific app installation. Using this token you are able to send push notifications to a specific device. It is common to request the token at app start and send it to the app server. There, the token can be assigned to a user so that individual users can be notified via push notifications using their FCM token. Note that the FCM token can change over time. - [`addListener('notificationReceived', ...)`](https://capawesome.io/plugins/firebase/cloud-messaging/#addlistenernotificationreceived): This listener notifies you about incoming push notifications. - [`addListener('notificationActionPerformed', ...)`](https://capawesome.io/plugins/firebase/cloud-messaging/#addlistenernotificationactionperformed): This listener is called when a push notification action is performed. This is the case, for example, when the user opens the notification. We now use these methods in the `HomePage` component. Therefore copy the following code into your `src/app/home.page.ts` file: ``` import { Component } from "@angular/core"; import { FirebaseMessaging, GetTokenOptions, } from "@capacitor-firebase/messaging"; import { Capacitor } from "@capacitor/core"; import { IonicModule } from "@ionic/angular"; import { environment } from "src/environments/environment"; @Component({ selector: "app-home", templateUrl: "home.page.html", styleUrls: ["home.page.scss"], standalone: true, imports: [IonicModule], }) export class HomePage { public token = ""; constructor() { FirebaseMessaging.addListener("notificationReceived", (event) => { console.log("notificationReceived: ", { event }); }); FirebaseMessaging.addListener("notificationActionPerformed", (event) => { console.log("notificationActionPerformed: ", { event }); }); if (Capacitor.getPlatform() === "web") { navigator.serviceWorker.addEventListener("message", (event: any) => { console.log("serviceWorker message: ", { event }); const notification = new Notification(event.data.notification.title, { body: event.data.notification.body, }); notification.onclick = (event) => { console.log("notification clicked: ", { event }); }; }); } } public async requestPermissions(): Promise { await FirebaseMessaging.requestPermissions(); } public async getToken(): Promise { const options: GetTokenOptions = { vapidKey: environment.firebase.vapidKey, }; if (Capacitor.getPlatform() === "web") { options.serviceWorkerRegistration = await navigator.serviceWorker.register("firebase-messaging-sw.js"); } const { token } = await FirebaseMessaging.getToken(options); this.token = token; } } ``` Lines 27 to 37 contain additional customizations for the web platform. The listener `navigator.serviceWorker.addEventListener("message", ...)` is called when the browser tab of the application is not focused. Using the [Notification Web API](https://developer.mozilla.org/en-US/docs/Web/API/notification), you can show a notification. Next, we will add the related template. To keep the example as simple as possible, we just create two buttons to manually call the `requestPermissions()` and `getToken()` methods. Copy the following code into your `src/app/home.page.html` file: ``` Firebase Cloud Messaging About ⚡️ Capacitor plugin for Firebase Cloud Messaging (FCM). Demo Token Request Permissions Get Token ``` ## Run the app Now you can launch your app for the first time: ``` # Run the web platform npx ionic serve # Run the Android platform npx ionic cap run android # Run the iOS platform npx ionic cap run ios ``` Make sure that the first thing you do is to request the permission using the `Request Permissions` button. Request Permissions Next, request the FCM token and copy the FCM token to your clipboard so that you can next send a push notification to this device via the Firebase Console. ## Send Push Notifications with Firebase There are several ways to send push notifications with Firebase. You can use the [Firebase Console](https://console.firebase.google.com/) as well as the REST API. Firebase also offers the [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup) for different environments. ### Firebase Console The Firebase Console is the easiest way to test your implementation. For this, navigate to the [Messaging](https://console.firebase.google.com/project/_/messaging/) page in your Firebase project. If you are visiting the messaging page for the first time, you will see a welcome screen. In this case, click on `Create your first campaign`, then select `Firebase Notification messages` and click `Create`. From here you can directly configure your Push Notification. Enter a notification title and a notification body and click on `Send text message`. Paste the FCM token from your clipboard into the input field `Add an FCM registration token`. Click on the on the right side to confirm the input. After that click on `Test` and the notification will be sent. If you did everything correctly, your first notification will arrive. Congratulations! ### Firebase REST API Firebase Cloud Messaging also provides a REST API. Take a look at the official Firebase documentation at [Send messages to specific devices](https://firebase.google.com/docs/cloud-messaging/send-message#send-messages-to-specific-devices#rest) to learn how you can send push notifications using it. ### Firebase Admin SDK The [Firebase Admin SDK](https://firebase.google.com/docs/admin/setup) is available on different platforms and is used for the server-side integration of Firebase. Among other things, you can use it to send push notifications. We'll take a quick look at this using Node.js. Before you can start with the implementation, you first need to generate a service account key. This key grants your application access to your Firebase project. Navigate to [Service accounts](https://console.firebase.google.com/project/_/settings/serviceaccounts/adminsdk) in the Project settings. Then click `Generate new private key` and `Generate key` to download the service account key. Next, create an empty folder and initialize a new npm project: ``` npm init ``` First install the Firebase Admin SDK: ``` npm i firebase-admin ``` Now create a new file named `main.js` with the following content: ``` import { initializeApp, cert } from "firebase-admin/app"; import { getMessaging } from "firebase-admin/messaging"; initializeApp({ credential: cert("YOUR_FIREBASE_KEY_PATH"), }); // This FCM token comes from the Capacitor Firebase Cloud Messaging plugin. const token = "YOUR_REGISTRATION_TOKEN"; const message = { notification: { title: "Capacitor Firebase Messaging", body: "Hello world!", }, token: token, }; // Send a message to the device corresponding to the provided FCM token getMessaging() .send(message) .then((response) => { console.log("Successfully sent message: ", response); }) .catch((error) => { console.log("Error sending message: ", error); }); ``` Replace the placeholder `YOUR_FIREBASE_KEY_PATH` with the path to your service account key that you just downloaded from the Firebase Console. Replace the placeholder `YOUR_REGISTRATION_TOKEN` with the FCM token you get from the Capacitor Firebase Cloud Messaging plugin via the [`getToken()`](https://capawesome.io/plugins/firebase/cloud-messaging/#gettoken) method. Now you can run the code and send a push notification using the Firebase Admin SDK: ``` node main.js ``` More information about the Firebase Admin SDK can be found at [Add the Firebase Admin SDK to your server](https://firebase.google.com/docs/admin/setup). Want to stay up to date with new Capacitor plugins and guides? Subscribe to the Capawesome newsletter. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion Adding push notifications to an app can be a real challenge depending on the platform: Android makes it easiest for the developer, and iOS even requires a paid developer account. Thanks to the [Capacitor Firebase Cloud Messaging](https://capawesome.io/plugins/firebase/cloud-messaging/index.md) plugin, once configured you get a single API for Android, iOS, and web. **Resources:** - [Demo app on GitHub](https://github.com/robingenz/ionic-capacitor-firebase-messaging-demo) - [API Reference](https://capawesome.io/plugins/firebase/cloud-messaging/#api) - Troubleshooting [Android](https://capawesome.io/blog/troubleshooting-capacitor-android-issues/index.md) - Troubleshooting [iOS](https://capawesome.io/blog/troubleshooting-capacitor-ios-issues/index.md) For questions and community support, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW), and subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # The Right Way to Update Your Capacitor App Remotely One of the most common questions we get is: "What's the difference between using Live Updates and `server.url` in Capacitor?" Many developers use the `server.url` configuration option to load their app's web content from a remote server in production — even though it was never designed for that. In this post, we'll break down what each approach does, how they compare, and why Live Updates are the better choice for production apps. ## What is `server.url`? Capacitor's [`server.url`](https://capacitorjs.com/docs/config) configuration option tells the WebView to load content from an external URL instead of the bundled app assets. When set, the app no longer uses the local HTML, CSS, and JavaScript files that were shipped with the binary — it fetches everything from a remote server at startup. According to the Capacitor documentation, `server.url` is **not** intended for production use. It's primarily meant for development purposes, allowing you to connect your app to a live-reload dev server so you can see changes in real time without rebuilding the app. However, many teams also use it in production to serve their app's web content from a remote server. It's easy to see why: with platforms like Cloudflare or Vercel, deploying an update to a static site takes seconds — making it natural to want the same workflow for your mobile app. What many developers don't realize is that a live update can be delivered just as quickly — if you choose the right platform. ## What are Live Updates? Live Updates — also known as Over-the-Air (OTA) Updates — let you push changes to the web layer of your Capacitor app directly to your users' devices, without going through app store review. Instead of loading content from a remote server on every launch, the app downloads updated web assets in the background, stores them locally, and loads them on the next restart. The key difference is that the updated files live on the device. Once downloaded, they behave exactly like the original bundled assets — they're fast, reliable, and available offline. If you want to dive deeper into the mechanics, check out our blog post [How Live Updates for Capacitor Work](https://capawesome.io/blog/how-live-updates-for-capacitor-work/index.md). ## Key Differences Both approaches let you update your app's web content without submitting a new binary to the app stores. But that's where the similarities end. ### Offline Support With Live Updates, the web assets are stored locally on the device. Once an update has been downloaded, your app works **fully offline** — just like a freshly installed app. With `server.url`, the app needs an active network connection every time it launches. **No connection means no content.** If your users are in areas with spotty connectivity — or simply on an airplane — your app shows a blank screen or an error page. ### Performance Local assets load **almost instantly** because there's no network round-trip involved. The WebView reads the files directly from the device's file system, which means your app feels fast and responsive from the moment it opens. When using `server.url`, the app has to fetch all its content from a remote server on every launch. This adds latency that depends entirely on the user's connection quality. On a slow or congested network, your app's startup time can increase noticeably — and that's a poor first impression. ### Reliability With `server.url`, your remote server becomes a **single point of failure**. If the server goes down, every user who opens the app sees nothing. There's no fallback to the bundled assets — the app simply can't load. Live Updates are far more resilient. If a download fails or the server is temporarily unreachable, the app continues to use whichever version of the web assets it already has on disk — whether that's a previous update or the original bundle. Your users may not get the latest changes immediately, but they **always get a working app**. ### Security Live Updates support [code signing](https://capawesome.io/cloud/live-updates/advanced/code-signing/index.md), which lets you verify both the **authenticity** and **integrity** of every update before it's applied. The update bundle is signed with a private key, and the app verifies the signature using a public key before loading the new files. This ensures that the content hasn't been tampered with and actually comes from a trusted source. With `server.url`, the content is fetched over the network without any built-in integrity checks. You're relying entirely on HTTPS to secure the connection, but there's no application-level verification that the content you're loading is what you intended to ship. ### Native Version Compatibility This is the **most critical difference** — and the one that's often overlooked. A Capacitor app consists of a web layer and a native layer. When you update a plugin, change native configuration, or bump a native dependency, the web build **must be compatible** with the native binary. If there's a mismatch, things break: plugin calls fail, features disappear, or the app crashes. With `server.url`, you can only serve **one** web bundle to all users, regardless of which native version they're running. The moment you update a plugin or change native configuration, you need the web build to match — but users who haven't updated the native app from the store will still load the new web content. There's **no way to target different native versions** with different bundles. Live Updates solve this problem with [Versioned Bundles and Versioned Channels](https://capawesome.io/cloud/live-updates/guides/best-practices/#binary-compatible-changes). Versioned Channels, for example, let you create a channel for each native version code and assign bundles accordingly. Users on native version 10 receive updates compatible with version 10, while users on version 12 receive updates compatible with version 12. This ensures that every user always gets a web build that matches their native binary — no matter how many native versions are in the wild. ### App Store Compliance Live Updates are **explicitly compliant** with both [Apple App Store](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) and [Google Play](https://support.google.com/googleplay/android-developer/answer/9888379/) policies. Apple allows downloading interpreted code as long as it doesn't change the app's primary purpose. Google's policy exempts JavaScript running in a WebView from its restriction on self-updating apps. Using `server.url` to load an entirely remote app is a grayer area. While it technically still runs JavaScript in a WebView, app reviewers may question an app that's essentially an empty shell loading all its content from a server — especially if the content changes significantly after review. In fact, a member of the Ionic team has [pointed out](https://github.com/ionic-team/capacitor/discussions/4080#discussioncomment-873991) that while Apple allows apps to run code not embedded in the binary, Capacitor plugins don't work in a standard WebKit view — which puts `server.url` in a gray area under [Section 4.7](https://developer.apple.com/app-store/review/guidelines/#third-party-software) of Apple's App Store Review Guidelines. They also confirmed that `server.url` was only added for live-reload during development and is not recommended for production use. ## When `server.url` Still Makes Sense To be fair, `server.url` has its place — it's just not in production. During development, it's an excellent way to connect your app to a local live-reload server so you can see changes in real time without rebuilding the app. This is exactly what it was designed for, and it works great in that context. It can also be acceptable for internal enterprise tools where offline support, performance, and app store compliance aren't concerns — for example, a kiosk app that's always connected to a local network. But for any app that's distributed to end users through the app stores, Live Updates are the better choice. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion `server.url` was designed as a development tool, not a production deployment strategy. While it can technically serve your app's web content from a remote server, it comes with significant trade-offs: no offline support, slower performance, no fallback when the server is down, no code signing, no native version targeting, and potential app store compliance concerns. Live Updates give you the same core benefit — updating your app's web content without going through app store review — while keeping your assets local, fast, and secure. With features like Versioned Channels, you can safely manage multiple native versions in the field without worrying about compatibility issues. If you're interested in related topics, check out [The App Update Delivery Guide for Capacitor](https://capawesome.io/blog/the-app-update-delivery-guide-for-capacitor/index.md) for a complete strategy that combines live updates with native app store updates. Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) if you have questions or want to connect with the community, and subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # Tips for Setting Up In-App Purchases with Capacitor Setting up in-app purchases in a mobile app involves more than just writing code. Between Apple's agreements, Google's compliance checks, and platform-specific gotchas, there's a lot of ground to cover before you can charge your first customer. Here are some practical tips to help you get started with in-app purchases on both iOS and Android using the [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin. ## Prerequisites Before you begin, make sure you have the following: - A **Capacitor app** with the [Purchases](https://capawesome.io/plugins/purchases/index.md) plugin installed. To install the plugin, please refer to the [Installation](https://capawesome.io/plugins/purchases/#installation) section in the plugin documentation. - An **Apple Developer account** with access to [App Store Connect](https://appstoreconnect.apple.com/) (for iOS). - A **Google Play Developer account** with access to the [Google Play Console](https://play.google.com/console/) (for Android). ## General Recommendations Before diving into platform-specific setup, here are a few best practices that apply to both iOS and Android. ### Handling Unfinished Transactions On every app launch, call [`getUnfinishedTransactions()`](https://capawesome.io/plugins/purchases/#getunfinishedtransactions) to check for any purchases that weren't properly confirmed. This can happen if the app was closed or crashed before a transaction was completed. If there are unfinished transactions, deliver the purchased content or enable the service and then call [`finishTransaction(...)`](https://capawesome.io/plugins/purchases/#finishtransaction) for each one. ### Adding a Restore Purchases Button Your app should include a restore purchases button so users can recover their purchases on a new device or after reinstalling. Use [`syncTransactions()`](https://capawesome.io/plugins/purchases/#synctransactions) for this. If there are any transactions, deliver the purchased content or enable the service. Unlike the unfinished transactions flow, there is no need to call [`finishTransaction(...)`](https://capawesome.io/plugins/purchases/#finishtransaction) since transactions returned by [`syncTransactions()`](https://capawesome.io/plugins/purchases/#synctransactions) have already been finished. ### Choosing Product IDs To simplify your backend logic, use the same product ID for equivalent products on both Apple and Google. Be careful when choosing your product IDs — they often cannot be changed or reused on either platform once created. ## Setting Up In-App Purchases on iOS Apple's setup process involves multiple steps across your Developer account and App Store Connect. Pay close attention to the details here, as missing a single step can prevent purchases from working. ### Accepting Developer Agreements Under the **Agreements** section of your [Apple Developer account](https://developer.apple.com/account), make sure the following are accepted: 1. Apple Developer Agreement 1. Apple Developer Program License Agreement These agreements are updated regularly and need to be accepted each time they change. ### Configuring App Store Connect Business Under the **Business** section of [App Store Connect](https://appstoreconnect.apple.com/business/), verify the following: 1. Free Apps Agreement has status **Active**. 1. Paid Apps Agreement has status **Active**. 1. A bank account has been added with status **Active**. 1. All tax forms are completed with status **Active** (the exact list depends on your region). ### Creating Products and Subscriptions Under the **Monetization** section of your app in App Store Connect: 1. Create your in-app purchases or subscriptions. 1. Fill out **all** fields on screen, including localization and optional fields. 1. The product or subscription must have the status **Ready to Submit** or **Waiting for Review**, which indicates that no information is missing. Note There may be a delay of up to a few hours between a product being properly created and the API returning a response. You may see "Product not found." until then. ### Small Business Program If you qualify as a small business, you can apply to Apple's [Small Business Program](https://developer.apple.com/app-store/small-business-program/) for a significant reduction in commission fees. ### Handling App Store Server Notifications If you offer renewable subscriptions, your backend must handle the different [notification types](https://developer.apple.com/documentation/appstoreservernotifications/notificationtype) of App Store Server Notifications. This is how you get notified about events that happen outside your app, such as a subscription renewal, cancellation, or expiration. Make sure to remove a user's access when their subscription has expired. ## Setting Up In-App Purchases on Android Google's setup is generally more straightforward, but there are still a few prerequisites to take care of before creating products. ### Completing Compliance and Metadata Under your app's section in the Google Play Console: 1. Make sure all compliance steps are completed. 1. Make sure all metadata steps are completed. ### Creating Products and Subscriptions Under the **Monetize with Play** section of the Google Play Console: 1. Under **Products**, create your one-time products or subscriptions. 1. Fill out **all** fields on screen, including price, availability, and tax and policy settings. 1. The product or subscription must have the status **Active**. Note You can configure your subscription settings and Real-time Developer Notifications under **Monetization setup**. ### Reduced Fee Program If you qualify as a small business, you can apply to Google's [reduced fee program](https://support.google.com/googleplay/android-developer/answer/10632485) for lower commission fees. ### Handling Real-Time Developer Notifications If you offer renewable subscriptions, your backend must handle the different [notification types](https://developer.android.com/google/play/billing/rtdn-reference#sub) of Real-time Developer Notifications. Similar to Apple, this is how you get notified about out-of-app events like renewals, cancellations, and expirations. ## Testing Before going live, you should thoroughly test your in-app purchases on both platforms. On **iOS**, Apple provides two approaches: Sandbox testing with test accounts created in App Store Connect, and StoreKit testing in Xcode using a local configuration file (iOS 14+). Sandbox testing requires a physical device with a sandbox account signed in under **Settings** > **Developer** > **Sandbox Apple Account** — you do not need to log out of your main Apple account. StoreKit testing works entirely locally without server connectivity. On **Android**, you need to upload your app to the Google Play Console (the internal testing track is sufficient), add test accounts under **Settings** > **License Testing**, and install the app from Google Play rather than via direct APK installation. For full details, see the [Testing](https://capawesome.io/plugins/purchases/#testing) section in the plugin documentation. ## Stay Updated Want to stay up to date with the latest features and guides? Subscribe to the Capawesome newsletter. [Subscribe to the Capawesome Newsletter](https://cloud.capawesome.io/newsletter) ## Conclusion Setting up in-app purchases requires careful attention to platform-specific requirements, from Apple's agreements and App Store Connect configuration to Google Play's compliance steps and product setup. By following this guide and handling edge cases like unfinished transactions and server notifications, you'll be well prepared to monetize your Capacitor app on both platforms. For the full API reference and additional details, check out the [Purchases plugin documentation](https://capawesome.io/plugins/purchases/index.md). Have suggestions or questions? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW) to connect with the community. And subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter) to stay updated on the latest news. # The Android Troubleshooting Guide for Capacitor Capacitor is a great tool to build cross-platform apps with web technologies. However, sometimes you might run into issues when building your Android app. This post will help you to troubleshoot common issues. Help us to improve this guide If you are aware of any other common issues, please send us an email to [support@capawesome.io](mailto:support@capawesome.io) so that we can update the guide. ## Errors ### `Minimum supported Gradle version is 8.4. Current version is 8.2.1.` This error occurs if you are using a version of the Android Gradle plugin that is incompatible with Capacitor. To fix the problem, you need to use the correct version of the Gradle plugin in your project. You can find the correct version in the [Capacitor documentation](https://capacitorjs.com/docs/). For Capacitor 6, for example, you must use version `8.2.1`. Update the Android Gradle plugin version in your root-level (project-level) Gradle file (usually `android/build.gradle`): android/build.gradle ``` buildscript { repositories { google() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:8.2.1' } } ``` ### Plugin is not implemented If Capacitor cannot find a plugin or cannot inject its code into the WebView, the following error is thrown, for example: ``` "Badge" plugin is not implemented ``` Follow the steps below to solve this issue: 1. Make sure that the plugin is installed and appears in the `package.json`. 1. Sync the native project by running `npx cap sync android`. 1. Sync the native project with Gradle files by using the `File` -> `Sync Project with Gradle Files` button in Android Studio. 1. Make sure you are using Node.js version 22 or higher. ### Blank screen A blank screen can have many causes. Here are some common reasons and solutions. #### With live reload If you see a blank screen when using live reload, check the following: 1. Make sure that your development server uses all network interfaces and not only `localhost`. For example, if you are using the Ionic CLI, you can use the `--external` option to bind to all network interfaces: ``` ionic cap run android -l --external ``` 1. Make sure that your development server is accessible from the device. You can check this by opening the URL in the device's browser. If the URL is not accessible, the app will not be able to load the content. This may be due to the following reasons: - The device is not connected to the same network as the development server. - The development server is not accessible from the device due to firewall settings. 1. Make sure that the content is not blocked by a Content Security Policy (CSP) or other security mechanisms. You can check this by opening the browser's developer tools and looking for errors in the console. #### Without live reload If you see a blank screen without live reload, check the following: 1. Make sure you are using the latest WebView version. Capacitor requires Android 5.1 as well as a WebView version of 60 or higher. You can check the WebView version in the device's settings. 1. If you are using **Angular**, make sure that your `.browserslistrc` or `browserslist` file includes the browser versions you need to support. The Angular CLI uses this file to determine which code it can optimize. Check out [this topic](https://forum.ionicframework.com/t/ionic-6-default-project-white-screen-on-android-30-or-below-when-using-prod/228087) for more information. # The iOS Troubleshooting Guide for Capacitor Capacitor is a great tool to build cross-platform apps with web technologies. However, sometimes you might run into issues when building your iOS app. This post will help you to troubleshoot common issues. Help us to improve this guide If you are aware of any other common issues, please send us an email to [support@capawesome.io](mailto:support@capawesome.io) so that we can update the guide. ## Errors ### `could not find module 'Capacitor' for target 'x86_64-apple-ios-simulator'` This error indicates that the `Capacitor` module is not available for the `x86_64` architecture which is used by the iOS simulator. Add the following `post_install` hook to your `Podfile` (usually `ios/App/Podfile`): ``` post_install do |installer| installer.pods_project.targets.each do |target| target.build_configurations.each do |config| # Build for all architectures config.build_settings['ONLY_ACTIVE_ARCH'] = 'NO' # Exclude arm64 architecture for the iOS simulator config.build_settings['EXCLUDED_ARCHS[sdk=iphonesimulator*]'] = 'arm64' end end end ``` The `ONLY_ACTIVE_ARCH` build setting is set to `YES` by default, which means that the build system will only build for the active architecture. By setting it to `NO`, the build system will build for both `arm64` and `x86_64` architectures, making the `Capacitor` module available for the iOS simulator. The `EXCLUDED_ARCHS[sdk=iphonesimulator*]` build setting is used to exclude the `arm64` architecture for the iOS simulator. ### `value of type 'WKWebView' has no member 'isInspectable'` This error occurs if you are using an outdated version of Xcode. For Capacitor 6 you need at least Xcode 15.0, so make sure you are using the latest version of Xcode. If this error occurs in GitHub Actions, you should update the macOS version to `macos-14` in your workflow file. Please note that `macos-latest` is not always the latest version (see [here](https://twitter.com/jcesarmobile/status/1780142061129204204)). ``` - runs-on: macos-latest + runs-on: macos-14 ``` ### `CocoaPods could not find compatible versions for pod "..."` This error occurs when CocoaPods cannot find compatible versions for a pod. This can either be due to outdated sources or other dependencies that require different versions of the same pod. Follow the steps below to find out if outdated sources are causing the problem: 1. Delete the `Podfile.lock` (usually `ios/App/Podfile.lock`) file and the `Pods` directory (usually `ios/App/Pods`). 1. Run `pod install --repo-update` where the `Podfile` is located (usually `ios/App`). If the problem persists, you need to check the dependencies in your `Podfile` and make sure that all dependencies use the same version of the pod. For example, this was one of the reasons why we created the [Capacitor Firebase](https://capawesome.io/plugins/firebase/index.md) Plugin Collection to ensure that all Firebase plugins use the same version of the Firebase SDK. ### Plugin is not implemented If Capacitor cannot find a plugin or cannot inject its code into the WebView, the following error is thrown, for example: ``` "Badge" plugin is not implemented ``` Follow the steps below to solve this issue: 1. Make sure that the plugin is installed and appears in the `package.json`. 1. Sync the native project by running `npx cap sync ios`. 1. Make sure that no other plugin is installed that might conflict with the plugin. For example, do not install two different Push Notification plugins. 1. Make sure that you do not have `WKAppBoundDomains` key in your `Info.plist` file. This key is used to restrict the domains that your app can access and can cause issues with plugins that need to inject code into the WebView. 1. If you are using CocoaPods instead of Swift Package Manager, make sure that the plugin is listed in `ios/App/Podfile`. 1. If you are using CocoaPods instead of Swift Package Manager, make sure that CocoaPods was installed correctly and no warning is shown when running `npx cap sync ios`. 1. Make sure to use the latest Node.js and npm version. Run `node -v` and `npm -v` to check your versions. 1. Make sure to use the latest Capacitor CLI version. Run `npx cap doctor` to check your versions. 1. Make sure you only have one version of Xcode installed. You can check this by running `xcode-select -p` in the terminal. If the problem persists, check the following GitHub issues and discussions for more information: - https://github.com/capacitor-community/sqlite/issues/662#issuecomment-3364677943 - https://github.com/capawesome-team/capacitor-plugins/discussions/617#discussioncomment-14527491 ### Blank screen A blank screen can have many causes. Here are some common reasons and solutions. #### With live reload If you see a blank screen when using live reload, check the following: 1. Make sure that your development server uses all network interfaces and not only `localhost`. For example, if you are using the Ionic CLI, you can use the `--external` option to bind to all network interfaces: ``` ionic cap run ios -l --external ``` 1. Make sure that your development server is accessible from the device. You can check this by opening the URL in the device's browser. If the URL is not accessible, the app will not be able to load the content. This may be due to the following reasons: - The device is not connected to the same network as the development server. - The development server is not accessible from the device due to firewall settings. 1. Make sure that the content is not blocked by a Content Security Policy (CSP) or other security mechanisms. You can check this by opening the browser's developer tools and looking for errors in the console. #### Without live reload If you see a blank screen without live reload, check the following: 1. If you are using **Angular**, make sure that your `.browserslistrc` or `browserslist` file includes the browser versions you need to support. The Angular CLI uses this file to determine which code it can optimize. Check out [this topic](https://forum.ionicframework.com/t/ionic-6-default-project-white-screen-on-android-30-or-below-when-using-prod/228087) for more information. ### Version 70 error You may encounter the following error when building your iOS app: ``` Unable to find compatibility version string for object version `70`. ``` This is caused by Xcode when you add an extension file to your project. The problem is in your Xcode project `pbxproj` file. Open this file and change objectVersion from 70 to 60. # Updating to Capacitor 6.0 Capacitor 6.0 is finally here and brings a lot of improvements! Make sure to check out the [official announcement post](https://ionic.io/blog/announcing-capacitor-6-0) from the Ionic team. In this article, you can find out what breaking changes have been made to the Capawesome plugins. AI-Assisted Upgrade For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capacitor-app-upgrades` and use the following prompt with your preferred AI coding assistant: ``` Use the `capacitor-app-upgrades` skill from `capawesome-team/skills` to help me upgrade my Capacitor app to Capacitor 6. ``` ## Plugins The following plugin functionality has been modified or removed. Update your code accordingly. More information can be found in the respective `BREAKING.md` file of each plugin. ### Android Foreground Service - You must specify appropriate foreground service types in your `AndroidManifest.xml` (see [Declare foreground services in your manifest](https://developer.android.com/develop/background-work/services/foreground-services#types) and [Request the foreground service permissions](https://developer.android.com/develop/background-work/services/foreground-services#request-foreground-service-permissions)). ### App Update - The `currentVersion` property has been replaced by the `currentVersionCode` and `currentVersionName` properties in the `AppUpdateInfo` interface. - The `availableVersion` property has been replaced by the `availableVersionCode` and `availableVersionName` properties in the `AppUpdateInfo` interface. ### File Picker - The `multiple` property has been replaced by a new `limit` property in the `PickFilesOptions` and `PickMediaOptions` interfaces. - The `File` interface has been replaced by the `PickedFile` interface. - The default value of the `skipTranscoding` property has been changed to `true`. ### Firebase Analytics - The Firebase Javascript SDK has been updated to `10.9.0`. - The Analytics component of the Firebase Android SDK has been updated to `20.5.1`. ### Firebase App - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseCommonVersion` variable has been updated to `20.4.2`. ### Firebase App Check - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseAppCheckPlayIntegrityVersion` variable has been updated to `17.1.2`. - On Android, the `firebaseAppCheckDebugVersion` variable has been updated to `17.1.2`. ### Firebase Authentication - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseAuthVersion` variable has been updated to `22.3.1`. - On Android, the `playServicesAuthVersion` variable has been updated to `21.0.0`. - On Android, the `facebookLoginVersion` variable has been updated to `16.3.0`. ### Firebase Crashlytics - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseCrashlyticsVersion` variable has been updated to `18.6.2`. ### Firebase Cloud Firestore - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - The `QueryDocumentSnapshot` interface has been replaced with the `DocumentSnapshot` interface. - On Android, the `firebaseFirestoreVersion` variable has been updated to `24.10.3`. ### Firebase Cloud Messaging - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseMessagingVersion` variable has been updated to `23.4.1`. ### Firebase Cloud Storage - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseStorageVersion` variable has been updated to `20.3.0`. ### Firebase Performance Monitoring - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebasePerfVersion` variable has been updated to `20.5.2`. ### Firebase Remote Config - The Firebase Javascript SDK has been updated to `10.9.0`. - The Firebase iOS SDK has been updated to `10.24.0`. - On Android, the `firebaseConfigVersion` variable has been updated to `21.6.3`. ### ML Kit Barcode Scanning - On Android, the `mlkitBarcodeScanningVersion` variable has been updated to `17.2.0`. - On Android, the `playServicesCodeScannerVersion` variable has been updated to `16.1.0`. ### ML Kit Face Detection - On Android, the `mlkitFaceDetectionVersion` variable has been updated to `16.1.6`. ### ML Kit Face Mesh Detection - On Android, the `mlkitFaceMeshDetectionVersion` variable has been updated to `16.0.0-beta2`. ### ML Kit Selfie Segmentation - On Android, the `mlkitSelfieSegmentationVersion` variable has been updated to `16.0.0-beta5`. ### ML Kit Translation - On Android, the `mlkitTranslateVersion` variable has been updated to `17.0.2`. ### NFC - The `techType` property of the `TransceiveOptions` interface is now only available for iOS. On Android, the `connect(...)` method must first be called to establish the connection to an NFC tag. The `transceive(...)` method can then be used to send and receive data. Finally, the `close(...)` method must be called to terminate the connection. # Updating to Capacitor 7.0 Capacitor 7.0 is finally here and brings a lot of improvements! Make sure to check out the [official announcement post](https://ionic.io/blog/capacitor-7-has-hit-ga) from the Ionic team. In this article, you can find out what breaking changes have been made to the Capawesome plugins. AI-Assisted Upgrade For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capacitor-app-upgrades` and use the following prompt with your preferred AI coding assistant: ``` Use the `capacitor-app-upgrades` skill from `capawesome-team/skills` to help me upgrade my Capacitor app to Capacitor 7. ``` ## Plugins The following plugin functionality has been modified or removed. Update your code accordingly. More information can be found in the respective `BREAKING.md` file of each plugin. ### App Update - The `country` property has been replaced by the `appId` property in the `OpenAppStoreOptions` interface. ### Cloudinary - On Android, the `cloudinaryAndroidVersion` variable has been updated to `3.0.2`. ### Firebase Analytics - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseAnalyticsVersion` variable has been updated to `22.2.0`. ### Firebase App - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseCommonVersion` variable has been updated to `21.0.0`. ### Firebase App Check - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseAppCheckPlayIntegrityVersion` variable has been updated to `18.0.0`. - On Android, the `firebaseAppCheckDebugVersion` variable has been updated to `18.0.0`. ### Firebase Authentication - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `facebookLoginVersion` variable has been updated to `18.0.0`. - On Android, the `firebaseAuthVersion` variable has been updated to `23.1.0`. - On Android, the `playServicesAuthVersion` variable has been updated to `20.7.0`. - On Android, the `accessToken` and `serverAuthCode` are now only requested when the `scopes` option is set. - Error codes are now prefixed with `auth/` to be consistent with the Firebase Web SDK. ### Firebase Crashlytics - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseCrashlyticsVersion` variable has been updated to `19.4.0`. ### Firebase Firestore - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseFirestoreVersion` variable has been updated to `25.1.1`. - Error codes are now prefixed with `firestore/` to be consistent with the Firebase Web SDK. ### Firebase Functions - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseFunctionsVersion` variable has been updated to `21.1.0`. ### Firebase Messaging - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseMessagingVersion` variable has been updated to `24.1.0`. ### Firebase Performance - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebasePerfVersion` variable has been updated to `21.0.4`. - The web implementation now throws an error when a `trace` is not found for a given `traceName`. This behavior was already in place on iOS and Android in earlier versions. ### Firebase Remote Config - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseConfigVersion` variable has been updated to `22.1.0`. ### Firebase Storage - The Firebase Javascript SDK has been updated to `11.2.0`. - On Android, the `firebaseStorageVersion` variable has been updated to `21.0.1`. ### Live Update - The `getBundle()` method has been replaced by the `getCurrentBundle()` method. - The `setBundle()` method has been replaced by the `setNextBundle()` method. - The `checksum` property has been removed from the `DownloadBundleOptions` interface. The server should now return a `X-Checksum` header instead. - The `enabled` configuration option has been removed. The plugin is now always enabled. - The `location` configuration option has been replaced by the `serverDomain` configuration option. - The default value of the `readyTimeout` configuration option has been changed from `10000` to `0` to disable the timeout by default. - The `resetOnUpdate` configuration option has been removed. Capacitor always resets the app to the default bundle during a native update. ### ML Kit Barcode Scanning - On Android, the `mlkitBarcodeScanningVersion` variable has been updated to `17.3.0`. - on Android, the image resolution used for barcode scanning has been increased to `1280x720` to improve the barcode scanning performance and be consistent with the iOS implementation. Previously, the resolution was `640x480`. - On iOS, make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`. - All the methods related to the torch have been moved into a separate [Torch](https://capawesome.io/plugins/torch/index.md) plugin. If you want to use the torch, just install the `@capawesome/capacitor-torch` package. - The `barcodeScanned` event has been replaced by the `barcodesScanned` event. The event payload now contains an array of `Barcode` objects instead of a single `Barcode` object since multiple barcodes can be scanned at the same time. ### ML Face Detection - On Android, the `mlkitFaceDetectionVersion` variable has been updated to `16.1.7`. - On iOS, make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`. ### ML Selfie Segmentation - On Android, the `mlkitSelfieSegmentationVersion` variable has been updated to `16.0.0-beta6`. - On iOS, make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`. ### ML Translation - On Android, the `mlkitTranslateVersion` variable has been updated to `17.0.3`. - On iOS, make sure to set the deployment target in your `ios/App/Podfile` to at least `15.5`. ### NFC - On iOS, the `id` property of the `NfcTag` interface was returned reversed for ISO 15693 tags. This issue has been fixed in this version. The `id` property is now returned correctly and behaves the same way as on Android. More information can be found in the discussion [#200](https://github.com/capawesome-team/capacitor-plugins/discussions/200). ### Posthog - On Android, the `posthogVersion` variable has been updated to `3.10.0`. # Updating to Capacitor 8.0 Capacitor 8.0 is finally here and brings a lot of improvements! Make sure to check out the [official announcement post](https://ionic.io/blog/announcing-capacitor-8) from the Ionic team. In this article, you can find out what breaking changes have been made to the Capawesome plugins. For a complete upgrade guide covering all migration approaches, see [How to Upgrade Your Capacitor App to Capacitor 8](https://capawesome.io/blog/how-to-upgrade-your-capacitor-app-to-capacitor-8/index.md) or [How to Upgrade Your Capacitor Plugin to Capacitor 8](https://capawesome.io/blog/how-to-upgrade-your-capacitor-plugin-to-capacitor-8/index.md). AI-Assisted Upgrade For a more guided experience, add the [Capawesome skills](https://github.com/capawesome-team/skills) to your project with `npx skills add capawesome-team/skills --skill capacitor-app-upgrades` and use the following prompt with your preferred AI coding assistant: ``` Use the `capacitor-app-upgrades` skill from `capawesome-team/skills` to help me upgrade my Capacitor app to Capacitor 8. ``` ## Plugins The following plugin functionality has been modified or removed. Update your code accordingly. More information can be found in the respective `BREAKING.md` file of each plugin. ### Accelerometer [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accelerometer/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accelerometer/BREAKING.md) - The plugin now automatically requests permissions when calling plugin methods instead of throwing an error. Use the `checkPermissions()` method beforehand if you need to check permissions first. ### Age Signals [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/age-signals/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/age-signals/BREAKING.md) - On Android, the `androidPlayAgeSignalsVersion` variable has been updated to `0.0.2`. ### Android Edge to Edge Support [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-edge-to-edge-support/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-edge-to-edge-support/BREAKING.md) - The default background color has been changed from white to transparent. To keep the white background, explicitly set `backgroundColor` to `#ffffff` in the plugin configuration. ### App Update [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-update/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-update/BREAKING.md) - On Android, the `androidPlayServicesBaseVersion` variable has been updated to `18.9.0`. ### Audio Recorder [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-recorder/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-recorder/BREAKING.md) - On Android, the `pauseRecording` and `resumeRecording` methods now return `UNAVAILABLE` instead of `UNIMPLEMENTED` when called on devices with API level below 24. - The default value of `audioSessionMode` has been changed from `AudioSessionMode.Measurement` to `AudioSessionMode.Default`. If you were relying on the previous behavior, you must now explicitly set the `audioSessionMode` option. ### Badge [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/badge/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/badge/BREAKING.md) - On iOS 16+, the plugin now uses `UNUserNotificationCenter` instead of the deprecated `applicationIconBadgeNumber` API, which may affect error handling behavior. ### Bluetooth Low Energy [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/bluetooth-low-energy/BREAKING.md) - On Web, all methods now return `UNIMPLEMENTED` instead of `UNAVAILABLE`. - On Android, the error message for `startForegroundService` has been improved when missing required permissions. The original system error is now logged via the Capacitor Logger. ### Cloudinary [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/cloudinary/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/cloudinary/BREAKING.md) - On Android, the `cloudinaryAndroidVersion` variable has been updated to `3.1.2`. ### Contacts [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/contacts/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/contacts/BREAKING.md) - The `limit` property in `GetContactsOptions` now defaults to `20`. - The `updateContactById` method now supports partial updates. Missing properties are preserved, and properties set to `null` or `[]` are deleted. If you were relying on missing properties being deleted, you must now explicitly set them to `null`. ### File Compressor [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-compressor/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-compressor/BREAKING.md) - On Android, the `androidxDocumentFileVersion` variable has been updated to `1.1.0`. - On Android, the `path` property in `CompressImageResult` is now a URI string instead of an absolute file path (e.g., `file:///data/...` instead of `/data/...`). ### File Opener [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-opener/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-opener/BREAKING.md) - On Android, the `androidxDocumentFileVersion` variable has been updated to `1.1.0`. ### Firebase Analytics [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/analytics/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/analytics/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On iOS, the Firebase iOS SDK has been updated to `12.7.0`. Deprecated pods have been removed: `GoogleAppMeasurementOnDeviceConversion`, `FirebaseAnalyticsOnDeviceConversion`, and `WithoutAdIdSupport`. - On Android, the `firebaseAnalyticsVersion` variable has been updated to `23.0.0`. ### Firebase App [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseCommonVersion` variable has been updated to `22.0.1`. ### Firebase App Check [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app-check/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/app-check/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseAppCheckPlayIntegrityVersion` variable has been updated to `19.0.1`. - On Android, the `firebaseAppCheckDebugVersion` variable has been updated to `19.0.1`. ### Firebase Authentication [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/authentication/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - The `dynamicLinkDomain` property in `ActionCodeSettings` has been replaced with `linkDomain`. - Error codes have been updated to return `UNIMPLEMENTED` instead of generic errors for platform-specific methods: - On Web: `getPendingAuthResult()`, `linkWithGameCenter(...)`, `linkWithPlayGames(...)`, `signInWithPlayGames()`, `signInWithGameCenter()`, `requestAppTrackingTransparencyPermission()`, `checkAppTrackingTransparencyPermission()` - On Android: `getRedirectResult()`, `setPersistence(...)`, `signInWithGameCenter()`, `requestAppTrackingTransparencyPermission()`, `checkAppTrackingTransparencyPermission()` - On iOS: `getPendingAuthResult()`, `getRedirectResult()`, `linkWithPlayGames(...)`, `setPersistence(...)`, `signInWithPlayGames()` ### Firebase Crashlytics [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/crashlytics/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/crashlytics/BREAKING.md) - On Android, the `firebaseCrashlyticsVersion` variable has been updated to `20.0.3`. ### Firebase Firestore [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/firestore/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/firestore/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseFirestoreVersion` variable has been updated to `26.0.2`. ### Firebase Functions [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/functions/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/functions/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseFunctionsVersion` variable has been updated to `22.1.0`. ### Firebase Messaging [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/messaging/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/messaging/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseMessagingVersion` variable has been updated to `25.0.1`. - On Web, mobile-specific methods now return `UNIMPLEMENTED` instead of `UNAVAILABLE`: `getDeliveredNotifications()`, `removeDeliveredNotifications(...)`, `removeAllDeliveredNotifications()`, `subscribeToTopic(...)`, `unsubscribeFromTopic(...)`, `createChannel(...)`, `deleteChannel(...)`, `listChannels()` ### Firebase Performance [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/performance/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/performance/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebasePerfVersion` variable has been updated to `22.0.4`. ### Firebase Remote Config [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/remote-config/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/remote-config/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseConfigVersion` variable has been updated to `23.0.1`. ### Firebase Storage [README](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/storage/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-firebase/blob/main/packages/storage/BREAKING.md) - The Firebase Javascript SDK has been updated to `12.6.0`. - On Android, the `firebaseStorageVersion` variable has been updated to `22.0.1`. ### LibSQL [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/libsql/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/libsql/BREAKING.md) - On Android, the `libsqlVersion` variable has been updated to `0.1.2`. - On Android, the `sync` method now returns "Not implemented on this platform." instead of "Not available on this platform." ### Live Update [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/live-update/BREAKING.md) - On Android, the `okhttp3Version` variable has been updated to `5.3.2`. ### Media Session [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/media-session/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/media-session/BREAKING.md) - On iOS, the `setCameraActive` and `setMicrophoneActive` methods now return `UNIMPLEMENTED` instead of `UNAVAILABLE`. ### ML Kit Barcode Scanning [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/barcode-scanning/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/barcode-scanning/BREAKING.md) - On Web, unimplemented methods now throw `Unimplemented` instead of `Unavailable`. - On iOS, the `isGoogleBarcodeScannerModuleAvailable` and `installGoogleBarcodeScannerModule` methods now throw `Unimplemented` instead of a generic error. - On Android, the `androidxCameraCamera2Version` variable has been updated to `1.5.2`. - On Android, the `androidxCameraCoreVersion` variable has been updated to `1.5.2`. - On Android, the `androidxCameraLifecycleVersion` variable has been updated to `1.5.2`. - On Android, the `androidxCameraViewVersion` variable has been updated to `1.5.2`. - The `rawValue` property in the `Barcode` interface is now optional and only available if the barcode is UTF-8 encoded. ### ML Kit Document Scanner [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/document-scanner/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/document-scanner/BREAKING.md) - On iOS, the `scanDocument` method now throws `Unimplemented` instead of a generic error. - On Android, the `mlkitDocumentScannerVersion` variable has been updated to `16.0.0`. ### ML Kit Face Detection [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-detection/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-detection/BREAKING.md) - On Web, unimplemented methods now throw `Unimplemented` instead of `Unavailable`. ### ML Kit Face Mesh Detection [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-mesh-detection/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/face-mesh-detection/BREAKING.md) - On Web, unimplemented methods now throw `Unimplemented` instead of `Unavailable`. - On iOS, the `processImage` method now throws `Unimplemented` instead of a generic error. ### ML Kit Selfie Segmentation [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/selfie-segmentation/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/selfie-segmentation/BREAKING.md) - On Web, unimplemented methods now throw `Unimplemented` instead of `Unavailable`. ### ML Kit Subject Segmentation [README](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/subject-segmentation/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-mlkit/blob/main/packages/subject-segmentation/BREAKING.md) - On iOS, the `processImage`, `isGoogleSubjectSegmentationModuleAvailable`, and `installGoogleSubjectSegmentationModule` methods now throw `Unimplemented` instead of a generic error. ### NFC [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/nfc/BREAKING.md) - On Web, the following methods now return `UNIMPLEMENTED` instead of `UNAVAILABLE`: `format`, `respond`, `transceive`, `connect`, `close`, `isEnabled`, `openSettings`, `getAntennaInfo`, and `setAlertMessage`. ### Pedometer [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/pedometer/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/pedometer/BREAKING.md) - The plugin now automatically requests permissions when calling plugin methods instead of throwing an error. Use the `checkPermissions()` method beforehand if you need to check permissions first. ### PostHog [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/posthog/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/posthog/BREAKING.md) - The `posthog-js` peer dependency has been updated to `^1.306.2`. ### Printer [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/printer/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/printer/BREAKING.md) - On Android, the `androidxDocumentFileVersion` variable has been updated to `1.1.0`. - On Android, the `androidxPrintVersion` variable has been updated to `1.1.0`. ### Purchases [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/purchases/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/purchases/BREAKING.md) - On Android, the `googlePlayBillingVersion` variable has been updated to `8.2.0`. ### RealtimeKit [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/realtimekit/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/realtimekit/BREAKING.md) - On Android, the `realtimekitUiVersion` variable has been updated to `0.3.1`. ### Share Target [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/share-target/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/share-target/BREAKING.md) - On Android, the `androidxExifInterfaceVersion` variable has been updated to `1.4.2`. ### Speech Recognition [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-recognition/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/speech-recognition/BREAKING.md) - The plugin now automatically requests permissions when calling plugin methods instead of throwing an error. Use the `checkPermissions()` method beforehand if you need to check permissions first. - On iOS, the default language has been changed from `Locale.current` to `Locale.preferredLanguages`. If you were relying on the previous behavior, you may need to explicitly set the `language` option. ### SQLite [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/sqlite/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/sqlite/BREAKING.md) - The SQLite WASM dependency is now restricted to version `3.50.3-build1` or lower due to a bug in higher versions. - On Android, the `androidxSqliteVersion` variable has been updated to `2.6.2`. - On Android, the `androidxSqliteFrameworkAndroidVersion` variable has been updated to `2.6.2`. - On Android, the `netZeteticSqlcipherVersion` variable has been updated to `4.12.0`. - The default database version management behavior has been changed. If neither `version` nor `upgradeStatements` are provided, no version is set (previously defaulted to `1`). ### Torch [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/torch/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/torch/BREAKING.md) - On Android, the `androidxCameraCoreVersion` variable has been updated to `1.5.2`. ### Wi-Fi [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/wifi/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/wifi/BREAKING.md) - The plugin now automatically requests permissions when calling plugin methods instead of throwing an error. Use the `checkPermissions()` method beforehand if you need to check permissions first. ### Zip [README](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/zip/README.md) | [BREAKING](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/zip/BREAKING.md) - The minimum iOS deployment target has been updated to **16.0** (instead of 15.0) to address several security vulnerabilities related to ZIP file handling in earlier iOS versions. # Using Environment Variables and Secrets in Capawesome Cloud Builds When building mobile apps in the cloud, it's common to need different configuration values depending on the environment. A staging build might use a different API endpoint than a production build, or you might need to inject API keys that shouldn't be committed to your repository. With [Capawesome Cloud](https://cloud.capawesome.io/), you can define environments, add environment variables and secrets, and customize the build stack using reserved variables—all without changing your source code. In this guide, you'll learn how to: - Create environments like staging and production - Add environment variables for configuration - Store sensitive values as secrets - Customize the build stack with reserved variables such as `NODE_VERSION` and `XCODE_VERSION` If you're new to Capawesome Cloud builds, you may want to first explore the [Native Builds](https://capawesome.io/cloud/native-builds/index.md) documentation. ## Create Environments Environments allow you to manage different configurations for your builds without changing your source code. For example, your staging build may connect to a staging API, while your production build connects to the live API. Typical environments include: - `development` - `staging` - `production` Each environment can have its own set of variables and secrets that are automatically injected during the build process. This makes it easy to keep build configurations organized and avoids manually changing values between builds. To create a custom environment, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page in the Capawesome Cloud Console. You can then select which environment to use when triggering a build. ## Add Environment Variables Environment variables are ideal for non-sensitive configuration values. A common example is defining which API endpoint your app should use. To add a variable, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page, click on "Manage Variables" in the "Actions" menu of an environment, and create a new variable. You can configure different values for each environment, for example: | Environment | Variable | | ----------- | ---------------------------------------------- | | Staging | `VITE_API_URL=https://staging-api.example.com` | | Production | `VITE_API_URL=https://api.example.com` | During the cloud build, these variables become available to your build process. For example, in an application built with Vite: ``` const apiUrl = import.meta.env.VITE_API_URL; fetch(`${apiUrl}/users`); ``` When the production environment is selected, the build will automatically use the production API endpoint. The value is replaced during the build step and bundled into your application, so you can configure different environments without modifying your application code. Bulk Import You can bulk import multiple variables at once by using the "Import" feature in the actions menu. This allows you to paste in multiple `KEY=VALUE` pairs, making it easier to set up your environment. ## Store Sensitive Values as Secrets Some values should never appear in logs or configuration files. These should be stored as secrets. Secrets are encrypted at rest and in transit, and their values are never displayed in build logs. This makes them ideal for storing sensitive credentials such as API tokens, authentication credentials, deployment keys, and license keys. To add a secret, navigate to the [Environments](https://console.cloud.capawesome.io/apps/_/environments) page, click on "Manage Secrets" in the "Actions" menu of an environment, and create a new secret. Secrets are added the same way as regular variables, but their values remain hidden throughout the build process. For example, you might store a Sentry authentication token as a secret: ``` SENTRY_AUTH_TOKEN=your-secret-token ``` This token could be used during the build to upload source maps: ``` npx sentry-cli releases new $CI_BUILD_ID npx sentry-cli releases files $CI_BUILD_ID upload-sourcemaps dist ``` Because the value is stored as a secret, it will not appear in build logs. ## Customize the Build Stack with Reserved Variables Capawesome Cloud supports reserved variables that allow you to configure the build environment itself. These variables control which tools and versions are used during the build, and **they are added to an environment the same way as regular variables**. Let's see some examples. ### Selecting the Node.js Version If your project requires a specific Node.js version, you can configure it using the `NODE_VERSION` variable: ``` NODE_VERSION=22 ``` During the build, Capawesome Cloud will automatically use that version. This is useful when your project depends on specific Node.js features or when you need to match your local development environment. ### Selecting the Xcode Version For iOS builds, you can specify which version of Xcode should be used with the `XCODE_VERSION` variable: ``` XCODE_VERSION=16 ``` This determines the iOS SDK, Swift version, and build tools used during compilation. This is particularly useful when Apple requires apps to be built with a newer SDK or when your project depends on a specific toolchain. For the complete list of reserved variables (including `JAVA_VERSION`, `IOS_SCHEME`, `ANDROID_FLAVOR`, and more), see the [Reserved Environment](https://capawesome.io/cloud/native-builds/environments/#reserved-environment) documentation. ## Default Variables During every build, Capawesome Cloud automatically provides default variables that give you information about the current build context. These include variables like `CI_BUILD_ID`, `CI_GIT_REFERENCE`, `CI_GIT_COMMIT_SHA`, and `CI_PLATFORM`. You can use these variables in your build scripts to make decisions based on the build context. For example, you might use `CI_BUILD_ID` to tag releases in your error monitoring tool, or use `CI_GIT_REFERENCE` to determine which branch triggered the build. ``` echo "Building from branch: $CI_GIT_REFERENCE" echo "Commit: $CI_GIT_COMMIT_SHA" ``` For the complete list of default variables, see the [Default Environment](https://capawesome.io/cloud/native-builds/environments/#default-environment) documentation. Reserved Variable Names Variables such as `CI` and any variable starting with `CI_` are reserved and cannot be overridden. They are automatically managed by the build system. ## Common Use Cases Here are some practical examples of how teams use environment variables and secrets in their builds. These are just a few ideas—the possibilities are endless. ### Switching Payment Provider Environments Payment providers like Stripe or PayPal typically offer sandbox environments for testing. You can use environment variables to switch between sandbox and live endpoints: | Environment | Variable | | ----------- | ------------------------------------------------- | | Staging | `VITE_STRIPE_API_URL=https://api.stripe.com/test` | | Production | `VITE_STRIPE_API_URL=https://api.stripe.com` | You can also store the API keys as secrets: | Environment | Secret | | ----------- | ------------------------------------ | | Staging | `STRIPE_PUBLISHABLE_KEY=pk_test_...` | | Production | `STRIPE_PUBLISHABLE_KEY=pk_live_...` | This ensures your staging builds never accidentally process real payments. ### Uploading Source Maps to Error Monitoring When using error monitoring tools like Sentry, uploading source maps during the build makes stack traces readable. Store your authentication token as a secret and use default variables to tag releases: ``` npx sentry-cli releases new $CI_BUILD_ID npx sentry-cli releases files $CI_BUILD_ID upload-sourcemaps dist npx sentry-cli releases set-commits $CI_BUILD_ID --commit "repo@$CI_GIT_COMMIT_SHA" ``` ### Feature Flags Per Environment Environment variables can control which features are enabled in each build: ``` VITE_ENABLE_ANALYTICS=true VITE_ENABLE_DEBUG_MODE=false ``` In your application code: ``` if (import.meta.env.VITE_ENABLE_DEBUG_MODE === 'true') { console.log('Debug mode enabled'); } ``` This allows you to enable experimental features in staging while keeping them disabled in production builds. ### Private npm Registry Access If your project uses private npm packages, you can store the registry token as a secret and configure npm to use it during the build. See the [Private npm Registry](https://capawesome.io/cloud/native-builds/guides/npm-private-registry/index.md) guide for details. ### Conditional Build Logic Use default variables to customize build behavior based on the target platform or branch: ``` if [ "$CI_PLATFORM" = "ios" ]; then echo "Running iOS-specific setup..." fi if [ "$CI_GIT_REFERENCE" = "main" ]; then echo "Production build detected" fi ``` ## Why Environment Variables Matter Whether you're building with Ionic, Angular, React, or Vue, environment variables allow you to change how your app builds and behaves without modifying source code or committing environment-specific configuration to your repository. This provides several advantages: - **Safer deployments**: Sensitive values never appear in your codebase - **Flexible builds**: Easily switch between staging and production configurations - **Better collaboration**: Team members can work with different environments without conflicts - **Simpler CI/CD**: No complex scripts or YAML files to manage ## Try Capawesome Cloud Ready to simplify your mobile CI/CD workflow? Capawesome Cloud provides a powerful way to manage your build configuration and automate your deployment pipeline for Capacitor and Ionic apps. [Try Capawesome Cloud Free](https://cloud.capawesome.io) ## Conclusion Environment variables and secrets make it easy to manage build configuration in Capawesome Cloud. By combining environments, variables, and reserved build settings, you can securely store sensitive values, configure builds for different environments, customize the build stack, and simplify your CI/CD workflow. For more details, check out the [Environments](https://capawesome.io/cloud/native-builds/environments/index.md) documentation. If you have any questions, join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). For the latest updates, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter). # Why Every Capacitor Team Needs a CI/CD Pipeline Somewhere right now, a developer is building a Capacitor app on their laptop, signing it manually, and uploading it to the App Store — while hoping they didn't forget a step. It works, until it doesn't. A wrong signing certificate, a missing environment variable, or a skipped test is all it takes to ship a broken build to production. If your team is still releasing manually, you're spending time on work that should be automated — and accepting risk that doesn't need to exist. This post makes the case for why CI/CD should be a priority for every Capacitor team and how you can get there without weeks of setup. ## The Cost of Manual Releases Manual releases feel manageable when it's one developer shipping one app. But the cracks show quickly as your team or product grows. A typical manual release process looks something like this: pull the latest code, run the build locally, sign the app with the right certificate, upload to the App Store or Google Play, then notify the team. Every step depends on the person doing it — their machine, their local environment, their memory of the correct sequence. Here's what goes wrong: - **Inconsistent builds.** Two developers building from the same branch can produce different artifacts because their local environments differ — different Node.js versions, different Xcode versions, different dependency caches. - **Human error.** Forgotten steps are inevitable when a release involves a dozen manual actions. A wrong keystore, a stale provisioning profile, or a missed test suite can ship bugs straight to your users. - **It doesn't scale.** What takes 20 minutes for one app takes an hour for two. Add a QA build, a staging build, and a production build, and suddenly releases eat half a day. When releasing is painful, teams release less often — and less often means bigger, riskier batches. The cost isn't just time. It's the cognitive load on your developers, the risk to your users, and the velocity your team loses week after week. ## What CI/CD Actually Gives You CI/CD replaces the manual checklist with an automated pipeline that runs every time you push code or merge a branch. For a Capacitor team, this translates into four concrete benefits: **Consistency.** Every build runs in the same environment with the same tools, the same dependency versions, and the same steps. It doesn't matter who pushes the code or what machine they use — the output is identical every time. **Speed.** A well-configured pipeline builds, signs, and deploys your app in minutes. Your team merges a PR and the release is on its way — no manual handoff, no waiting for someone to "run the build." **Confidence.** Automated tests run on every build. Bugs, regressions, and breaking changes get caught before they reach a single user. When something does go wrong, you can pinpoint exactly which commit introduced the issue. **Traceability.** Every release is tied to a specific commit, a specific build, and a specific pipeline run. Rolling back is straightforward. Auditing what shipped and when is trivial. For teams in regulated industries, this alone can justify the investment. ## Why Mobile CI/CD Is Different from Web If you've set up CI/CD for a web app, you might wonder what the big deal is. For web, it's straightforward: build your static files, run your tests, deploy to a CDN or server. Done. Mobile is a different game: - **Native compilation.** Capacitor apps include native Android and iOS projects. You need Gradle for Android and Xcode for iOS — and Xcode only runs on macOS, which means you need macOS build environments in your pipeline. - **Code signing.** Android apps must be signed with a keystore. iOS apps require distribution certificates and provisioning profiles. Managing these securely in CI is one of the most common sources of build failures for mobile teams. - **Two platforms, double the complexity.** Every build potentially produces two artifacts with different toolchains, signing requirements, and store submission processes. - **App store review cycles.** Unlike web, you can't just deploy and have your changes live instantly. Store reviews add delay — which makes fast iteration harder and makes features like [Live Updates](https://cloud.capawesome.io/live-updates/) valuable for shipping web layer changes without waiting for approval. None of this is insurmountable, but it means that mobile CI/CD requires more planning and more infrastructure than web CI/CD. That's exactly why many teams put it off — and why the teams that invest in it early gain a real advantage. ## Signs Your Team Is Ready for CI/CD Not sure if it's time to invest? If any of these sound familiar, it probably is: - **More than one developer** is working on the app. As soon as two people can trigger a release, you need a single source of truth for how builds are produced. - **Releases take more than 15 minutes** of manual work. That's time your developers could spend building features. - **You've shipped a broken build** because someone skipped a step, used the wrong certificate, or forgot to run tests. Once is a learning experience. Twice is a process problem. - **You're avoiding releases** because the process is too painful. When shipping is easy, your team ships more often — and more often means smaller, safer changes. - **You're managing multiple apps or environments.** QA builds, staging builds, production builds, Android, iOS — the matrix grows fast, and manual processes don't scale with it. If you checked even one of these boxes, CI/CD will pay for itself quickly — in time saved, bugs avoided, and developer sanity preserved. ## Getting Started Without the Overhead The biggest misconception about mobile CI/CD is that it requires a dedicated DevOps engineer and weeks of pipeline configuration. That used to be true. It doesn't have to be anymore. [Capawesome Cloud](https://cloud.capawesome.io/) lets your team go from zero to automated builds without writing pipeline scripts or managing macOS infrastructure. Connect your Git repository, and you can trigger your first native build immediately — no configuration files required for standard Capacitor projects. From there, the platform handles what usually takes the most effort to set up yourself: - **Native builds** for Android and iOS on managed macOS infrastructure. - **Code signing** managed through the platform — upload your certificates once. - **App store publishing** directly to Google Play, the App Store, and TestFlight. - **Live updates** for shipping web layer changes instantly without store review. For teams that want full control over every step, building your own pipeline with GitHub Actions or GitLab CI is always an option. For a detailed comparison of both approaches, read our post on [choosing the right CI/CD approach for Capacitor apps](https://capawesome.io/blog/choosing-the-right-ci-cd-approach-for-capacitor-apps/index.md). ## Get Started Ready to automate your release process? Book a demo to see how Capawesome Cloud can help your team ship faster with less effort. [Book a Capawesome Cloud Demo](https://cal.com/team/capawesome/cloud-demo) ## Conclusion Manual releases cost more than most teams realize — in time, in risk, and in the releases that never happen because the process is too painful. CI/CD eliminates that friction and gives your team consistency, speed, and confidence with every release. And with platforms like Capawesome Cloud, getting started doesn't require a DevOps hire or weeks of setup. If you already have a pipeline and want to make sure you're not falling into common traps, check out our post on [common CI/CD pitfalls for Capacitor apps](https://capawesome.io/blog/ci-cd-for-capacitor-common-pitfalls/index.md). Have questions or want to talk through your team's setup? Join the [Capawesome Discord server](https://discord.gg/VCXxSVjefW). And to stay updated on new guides and features, subscribe to the [Capawesome newsletter](https://cloud.capawesome.io/newsletter).