# 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 almost 200 high-quality [Capacitor SDKs](sdks/capacitor/index.md) with more than **5,000,000 monthly downloads**. Besides that, Capawesome offers a [Cloud](cloud/index.md) solution for Capacitor Live Updates, Native Builds, App Store Publishing, and Automations. # MCP servers # MCP Servers The [Model Context Protocol](https://modelcontextprotocol.io) lets an AI assistant call tools on a server instead of answering from its training data. It can search the current documentation, fetch the page it needs, and act on your account, without you pasting anything into the conversation. We host three MCP servers, one per part of the stack. All three are remote HTTP servers, so there is nothing to install and nothing to keep up to date, and they work with Claude Code, Claude Desktop, Cursor, VS Code, and any other MCP client. ## Capawesome MCP Server Searches the Capawesome documentation and blog. With an [API token](https://capawesome.io/docs/cloud/accounts/tokens/index.md) it also exposes the [Capawesome Cloud](https://capawesome.io/docs/cloud/index.md) management API: trigger a native build, ship a live update, roll back a channel, or diagnose a failed job. This is the official Capawesome MCP server, maintained by us. ``` https://mcp.capawesome.io/mcp ``` [Set up the Capawesome MCP server](https://capawesome.io/docs/ai/mcp/capawesome/index.md) ## Capacitor MCP Server Serves the official [Capacitor](https://capacitorjs.com) documentation for v8, v7, v6 and `next`, the official and community plugin list, and the Capacitor posts from the Ionic blog. Free, no token. Unofficial server, maintained by Capawesome and not affiliated with or endorsed by Ionic or OutSystems. ``` https://capacitor-mcp.capawesome.io/mcp ``` [Set up the Capacitor MCP server](https://capawesome.io/docs/ai/mcp/capacitor/index.md) ## Ionic Framework MCP Server Serves the official [Ionic Framework](https://ionicframework.com) documentation for v9 and v8, including the component API reference and the usage examples for Angular, React, Vue and vanilla JavaScript. Free, no token. Unofficial server, maintained by Capawesome and not affiliated with or endorsed by Ionic or OutSystems. ``` https://ionic-framework-mcp.capawesome.io/mcp ``` [Set up the Ionic Framework MCP server](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md) ## Next steps - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. - [Skills](https://capawesome.io/docs/ai/skills/index.md) — on-demand agent skills that automate migrations and Cloud setup. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent. # Capawesome MCP Server The Capawesome MCP server connects your AI assistant to Capawesome — the documentation and blog, and your [Capawesome Cloud](https://capawesome.io/docs/cloud/index.md) apps, builds, deployments, and devices. It implements the [Model Context Protocol](https://modelcontextprotocol.io), so it works with Claude Code, Claude Desktop, Cursor, VS Code, and any other MCP client. It's hosted by us — there's nothing to install and nothing to keep up to date: ``` https://mcp.capawesome.io/mcp ``` On its own, the server exposes documentation search, so your assistant answers from the current docs instead of stale training data. Add your API token and enable the Cloud tools, and it also exposes the Capawesome Cloud management surface: trigger an app build, ship a live update, roll back a channel, or diagnose a failed job — without leaving the conversation. ## Authentication The documentation tools work without authentication. Everything else requires an [API token](https://capawesome.io/docs/cloud/accounts/tokens/index.md), sent as a bearer token in the `Authorization` header. Create one in the [Capawesome Cloud Console](https://console.cloud.capawesome.io/settings/tokens) — the token is shown only once, so copy it right away. A token acts on behalf of the account that created it, so the server can do whatever you can do across your organizations and apps. Requests go through the same [Cloud API](https://capawesome.io/docs/cloud/api/index.md) as the CLI, which means the same permissions, [network restrictions](https://capawesome.io/docs/cloud/organizations/network-restrictions/index.md), and rate limits apply. Keep tokens secret Never commit a token to version control. Store it in a secret manager or an environment variable, and revoke it in the Console if it might have leaked. ## Setup ### AI-Assisted Setup (Recommended) The fastest way to connect the server is with 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-mcp ``` Then use the following prompt in your preferred AI tool (e.g. [Claude Code](https://www.anthropic.com/claude-code), [Cursor](https://www.cursor.com/), or [GitHub Copilot](https://github.com/features/copilot)): ``` Use the `capawesome-mcp` skill from `capawesome-team/skills` to help me set up the Capawesome MCP server in my project. ``` The assistant will pick the [toolsets](#toolsets) you need, walk you through creating an [API token](https://capawesome.io/docs/cloud/accounts/tokens/index.md), write the configuration for your MCP client, and verify that the tools are available. ### Manual Setup If you prefer to set things up manually, add the server to your MCP client and restart it. The examples use `?toolsets=all`, which registers the documentation tools plus — once you add a token — the Capawesome Cloud tools. Replace `YOUR_TOKEN` with your API token, or drop the `Authorization` header for documentation tools only. Run the following command in your project: ``` claude mcp add --transport http capawesome "https://mcp.capawesome.io/mcp?toolsets=all" \ --header "Authorization: Bearer YOUR_TOKEN" ``` Add `--scope user` to make the server available in every project instead of just the current one. Go to **Settings → Connectors → Add custom connector** and enter: - **Name**: `Capawesome` - **URL**: `https://mcp.capawesome.io/mcp` The same connector works on [claude.ai](https://claude.ai). It cannot send an `Authorization` header, so it gives you the documentation tools only. To reach the Capawesome Cloud tools as well, start the server through [`mcp-remote`](https://www.npmjs.com/package/mcp-remote) instead. Add the following to your `claude_desktop_config.json` (open it via **Settings → Developer → Edit Config**): ``` { "mcpServers": { "capawesome": { "command": "npx", "args": [ "-y", "mcp-remote", "https://mcp.capawesome.io/mcp?toolsets=all", "--header", "Authorization:${AUTH_HEADER}" ], "env": { "AUTH_HEADER": "Bearer YOUR_TOKEN" } } } } ``` The header value comes from `env` because a header with spaces isn't passed through reliably on the command line. The badge installs the server without a token. To add one, or to set things up by hand, add the following to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` to use the server everywhere: ``` { "mcpServers": { "capawesome": { "url": "https://mcp.capawesome.io/mcp?toolsets=all", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } } ``` [Install in VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=capawesome&config=%7B%22name%22%3A%22capawesome%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fmcp.capawesome.io%2Fmcp%3Ftoolsets%3Dall%22%7D) The link installs the server without a token. To add one, or to set things up by hand, add the following to `.vscode/mcp.json` in your project: ``` { "servers": { "capawesome": { "type": "http", "url": "https://mcp.capawesome.io/mcp?toolsets=all", "headers": { "Authorization": "Bearer ${input:capawesome-token}" } } }, "inputs": [ { "id": "capawesome-token", "type": "promptString", "description": "Capawesome Cloud API token", "password": true } ] } ``` VS Code prompts for the token the first time the server starts and stores it securely, so the file stays safe to commit. Add the server to `~/.codeium/windsurf/mcp_config.json`: ``` { "mcpServers": { "capawesome": { "serverUrl": "https://mcp.capawesome.io/mcp?toolsets=all", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } } ``` Add the server to your Zed `settings.json`: ``` { "context_servers": { "capawesome": { "url": "https://mcp.capawesome.io/mcp?toolsets=all", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } } ``` Clients that cannot connect to a remote server over HTTP can run the [`@capawesome/mcp`](https://www.npmjs.com/package/@capawesome/mcp) package, which proxies stdio to the hosted server: ``` { "mcpServers": { "capawesome": { "command": "npx", "args": ["-y", "@capawesome/mcp"], "env": { "CAPAWESOME_TOKEN": "YOUR_TOKEN", "CAPAWESOME_MCP_TOOLSETS": "all" } } } } ``` Requires Node.js 22 or later. Omit `env` to use the documentation tools only. The proxy is configured with these environment variables instead of a header and query parameters: | Variable | Purpose | | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `CAPAWESOME_TOKEN` | API token, sent as `Authorization: Bearer`. Optional; without it only the documentation tools are available. | | `CAPAWESOME_MCP_TOOLSETS` | Comma-separated toolsets to register, sent as `?toolsets=`. Use `cloud` for every Capawesome Cloud toolset or `all` for everything. Defaults to `docs`. | | `CAPAWESOME_MCP_READONLY` | Set to `true` to skip every tool that writes or deletes, sent as `?readonly=true`. | | `CAPAWESOME_MCP_URL` | Override the endpoint. For development only. | Once connected, ask your assistant something like *"Which channels does my app have?"* or *"Why did my last iOS build fail?"* to check that the tools are available. ## Toolsets By default the server registers just the public `docs` toolset — documentation and blog search, no token needed. To add [Capawesome Cloud](https://capawesome.io/docs/cloud/index.md) management, use `?toolsets=all`, which registers the documentation tools alongside every Cloud toolset: ``` https://mcp.capawesome.io/mcp?toolsets=all ``` That's what most setups want — documentation search plus the full Cloud surface. If you don't need documentation search, `?toolsets=cloud` registers the Cloud toolsets on their own. To register a smaller, sharper set — every tool takes up context in your assistant, and fewer tools makes it pick the right one more often — name the specific toolsets you need, keeping `docs` for documentation search: ``` https://mcp.capawesome.io/mcp?toolsets=docs,cloud-apps,cloud-app-builds,cloud-app-deployments ``` Put the URL in quotes when you pass query parameters on the command line, otherwise your shell interprets them. Add `?readonly=true` to register read-only tools only. Nothing can then be created, changed, or deleted — a good fit for a shared or unattended setup. ### Documentation Registered by default. Needs no token. | Toolset | Tools | Description | | ------- | ----- | ---------------------------------- | | `docs` | 3 | Search the documentation and blog. | ### Cloud Opt-in with `?toolsets=cloud` (all of them) or by name. Each requires an [API token](https://capawesome.io/docs/cloud/accounts/tokens/index.md). A toolset named `cloud-app-*` acts on a single app, so its tools all take an app ID. | Toolset | Tools | Description | | -------------------------- | ----- | ------------------------------------------------------------------------------------------------------------------------------ | | `cloud-app-automations` | 5 | Manage [automations](https://capawesome.io/docs/cloud/automations/index.md) that build on Git events. | | `cloud-app-builds` | 5 | Trigger [app builds](https://capawesome.io/docs/cloud/native-builds/index.md) and share them. | | `cloud-app-certificates` | 5 | Manage [signing certificates](https://capawesome.io/docs/cloud/native-builds/certificates/index.md) and provisioning profiles. | | `cloud-app-channels` | 5 | Manage [live update channels](https://capawesome.io/docs/cloud/live-updates/channels/index.md). | | `cloud-app-configurations` | 5 | Manage [native configurations](https://capawesome.io/docs/cloud/native-builds/native-configurations/index.md). | | `cloud-app-deployments` | 4 | Deploy builds to channels and app stores, and roll back. | | `cloud-app-destinations` | 5 | Manage [app store destinations](https://capawesome.io/docs/cloud/app-store-publishing/index.md). | | `cloud-app-devices` | 5 | Inspect devices and control which update they receive. | | `cloud-app-environments` | 5 | Manage build [environments](https://capawesome.io/docs/cloud/native-builds/environments/index.md), variables, and secrets. | | `cloud-app-repository` | 3 | Link an app to a Git repository, and see which one it uses. | | `cloud-apps` | 4 | Create, list, and update apps. | | `cloud-git` | 6 | Manage the Git connections of an organization and browse their repositories. | | `cloud-jobs` | 5 | Track, cancel, and diagnose builds and deployments, and read their logs. | | `cloud-license-keys` | 6 | Manage [license keys](https://capawesome.io/docs/cloud/license-keys/index.md) for Insiders and Enterprise SDKs. | | `cloud-organizations` | 10 | Identify yourself, list your organizations (and the IDs other tools need), and manage members and invitations. | | `cloud-teams` | 5 | Manage teams and their apps and members. | ## Tool reference All 86 tools, grouped by toolset. Each one carries MCP annotations that mark it as read-only or destructive, which is what clients use to decide when to ask you for confirmation before running a tool. ### docs | Tool | Description | | ----------------- | ------------------------------------------------------- | | `search_docs` | Search the Capawesome documentation and blog. | | `get_doc_page` | Fetch a documentation or blog page as Markdown. | | `list_blog_posts` | List the 20 most recent posts from the Capawesome blog. | ### cloud-app-automations | Tool | Description | | ----------------------------- | ------------------------------- | | `cloud_list_app_automations` | List the automations of an app. | | `cloud_get_app_automation` | Get a single automation. | | `cloud_create_app_automation` | Create an automation. | | `cloud_update_app_automation` | Update an automation. | | `cloud_delete_app_automation` | Delete an automation. | ### cloud-app-builds | Tool | Description | | ------------------------------ | ------------------------------------------------------------ | | `cloud_list_app_builds` | List builds, filtered by platform or build number. | | `cloud_get_app_build` | Get a build with its job status and artifact download links. | | `cloud_create_app_build` | Start a build from a Git ref or a ZIP URL. | | `cloud_create_app_build_share` | Create a share link with QR code for a build. | | `cloud_delete_app_build_share` | Revoke a share link immediately. | ### cloud-app-certificates | Tool | Description | | ------------------------------ | ---------------------------------------------------------------- | | `cloud_list_app_certificates` | List the signing certificates of an app, including their expiry. | | `cloud_get_app_certificate` | Get a single certificate. | | `cloud_create_app_certificate` | Upload a signing certificate and optional provisioning profiles. | | `cloud_update_app_certificate` | Update the name, passwords, or type of a certificate. | | `cloud_delete_app_certificate` | Delete a certificate. | ### cloud-app-channels | Tool | Description | | -------------------------- | ----------------------------------- | | `cloud_list_app_channels` | List the channels of an app. | | `cloud_get_app_channel` | Get a single channel. | | `cloud_create_app_channel` | Create a channel. | | `cloud_update_app_channel` | Rename, pause, or resume a channel. | | `cloud_delete_app_channel` | Delete a channel. | ### cloud-app-configurations | Tool | Description | | -------------------------------- | ------------------------------------------------------------------------- | | `cloud_list_app_configurations` | List the native configurations of an app. | | `cloud_get_app_configuration` | Get a single native configuration. | | `cloud_create_app_configuration` | Create a native configuration. | | `cloud_update_app_configuration` | Update the display name, name, or package name of a native configuration. | | `cloud_delete_app_configuration` | Delete a native configuration. | ### cloud-app-deployments | Tool | Description | | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `cloud_list_app_deployments` | List deployments, filtered by build, channel, or destination. | | `cloud_get_app_deployment` | Get a deployment with its job status. | | `cloud_create_app_deployment` | Deploy a build to a channel or an app store destination, optionally with release notes. Deploying an earlier build is how you roll back. | | `cloud_update_app_deployment` | Adjust the rollout percentage of a deployment. | ### cloud-app-destinations | Tool | Description | | ------------------------------ | ------------------------------------------------------------------------------------------------------- | | `cloud_list_app_destinations` | List the app store destinations of an app. | | `cloud_get_app_destination` | Get a single destination. | | `cloud_create_app_destination` | Connect an App Store Connect, Google Play, Huawei AppGallery, or Firebase App Distribution destination. | | `cloud_update_app_destination` | Update a destination, such as its Google Play track, TestFlight beta groups, or App Review submission. | | `cloud_delete_app_destination` | Delete a destination. | ### cloud-app-devices | Tool | Description | | ------------------------- | --------------------------------------------------------- | | `cloud_list_app_devices` | List the devices registered for an app. | | `cloud_get_app_device` | Get a single device. | | `cloud_probe_app_device` | Check which live update a device would receive right now. | | `cloud_update_app_device` | Assign or clear the channel a device is pinned to. | | `cloud_delete_app_device` | Delete a device. It re-registers on its next sync. | ### cloud-app-environments | Tool | Description | | ------------------------------ | ----------------------------------------------------------------- | | `cloud_list_app_environments` | List the environments of an app. | | `cloud_get_app_environment` | Get a single environment. | | `cloud_create_app_environment` | Create an environment. | | `cloud_update_app_environment` | Rename an environment and set or unset its variables and secrets. | | `cloud_delete_app_environment` | Delete an environment. | ### cloud-app-repository | Tool | Description | | ----------------------------- | ------------------------------------------ | | `cloud_get_app_repository` | Show which repository an app is linked to. | | `cloud_set_app_repository` | Link an app to a repository. | | `cloud_delete_app_repository` | Unlink an app from its repository. | ### cloud-apps | Tool | Description | | ------------------ | --------------------------------- | | `cloud_create_app` | Create an app in an organization. | | `cloud_list_apps` | List the apps of an organization. | | `cloud_get_app` | Get a single app. | | `cloud_update_app` | Update an app. | ### cloud-git | Tool | Description | | ----------------------------- | ------------------------------------------------------- | | `cloud_list_git_connections` | List the Git connections of an organization. | | `cloud_get_git_connection` | Get a single Git connection. | | `cloud_create_git_connection` | Create a token or HTTP(S) Git connection. | | `cloud_update_git_connection` | Update a Git connection. | | `cloud_delete_git_connection` | Delete a Git connection. | | `cloud_list_git_repositories` | Browse the repositories available through a connection. | ### cloud-jobs | Tool | Description | | -------------------- | -------------------------------------------------------------------------- | | `cloud_list_jobs` | List the jobs of an organization, filtered by status. | | `cloud_get_job` | Get a single job. | | `cloud_get_job_logs` | Read the last lines of a build or deployment log, whatever the job status. | | `cloud_cancel_job` | Cancel a running build or deployment. | | `cloud_diagnose_job` | Fetch the AI failure summary of a failed job. | ### cloud-license-keys | Tool | Description | | -------------------------- | ------------------------------------------------------------ | | `cloud_list_license_keys` | List the license keys of an organization. | | `cloud_get_license_key` | Get a single license key. | | `cloud_create_license_key` | Create a license key and assign packages to it. | | `cloud_update_license_key` | Update a license key. | | `cloud_rotate_license_key` | Rotate a license key. The old key stops working immediately. | | `cloud_delete_license_key` | Delete a license key. | ### cloud-organizations | Tool | Description | | --------------------------- | ----------------------------------------------------- | | `cloud_get_current_user` | Show which account the token belongs to. | | `cloud_list_organizations` | List the organizations you have access to. | | `cloud_get_organization` | Get a single organization. | | `cloud_create_organization` | Create an organization, optionally with a trial code. | | `cloud_update_organization` | Update an organization. | | `cloud_list_members` | List the members of an organization. | | `cloud_delete_member` | Remove a member from an organization. | | `cloud_list_invitations` | List the pending invitations of an organization. | | `cloud_create_invitation` | Invite someone to an organization with a given role. | | `cloud_delete_invitation` | Revoke a pending invitation. | ### cloud-teams | Tool | Description | | ------------------- | ------------------------------------------------- | | `cloud_list_teams` | List the teams of an organization. | | `cloud_get_team` | Get a team with its apps and members. | | `cloud_create_team` | Create a team. | | `cloud_update_team` | Rename a team and add or remove apps and members. | | `cloud_delete_team` | Delete a team. | ## Working with secrets A few tools accept sensitive values — environment secrets, signing certificates, and app store credentials. Anything you pass to a tool becomes part of the conversation and is sent to your AI provider, and it may end up in chat history or logs you don't control. Use the CLI for production secrets Set production secrets, upload signing certificates, and connect store destinations with the [CLI](https://capawesome.io/docs/cloud/cli/index.md) or the [Console](https://console.cloud.capawesome.io) instead. Keep the MCP server for reading and for values you'd be fine seeing in a transcript. ## Rate limits The endpoint is limited to **100 requests per minute per IP**. Requests over the limit are answered with `429 Too Many Requests`; retry after a short wait. ## Privacy Your IP address is processed for rate limiting only. Queries and tool arguments are not stored, not logged beyond Cloudflare's standard edge logs, and never used for training. Your API token is used to authenticate against the Capawesome Cloud API on your behalf and is not stored by the server. See our [privacy policy](https://capawesome.io/legal/privacy-policy/) for details. ## Related servers - [Capacitor MCP Server](https://capawesome.io/docs/ai/mcp/capacitor/index.md) — the official Capacitor documentation, the official and community plugin list, and the Capacitor posts from the Ionic blog. - [Ionic Framework MCP Server](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md) — the official Ionic Framework documentation, its component API reference, and the usage examples for Angular, React, Vue and vanilla JavaScript. ## Next steps - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. - [Skills](https://capawesome.io/docs/ai/skills/index.md) — on-demand agent skills that automate migrations and Cloud setup. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent. # Capacitor MCP Server The Capacitor MCP server gives your AI assistant the current [Capacitor](https://capacitorjs.com) documentation for v8, v7, v6 and `next`, the official and community plugin list, and the Capacitor posts from the Ionic blog — so it stops guessing plugin APIs and CLI flags. It implements the [Model Context Protocol](https://modelcontextprotocol.io), so it works with Claude Code, Claude Desktop, Cursor, VS Code, and any other MCP client. Unofficial server This server is maintained by Capawesome and is not affiliated with or endorsed by Ionic or OutSystems. It serves the official Capacitor documentation, © the Ionic team and licensed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). ## Endpoint The server is hosted by us and ready to use. No account, no token, no installation: ``` https://capacitor-mcp.capawesome.io/mcp ``` ## Setup ### AI-Assisted Setup (Recommended) The fastest way to connect the server is with 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 capacitor-mcp ``` Then use the following prompt in your preferred AI tool (e.g. [Claude Code](https://www.anthropic.com/claude-code), [Cursor](https://www.cursor.com/), or [GitHub Copilot](https://github.com/features/copilot)): ``` Use the `capacitor-mcp` skill from `capawesome-team/skills` to help me set up the Capacitor MCP server in my project. ``` The assistant will detect your MCP client, write the configuration, verify that the tools are available, and pass the [documentation version](#versions) that matches the `@capacitor/core` version in your project. ### Manual Setup If you prefer to set things up manually, add the server to your MCP client and restart it. Run the following command in your project: ``` claude mcp add --transport http capacitor https://capacitor-mcp.capawesome.io/mcp ``` Add `--scope user` to make the server available in every project instead of just the current one. Go to **Settings → Connectors → Add custom connector** and enter: - **Name**: `Capacitor` - **URL**: `https://capacitor-mcp.capawesome.io/mcp` The same connector works on [claude.ai](https://claude.ai). Or add the following to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` to use the server everywhere: ``` { "mcpServers": { "capacitor": { "url": "https://capacitor-mcp.capawesome.io/mcp" } } } ``` [Install in VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=capacitor&config=%7B%22name%22%3A%22capacitor%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fcapacitor-mcp.capawesome.io%2Fmcp%22%7D) Or add the following to `.vscode/mcp.json` in your project: ``` { "servers": { "capacitor": { "type": "http", "url": "https://capacitor-mcp.capawesome.io/mcp" } } } ``` Add the server to `~/.codeium/windsurf/mcp_config.json`: ``` { "mcpServers": { "capacitor": { "serverUrl": "https://capacitor-mcp.capawesome.io/mcp" } } } ``` Add the server to your Zed `settings.json`: ``` { "context_servers": { "capacitor": { "url": "https://capacitor-mcp.capawesome.io/mcp" } } } ``` Clients that cannot connect to a remote server over HTTP can run the [`@capawesome/capacitor-mcp`](https://www.npmjs.com/package/@capawesome/capacitor-mcp) package, which proxies stdio to the hosted server: ``` { "mcpServers": { "capacitor": { "command": "npx", "args": ["-y", "@capawesome/capacitor-mcp"] } } } ``` Requires Node.js 22 or later. Once connected, ask your assistant something like *"How do I configure deep links in Capacitor 8?"* or *"Which Capacitor plugin should I use to pick a file?"* to check that the tools are available. ## Tools | Tool | Description | Parameters | | ----------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------- | | `search_docs` | Search the documentation by keyword. The starting point for any Capacitor question. | `query` (required), `section`, `version`, `limit` | | `get_doc_page` | Fetch a full documentation page returned by `search_docs` as Markdown. | `url` (required), `version` | | `list_plugins` | List official and community Capacitor plugins. | `query`, `source`, `limit` | | `list_blog_posts` | List the 10 most recent Capacitor-tagged posts from the Ionic blog. | — | `list_plugins` takes a `source` of `official`, `community` or `all`. Official plugins come from the Capacitor documentation, community plugins from [awesome-capacitorjs](https://github.com/capawesome-team/awesome-capacitorjs). Results are ranked official plugins first, then Capawesome plugins, then the remaining community plugins. The server discloses that ranking in its response, so your assistant can weigh it. ## Versions The documentation is available for Capacitor **v8** (default), **v7**, **v6** and **next**. Pass the `version` parameter to `search_docs` and `get_doc_page` to target a specific version. Set it to the major version of `@capacitor/core` in your project. On Capacitor 7, `version: "v7"` keeps the answers on the APIs and CLI flags that release actually has. ## Documentation source The index is rebuilt daily from [`ionic-team/capacitor-docs`](https://github.com/ionic-team/capacitor-docs) at a pinned commit. The documentation content is © the Ionic team, licensed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), converted from MDX to Markdown for delivery over MCP and otherwise served unmodified in substance. Blog excerpts come from the [Ionic blog](https://ionic.io/blog) and link to the original post. The client package is open source at [`capawesome-team/capacitor-mcp`](https://github.com/capawesome-team/capacitor-mcp). ## Rate limits The endpoint is limited to **100 requests per minute per IP**. Requests over the limit are answered with `429 Too Many Requests`; retry after a short wait. ## Privacy Your IP address is processed for rate limiting only. Queries and tool arguments are not stored, not logged beyond Cloudflare's standard edge logs, and never used for training. See our [privacy policy](https://capawesome.io/legal/privacy-policy/) for details. ## Related servers - [Ionic Framework MCP Server](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md) — the same for the Ionic Framework documentation, its component API reference, and the usage examples for Angular, React, Vue and vanilla JavaScript. - [Capawesome MCP Server](https://capawesome.io/docs/ai/mcp/capawesome/index.md) — the Capawesome documentation and the Capawesome Cloud management API for Live Updates, Native Builds, and App Store Publishing. ## Next steps - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. - [Skills](https://capawesome.io/docs/ai/skills/index.md) — on-demand agent skills that automate migrations and Cloud setup. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent. # Ionic Framework MCP Server The Ionic Framework MCP server gives your AI assistant the current [Ionic Framework](https://ionicframework.com) documentation for v9 and v8, including the component API reference and the usage examples for Angular, React, Vue and vanilla JavaScript, plus the Ionic blog. It implements the [Model Context Protocol](https://modelcontextprotocol.io), so it works with Claude Code, Claude Desktop, Cursor, VS Code, and any other MCP client. For the story behind the server and what it changes in practice, read [Ionic Framework MCP Server for Your AI Assistant](https://capawesome.io/blog/ionic-framework-mcp-server/index.md). Unofficial server This server is maintained by Capawesome and is not affiliated with or endorsed by Ionic or OutSystems. It serves the official Ionic Framework documentation, © the Ionic team and licensed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0). ## Endpoint The server is hosted by us and ready to use. No account, no token, no installation: ``` https://ionic-framework-mcp.capawesome.io/mcp ``` ## Setup ### AI-Assisted Setup (Recommended) The fastest way to connect the server is with 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 ionic-framework-mcp ``` Then use the following prompt in your preferred AI tool (e.g. [Claude Code](https://www.anthropic.com/claude-code), [Cursor](https://www.cursor.com/), or [GitHub Copilot](https://github.com/features/copilot)): ``` Use the `ionic-framework-mcp` skill from `capawesome-team/skills` to help me set up the Ionic Framework MCP server in my project. ``` The assistant will detect your MCP client, write the configuration, verify that the tools are available, and pass the [documentation version](#versions) and the framework that match your project. ### Manual Setup If you prefer to set things up manually, add the server to your MCP client and restart it. Run the following command in your project: ``` claude mcp add --transport http ionic-framework https://ionic-framework-mcp.capawesome.io/mcp ``` Add `--scope user` to make the server available in every project instead of just the current one. Go to **Settings → Connectors → Add custom connector** and enter: - **Name**: `Ionic Framework` - **URL**: `https://ionic-framework-mcp.capawesome.io/mcp` The same connector works on [claude.ai](https://claude.ai). Or add the following to `.cursor/mcp.json` in your project, or to `~/.cursor/mcp.json` to use the server everywhere: ``` { "mcpServers": { "ionic-framework": { "url": "https://ionic-framework-mcp.capawesome.io/mcp" } } } ``` [Install in VS Code](https://insiders.vscode.dev/redirect/mcp/install?name=ionic-framework&config=%7B%22name%22%3A%22ionic-framework%22%2C%22type%22%3A%22http%22%2C%22url%22%3A%22https%3A%2F%2Fionic-framework-mcp.capawesome.io%2Fmcp%22%7D) Or add the following to `.vscode/mcp.json` in your project: ``` { "servers": { "ionic-framework": { "type": "http", "url": "https://ionic-framework-mcp.capawesome.io/mcp" } } } ``` Add the server to `~/.codeium/windsurf/mcp_config.json`: ``` { "mcpServers": { "ionic-framework": { "serverUrl": "https://ionic-framework-mcp.capawesome.io/mcp" } } } ``` Add the server to your Zed `settings.json`: ``` { "context_servers": { "ionic-framework": { "url": "https://ionic-framework-mcp.capawesome.io/mcp" } } } ``` Clients that cannot connect to a remote server over HTTP can run the [`@capawesome/ionic-framework-mcp`](https://www.npmjs.com/package/@capawesome/ionic-framework-mcp) package, which proxies stdio to the hosted server: ``` { "mcpServers": { "ionic-framework": { "command": "npx", "args": ["-y", "@capawesome/ionic-framework-mcp"] } } } ``` Requires Node.js 22 or later. Once connected, ask your assistant something like *"Which events does `ion-modal` emit?"* or *"Show me an inline `ion-datetime` in React"* to check that the tools are available. ## Tools | Tool | Description | Parameters | | --------------------- | ----------------------------------------------------------------------------------------- | --------------------------------------------------------- | | `search_docs` | Search the documentation by keyword. The starting point for any Ionic Framework question. | `query` (required), `section`, `version`, `limit` | | `get_doc_page` | Fetch a full documentation page returned by `search_docs` as Markdown. | `url` (required), `version` | | `list_components` | List Ionic Framework components with their API reference. | `query`, `version`, `limit` | | `get_component_usage` | Read the official usage examples for a component, per framework. | `component` (required), `example`, `framework`, `version` | | `list_blog_posts` | List recent posts from the Ionic blog. | — | A component page returned by `get_doc_page` carries the full API reference: properties, events, methods, CSS shadow parts, CSS custom properties, and slots. That is what an assistant needs to style a component correctly instead of inventing a class name. `get_component_usage` returns the official examples from the documentation. Pass `framework` as `angular`, `react`, `vue` or `javascript` to get the variant that matches your project. ## Versions The documentation is available for Ionic Framework **v9** (default) and **v8**. Pass the `version` parameter to target a specific version. Set it to the major version of `@ionic/core`, `@ionic/angular`, `@ionic/react` or `@ionic/vue` in your project, so the properties and events your assistant suggests exist in the release you ship. ## Documentation source The index is rebuilt daily from [`ionic-team/ionic-docs`](https://github.com/ionic-team/ionic-docs) at a pinned commit. The documentation content is © the Ionic team, licensed under [Apache-2.0](https://www.apache.org/licenses/LICENSE-2.0), converted from MDX to Markdown for delivery over MCP and otherwise served unmodified in substance. Blog excerpts come from the [Ionic blog](https://ionic.io/blog) and link to the original post. The client package is open source at [`capawesome-team/ionic-framework-mcp`](https://github.com/capawesome-team/ionic-framework-mcp). ## Rate limits The endpoint is limited to **100 requests per minute per IP**. Requests over the limit are answered with `429 Too Many Requests`; retry after a short wait. ## Privacy Your IP address is processed for rate limiting only. Queries and tool arguments are not stored, not logged beyond Cloudflare's standard edge logs, and never used for training. See our [privacy policy](https://capawesome.io/legal/privacy-policy/) for details. ## Related servers - [Capacitor MCP Server](https://capawesome.io/docs/ai/mcp/capacitor/index.md) — the same for the Capacitor documentation, the official and community plugin list, and the Capacitor posts from the Ionic blog. - [Capawesome MCP Server](https://capawesome.io/docs/ai/mcp/capawesome/index.md) — the Capawesome documentation and the Capawesome Cloud management API for Live Updates, Native Builds, and App Store Publishing. ## Next steps - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. - [Skills](https://capawesome.io/docs/ai/skills/index.md) — on-demand agent skills that automate migrations and Cloud setup. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent. # AI # AI Capawesome is built to work well with AI agents and large language models. Whether you're using an AI coding assistant in your editor or a script that calls an LLM, this section collects the resources that help those tools understand Capawesome and carry out real tasks — installing plugins, migrating versions, and setting up Capawesome Cloud. There are two sides to AI at Capawesome: - **Bring your own AI** — feed our machine-readable docs, agent skills, and editor rules to whatever assistant you already use, or connect it to our [MCP servers](https://capawesome.io/docs/ai/mcp/index.md) to let it read the current docs and manage Capawesome Cloud directly. That's what the pages below cover. - **AI inside the product** — [Cloud Assist](https://capawesome.io/docs/cloud/assist/index.md) brings AI directly into the Console, including [Ask AI](https://capawesome.io/docs/cloud/assist/ask-ai/index.md) for diagnosing failed builds and deployments. ## Resources - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. - [MCP Servers](https://capawesome.io/docs/ai/mcp/index.md) — connect Claude, Cursor, or any MCP client to the [Capawesome](https://capawesome.io/docs/ai/mcp/capawesome/index.md) docs and your Cloud apps, and to the [Capacitor](https://capawesome.io/docs/ai/mcp/capacitor/index.md) and [Ionic Framework](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md) documentation. - [Skills](https://capawesome.io/docs/ai/skills/index.md) — on-demand agent skills that automate complex tasks like migrations and Cloud setup. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent with Capawesome conventions. - [Guides](https://capawesome.io/docs/ai/guides/index.md) — practical walkthroughs for AI-assisted development. # Agent Skills Agent skills are folders of instructions that an AI coding agent loads on demand, whenever the task at hand matches the skill's description. They follow the [Agent Skills specification](https://agentskills.io/specification), so the same folder works in Claude Code, Cursor, GitHub Copilot, Codex, and every other agent that implements it. [Capawesome Skills](https://github.com/capawesome-team/skills) is an open source collection of 30 such skills for Capacitor, the Ionic Framework, and Capawesome Cloud. Each one spells out the exact file paths, diffs, and commands a task needs, so the agent follows a documented procedure instead of guessing. ## Install Add the plugin marketplace, then install the plugins you need: ``` claude plugin marketplace add capawesome-team/skills claude plugin install capacitor-core@capawesome-skills ``` | Plugin | Skills | | ------------------------ | ------------------------------------------------------------------------------------------------------------- | | `capacitor-core` | App creation, development, upgrades, expert reference, plugin setup, desktop platforms, and MCP server setup. | | `capacitor-frameworks` | Angular, React, and Vue patterns for Capacitor. | | `capacitor-integrations` | Push notifications (FCM) and in-app purchases. | | `capacitor-plugin-dev` | Create, upgrade, and add SPM support to Capacitor plugins. | | `capawesome-cloud` | MCP server, CLI setup, native builds, live updates, and app store publishing. | | `ionic-core` | App creation, development, upgrades, expert reference, and MCP server setup. | | `ionic-frameworks` | Angular, React, and Vue patterns for Ionic. | | `ionic-migrations` | Migrations from Ionic Appflow and the Ionic Enterprise SDK. | The plugins cover every skill except `capgo-cloud-migration`. Install that one with the `skills` CLI. ``` npx skills add capawesome-team/skills --agent cursor ``` The [`skills` CLI](https://skills.sh) installs into any supported agent and asks which ones to use: ``` npx skills add capawesome-team/skills ``` Pass `--agent` to skip that prompt — for example `--agent claude-code`, `--agent codex`, `--agent github-copilot`, or `--agent windsurf`. Add `--global` to install the skills for every project instead of the current one. To add a single skill instead of all of them, name it with `--skill`: ``` npx skills add capawesome-team/skills --skill capawesome-cloud ``` The skills are updated as Capacitor, Ionic, and Capawesome Cloud change. Pull the latest version with: ``` npx skills update ``` ## Available skills ### Capacitor | Skill | Description | Install command | | ------------------------------ | ---------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------- | | `capacitor-angular` | Angular patterns and best practices for Capacitor apps. | `npx skills add capawesome-team/skills --skill capacitor-angular` | | `capacitor-app-creation` | Create a new Capacitor app with platform setup. | `npx skills add capawesome-team/skills --skill capacitor-app-creation` | | `capacitor-app-development` | Core concepts, CLI usage, app configuration, and troubleshooting. | `npx skills add capawesome-team/skills --skill capacitor-app-development` | | `capacitor-app-spm-migration` | Migrate an app's iOS dependencies from CocoaPods to SPM. | `npx skills add capawesome-team/skills --skill capacitor-app-spm-migration` | | `capacitor-app-upgrades` | Upgrade a Capacitor app to a newer major version. | `npx skills add capawesome-team/skills --skill capacitor-app-upgrades` | | `capacitor-expert` | Comprehensive Capacitor reference and starting point. | `npx skills add capawesome-team/skills --skill capacitor-expert` | | `capacitor-in-app-purchases` | Set up in-app purchases and subscriptions. | `npx skills add capawesome-team/skills --skill capacitor-in-app-purchases` | | `capacitor-mcp` | Connect an MCP client to the [Capacitor MCP server](https://capawesome.io/docs/ai/mcp/capacitor/index.md). | `npx skills add capawesome-team/skills --skill capacitor-mcp` | | `capacitor-platforms` | Add the Electron and Tauri desktop platforms. | `npx skills add capawesome-team/skills --skill capacitor-platforms` | | `capacitor-plugin-development` | Create and maintain Capacitor plugins from scratch. | `npx skills add capawesome-team/skills --skill capacitor-plugin-development` | | `capacitor-plugin-spm-support` | Add Swift Package Manager support to a plugin. | `npx skills add capawesome-team/skills --skill capacitor-plugin-spm-support` | | `capacitor-plugin-upgrades` | Upgrade a Capacitor plugin to a newer major version. | `npx skills add capawesome-team/skills --skill capacitor-plugin-upgrades` | | `capacitor-plugins` | Install and configure plugins from official and community sources. | `npx skills add capawesome-team/skills --skill capacitor-plugins` | | `capacitor-push-notifications` | Set up push notifications with Firebase Cloud Messaging. | `npx skills add capawesome-team/skills --skill capacitor-push-notifications` | | `capacitor-react` | React patterns and best practices for Capacitor apps. | `npx skills add capawesome-team/skills --skill capacitor-react` | | `capacitor-vue` | Vue patterns and best practices for Capacitor apps. | `npx skills add capawesome-team/skills --skill capacitor-vue` | ### Ionic Framework | Skill | Description | Install command | | ----------------------- | ---------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | `ionic-angular` | Angular patterns for Ionic — navigation, lifecycle, and forms. | `npx skills add capawesome-team/skills --skill ionic-angular` | | `ionic-app-creation` | Create a new Ionic app with Capacitor integration. | `npx skills add capawesome-team/skills --skill ionic-app-creation` | | `ionic-app-development` | Core concepts, component reference, layout, and theming. | `npx skills add capawesome-team/skills --skill ionic-app-development` | | `ionic-app-upgrades` | Upgrade an Ionic app to a newer major version. | `npx skills add capawesome-team/skills --skill ionic-app-upgrades` | | `ionic-expert` | Comprehensive Ionic Framework reference and starting point. | `npx skills add capawesome-team/skills --skill ionic-expert` | | `ionic-framework-mcp` | Connect an MCP client to the [Ionic Framework MCP server](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md). | `npx skills add capawesome-team/skills --skill ionic-framework-mcp` | | `ionic-react` | React patterns for Ionic — components, routing, and lifecycle hooks. | `npx skills add capawesome-team/skills --skill ionic-react` | | `ionic-vue` | Vue patterns for Ionic — components, navigation, and composables. | `npx skills add capawesome-team/skills --skill ionic-vue` | ### Capawesome | Skill | Description | Install command | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | | `capawesome-cli` | Install, authenticate, and use the [Capawesome CLI](https://capawesome.io/docs/cloud/cli/index.md). | `npx skills add capawesome-team/skills --skill capawesome-cli` | | `capawesome-cloud` | Set up [native builds](https://capawesome.io/docs/cloud/native-builds/index.md), [live updates](https://capawesome.io/docs/cloud/live-updates/index.md), and [app store publishing](https://capawesome.io/docs/cloud/app-store-publishing/index.md). | `npx skills add capawesome-team/skills --skill capawesome-cloud` | | `capawesome-mcp` | Connect an MCP client to the [Capawesome MCP server](https://capawesome.io/docs/ai/mcp/capawesome/index.md). | `npx skills add capawesome-team/skills --skill capawesome-mcp` | ### Migrations | Skill | Description | Install command | | -------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------ | | `capgo-cloud-migration` | Migrate from Capgo to Capawesome Cloud. | `npx skills add capawesome-team/skills --skill capgo-cloud-migration` | | `ionic-appflow-migration` | Migrate from Ionic Appflow to Capawesome Cloud. | `npx skills add capawesome-team/skills --skill ionic-appflow-migration` | | `ionic-enterprise-sdk-migration` | Migrate from Ionic Enterprise SDK plugins to Capawesome alternatives. | `npx skills add capawesome-team/skills --skill ionic-enterprise-sdk-migration` | ## Skills or the MCP servers The skills and the [MCP servers](https://capawesome.io/docs/ai/mcp/index.md) cover different ground, so most projects use both. The skills detect a connected server and prefer it for documentation, falling back to their bundled reference files when none is connected. | | MCP servers | Skills | | ----------------------------------------------------------- | -------------------------------------------- | --------------- | | Capawesome plugin, CLI, and Cloud documentation | ✅ always current | ✅ bundled | | Capacitor Firebase and Capacitor MLKit plugin documentation | ✅ always current | ✅ bundled | | Official and community Capacitor plugins | ✅ Capacitor MCP server | ✅ | | RevenueCat plugins | ❌ | ✅ | | Capacitor and Ionic upgrade guides | ✅ Capacitor and Ionic Framework MCP servers | ✅ step by step | | Ionic Appflow, Ionic Enterprise SDK, and Capgo migrations | ❌ | ✅ | | Managing Capawesome Cloud apps, builds, and deployments | ✅ | via the CLI | | Works without a network round trip | ❌ | ✅ | ## Example prompts Once the skills are installed, describe the task and the agent picks the matching skill on its own. An upgrade across two major versions: ``` Upgrade my Capacitor app from version 6 to version 8. ``` A plugin setup, including the native configuration for Android and iOS: ``` Install and configure the @capawesome/capacitor-file-picker plugin in my project. ``` A Capawesome Cloud feature end to end: ``` Set up Capacitor Live Updates with Capawesome Cloud in my project. ``` To pin the agent to one skill, name it: *"Use the `capawesome-cloud` skill from `capawesome-team/skills` to set up Live Updates in my project."* ## FAQ ### Which AI agents can use the skills? Any agent that supports the Agent Skills specification. The `skills` CLI installs into more than 70 of them, including Claude Code, Cursor, GitHub Copilot, Codex, Windsurf, and OpenCode. ### Do I need a Capawesome Cloud account? Only for the skills that manage Capawesome Cloud. The Capacitor, Ionic Framework, and plugin skills work without an account. ### Do the skills require an MCP server? No. Every skill ships with its own reference files and works offline. When one of the [MCP servers](https://capawesome.io/docs/ai/mcp/index.md) is connected, the skills use it for documentation so the answers stay current between releases. The `capacitor-mcp`, `ionic-framework-mcp`, and `capawesome-mcp` skills set the servers up for you. ### Are the skills free and open source? Yes. They are published under the MIT license in the [Capawesome Skills repository](https://github.com/capawesome-team/skills), and contributions are welcome. ### How do I keep the skills up to date? Run `npx skills update`. Claude Code plugin installations update through `claude plugin update`. ### Where are the skills installed? The `skills` CLI writes to the agent's skills directory in your project — `.claude/skills/` for Claude Code, `.agents/skills/` for Cursor, Codex, and GitHub Copilot. Pass `--global` to install them to your home directory instead. ## Next steps - [MCP Servers](https://capawesome.io/docs/ai/mcp/index.md) — connect Claude, Cursor, or any MCP client to the Capawesome, Capacitor, and Ionic Framework documentation. - [Editor Rules](https://capawesome.io/docs/ai/rules/index.md) — always-on project rules that keep generated code consistent. - [LLMs](https://capawesome.io/docs/ai/llms/index.md) — `llms.txt` files and Markdown endpoints that make the docs readable by any model. # Editor Rules Editor rules are project files that AI coding assistants read on every interaction — standing conventions that keep generated code consistent. Unlike [skills](https://capawesome.io/docs/ai/skills/index.md) (which an agent runs on demand), rules are always-on context. Most assistants support a project rules file: - **AGENTS.md** — a cross-tool convention read by many agents. - **Cursor** — `.cursor/rules/*.mdc`. - **Windsurf** — `.windsurfrules`. - **Claude Code** — `CLAUDE.md`. ## Starter rules Add a file like the following (e.g. `AGENTS.md`) to your project root and adapt it: ``` # Project rules - This is a Capacitor app. Prefer Capacitor APIs and Capawesome plugins. - Use the Capawesome CLI (`@capawesome/cli`) for Capawesome Cloud tasks. - For OTA updates, use `@capawesome/capacitor-live-update` and publish via `apps:liveupdates:upload`. Keep web bundles binary-compatible with the installed native version. - Never commit secrets, signing keys, or API tokens. ``` ## Skills vs. rules - **Rules** are standing conventions the agent always reads. - **[Skills](https://capawesome.io/docs/ai/skills/index.md)** are on-demand procedures the agent runs for a specific task. Use both: rules to set the baseline, skills to execute complex tasks like migrations or Cloud setup. # LLMs Capawesome provides documentation resources designed for large language models and AI applications, formatted for the [llms.txt](https://llmstxt.org/) initiative. These files give an AI assistant accurate, up-to-date context about Capawesome's SDKs and Cloud — point your tool at one of the URLs below, or paste its contents in, so answers and generated code reflect the current docs instead of stale training data. ## Documentation Files - **Index File** ______________________________________________________________________ A list of all available documentation files. [/llms.txt](/docs/llms.txt) - **Complete Capawesome Documentation** ______________________________________________________________________ Comprehensive coverage including all Capawesome SDKs, Capawesome Cloud, and development workflows. [/llms-full.txt](/docs/llms-full.txt) ## Markdown endpoints Each documentation page can also be accessed as Markdown by appending `/index.md` to the page URL. For example, `https://capawesome.io/docs/sdks/capacitor/` becomes `https://capawesome.io/docs/sdks/capacitor/index.md`. ## MCP servers For assistants that speak the Model Context Protocol, we host three servers that answer from the current documentation instead of a file you have to keep in sync: | Server | Endpoint | Covers | | ----------------------------------------------------------------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | [Capawesome](https://capawesome.io/docs/ai/mcp/capawesome/index.md) | `https://mcp.capawesome.io/mcp` | Official server for the Capawesome documentation and blog, plus the Capawesome Cloud management API. | | [Capacitor](https://capawesome.io/docs/ai/mcp/capacitor/index.md) | `https://capacitor-mcp.capawesome.io/mcp` | Unofficial server for the official Capacitor documentation, the plugin list, and the Capacitor posts from the Ionic blog. | | [Ionic Framework](https://capawesome.io/docs/ai/mcp/ionic-framework/index.md) | `https://ionic-framework-mcp.capawesome.io/mcp` | Unofficial server for the official Ionic Framework documentation, the component API reference, and the usage examples. | ## Agent skills For automated development workflows, Capawesome also publishes [AI agent skills](https://capawesome.io/docs/ai/skills/index.md) — install them with `npx skills add capawesome-team/skills`. # Guides Practical guides for building with Capawesome using AI agents. ## Set up your AI assistant 1. Add the [Capawesome skills](https://capawesome.io/docs/ai/skills/index.md) to your project: `npx skills add capawesome-team/skills`. 1. Add [editor rules](https://capawesome.io/docs/ai/rules/index.md) so your assistant follows your project's conventions. 1. Make the docs available to your assistant — see [LLMs](https://capawesome.io/docs/ai/llms/index.md) for the `llms.txt` files and Markdown endpoints. ## Common AI-assisted tasks - **Set up Capawesome Cloud** — ask your assistant to use the `capawesome-cloud` skill to set up Live Updates, Native Builds, or App Store Publishing. - **Migrate between major versions** — use the migration skills to upgrade a Capacitor app or plugin. - **Diagnose a failed build** — use [Ask AI](https://capawesome.io/docs/cloud/assist/ask-ai/index.md) in the Console, then hand the diagnosis to your local agent to apply the fix. > More step-by-step guides are on the way. # SDKs # Capacitor Plugins Capacitor plugins let you access native device features and third-party SDKs from your web-based app with a single JavaScript API that works across Android, iOS, and the Web. This page lists our Capacitor plugins — part of a collection of over 100 Capacitor SDKs with more than 5 million monthly downloads. Browse by category below to find the right plugin for your app, and open its page for installation steps, supported platforms, and the full API reference. ## Browse plugins by category ### Authentication & Security - [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/index.md) — Verify app and device integrity using Play Integrity and App Attest. - [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/index.md) — Read tracking status, request permission, and access the IDFA on iOS. - [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/index.md) — Sign in with Apple on Android, iOS, and Web. - [Biometrics](https://capawesome.io/docs/sdks/capacitor/biometrics/index.md) — Face ID, fingerprint, and device-credential authentication. - [Facebook Sign-In](https://capawesome.io/docs/sdks/capacitor/facebook-sign-in/index.md) — Sign in users with their Facebook account, including Limited Login on iOS. - [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/index.md) — Sign in with Google on Android, iOS, and Web. - [Intune](https://capawesome.io/docs/sdks/capacitor/intune/index.md) — Enforce Microsoft Intune app protection policies (MAM) with MSAL authentication. - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/index.md) — OAuth 2.0 and OpenID Connect with PKCE and token refresh. - [Passkeys](https://capawesome.io/docs/sdks/capacitor/passkeys/index.md) — Create and authenticate with passkeys based on the WebAuthn standard. - [Password Autofill](https://capawesome.io/docs/sdks/capacitor/password-autofill/index.md) — Save passwords to the platform credential store after in-app login. - [Permissions](https://capawesome.io/docs/sdks/capacitor/permissions/index.md) — Check and request device permissions through one unified API. - [Privacy Screen](https://capawesome.io/docs/sdks/capacitor/privacy-screen/index.md) — Hide sensitive content in the app switcher and block screenshots. - [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/index.md) — Detect rooted and jailbroken devices, emulators, and developer mode. - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/index.md) — Securely store sensitive data like tokens and passwords. - [Vault](https://capawesome.io/docs/sdks/capacitor/vault/index.md) — Store key/value pairs in lockable, biometric-protected vaults. ### Payments & Monetization - [AdMob](https://capawesome.io/docs/sdks/capacitor/admob/index.md) — Monetize your app with banner, interstitial, rewarded, and app open ads. - [Purchases](https://capawesome.io/docs/sdks/capacitor/purchases/index.md) — Native in-app purchases and subscriptions with StoreKit 2 and Google Play Billing. - [Superwall](https://capawesome.io/docs/sdks/capacitor/superwall/index.md) — Manage paywalls and subscriptions with the Superwall SDK. - [Square Mobile Payments](https://capawesome.io/docs/sdks/capacitor/square-mobile-payments/index.md) — Accept in-person payments with the Square SDK. ### Media & Audio - [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/index.md) — Play audio through the device speakers with background support. - [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/index.md) — Record audio from the device microphone. - [Audio Session](https://capawesome.io/docs/sdks/capacitor/audio-session/index.md) — Configure and observe the iOS audio session, output routing, and interruptions. - [Exif](https://capawesome.io/docs/sdks/capacitor/exif/index.md) — Losslessly read, write, and strip EXIF image metadata including HEIC. - [Media Session](https://capawesome.io/docs/sdks/capacitor/media-session/index.md) — Show media controls and metadata on the lock screen. - [Photo Editor](https://capawesome.io/docs/sdks/capacitor/photo-editor/index.md) — Edit photos with filters, cropping, and annotations. - [Cloudinary](https://capawesome.io/docs/sdks/capacitor/cloudinary/index.md) — Upload files directly from the filesystem to Cloudinary. - [Photo Manipulator](https://capawesome.io/docs/sdks/capacitor/photo-manipulator/index.md) — Headless crop, resize, rotate, flip and HEIC-to-JPEG image conversion. - [RealtimeKit](https://capawesome.io/docs/sdks/capacitor/realtimekit/index.md) — Add real-time audio, video, and chat to your app. - [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/index.md) — Transcribe speech into text (speech-to-text). - [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/index.md) — Synthesize speech from text (text-to-speech). - [YouTube Player](https://capawesome.io/docs/sdks/capacitor/youtube-player/index.md) — Embed and control inline YouTube players. ### Scanning - [Barcode Scanner](https://capawesome.io/docs/sdks/capacitor/barcode-scanner/index.md) — Scan barcodes and QR codes with the camera or from images. - [Document Scanner](https://capawesome.io/docs/sdks/capacitor/document-scanner/index.md) — Scan documents with automatic edge detection and JPEG or PDF output. ### Files & Storage - [Asset Manager](https://capawesome.io/docs/sdks/capacitor/asset-manager/index.md) — Read and copy files from the native app bundle. - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/index.md) — Compress images and other files. - [File Manager](https://capawesome.io/docs/sdks/capacitor/file-manager/index.md) — Read, write, copy, and move files and directories with persistent folder access. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/index.md) — Open files like PDF, DOCX, and XLSX in their default app. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/index.md) — Let users pick files, images, or videos from the device. - [File Transfer](https://capawesome.io/docs/sdks/capacitor/file-transfer/index.md) — Download and upload files with progress, pause and resume, and background support. - [PDF Annotator](https://capawesome.io/docs/sdks/capacitor/pdf-annotator/index.md) — Let users annotate PDF documents with the native markup tools. - [PDF Generator](https://capawesome.io/docs/sdks/capacitor/pdf-generator/index.md) — Generate paginated PDF files from HTML content or a URL. - [PDF Viewer](https://capawesome.io/docs/sdks/capacitor/pdf-viewer/index.md) — Display PDF documents in a fullscreen native viewer. - [Zip](https://capawesome.io/docs/sdks/capacitor/zip/index.md) — Zip and unzip files and directories, with encryption support. ### Databases - [libSQL](https://capawesome.io/docs/sdks/capacitor/libsql/index.md) — Work with libSQL databases in your app. - [SQLite](https://capawesome.io/docs/sdks/capacitor/sqlite/index.md) — Access encrypted SQLite databases with migrations and transactions. ### Device & Sensors - [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/index.md) — Read acceleration force along the x, y, and z axes. - [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/index.md) — Read air pressure and altitude from the barometer sensor. - [Battery](https://capawesome.io/docs/sdks/capacitor/battery/index.md) — Read the battery level, state, and low power mode, and listen for changes. - [Bluetooth Low Energy](https://capawesome.io/docs/sdks/capacitor/bluetooth-low-energy/index.md) — Communicate with BLE devices, including headless tasks. - [Calendar](https://capawesome.io/docs/sdks/capacitor/calendar/index.md) — Create, read, update, and delete calendars and events with recurrence support. - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/index.md) — Read the device compass heading with live updates and true north on iOS. - [Contacts](https://capawesome.io/docs/sdks/capacitor/contacts/index.md) — Read, write, and select device contacts. - [Device Info](https://capawesome.io/docs/sdks/capacitor/device-info/index.md) — Read device model, OS, memory, and per-install identifier. - [Flic](https://capawesome.io/docs/sdks/capacitor/flic/index.md) — Connect and interact with Flic smart buttons. - [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/index.md) — Geocode and reverse-geocode addresses and coordinates. - [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/index.md) — Read the device's gyroscope sensor with real-time rotation rate measurements. - [Haptics](https://capawesome.io/docs/sdks/capacitor/haptics/index.md) — Trigger impacts, notifications, vibrations, and custom haptic patterns. - [Network](https://capawesome.io/docs/sdks/capacitor/network/index.md) — Read network status and type, and listen for changes. - [NFC](https://capawesome.io/docs/sdks/capacitor/nfc/index.md) — Read, write, and emulate NFC tags. - [Pedometer](https://capawesome.io/docs/sdks/capacitor/pedometer/index.md) — Count steps and track distance traveled. - [Proximity Sensor](https://capawesome.io/docs/sdks/capacitor/proximity-sensor/index.md) — Read the device proximity sensor with real-time measurement updates. - [Shake](https://capawesome.io/docs/sdks/capacitor/shake/index.md) — Detect physical shake gestures with configurable sensitivity. - [Silent Mode](https://capawesome.io/docs/sdks/capacitor/silent-mode/index.md) — Detect silent mode, read the ringer mode, and listen for changes. - [SIM](https://capawesome.io/docs/sdks/capacitor/sim/index.md) — Read SIM card and carrier information such as name and country code. - [Thermal State](https://capawesome.io/docs/sdks/capacitor/thermal-state/index.md) — Read the device thermal state before the OS throttles your app. - [Torch](https://capawesome.io/docs/sdks/capacitor/torch/index.md) — Toggle the device flashlight. - [Volume](https://capawesome.io/docs/sdks/capacitor/volume/index.md) — Control the volume and observe hardware volume button presses. - [Wifi](https://capawesome.io/docs/sdks/capacitor/wifi/index.md) — Manage Wi-Fi connectivity and network connections. - [Zeroconf](https://capawesome.io/docs/sdks/capacitor/zeroconf/index.md) — Discover and advertise services on the local network via mDNS/DNS-SD. ### Location - [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/index.md) — Track the device location in the background with native HTTP sync. - [Geofences](https://capawesome.io/docs/sdks/capacitor/geofences/index.md) — Monitor circular regions and receive transitions, even while the app is terminated. ### Health & Fitness - [Health](https://capawesome.io/docs/sdks/capacitor/health/index.md) — Read and write health and workout data via Health Connect and HealthKit. ### App Experience & UI - [Action Sheet](https://capawesome.io/docs/sdks/capacitor/action-sheet/index.md) — Present native action sheets with styled and destructive buttons. - [Age Signals](https://capawesome.io/docs/sdks/capacitor/age-signals/index.md) — Retrieve age-assurance signals to gate sensitive content. - [Alarm](https://capawesome.io/docs/sdks/capacitor/alarm/index.md) — Create real system alarms and timers via AlarmKit. - [App Icon](https://capawesome.io/docs/sdks/capacitor/app-icon/index.md) — Change the app icon at runtime by switching between alternate icons. - [App Language](https://capawesome.io/docs/sdks/capacitor/app-language/index.md) — Manage the app's language override independently of the device language. - [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/index.md) — Check whether an app can open and launch it. - [App Review](https://capawesome.io/docs/sdks/capacitor/app-review/index.md) — Request in-app store reviews and ratings. - [App Shortcuts](https://capawesome.io/docs/sdks/capacitor/app-shortcuts/index.md) — Add home-screen shortcuts for quick actions. - [Background Task](https://capawesome.io/docs/sdks/capacitor/background-task/index.md) — Run tasks after the app moves to the background. - [Badge](https://capawesome.io/docs/sdks/capacitor/badge/index.md) — Set and clear the app icon badge count. - [Clipboard](https://capawesome.io/docs/sdks/capacitor/clipboard/index.md) — Read and write clipboard text, HTML, images, and URLs. - [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/index.md) — Show a native date and time picker. - [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/index.md) — Show native alert, confirm, and prompt dialogs. - [Home Indicator](https://capawesome.io/docs/sdks/capacitor/home-indicator/index.md) — Hide and show the iOS home indicator for fullscreen experiences. - [In-App Browser](https://capawesome.io/docs/sdks/capacitor/in-app-browser/index.md) — Open URLs in the external browser, system browser, or embedded web view. - [Keep Awake](https://capawesome.io/docs/sdks/capacitor/keep-awake/index.md) — Keep the device screen awake and prevent it from sleeping. - [Localization](https://capawesome.io/docs/sdks/capacitor/localization/index.md) — Read the user's preferred locales, time zone, and regional formatting. - [Mail Composer](https://capawesome.io/docs/sdks/capacitor/mail-composer/index.md) — Open the native email composer prefilled with recipients, subject, and attachments. - [MapLibre](https://capawesome.io/docs/sdks/capacitor/maplibre/index.md) — Render native MapLibre maps with markers, polylines, and GeoJSON layers. - [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/index.md) — Launch navigation apps with turn-by-turn directions to a destination. - [Node.js](https://capawesome.io/docs/sdks/capacitor/nodejs/index.md) — Run a full Node.js runtime in your app. - [Option Picker](https://capawesome.io/docs/sdks/capacitor/option-picker/index.md) — Let the user pick an option from a list with a native picker. - [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/index.md) — Open the native phone dialer prefilled with a number. - [PixLive](https://capawesome.io/docs/sdks/capacitor/pixlive/index.md) — Display augmented reality content with the PixLive SDK by Vidinoti. - [Printer](https://capawesome.io/docs/sdks/capacitor/printer/index.md) — Print HTML, PDFs, files, and web views. - [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/index.md) — Read, set, and reset the app's screen brightness. - [Screen Orientation](https://capawesome.io/docs/sdks/capacitor/screen-orientation/index.md) — Lock the screen to portrait, landscape, or any mode. - [Screenshot](https://capawesome.io/docs/sdks/capacitor/screenshot/index.md) — Capture and save screenshots. - [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/index.md) — Open native app, notification, and system settings screens. - [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/index.md) — Receive text, links, and files shared from other apps. - [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/index.md) — Open the native SMS composer prefilled with recipients and a body. - [Tab Bar](https://capawesome.io/docs/sdks/capacitor/tab-bar/index.md) — Show a native iOS tab bar with Liquid Glass over the web view. - [Text Interaction](https://capawesome.io/docs/sdks/capacitor/text-interaction/index.md) — Enable or disable WebView text selection and callout menus on iOS. - [Text Zoom](https://capawesome.io/docs/sdks/capacitor/text-zoom/index.md) — Read and control the WebView text zoom level. - [Toast](https://capawesome.io/docs/sdks/capacitor/toast/index.md) — Show native toast notifications with position and duration. - [Wallet](https://capawesome.io/docs/sdks/capacitor/wallet/index.md) — Add passes to Apple Wallet and Google Wallet from your app. ### Accessibility - [Accessibility Preferences](https://capawesome.io/docs/sdks/capacitor/accessibility-preferences/index.md) — Read the user's system accessibility preferences like font scale and reduce motion. - [Screen Reader](https://capawesome.io/docs/sdks/capacitor/screen-reader/index.md) — Detect screen readers and post accessibility announcements. ### App Updates - [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/index.md) — Check for and prompt users to install app store updates. - [Live Update](https://capawesome.io/docs/sdks/capacitor/live-update/index.md) — Ship over-the-air updates with auto-update and rollback. ### Android - [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/index.md) — Manage battery optimization and Doze exemptions. - [Android Dark Mode Support](https://capawesome.io/docs/sdks/capacitor/android-dark-mode-support/index.md) — Add dark mode support via the prefers-color-scheme media feature. - [Android Edge-to-Edge Support](https://capawesome.io/docs/sdks/capacitor/android-edge-to-edge-support/index.md) — Enable edge-to-edge display and style the system bars. - [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/index.md) — Run foreground services with notifications. - [Android Intent Launcher](https://capawesome.io/docs/sdks/capacitor/android-intent-launcher/index.md) — Launch arbitrary Android intents, start activities, and read their results. - [Android SMS Retriever](https://capawesome.io/docs/sdks/capacitor/android-sms-retriever/index.md) — OTP autofill via the SMS User Consent and Phone Number Hint APIs. - [Google Play Services](https://capawesome.io/docs/sdks/capacitor/google-play-services/index.md) — Check whether Google Play Services is available and prompt the user to install or update it. - [Light Sensor](https://capawesome.io/docs/sdks/capacitor/light-sensor/index.md) — Read real-time ambient light illuminance in lux via event listeners. - [Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/index.md) — Read MDM-managed app configuration settings. - [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/index.md) — Customize the Android navigation bar. - [System WebView](https://capawesome.io/docs/sdks/capacitor/system-webview/index.md) — Detect an outdated Android System WebView and guide users to update. ### Platforms - [Electron](https://capawesome.io/docs/sdks/capacitor/electron/index.md) — Build, run, and package desktop apps for macOS, Windows, and Linux. - [Tauri](https://capawesome.io/docs/sdks/capacitor/tauri/index.md) — Build lean, secure desktop apps with tiny binaries for macOS, Windows, and Linux. ### Wearables - [Watch](https://capawesome.io/docs/sdks/capacitor/watch/index.md) — Exchange messages, user info, and state with Apple Watch and Wear OS apps. ### Analytics & Feedback - [Formbricks](https://capawesome.io/docs/sdks/capacitor/formbricks/index.md) — Run surveys and collect user feedback with Formbricks. - [Grafana Faro](https://capawesome.io/docs/sdks/capacitor/grafana-faro/index.md) — Send logs, errors, and crash reports to Grafana Faro. - [Install Referrer](https://capawesome.io/docs/sdks/capacitor/install-referrer/index.md) — Read install attribution data from Play Install Referrer and Apple Ad Services. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/index.md) — Track events and user interactions with PostHog. - [Singular](https://capawesome.io/docs/sdks/capacitor/singular/index.md) — Attribute app installs and track events, revenue and deep links with Singular. - [TikTok App Events](https://capawesome.io/docs/sdks/capacitor/tiktok-app-events/index.md) — Track installs, purchases and custom events for TikTok ad campaigns. ### Customer Support & Chat - [Crisp](https://capawesome.io/docs/sdks/capacitor/crisp/index.md) — Add the Crisp chatbox, helpdesk, and push notifications to your app. - [Intercom](https://capawesome.io/docs/sdks/capacitor/intercom/index.md) — Present the Intercom Messenger with identity verification and push notifications. ### AI - [LLM](https://capawesome.io/docs/sdks/capacitor/llm/index.md) — Run on-device AI with Gemini Nano and Apple Intelligence. ### Firebase A dedicated suite of [Firebase plugins](https://capawesome.io/docs/sdks/capacitor/firebase/index.md) covers Analytics, Authentication, Crashlytics, Cloud Firestore, Cloud Messaging, Cloud Storage, Remote Config, and more. ### ML Kit A dedicated suite of [ML Kit plugins](https://capawesome.io/docs/sdks/capacitor/mlkit/index.md) adds on-device machine learning, including barcode scanning, document scanning, face detection, translation, and selfie segmentation. ## FAQ ### What are Capacitor plugins? Capacitor plugins are packages that expose native Android, iOS, and Web functionality through a unified JavaScript API. They let you call platform features — such as the camera, biometrics, or Bluetooth — from the same codebase, without writing native code yourself. To learn more about the underlying framework, see the [official Capacitor documentation](https://capacitorjs.com/docs). ### Are these Capacitor plugins free? Most plugins are open source and free to use, while a few are offered as paid plugins through [Capawesome Insiders](https://capawesome.io/docs/insiders/index.md). Licensing and pricing are documented on each plugin's page. ### What's the difference between the `@capawesome` and `@capawesome-team` packages? The npm scope tells you how a plugin is distributed: - **`@capawesome`** — free plugins published to the public npm registry. Install them directly with npm. - **`@capawesome-team`** — plugins that are paid or used to be paid. Currently paid plugins are available only through the private Capawesome npm registry and require an Insiders license. Plugins that were paid in the past but are now free are published to both the public npm registry and the Capawesome npm registry, so you can install them from either one. The [Insiders Getting Started](https://capawesome.io/docs/insiders/getting-started/index.md) guide explains how to configure the Capawesome npm registry. ### Which platforms do the plugins support? Most plugins support Android and iOS, and many also support the Web. With the [Electron](https://capawesome.io/docs/sdks/capacitor/electron/index.md) and [Tauri](https://capawesome.io/docs/sdks/capacitor/tauri/index.md) platforms, you can also bring your app to the desktop. The supported platforms are listed at the top of each plugin's documentation page. ### How do I get support? For bugs and feature requests, open an issue on the plugin's GitHub repository. For dedicated help, see our [support options](/docs/support/). ## Next steps - Explore [Capawesome Cloud](https://capawesome.io/docs/cloud/index.md) for Live Updates, Native Builds, and App Store Publishing. - Browse the [Firebase plugins](https://capawesome.io/docs/sdks/capacitor/firebase/index.md) and [ML Kit plugins](https://capawesome.io/docs/sdks/capacitor/mlkit/index.md). - View the source on [GitHub](https://github.com/capawesome-team). - Need help choosing or integrating a plugin? See our [support options](/docs/support/). # Capacitor Accelerometer Plugin Capacitor plugin to capture the acceleration force along the x, y, and z axes. ## Features The Capacitor Accelerometer plugin is one of the most complete motion sensing solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/) and [Shake](https://capawesome.io/docs/sdks/capacitor/shake/) 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! ## Use Cases The Accelerometer plugin is typically used whenever an app needs to react to device motion, for example: - **Shake gestures**: React to rapid device movement, for example to trigger a feedback dialog or undo an action. - **Motion-based games**: Use the tilt of the device as game input by reading the acceleration along the x, y, and z axes. - **Fitness and activity tracking**: Analyze continuous motion data to detect movement patterns during workouts. - **Device orientation**: Determine how the device is being held based on the gravitational force along each axis. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 0.1.x | 7.x.x | Deprecated | ## Guides - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How this plugin pairs with the Pedometer for fitness tracking and the Gyroscope for motion-based games. ## 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 The following examples show how to check whether the accelerometer is available, request permissions, read the latest measurement, and start and stop continuous measurement updates. ### Check if the accelerometer is available Check whether the device has an accelerometer sensor before using the other methods: ``` import { Accelerometer } from '@capawesome-team/capacitor-accelerometer'; const isAvailable = async () => { const result = await Accelerometer.isAvailable(); return result.isAvailable; }; ``` ### Check and request permissions Check and request permission to access the accelerometer sensor. On iOS, make sure you have added the `NSMotionUsageDescription` key to your `Info.plist` file (see [Installation](#installation)): ``` import { Accelerometer } from '@capawesome-team/capacitor-accelerometer'; const checkPermissions = async () => { const result = await Accelerometer.checkPermissions(); return result; }; const requestPermissions = async () => { const result = await Accelerometer.requestPermissions(); return result; }; ``` ### Get the latest measurement Get the most recent measurement from the accelerometer sensor. The acceleration along the x, y, and z axes is returned in G's (gravitational force): ``` 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); }; ``` ### Start and stop measurement updates Start emitting `measurement` events to receive continuous accelerometer data. When you no longer need updates, stop them and remove your listeners: ``` import { Accelerometer } from '@capawesome-team/capacitor-accelerometer'; const startMeasurementUpdates = async () => { await Accelerometer.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Accelerometer.stopMeasurementUpdates(); }; 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` ## FAQ ### Which platforms are supported by this plugin? The Accelerometer plugin supports Android and iOS. You can use the `isAvailable()` method to check whether the accelerometer sensor is available on the current device. ### What unit are the measurements returned in? The `getMeasurement()` method and the `measurement` event return the acceleration along the x, y, and z axes in G's (gravitational force). ### How do I receive continuous accelerometer updates? Call `startMeasurementUpdates()` to start emitting `measurement` events and add a listener for the `measurement` event to receive them. When you no longer need updates, call `stopMeasurementUpdates()` and remove your listeners with `removeAllListeners()`. See the [usage example](#start-and-stop-measurement-updates) above. ### Do I need any permissions to use the accelerometer? The plugin provides the `checkPermissions()` and `requestPermissions()` methods to manage access to the accelerometer sensor. On iOS, you also need to add the `NSMotionUsageDescription` key to your `Info.plist` file as described in the [Installation](#installation) section. ### How can I check if a device has an accelerometer sensor? Call the `isAvailable()` method. It returns whether the accelerometer sensor is available on the device, so you can check this before calling any other methods. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/): Obtain the static air pressure, measured in hectopascals (hPa). - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/): Read the device's compass heading. - [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/): Read the device's gyroscope sensor. - [Shake](https://capawesome.io/docs/sdks/capacitor/shake/): Detect shake gestures on the device. ## 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/). ## 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). # Capacitor Accessibility Preferences Plugin Capacitor plugin for reading the user's system accessibility preferences. ## Features - ♿ **Accessibility preferences**: Read the user's system accessibility preferences. - 🔤 **Font scale**: Adapt your typography to the user's preferred text size. - 🎞️ **Reduce motion**: Disable animations when the user prefers reduced motion. - 🎨 **Contrast & colors**: React to increased contrast and inverted colors. - 🌐 **Cross-platform**: Works on Android, iOS, and the Web. - 🤝 **Compatibility**: Works alongside the [Localization](https://capawesome.io/docs/sdks/capacitor/localization/), [Screen Reader](https://capawesome.io/docs/sdks/capacitor/screen-reader/) and [Text Zoom](https://capawesome.io/docs/sdks/capacitor/text-zoom/) 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! ## Use Cases The Accessibility Preferences plugin is typically used to adapt an app's user interface to the user's system accessibility settings, for example: - **Dynamic typography**: Scale your app's font sizes based on the system font scale so text stays readable for users who prefer larger text. - **Reduced animations**: Disable or simplify animations and transitions when the user prefers reduced motion. - **High-contrast themes**: Switch to a higher-contrast color scheme when the user has enabled increased contrast. - **Bold text support**: Render heavier font weights when the user prefers bold text. - **Color-aware rendering**: Adjust images or charts when the user has enabled inverted colors. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-accessibility-preferences` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following command: ``` npm install @capawesome/capacitor-accessibility-preferences npx cap sync ``` This plugin does not require any additional permissions or configuration. ## Configuration No configuration required for this plugin. ## Usage The following example shows how to read the user's system accessibility preferences. ### Read the user's accessibility preferences Call `getPreferences()` to read all system accessibility preferences at once, such as the font scale, reduce motion, bold text, and contrast settings. Fields that the current platform cannot provide are set to `null` (see [Platform Availability](#platform-availability)): ``` import { AccessibilityPreferences } from '@capawesome/capacitor-accessibility-preferences'; const getPreferences = async () => { const preferences = await AccessibilityPreferences.getPreferences(); return preferences; }; ``` ## API - [`getPreferences()`](#getpreferences) - [Interfaces](#interfaces) ### getPreferences() ``` getPreferences() => Promise ``` Get the user's current system accessibility preferences. Fields that the current platform cannot provide are set to `null`. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetPreferencesResult | Prop | Type | Description | Since | | --------------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`fontScale`** | `number` | The system font scale factor. A value of `1.0` represents the default font size. Values greater than `1.0` indicate that the user prefers larger text, values less than `1.0` smaller text. On iOS, the value is derived from the preferred content size category. On Web, this is always `1.0` because the font scale is not exposed to web content. | 0.1.0 | | **`isBoldTextEnabled`** | \`boolean | null\` | Whether the user prefers bold text. Returns `null` on platforms where the value cannot be read (Web and Android versions below 12). | | **`isHighContrastEnabled`** | \`boolean | null\` | Whether the user prefers increased contrast. On iOS, this reflects the "Increase Contrast" setting (darker system colors). On Android, this reflects the high text contrast setting. Returns `null` on platforms where the value cannot be read. | | **`isInvertColorsEnabled`** | \`boolean | null\` | Whether the user has enabled inverted colors. Returns `null` on Web. | | **`isReduceMotionEnabled`** | `boolean` | Whether the user prefers reduced motion (fewer animations). | 0.1.0 | | **`isReduceTransparencyEnabled`** | \`boolean | null\` | Whether the user prefers reduced transparency. Only available on iOS. Returns `null` on Android and Web. | ## Platform Availability Not every preference can be read on every platform. Fields that the current platform cannot provide are set to `null`. | Field | Android | iOS | Web | | ----------------------------- | ------------ | --- | ---- | | `fontScale` | ✅ | ✅ | ⚠️\* | | `isReduceMotionEnabled` | ✅ | ✅ | ✅ | | `isBoldTextEnabled` | ✅ (API 31+) | ✅ | ❌ | | `isInvertColorsEnabled` | ✅ | ✅ | ❌ | | `isReduceTransparencyEnabled` | ❌ | ✅ | ❌ | | `isHighContrastEnabled` | ✅ | ✅ | ✅ | ✅ = available · ❌ = always `null` · ⚠️\* = `fontScale` is always `1.0` on Web because it is not exposed to web content. On iOS, `fontScale` is derived from the preferred content size category using the following mapping (relative to the default body text size of 17 pt): | Content size category | `fontScale` | | ------------------------------- | ----------- | | Extra Small | 0.82 | | Small | 0.88 | | Medium | 0.94 | | Large (default) | 1.0 | | Extra Large | 1.12 | | Extra Extra Large | 1.24 | | Extra Extra Extra Large | 1.35 | | Accessibility Medium | 1.65 | | Accessibility Large | 1.94 | | Accessibility Extra Large | 2.35 | | Accessibility Extra Extra Large | 2.76 | | Accessibility 3X Large | 3.12 | CSS media queries such as `prefers-reduced-motion` and `prefers-contrast` already cover reduce-motion and contrast inside the WebView. The value of this plugin is the fields CSS cannot see (`fontScale`, bold text, inverted colors) and having a single API for your app logic on all platforms. The screen reader *state* (VoiceOver/TalkBack) is intentionally out of scope. Use the official [`@capacitor/screen-reader`](https://capacitorjs.com/docs/apis/screen-reader) plugin for that. ## FAQ ### Which accessibility preferences can be read on which platforms? Not every preference is exposed on every platform. The font scale, reduce motion, and high contrast settings are available on Android, iOS, and the Web, while bold text (Android 12+ and iOS), inverted colors (Android and iOS), and reduced transparency (iOS only) are more limited. Check the [Platform Availability](#platform-availability) section for the complete matrix. ### Why is the font scale always 1.0 on the Web? The system font scale is not exposed to web content, so the plugin cannot read it in a browser. On Android, the plugin returns the system font scale factor, and on iOS the value is derived from the preferred content size category. ### Why are some fields null in the result? Fields that the current platform cannot provide are set to `null` instead of a fabricated value. For example, `isBoldTextEnabled` is `null` on the Web and on Android versions below 12, and `isReduceTransparencyEnabled` is `null` on Android and Web because it is an iOS-only setting. ### Do I still need this plugin if I use CSS media queries like prefers-reduced-motion? CSS media queries such as `prefers-reduced-motion` and `prefers-contrast` already cover reduce motion and contrast inside the WebView. The value of this plugin lies in the fields CSS cannot see, such as the font scale, bold text, and inverted colors, and in having a single API for your app logic on all platforms. ### Can this plugin detect whether a screen reader like VoiceOver or TalkBack is running? No, the screen reader state is intentionally out of scope for this plugin. Use the official [`@capacitor/screen-reader`](https://capacitorjs.com/docs/apis/screen-reader) plugin for that. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Localization](https://capawesome.io/docs/sdks/capacitor/localization/): Read the user's localization preferences, such as preferred locales and time zone. - [Screen Reader](https://capawesome.io/docs/sdks/capacitor/screen-reader/): Interact with screen readers such as VoiceOver and TalkBack. - [Text Zoom](https://capawesome.io/docs/sdks/capacitor/text-zoom/): Read and control the WebView text zoom. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accessibility-preferences/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/accessibility-preferences/LICENSE). # Capacitor Action Sheet Plugin Capacitor plugin for native action sheets. ## Features - 📋 **Action Sheet**: Present a native action sheet with a list of buttons. - 🎨 **Button Styles**: Highlight destructive actions and pin a cancel button. - 📝 **Title & Message**: Show a title and message on both Android and iOS. - 📱 **iPad Ready**: Automatically anchored as a popover on iPad. - 🔒 **App Store safe**: Uses only official platform APIs. - 🤝 **Compatibility**: Works alongside the [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/) plugin. - 📦 **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! ## Use Cases The Action Sheet plugin is typically used to let the user choose between several actions in a native way, for example: - **Contextual item actions**: Show options such as upload, share, or delete when the user taps an item in a list. - **Destructive confirmations**: Highlight irreversible actions like deleting a photo using the destructive button style. - **Photo source selection**: Ask the user whether to take a new photo or pick an existing one before opening the camera. - **Cancelable flows**: Pin a cancel button to the sheet so the user can always back out of an action. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## 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-action-sheet` 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-action-sheet npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$androidxMaterialVersion` version of `com.google.android.material:material` (default: `1.12.0`) ### iOS No additional configuration is required for this plugin. ## Configuration No configuration required for this plugin. ## Usage The following example shows how to present a native action sheet and read the selected button. ### Show an action sheet Present a native action sheet with a title, a message, and a list of buttons. Use `ActionSheetButtonStyle.Destructive` to highlight irreversible actions and `ActionSheetButtonStyle.Cancel` to pin a cancel button. The result contains the zero-based index of the selected button. If the user selects the cancel button or dismisses the action sheet, the promise is rejected with the `CANCELED` error code. Only available on Android and iOS: ``` import { ActionSheet, ActionSheetButtonStyle, ErrorCode } from '@capawesome/capacitor-action-sheet'; const showActions = async () => { try { const { index } = await ActionSheet.showActions({ title: 'Photo Options', message: 'Select an option to perform.', options: [ { title: 'Upload' }, { title: 'Share' }, { title: 'Delete', style: ActionSheetButtonStyle.Destructive }, { title: 'Cancel', style: ActionSheetButtonStyle.Cancel }, ], }); console.log('Index:', index); } catch (error) { if (error.code === ErrorCode.Canceled) { console.log('The user canceled the action sheet.'); } } }; ``` ## API - [`showActions(...)`](#showactions) - [Interfaces](#interfaces) - [Enums](#enums) ### showActions(...) ``` showActions(options: ShowActionsOptions) => Promise ``` Show an action sheet with a list of buttons. If the user selects a button with the [`ActionSheetButtonStyle.Cancel`](#actionsheetbuttonstyle) style or dismisses the action sheet, the promise is rejected with the `CANCELED` error code. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `ShowActionsOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ShowActionsResult | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------- | ----- | | **`index`** | `number` | The index of the selected button in the `options` array (zero-based). | 0.1.0 | #### ShowActionsOptions | Prop | Type | Description | Default | Since | | ---------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`cancelable`** | `boolean` | Whether the action sheet can be dismissed by tapping outside of it or pressing the back button. On iOS, this is a system behavior: on iPhone, tapping outside dismisses the action sheet only if it has a button with the `Cancel` style. On iPad, tapping outside always dismisses it. Only available on Android. | `true` | 0.1.0 | | **`message`** | `string` | The message to display below the title. | | 0.1.0 | | **`options`** | `ActionSheetButton[]` | The buttons to display in the action sheet. | | 0.1.0 | | **`title`** | `string` | The title of the action sheet. | | 0.1.0 | #### ActionSheetButton | Prop | Type | Description | Default | Since | | ----------- | ------------------------ | ------------------------ | -------------------------------- | ----- | | **`style`** | `ActionSheetButtonStyle` | The style of the button. | `ActionSheetButtonStyle.Default` | 0.1.0 | | **`title`** | `string` | The title of the button. | | 0.1.0 | ### Enums #### ActionSheetButtonStyle | Members | Value | Description | Since | | ----------------- | --------------- | -------------------------------------------------------------------------------------------- | ----- | | **`Cancel`** | `'CANCEL'` | A button that cancels the action sheet. It should be the last button in the `options` array. | 0.1.0 | | **`Default`** | `'DEFAULT'` | A button with the default style. | 0.1.0 | | **`Destructive`** | `'DESTRUCTIVE'` | A button that indicates a destructive action. | 0.1.0 | ## Migrating from `@capacitor/action-sheet` This plugin is largely API-compatible with the official [`@capacitor/action-sheet`](https://github.com/ionic-team/capacitor-plugins/tree/main/action-sheet) plugin. The `ActionSheetButtonStyle` enum uses the same values (`DEFAULT`, `DESTRUCTIVE`, `CANCEL`), so no enum changes are required. The following differences apply: | `@capacitor/action-sheet` | `@capawesome/capacitor-action-sheet` | | ---------------------------------------------- | ------------------------------------------------------ | | `showActions({ title, message, options })` | `showActions({ title, message, options, cancelable })` | | Cancel button resolves with its `index` | Cancel button rejects with the `CANCELED` error code | | `message` and button styles ignored on Android | `message` and button styles rendered on Android | | `cancelable` not available | `cancelable` (default `true`) | ## FAQ ### How is this plugin different from other similar plugins? It presents the platform's own native action sheet on Android and iOS through a fully typed API, rendering the title, message, and button styles — including destructive and cancel styles — consistently on both platforms, and automatically anchoring as a popover on iPad. The result reports the selected button index, a cancellation rejects the promise with the `CANCELED` error code, and the `cancelable` option controls dismissal, all using only official platform APIs. Actively maintained against the latest Capacitor version, it lets a single dependency cover the whole action-sheet story. ### On which platforms can I show an action sheet? The `showActions(...)` method is only available on Android and iOS, where it presents the native action sheet of the respective platform. On iPad, the action sheet is automatically anchored as a popover. ### How do I know which button the user selected? The result of `showActions(...)` contains the zero-based `index` of the selected button in the `options` array. If the user selects a button with the `ActionSheetButtonStyle.Cancel` style or dismisses the action sheet, the promise is rejected with the `CANCELED` error code instead. ### Can the user dismiss the action sheet without selecting a button? Yes. On Android, the action sheet can be dismissed by tapping outside of it or pressing the back button unless you set the `cancelable` option to `false`. On iOS, the `cancelable` option has no effect: on iPhone, tapping outside dismisses the action sheet only if it has a button with the `ActionSheetButtonStyle.Cancel` style, while on iPad, tapping outside always dismisses it. If the action sheet is dismissed, the promise is rejected with the `CANCELED` error code. ### How is this plugin different from the official Capacitor Action Sheet plugin? This plugin is largely API-compatible with the official `@capacitor/action-sheet` plugin and uses the same `ActionSheetButtonStyle` enum values. In addition, it renders the message and button styles on Android, rejects with the `CANCELED` error code when the user cancels, and supports the `cancelable` option. See the [migration section](#migrating-from-capacitoraction-sheet) for a detailed comparison. ### Can I use this plugin together with the Capawesome Dialog plugin? Yes, the plugin is designed to work alongside the [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/) plugin. Use an action sheet when the user has to choose between several actions, and a dialog for alerts, confirmations, and text input. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/): Display native alert, confirm, and prompt dialogs. - [Haptics](https://capawesome.io/docs/sdks/capacitor/haptics/): Provide haptic feedback such as impacts, notifications, and vibrations. - [Toast](https://capawesome.io/docs/sdks/capacitor/toast/): Show native toast notifications. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/action-sheet/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/action-sheet/LICENSE). # Capacitor AdMob Plugin Capacitor plugin for monetizing your app with Google AdMob ads on Android and iOS. Built on the Android Next-Gen Google Mobile Ads SDK with banner, interstitial, rewarded, rewarded interstitial and app open ads, ad revenue events, and the canonical User Messaging Platform (UMP) consent flow. ⚠️ **Experimental:** This plugin could not be tested extensively yet. Use it with caution in production and [report any issues](https://github.com/capawesome-team/capacitor-plugins/issues) you encounter. ## Features The Capacitor AdMob plugin is the most complete way to serve Google AdMob ads in a Capacitor app. Here are some of the key features: - 🚀 **Next-Gen SDK**: Built on the Android Next-Gen Google Mobile Ads SDK and the latest iOS SDK from day one. - 🖼️ **Banner Ads**: Anchored adaptive, inline adaptive, fixed sizes, and collapsible banners. - 📐 **Banner Layout Done Right**: Overlay and resize modes with correct edge-to-edge insets, plus inline placement at a frame you measure in CSS pixels. - 🎬 **Full-Screen Ads**: Interstitial, rewarded, rewarded interstitial, and app open ads with a uniform load/show API. - 🔁 **App Open Automation**: Automatically load and show app open ads when the app returns to the foreground, with a frequency cap. - 💰 **Revenue Events**: Receive the paid event of every ad format for your LTV and analytics pipelines. - 🛡️ **Consent Management**: Canonical User Messaging Platform (UMP) flow with a single method call, privacy options form, and debug geography testing. - 🔢 **Multiple Instances**: Load and show multiple typed ad instances at the same time, each with its own identifier. - 🚨 **Typed Errors**: Every rejection carries a typed error code plus the underlying Google Mobile Ads SDK error message. - 🧪 **Test Device Support**: Register test devices for ads and consent forms. - 🤝 **Compatibility**: Works hand in hand with the [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/) and [Purchases](https://capawesome.io/docs/sdks/capacitor/purchases/) 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! ## Use Cases The AdMob plugin is typically used to monetize free apps, for example: - **Banner ads**: Display a persistent banner at the top or bottom of your app, or inline within scrolling content. - **Interstitial ads**: Show a full-screen ad at natural transition points, such as between levels of a game. - **Rewarded ads**: Grant users in-app rewards, such as coins or extra lives, for watching an ad. - **App open ads**: Show an ad when users bring your app back to the foreground. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [Announcing the Capacitor AdMob Plugin](https://capawesome.io/blog/announcing-the-capacitor-admob-plugin/): A tour of the five ad formats on the Google Mobile Ads Next-Gen SDK. - [How to Handle AdMob GDPR Consent (UMP) in a Capacitor App](https://capawesome.io/blog/how-to-handle-admob-gdpr-consent-in-a-capacitor-app/): The consent flow required before serving ads to EEA users. ## 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-admob` 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-admob npx cap sync ``` ### Android #### AdMob App ID Add your [AdMob app ID](https://support.google.com/admob/answer/7356431) as a `meta-data` element to the `application` element of your `AndroidManifest.xml` file: ``` ``` **Attention**: The value above is Google's official public **test** app ID. Replace it with your own AdMob app ID before releasing your app. If the `meta-data` element is missing, `initialize(...)` rejects with a clear error message. #### 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 variables in your app’s `variables.gradle` file to change the default version of the dependencies: - `$adsMobileSdkVersion` version of `com.google.android.libraries.ads.mobile.sdk:ads-mobile-sdk` (default: `1.2.1`) - `$userMessagingPlatformVersion` version of `com.google.android.ump:user-messaging-platform` (default: `3.2.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. The plugin uses the **Next-Gen** Google Mobile Ads SDK for Android, which is fetched from Google's Maven repository by your app — it is not bundled with the plugin. ### iOS #### AdMob App ID Add the `GADApplicationIdentifier` key with your [AdMob app ID](https://support.google.com/admob/answer/7356431) as value to the `Info.plist` file of your app: ``` GADApplicationIdentifier ca-app-pub-3940256099942544~1458002511 ``` **Attention**: The value above is Google's official public **test** app ID. Replace it with your own AdMob app ID before releasing your app. If the key is missing, `initialize(...)` rejects with a clear error message. #### SKAdNetwork Add the [SKAdNetwork identifiers](https://developers.google.com/admob/ios/quick-start#update_your_infoplist) recommended by Google to the `Info.plist` file of your app so that ad attribution works correctly: ``` SKAdNetworkItems SKAdNetworkIdentifier cstr6suwn9.skadnetwork ``` **Attention**: The list above only contains Google's own identifier. Google recommends adding the complete list of identifiers from the [official documentation](https://developers.google.com/admob/ios/quick-start#update_your_infoplist), which also covers third-party buyers. #### App Tracking Transparency If you want to serve personalized ads, you must request the user's permission to track them across apps and websites. Use the [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/) plugin for this and follow Google's recommended order: 1. Call `requestConsent(...)` (see [Usage](#gather-consent-and-initialize-the-sdk)). The UMP consent form includes the App Tracking Transparency context on iOS. 1. Request the tracking permission with the App Tracking Transparency plugin **before** initializing the Google Mobile Ads SDK. 1. Call `initialize(...)`. This order is important to avoid App Store rejections. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to gather the user's consent, initialize the SDK, show banner, interstitial, rewarded and app open ads, track ad revenue, and test your integration. ### Gather consent and initialize the SDK Google requires that you gather the user's consent with the User Messaging Platform (UMP) before requesting ads if the user is located in the European Economic Area (EEA), the UK, or a regulated US state. Set up your consent message in the [AdMob console](https://support.google.com/admob/answer/10113207) under **Privacy & messaging** first. The plugin implements the canonical UMP flow in a single method call. Call `requestConsent(...)` on **every app launch** before initializing the Google Mobile Ads SDK. It requests the latest consent information and shows the consent form if consent is required: ``` import { Admob } from '@capawesome-team/capacitor-admob'; const setupAds = async () => { // 1. Gather consent (shows the consent form only if required) const { canRequestAds, privacyOptionsRequired } = await Admob.requestConsent(); // 2. Initialize the Google Mobile Ads SDK if (canRequestAds) { await Admob.initialize(); } return privacyOptionsRequired; }; ``` If `privacyOptionsRequired` is `true`, you must offer the user a way to change their consent settings, for example from a privacy settings page: ``` const showPrivacyOptions = async () => { await Admob.showPrivacyOptionsForm(); }; ``` **Note**: The load methods of the plugin reject with the `CONSENT_NOT_GATHERED` error code if ads cannot be requested yet. This prevents ad requests that violate Google's EU User Consent Policy. ### Show a banner ad The plugin supports three ways to place a banner ad. Choose the one that fits your layout: | Mode | Description | When to use | | -------------------------------- | -------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | | `mode: 'overlay'` (default) | The banner ad is displayed on top of the web view, anchored to the top or bottom edge (safe-area aware). | Your layout already reserves space for the banner (e.g. with a fixed footer). | | `mode: 'resize'` | The web view is resized so that the banner ad never overlaps your web content. | You want the banner to never cover any content without changing your CSS. | | `frame: { x, y, width, height }` | The banner ad is placed at a frame you measure in CSS pixels, e.g. with `getBoundingClientRect()`. | Inline placement within your content, e.g. between list items. | Load and show a banner ad with `showBanner(...)`. Use the returned identifier to hide, resume, or remove the banner ad later: ``` import { Admob, BannerSize } from '@capawesome-team/capacitor-admob'; const showBanner = async () => { const { id } = await Admob.showBanner({ adUnitId: 'ca-app-pub-3940256099942544/6300978111', // Test ad unit ID size: BannerSize.AdaptiveBanner, position: 'bottom', }); return id; }; ``` For inline placement, measure the anchor element and update the frame on layout changes: ``` import { Admob, BannerSize } from '@capawesome-team/capacitor-admob'; const showInlineBanner = async () => { const rect = document.querySelector('#banner-anchor').getBoundingClientRect(); const { id } = await Admob.showBanner({ adUnitId: 'ca-app-pub-3940256099942544/6300978111', // Test ad unit ID size: BannerSize.InlineAdaptiveBanner, frame: { x: rect.x, y: rect.y, width: rect.width, height: rect.height }, }); // Update the frame when the layout changes (e.g. on scroll or resize) window.addEventListener('resize', async () => { const newRect = document.querySelector('#banner-anchor').getBoundingClientRect(); await Admob.setBannerFrame({ id, frame: { x: newRect.x, y: newRect.y, width: newRect.width, height: newRect.height }, }); }); }; ``` ### Show an interstitial ad Load an interstitial ad in advance and show it at a natural transition point, for example between two levels of a game: ``` import { Admob } from '@capawesome-team/capacitor-admob'; const showInterstitialAd = async () => { const { id } = await Admob.loadInterstitialAd({ adUnitId: 'ca-app-pub-3940256099942544/1033173712', // Test ad unit ID }); await Admob.showInterstitialAd({ id }); }; ``` ### Show a rewarded ad Listen for the `rewardEarned` event to grant the reward, then load and show the rewarded ad. Rewarded interstitial ads work the same way with `loadRewardedInterstitialAd(...)` and `showRewardedInterstitialAd(...)`: ``` import { Admob } from '@capawesome-team/capacitor-admob'; const showRewardedAd = async () => { await Admob.addListener('rewardEarned', event => { console.log(`User earned ${event.amount} ${event.type}`); }); const { id } = await Admob.loadRewardedAd({ adUnitId: 'ca-app-pub-3940256099942544/5224354917', // Test ad unit ID }); await Admob.showRewardedAd({ id }); }; ``` ### Show an app open ad App open ads are shown when users bring your app back to the foreground. You can load and show them manually with `loadAppOpenAd(...)` and `showAppOpenAd(...)`, or let the plugin handle the entire lifecycle: ``` import { Admob } from '@capawesome-team/capacitor-admob'; const enableAppOpenAds = async () => { await Admob.enableAppOpenAutoShow({ adUnitId: 'ca-app-pub-3940256099942544/9257395921', // Test ad unit ID minInterval: 14400, // Show at most one ad every 4 hours }); }; ``` The plugin automatically loads and shows an app open ad every time the app returns to the foreground, honoring the frequency cap. The ad is never shown while a consent form or another full-screen ad is visible. ### Track ad revenue Listen for the `adRevenuePaid` event to receive the revenue of every ad format, for example for your lifetime value (LTV) and analytics pipelines: ``` import { Admob } from '@capawesome-team/capacitor-admob'; const trackAdRevenue = async () => { await Admob.addListener('adRevenuePaid', event => { console.log(`Ad revenue: ${event.value} ${event.currencyCode} (${event.precision})`); }); }; ``` ### Test your integration Always use Google's official public test ad units during development. Using your own ad units during development can lead to your AdMob account being suspended. | Format | Android | iOS | | --------------------- | ---------------------------------------- | ---------------------------------------- | | Banner | `ca-app-pub-3940256099942544/6300978111` | `ca-app-pub-3940256099942544/2934735716` | | Interstitial | `ca-app-pub-3940256099942544/1033173712` | `ca-app-pub-3940256099942544/4411468910` | | Rewarded | `ca-app-pub-3940256099942544/5224354917` | `ca-app-pub-3940256099942544/1712485313` | | Rewarded Interstitial | `ca-app-pub-3940256099942544/5354046379` | `ca-app-pub-3940256099942544/6978759866` | | App Open | `ca-app-pub-3940256099942544/9257395921` | `ca-app-pub-3940256099942544/5575463023` | You can also register your own devices as test devices with the `testDeviceIds` option of `initialize(...)`. To test the consent flow, use the `debugGeography` and `testDeviceIds` options of `requestConsent(...)` and reset the consent state with `resetConsent()`: ``` import { Admob, DebugGeography } from '@capawesome-team/capacitor-admob'; const testConsent = async () => { await Admob.resetConsent(); await Admob.requestConsent({ debugGeography: DebugGeography.Eea, testDeviceIds: ['YOUR_TEST_DEVICE_ID'], }); }; ``` ### Handle errors Every rejection that the plugin can recover from carries a typed error code in `error.code`. Use the `ErrorCode` enum to branch on it: ``` import { Admob, ErrorCode } from '@capawesome-team/capacitor-admob'; const loadInterstitialAd = async () => { try { return await Admob.loadInterstitialAd({ adUnitId: 'ca-app-pub-3940256099942544/1033173712', // Test ad unit ID }); } catch (error) { if (error.code === ErrorCode.ConsentNotGathered) { console.log('Gather the consent with requestConsent() first.'); } else if (error.code === ErrorCode.NotInitialized) { console.log('Initialize the SDK with initialize() first.'); } else if (error.code === ErrorCode.LoadFailed) { console.log('No ad was available:', error.message); } return undefined; } }; ``` The plugin uses the following error codes: | Code | Description | | -------------------------- | ------------------------------------------------------------------------ | | `AD_ALREADY_SHOWING` | The ad is already showing. | | `AD_NOT_LOADED` | No loaded ad was found for the given identifier. | | `APPLICATION_ID_MISSING` | The AdMob application ID is missing in the native project configuration. | | `CONSENT_FORM_UNAVAILABLE` | The consent form is not available. | | `CONSENT_NOT_GATHERED` | Ads cannot be requested because the consent has not been gathered yet. | | `CONSENT_REQUEST_FAILED` | The consent information could not be requested. | | `LOAD_FAILED` | The ad could not be loaded. | | `NOT_INITIALIZED` | The Google Mobile Ads SDK has not been initialized. | **Note**: The `errorCode` property of the `adFailedToLoad` and `adFailedToShow` events is not an `ErrorCode`. It is the numeric error code reported by the Google Mobile Ads SDK (see [Android](https://developers.google.com/admob/android/reference/com/google/android/gms/ads/AdRequest#constant-summary) and [iOS](https://developers.google.com/admob/ios/reference/enum/GADErrorCode)). ## API - [`disableAppOpenAutoShow()`](#disableappopenautoshow) - [`enableAppOpenAutoShow(...)`](#enableappopenautoshow) - [`hideBanner(...)`](#hidebanner) - [`initialize(...)`](#initialize) - [`loadAppOpenAd(...)`](#loadappopenad) - [`loadInterstitialAd(...)`](#loadinterstitialad) - [`loadRewardedAd(...)`](#loadrewardedad) - [`loadRewardedInterstitialAd(...)`](#loadrewardedinterstitialad) - [`removeBanner(...)`](#removebanner) - [`requestConsent(...)`](#requestconsent) - [`resetConsent()`](#resetconsent) - [`resumeBanner(...)`](#resumebanner) - [`setApplicationMuted(...)`](#setapplicationmuted) - [`setApplicationVolume(...)`](#setapplicationvolume) - [`setBannerFrame(...)`](#setbannerframe) - [`showAppOpenAd(...)`](#showappopenad) - [`showBanner(...)`](#showbanner) - [`showInterstitialAd(...)`](#showinterstitialad) - [`showPrivacyOptionsForm()`](#showprivacyoptionsform) - [`showRewardedAd(...)`](#showrewardedad) - [`showRewardedInterstitialAd(...)`](#showrewardedinterstitialad) - [`addListener('adClicked', ...)`](#addlisteneradclicked-) - [`addListener('adDismissed', ...)`](#addlisteneraddismissed-) - [`addListener('adFailedToLoad', ...)`](#addlisteneradfailedtoload-) - [`addListener('adFailedToShow', ...)`](#addlisteneradfailedtoshow-) - [`addListener('adImpressionRecorded', ...)`](#addlisteneradimpressionrecorded-) - [`addListener('adLoaded', ...)`](#addlisteneradloaded-) - [`addListener('adRevenuePaid', ...)`](#addlisteneradrevenuepaid-) - [`addListener('adShowed', ...)`](#addlisteneradshowed-) - [`addListener('bannerSizeChanged', ...)`](#addlistenerbannersizechanged-) - [`addListener('rewardEarned', ...)`](#addlistenerrewardearned-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### disableAppOpenAutoShow() ``` disableAppOpenAutoShow() => Promise ``` Disable the automatic loading and showing of app open ads. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### enableAppOpenAutoShow(...) ``` enableAppOpenAutoShow(options: EnableAppOpenAutoShowOptions) => Promise ``` Enable the automatic loading and showing of app open ads when the app is brought back to the foreground. The ad is never shown while a consent form is visible. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `EnableAppOpenAutoShowOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### hideBanner(...) ``` hideBanner(options: HideBannerOptions) => Promise ``` Hide a banner ad without destroying it. Use `resumeBanner(...)` to show it again. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `HideBannerOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Initialize the Google Mobile Ads SDK. Must be called once before loading any ads, after the consent has been gathered with `requestConsent(...)`. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### loadAppOpenAd(...) ``` loadAppOpenAd(options: LoadAppOpenAdOptions) => Promise ``` Load an app open ad. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `LoadAppOpenAdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### loadInterstitialAd(...) ``` loadInterstitialAd(options: LoadInterstitialAdOptions) => Promise ``` Load an interstitial ad. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `LoadInterstitialAdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### loadRewardedAd(...) ``` loadRewardedAd(options: LoadRewardedAdOptions) => Promise ``` Load a rewarded ad. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `LoadRewardedAdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### loadRewardedInterstitialAd(...) ``` loadRewardedInterstitialAd(options: LoadRewardedInterstitialAdOptions) => Promise ``` Load a rewarded interstitial ad. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `LoadRewardedInterstitialAdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeBanner(...) ``` removeBanner(options: RemoveBannerOptions) => Promise ``` Remove and destroy a banner ad. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `RemoveBannerOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### requestConsent(...) ``` requestConsent(options?: RequestConsentOptions | undefined) => Promise ``` Request the latest consent information from the User Messaging Platform (UMP) and show the consent form if consent is required. Call this method on every app launch before initializing the Google Mobile Ads SDK. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `RequestConsentOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### resetConsent() ``` resetConsent() => Promise ``` Reset the consent state of the User Messaging Platform (UMP). This method should only be used for testing purposes. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### resumeBanner(...) ``` resumeBanner(options: ResumeBannerOptions) => Promise ``` Show a hidden banner ad again. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ResumeBannerOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setApplicationMuted(...) ``` setApplicationMuted(options: SetApplicationMutedOptions) => Promise ``` Set whether the app is muted for ad playback. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `SetApplicationMutedOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setApplicationVolume(...) ``` setApplicationVolume(options: SetApplicationVolumeOptions) => Promise ``` Set the app volume for ad playback. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `SetApplicationVolumeOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setBannerFrame(...) ``` setBannerFrame(options: SetBannerFrameOptions) => Promise ``` Update the frame of a banner ad that was shown with a frame, for example after a layout change. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `SetBannerFrameOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### showAppOpenAd(...) ``` showAppOpenAd(options: ShowAppOpenAdOptions) => Promise ``` Show a loaded app open ad. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `ShowAppOpenAdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### showBanner(...) ``` showBanner(options: ShowBannerOptions) => Promise ``` Load and show a banner ad. Multiple banner ads can be shown at the same time by using different identifiers. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `ShowBannerOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### showInterstitialAd(...) ``` showInterstitialAd(options: ShowInterstitialAdOptions) => Promise ``` Show a loaded interstitial ad. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `ShowInterstitialAdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### showPrivacyOptionsForm() ``` showPrivacyOptionsForm() => Promise ``` Show the privacy options form of the User Messaging Platform (UMP). Call this method when the user wants to change the consent settings, for example from a privacy settings page. Only required if `privacyOptionsRequired` is `true` in the result of `requestConsent(...)`. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### showRewardedAd(...) ``` showRewardedAd(options: ShowRewardedAdOptions) => Promise ``` Show a loaded rewarded ad. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `ShowRewardedAdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### showRewardedInterstitialAd(...) ``` showRewardedInterstitialAd(options: ShowRewardedInterstitialAdOptions) => Promise ``` Show a loaded rewarded interstitial ad. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `ShowRewardedInterstitialAdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adClicked', ...) ``` addListener(eventName: 'adClicked', listenerFunc: (event: AdClickedEvent) => void) => Promise ``` Called when an ad has been clicked. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'adClicked'` | | **`listenerFunc`** | `(event: AdClickedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adDismissed', ...) ``` addListener(eventName: 'adDismissed', listenerFunc: (event: AdDismissedEvent) => void) => Promise ``` Called when a full-screen ad has been dismissed. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'adDismissed'` | | **`listenerFunc`** | `(event: AdDismissedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adFailedToLoad', ...) ``` addListener(eventName: 'adFailedToLoad', listenerFunc: (event: AdFailedToLoadEvent) => void) => Promise ``` Called when an ad has failed to load. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'adFailedToLoad'` | | **`listenerFunc`** | `(event: AdFailedToLoadEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adFailedToShow', ...) ``` addListener(eventName: 'adFailedToShow', listenerFunc: (event: AdFailedToShowEvent) => void) => Promise ``` Called when an ad has failed to show. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'adFailedToShow'` | | **`listenerFunc`** | `(event: AdFailedToShowEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adImpressionRecorded', ...) ``` addListener(eventName: 'adImpressionRecorded', listenerFunc: (event: AdImpressionRecordedEvent) => void) => Promise ``` Called when an ad has recorded an impression. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------------- | | **`eventName`** | `'adImpressionRecorded'` | | **`listenerFunc`** | `(event: AdImpressionRecordedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adLoaded', ...) ``` addListener(eventName: 'adLoaded', listenerFunc: (event: AdLoadedEvent) => void) => Promise ``` Called when an ad has been loaded. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------- | | **`eventName`** | `'adLoaded'` | | **`listenerFunc`** | `(event: AdLoadedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adRevenuePaid', ...) ``` addListener(eventName: 'adRevenuePaid', listenerFunc: (event: AdRevenuePaidEvent) => void) => Promise ``` Called when an ad has earned revenue. Use this event to track the ad revenue of your app, for example for lifetime value (LTV) pipelines. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'adRevenuePaid'` | | **`listenerFunc`** | `(event: AdRevenuePaidEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('adShowed', ...) ``` addListener(eventName: 'adShowed', listenerFunc: (event: AdShowedEvent) => void) => Promise ``` Called when an ad has been shown. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------- | | **`eventName`** | `'adShowed'` | | **`listenerFunc`** | `(event: AdShowedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('bannerSizeChanged', ...) ``` addListener(eventName: 'bannerSizeChanged', listenerFunc: (event: BannerSizeChangedEvent) => void) => Promise ``` Called when the size of a banner ad has changed, for example after a collapsible banner has been expanded or collapsed. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'bannerSizeChanged'` | | **`listenerFunc`** | `(event: BannerSizeChangedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('rewardEarned', ...) ``` addListener(eventName: 'rewardEarned', listenerFunc: (event: RewardEarnedEvent) => void) => Promise ``` Called when the user has earned a reward from a rewarded ad or a rewarded interstitial ad. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'rewardEarned'` | | **`listenerFunc`** | `(event: RewardEarnedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### EnableAppOpenAutoShowOptions | Prop | Type | Description | Default | Since | | ----------------- | -------- | ----------------------------------------------------------------------------- | ------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the app open ad. | | 0.0.1 | | **`minInterval`** | `number` | The minimum interval in seconds between two automatically shown app open ads. | `14400` | 0.0.1 | #### HideBannerOptions | Prop | Type | Description | Since | | -------- | -------- | ---------------------------------------- | ----- | | **`id`** | `string` | The identifier of the banner ad to hide. | 0.0.1 | #### InitializeOptions | Prop | Type | Description | Since | | ---------------------------------- | -------------------- | ------------------------------------------------------------------------------------------------------------------------- | ----- | | **`maxAdContentRating`** | `MaxAdContentRating` | The maximum ad content rating for all ad requests. | 0.0.1 | | **`tagForChildDirectedTreatment`** | `boolean` | Whether the app should be treated as child-directed for purposes of the Children's Online Privacy Protection Act (COPPA). | 0.0.1 | | **`tagForUnderAgeOfConsent`** | `boolean` | Whether the ad requests should be handled in a manner suitable for users under the age of consent. | 0.0.1 | | **`testDeviceIds`** | `string[]` | The identifiers of the devices that should receive test ads. | 0.0.1 | #### LoadAppOpenAdResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------- | ----- | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### LoadAppOpenAdOptions | Prop | Type | Description | Since | | -------------------- | ------------------ | ---------------------------------------------------------------------------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the app open ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. If not provided, a random identifier is generated. | 0.0.1 | | **`requestOptions`** | `AdRequestOptions` | The additional options for the ad request. | 0.0.1 | #### AdRequestOptions | Prop | Type | Description | Since | | ---------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`contentUrl`** | `string` | The URL string for a web page whose content matches the app's primary content. This web page content is used for targeting and brand safety purposes. | 0.0.1 | | **`keywords`** | `string[]` | The keywords describing the app's content for targeting purposes. | 0.0.1 | #### LoadInterstitialAdResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------- | ----- | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### LoadInterstitialAdOptions | Prop | Type | Description | Since | | -------------------- | ------------------ | ---------------------------------------------------------------------------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the interstitial ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. If not provided, a random identifier is generated. | 0.0.1 | | **`requestOptions`** | `AdRequestOptions` | The additional options for the ad request. | 0.0.1 | #### LoadRewardedAdResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------- | ----- | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### LoadRewardedAdOptions | Prop | Type | Description | Since | | ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the rewarded ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. If not provided, a random identifier is generated. | 0.0.1 | | **`requestOptions`** | `AdRequestOptions` | The additional options for the ad request. | 0.0.1 | | **`serverSideVerification`** | `ServerSideVerificationOptions` | The options for the server-side verification (SSV) of reward grants. | 0.0.1 | #### ServerSideVerificationOptions | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------------------------------------------ | ----- | | **`customData`** | `string` | The custom data to include in the server-side verification callback. | 0.0.1 | | **`userId`** | `string` | The user identifier to include in the server-side verification callback. | 0.0.1 | #### LoadRewardedInterstitialAdResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------- | ----- | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### LoadRewardedInterstitialAdOptions | Prop | Type | Description | Since | | ---------------------------- | ------------------------------- | ---------------------------------------------------------------------------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the rewarded interstitial ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. If not provided, a random identifier is generated. | 0.0.1 | | **`requestOptions`** | `AdRequestOptions` | The additional options for the ad request. | 0.0.1 | | **`serverSideVerification`** | `ServerSideVerificationOptions` | The options for the server-side verification (SSV) of reward grants. | 0.0.1 | #### RemoveBannerOptions | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------ | ----- | | **`id`** | `string` | The identifier of the banner ad to remove. | 0.0.1 | #### RequestConsentResult | Prop | Type | Description | Since | | ---------------------------- | --------------- | ------------------------------------------------------------------------------------------------------- | ----- | | **`canRequestAds`** | `boolean` | Whether ads can be requested. | 0.0.1 | | **`privacyOptionsRequired`** | `boolean` | Whether a privacy options form should be offered to the user, for example from a privacy settings page. | 0.0.1 | | **`status`** | `ConsentStatus` | The consent status. | 0.0.1 | #### RequestConsentOptions | Prop | Type | Description | Since | | -------------------- | ---------------- | ------------------------------------------------------------------- | ----- | | **`debugGeography`** | `DebugGeography` | The debug geography for testing purposes. | 0.0.1 | | **`testDeviceIds`** | `string[]` | The identifiers of the devices that should use the debug geography. | 0.0.1 | #### ResumeBannerOptions | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------ | ----- | | **`id`** | `string` | The identifier of the banner ad to resume. | 0.0.1 | #### SetApplicationMutedOptions | Prop | Type | Description | Since | | ----------- | --------- | ----------------------------------------- | ----- | | **`muted`** | `boolean` | Whether the app is muted for ad playback. | 0.0.1 | #### SetApplicationVolumeOptions | Prop | Type | Description | Since | | ------------ | -------- | ----------------------------------------------------------------------------------------------- | ----- | | **`volume`** | `number` | The app volume for ad playback as a value between `0` (silent) and `1` (current device volume). | 0.0.1 | #### SetBannerFrameOptions | Prop | Type | Description | Since | | ----------- | ------------- | -------------------------------- | ----- | | **`frame`** | `BannerFrame` | The new frame of the banner ad. | 0.0.1 | | **`id`** | `string` | The identifier of the banner ad. | 0.0.1 | #### BannerFrame The frame of a banner ad in CSS pixels, relative to the top-left corner of the web view. | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`height`** | `number` | The height of the frame in CSS pixels. Used as the maximum height for the inline adaptive banner size. Ignored for the adaptive banner size because its height is determined by the Google Mobile Ads SDK. | 0.0.1 | | **`width`** | `number` | The width of the frame in CSS pixels. | 0.0.1 | | **`x`** | `number` | The x-coordinate of the frame in CSS pixels. | 0.0.1 | | **`y`** | `number` | The y-coordinate of the frame in CSS pixels. | 0.0.1 | #### ShowAppOpenAdOptions | Prop | Type | Description | Since | | -------- | -------- | --------------------------------- | ----- | | **`id`** | `string` | The identifier of the ad to show. | 0.0.1 | #### ShowBannerResult | Prop | Type | Description | Since | | -------- | -------- | -------------------------------- | ----- | | **`id`** | `string` | The identifier of the banner ad. | 0.0.1 | #### ShowBannerOptions | Prop | Type | Description | Default | Since | | -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------- | ----- | | **`adUnitId`** | `string` | The ad unit ID of the banner ad. | | 0.0.1 | | **`collapsible`** | `boolean` | Whether the banner ad is collapsible. | `false` | 0.0.1 | | **`frame`** | `BannerFrame` | The frame of the banner ad in CSS pixels for inline placement, for example measured with `getBoundingClientRect()`. If provided, the banner ad is placed at the given frame instead of the given position. | | 0.0.1 | | **`id`** | `string` | The identifier of the banner ad. If not provided, a random identifier is generated. | | 0.0.1 | | **`mode`** | `BannerMode` | The layout mode of the banner ad. In `'overlay'` mode, the banner ad is displayed on top of the web view. In `'resize'` mode, the web view is resized so that the banner ad never overlaps the web content. Ignored if a frame is provided. | `'overlay'` | 0.0.1 | | **`position`** | `BannerPosition` | The position of the banner ad. Ignored if a frame is provided. | `'bottom'` | 0.0.1 | | **`requestOptions`** | `AdRequestOptions` | The additional options for the ad request. | | 0.0.1 | | **`size`** | `BannerSize` | The size of the banner ad. | `BannerSize.AdaptiveBanner` | 0.0.1 | #### ShowInterstitialAdOptions | Prop | Type | Description | Since | | -------- | -------- | --------------------------------- | ----- | | **`id`** | `string` | The identifier of the ad to show. | 0.0.1 | #### ShowRewardedAdOptions | Prop | Type | Description | Since | | -------- | -------- | --------------------------------- | ----- | | **`id`** | `string` | The identifier of the ad to show. | 0.0.1 | #### ShowRewardedInterstitialAdOptions | Prop | Type | Description | Since | | -------- | -------- | --------------------------------- | ----- | | **`id`** | `string` | The identifier of the ad to show. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### AdClickedEvent | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------- | ----- | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdDismissedEvent | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------- | ----- | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdFailedToLoadEvent | Prop | Type | Description | Since | | ------------------ | ---------- | -------------------------------------------------------- | ----- | | **`errorCode`** | `string` | The error code reported by the Google Mobile Ads SDK. | 0.0.1 | | **`errorMessage`** | `string` | The error message reported by the Google Mobile Ads SDK. | 0.0.1 | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdFailedToShowEvent | Prop | Type | Description | Since | | ------------------ | ---------- | -------------------------------------------------------- | ----- | | **`errorCode`** | `string` | The error code reported by the Google Mobile Ads SDK. | 0.0.1 | | **`errorMessage`** | `string` | The error message reported by the Google Mobile Ads SDK. | 0.0.1 | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdImpressionRecordedEvent | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------- | ----- | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdLoadedEvent | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------- | ----- | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### AdRevenuePaidEvent | Prop | Type | Description | Since | | ------------------ | ------------------ | -------------------------------------------------------------------- | ----- | | **`currencyCode`** | `string` | The ISO 4217 currency code of the value. | 0.0.1 | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | | **`precision`** | `RevenuePrecision` | The precision of the reported value. | 0.0.1 | | **`value`** | `number` | The monetary value earned by the ad in the currency's standard unit. | 0.0.1 | #### AdShowedEvent | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------- | ----- | | **`format`** | `AdFormat` | The format of the ad. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | #### BannerSizeChangedEvent | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------ | ----- | | **`height`** | `number` | The height of the banner ad in CSS pixels. | 0.0.1 | | **`id`** | `string` | The identifier of the banner ad. | 0.0.1 | | **`width`** | `number` | The width of the banner ad in CSS pixels. | 0.0.1 | #### RewardEarnedEvent | Prop | Type | Description | Since | | ------------ | -------- | ------------------------- | ----- | | **`amount`** | `number` | The amount of the reward. | 0.0.1 | | **`id`** | `string` | The identifier of the ad. | 0.0.1 | | **`type`** | `string` | The type of the reward. | 0.0.1 | ### Type Aliases #### ConsentStatus The consent status of the User Messaging Platform (UMP). `'not-required' | 'obtained' | 'required' | 'unknown'` #### BannerMode The layout mode of a banner ad. `'overlay' | 'resize'` #### BannerPosition The position of a banner ad. `'bottom' | 'top'` ### Enums #### MaxAdContentRating | Members | Value | Description | Since | | -------- | ------ | ----------------------------------------------------------- | ----- | | **`G`** | `'G'` | Content suitable for general audiences. | 0.0.1 | | **`Ma`** | `'MA'` | Content suitable only for mature audiences. | 0.0.1 | | **`Pg`** | `'PG'` | Content suitable for most audiences with parental guidance. | 0.0.1 | | **`T`** | `'T'` | Content suitable for teen and older audiences. | 0.0.1 | #### DebugGeography | Members | Value | Description | Since | | ---------------------- | ---------------------- | ---------------------------------------------------------------------- | ----- | | **`Disabled`** | `'DISABLED'` | The debug geography is disabled. | 0.0.1 | | **`Eea`** | `'EEA'` | The device appears as located in the European Economic Area (EEA). | 0.0.1 | | **`Other`** | `'OTHER'` | The device appears as located in a region with no regulation in force. | 0.0.1 | | **`RegulatedUsState`** | `'REGULATED_US_STATE'` | The device appears as located in a regulated US state. | 0.0.1 | #### BannerSize | Members | Value | Description | Since | | -------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`AdaptiveBanner`** | `'ADAPTIVE_BANNER'` | An adaptive banner with a width that matches the screen width (or the frame width if a frame is provided) and a height that is determined by the Google Mobile Ads SDK. | 0.0.1 | | **`Banner`** | `'BANNER'` | A standard banner with a size of 320x50. | 0.0.1 | | **`FullBanner`** | `'FULL_BANNER'` | A full-size banner with a size of 468x60 (tablets only). | 0.0.1 | | **`InlineAdaptiveBanner`** | `'INLINE_ADAPTIVE_BANNER'` | An adaptive banner for inline placement in scrolling content with a height that is limited by the height of the provided frame. | 0.0.1 | | **`LargeBanner`** | `'LARGE_BANNER'` | A large banner with a size of 320x100. | 0.0.1 | | **`Leaderboard`** | `'LEADERBOARD'` | A leaderboard banner with a size of 728x90 (tablets only). | 0.0.1 | | **`MediumRectangle`** | `'MEDIUM_RECTANGLE'` | A medium rectangle banner with a size of 300x250. | 0.0.1 | #### AdFormat | Members | Value | Description | Since | | -------------------------- | ------------------------- | --------------------------- | ----- | | **`AppOpen`** | `'APP_OPEN'` | An app open ad. | 0.0.1 | | **`Banner`** | `'BANNER'` | A banner ad. | 0.0.1 | | **`Interstitial`** | `'INTERSTITIAL'` | An interstitial ad. | 0.0.1 | | **`Rewarded`** | `'REWARDED'` | A rewarded ad. | 0.0.1 | | **`RewardedInterstitial`** | `'REWARDED_INTERSTITIAL'` | A rewarded interstitial ad. | 0.0.1 | #### RevenuePrecision | Members | Value | Description | Since | | ----------------------- | ---------------------- | ----------------------------------------------- | ----- | | **`Estimated`** | `'ESTIMATED'` | The value is estimated. | 0.0.1 | | **`Precise`** | `'PRECISE'` | The value is the precise value paid for the ad. | 0.0.1 | | **`PublisherProvided`** | `'PUBLISHER_PROVIDED'` | The value is provided by the publisher. | 0.0.1 | | **`Unknown`** | `'UNKNOWN'` | The value is unknown. | 0.0.1 | ## FAQ ### How is this plugin different from other similar plugins? It covers the full AdMob surface on Android and iOS — banner, interstitial, rewarded, rewarded interstitial and app open ads — built on the Next-Gen Google Mobile Ads SDK, with correct edge-to-edge banner layout, ad revenue events for your LTV pipelines, and the canonical User Messaging Platform (UMP) consent flow in a single method call. Every ad format shares a uniform load/show API, rejections carry typed error codes, and you can run multiple typed ad instances at once — all fully typed, actively maintained against the latest SDK and Capacitor versions, and backed by dedicated support. If you only need a single banner, a minimal integration may be enough; if you want the complete monetization and consent story in one dependency, this plugin is built for it. ### Are ads available on the web? No. AdMob is a mobile advertising product, so all methods are only available on Android and iOS. On the web, all methods reject with an unimplemented error. For web advertising, take a look at [Google AdSense](https://adsense.google.com/). ### Why do my ad requests fail with `CONSENT_NOT_GATHERED`? The plugin only requests ads if the consent requirements are met. Call `requestConsent(...)` on every app launch before loading any ads. See [Usage](#gather-consent-and-initialize-the-sdk) for the canonical flow. ### Why does my app crash on Android without this plugin's error message? The Google Mobile Ads SDK crashes the app if the AdMob app ID is missing or invalid. The plugin detects a missing app ID and rejects `initialize(...)` with a clear error message instead. However, it cannot detect an *invalid* app ID, so double-check the value in your `AndroidManifest.xml` and `Info.plist` files. ### Do I have to comply with any ad policies? Yes. Apps that show AdMob ads must comply with [Google's ad policies](https://support.google.com/admob/answer/6128543) and, in the EEA and the UK, with Google's [EU User Consent Policy](https://www.google.com/about/company/user-consent-policy/). This plugin ships the User Messaging Platform (UMP) consent flow to help you with that (see [Usage](#gather-consent-and-initialize-the-sdk)). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Review](https://capawesome.io/docs/sdks/capacitor/app-review/): Ask happy users for a review at the right moment. - [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/): Request the tracking permission on iOS for personalized ads. - [Purchases](https://capawesome.io/docs/sdks/capacitor/purchases/): Offer an ad-free experience with in-app purchases. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/admob/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/admob/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/admob/LICENSE). # Capacitor Age Signals Plugin 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 The Capacitor Age Signals plugin is one of the most complete age assurance solutions for Capacitor apps. 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). - 🤝 **Compatibility**: Works alongside the [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/) and [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/) 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! ## Use Cases The Age Signals plugin is typically used to comply with age verification requirements and to tailor the app experience to the user's age, for example: - **Age-gated content**: Restrict access to mature or sensitive content based on the user's verification status and age range. - **Regulatory compliance**: Meet US state age verification requirements by requesting age signals from Google Play and Apple's DeclaredAgeRange API. - **Parental supervision**: Detect supervised accounts and react to pending or denied parental approvals. - **Regional eligibility checks**: Use `getRegulatoryRequirements()` on iOS to determine whether the user is in a region with additional age-related obligations. - **Automated testing**: Simulate different age verification scenarios in your Android tests using the built-in fake manager. ## 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.4`) 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 The following examples show how to request the user's age range and how to react to the regulatory requirements that apply to the user. ### Request the user's age range Call `requestAgeRange(...)` to ask the user to share their age range. The status tells you whether the age range was shared, and the `ageRange` property contains the bounds: ``` import { AgeRangeStatus, AgeSignals } from '@capawesome/capacitor-age-signals'; const requestAgeRange = async () => { const result = await AgeSignals.requestAgeRange(); if (result.status !== AgeRangeStatus.Shared) { console.log('The user has not shared their age range:', result.status); return; } console.log('Lower Bound:', result.ageRange?.lowerBound); console.log('Upper Bound:', result.ageRange?.upperBound); }; ``` On Android, a status of `VERIFICATION_REQUIRED` means the user must resolve their status in the Google Play Store before an age range can be shared. ### Read the age range without prompting Call `getAgeRange()` to read the age range again without showing a prompt. This is useful to poll for a `significantChange` status that moves from `PENDING` to `APPROVED`. Only available on Android: ``` import { AgeSignals } from '@capawesome/capacitor-age-signals'; const getAgeRange = async () => { const result = await AgeSignals.getAgeRange(); console.log('Significant Change:', result.significantChange?.status); }; ``` ### Check the regulatory requirements Check whether the user is in a region and account state that requires age assurance, and which regulatory features your app must support. Only available on iOS: ``` import { AgeSignals, RegulatoryFeature, } from '@capawesome/capacitor-age-signals'; const getRegulatoryRequirements = async () => { const result = await AgeSignals.getRegulatoryRequirements(); if ( result.regulatoryFeatures.includes( RegulatoryFeature.SignificantAppChangeRequiresAdultNotification, ) ) { await AgeSignals.showSignificantUpdateAcknowledgment({ updateDescription: 'This app now supports direct messages between users.', }); } }; ``` ## API - [`getAgeRange()`](#getagerange) - [`getRegulatoryRequirements()`](#getregulatoryrequirements) - [`isAvailable()`](#isavailable) - [`requestAgeRange(...)`](#requestagerange) - [`setNextAgeSignalsAccessResult(...)`](#setnextagesignalsaccessresult) - [`setNextAgeSignalsException(...)`](#setnextagesignalsexception) - [`setNextAgeSignalsResult(...)`](#setnextagesignalsresult) - [`setNextRequestAgeSignalsAccessException(...)`](#setnextrequestagesignalsaccessexception) - [`setUseFakeManager(...)`](#setusefakemanager) - [`showSignificantUpdateAcknowledgment(...)`](#showsignificantupdateacknowledgment) - [Interfaces](#interfaces) - [Enums](#enums) ### getAgeRange() ``` getAgeRange() => Promise ``` Get the age range that the user has already shared with the app. This method never shows a system prompt. Call `requestAgeRange(...)` first and only call this method if the returned status was `SHARED`. Use this method to poll for changes (for example a `significantChange` status that moves from `PENDING` to `APPROVED`) without prompting the user again. Only available on Android. **Returns:** `Promise` **Since:** 0.5.0 ______________________________________________________________________ ### getRegulatoryRequirements() ``` getRegulatoryRequirements() => Promise ``` Get the regulatory requirements that apply to the current user. Call this method before starting any age assurance flow to find out whether the user is in a region and account state where age assurance applies at all. Only available on iOS (26.2+). **Returns:** `Promise` **Since:** 0.5.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether age signals are available on this device. On **Android**, this checks whether the Google Play Store is installed. On **iOS**, this checks whether the device runs iOS 26.0 or later. A result of `true` does not guarantee that an age range can be retrieved. Always handle the errors of the other methods as well. **Returns:** `Promise` **Since:** 0.5.0 ______________________________________________________________________ ### requestAgeRange(...) ``` requestAgeRange(options?: RequestAgeRangeOptions | undefined) => Promise ``` Ask the user to share their age range with the app. This method may show a system prompt. The system caches the decision of the user, so subsequent calls may resolve without any user interaction. On **Android**, the prompt is only shown to unsupervised users who have chosen to be asked before sharing. It is not shown in regions where age verification is mandatory. In that case the status is `VERIFICATION_REQUIRED` and the user must resolve their status in the Google Play Store. Google Play also suppresses the prompt after the user has dismissed or declined it a few times. On **iOS**, the prompt is not shown in regions where age assurance is mandatory. In that case the age range is shared without user interaction. | Param | Type | | ------------- | ------------------------ | | **`options`** | `RequestAgeRangeOptions` | **Returns:** `Promise` **Since:** 0.5.0 ______________________________________________________________________ ### setNextAgeSignalsAccessResult(...) ``` setNextAgeSignalsAccessResult(options: SetNextAgeSignalsAccessResultOptions) => Promise ``` Set the next access result to be returned by the fake manager. Only available on Android. | Param | Type | | ------------- | -------------------------------------- | | **`options`** | `SetNextAgeSignalsAccessResultOptions` | **Since:** 0.5.0 ______________________________________________________________________ ### setNextAgeSignalsException(...) ``` setNextAgeSignalsException(options: SetNextAgeSignalsExceptionOptions) => Promise ``` Set the next error to be thrown by the fake manager when the age range is read. Only available on Android. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `SetNextAgeSignalsExceptionOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### setNextAgeSignalsResult(...) ``` setNextAgeSignalsResult(options: SetNextAgeSignalsResultOptions) => Promise ``` Set the next age range result to be returned by the fake manager. Only available on Android. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SetNextAgeSignalsResultOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### setNextRequestAgeSignalsAccessException(...) ``` setNextRequestAgeSignalsAccessException(options: SetNextRequestAgeSignalsAccessExceptionOptions) => Promise ``` Set the next error to be thrown by the fake manager when access to the age range is requested. Only available on Android. | Param | Type | | ------------- | ------------------------------------------------ | | **`options`** | `SetNextRequestAgeSignalsAccessExceptionOptions` | **Since:** 0.5.0 ______________________________________________________________________ ### setUseFakeManager(...) ``` setUseFakeManager(options: SetUseFakeManagerOptions) => Promise ``` Enable or disable the fake manager for testing. The fake manager is only available in debuggable builds. In release builds this method rejects with `FAKE_MANAGER_NOT_ALLOWED`, because it would otherwise allow age signals to be forged from the web layer. Only available on Android. | Param | Type | | ------------- | -------------------------- | | **`options`** | `SetUseFakeManagerOptions` | **Since:** 0.3.1 ______________________________________________________________________ ### showSignificantUpdateAcknowledgment(...) ``` showSignificantUpdateAcknowledgment(options: ShowSignificantUpdateAcknowledgmentOptions) => Promise ``` Show a system interface that lets the user acknowledge a significant change to the app. Only call this method if `getRegulatoryRequirements()` returned `SIGNIFICANT_APP_CHANGE_REQUIRES_ADULT_NOTIFICATION`. Only available on iOS (26.4+). | Param | Type | | ------------- | -------------------------------------------- | | **`options`** | `ShowSignificantUpdateAcknowledgmentOptions` | **Since:** 0.5.0 ______________________________________________________________________ ### Interfaces #### GetAgeRangeResult | Prop | Type | Description | Since | | ----------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ageRange`** | `AgeRange` | The age range that the user has shared with the app. `undefined` if the user has not shared their age range. | 0.5.0 | | **`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 on Android. | 0.5.0 | | **`significantChange`** | `SignificantChange` | The parental approval state for the significant changes of the app. Significant changes are declared in the Google Play Console. Google Play then asks the parent or guardian of a supervised user to approve them. `undefined` if the user is not supervised, if no significant change has been declared yet or if significant changes do not apply in the jurisdiction of the user. Only available on Android. | 0.5.0 | #### AgeRange | Prop | Type | Description | Since | | ---------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`activeParentalControls`** | `ParentalControl[]` | The parental controls that are active for the user. Only available on iOS. | 0.5.0 | | **`ageRangeDeclaration`** | `AgeRangeDeclaration` | How the age range was declared. The more granular values are only returned in some regions. `undefined` if the system does not provide this information. Only available on iOS. | 0.5.0 | | **`ageRangeSource`** | `AgeRangeSource` | How the age range was established. `undefined` if the system does not provide this information. Only available on Android. | 0.5.0 | | **`lowerBound`** | `number` | The (inclusive) lower bound of the age range. `undefined` if the user is below the lowest requested age gate. | 0.5.0 | | **`upperBound`** | `number` | The (inclusive) upper bound of the age range. `undefined` if the user meets or exceeds the highest requested age gate. | 0.5.0 | #### SignificantChange | Prop | Type | Description | Since | | ------------------ | ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`approvalDate`** | `string` | The effective date of the most recently approved significant change, in ISO 8601 format. All significant changes with an earlier effective date are approved as well. `undefined` if no significant change has been approved yet. | 0.5.0 | | **`status`** | `SignificantChangeStatus` | The parental approval state for the most recent significant change. | 0.5.0 | #### GetRegulatoryRequirementsResult | Prop | Type | Description | Since | | -------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------- | ----- | | **`ageAssuranceRequired`** | `boolean` | Whether the user must share their age range because of a law or regulation that applies to their region and account. | 0.5.0 | | **`regulatoryFeatures`** | `RegulatoryFeature[]` | The regulatory features that the app must support for this user. Empty on iOS versions below 26.4. | 0.5.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether age signals are available on this device. | 0.5.0 | #### RequestAgeRangeResult | Prop | Type | Description | Since | | ----------------------- | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ageRange`** | `AgeRange` | The age range that the user has shared with the app. `undefined` if the status is not `SHARED`. | 0.5.0 | | **`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 on Android. | 0.5.0 | | **`significantChange`** | `SignificantChange` | The parental approval state for the significant changes of the app. Significant changes are declared in the Google Play Console. Google Play then asks the parent or guardian of a supervised user to approve them. `undefined` if the user is not supervised, if no significant change has been declared yet or if significant changes do not apply in the jurisdiction of the user. Only available on Android. | 0.5.0 | | **`status`** | `AgeRangeStatus` | Whether the user has shared their age range with the app. | 0.5.0 | #### RequestAgeRangeOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- | ----- | | **`ageGates`** | `number[]` | The age thresholds that are relevant for the app. The provided array must contain at least 1 and at most 3 ages. The ages must be sorted in ascending order and must be at least two years apart from each other. The system may ignore these thresholds and return a different age range if a law or regulation requires it. Only available on iOS. | `[13, 15, 18]` | 0.5.0 | #### SetNextAgeSignalsAccessResultOptions | Prop | Type | Description | Since | | ------------ | ---------------- | ---------------------------------------------- | ----- | | **`status`** | `AgeRangeStatus` | The status to be returned by the fake manager. | 0.5.0 | #### SetNextAgeSignalsExceptionOptions | Prop | Type | Description | Since | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | ----- | | **`errorCode`** | `ErrorCode` | The error code to be thrown by the fake manager. Only the error codes that are available on Android are supported. | 0.3.1 | #### SetNextAgeSignalsResultOptions | Prop | Type | Description | Since | | ----------------------------------- | ------------------------- | ---------------------------------------------------------------------------------------- | ----- | | **`ageLower`** | `number` | The (inclusive) lower bound of the age range. | 0.3.1 | | **`ageRangeSource`** | `AgeRangeSource` | How the age range was established. | 0.5.0 | | **`ageUpper`** | `number` | The (inclusive) upper bound of the age range. | 0.3.1 | | **`installId`** | `string` | An ID assigned to supervised user installs by Google Play. | 0.3.1 | | **`significantChangeApprovalDate`** | `string` | The effective date of the most recently approved significant change, in ISO 8601 format. | 0.5.0 | | **`significantChangeStatus`** | `SignificantChangeStatus` | The parental approval state for the most recent significant change. | 0.5.0 | #### SetNextRequestAgeSignalsAccessExceptionOptions | Prop | Type | Description | Since | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | ----- | | **`errorCode`** | `ErrorCode` | The error code to be thrown by the fake manager. Only the error codes that are available on Android are supported. | 0.5.0 | #### SetUseFakeManagerOptions | Prop | Type | Description | Default | Since | | ------------- | --------- | -------------------------------------------- | ------- | ----- | | **`useFake`** | `boolean` | Whether to use the fake manager for testing. | `false` | 0.3.1 | #### ShowSignificantUpdateAcknowledgmentOptions | Prop | Type | Description | Since | | ----------------------- | -------- | ---------------------------------------------------------------------------------------- | ----- | | **`updateDescription`** | `string` | A short description of what has changed in the app and why the user must acknowledge it. | 0.5.0 | ### Enums #### ParentalControl | Members | Value | Description | Since | | ------------------------- | ------------------------ | ---------------------------------------------------------- | ----- | | **`CommunicationLimits`** | `'COMMUNICATION_LIMITS'` | The system limits the communication features for the user. | 0.5.0 | #### AgeRangeDeclaration | Members | Value | Description | Since | | ---------------------------------- | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`CheckedByOtherMethod`** | `'CHECKED_BY_OTHER_METHOD'` | The user set their own age range using an unspecified method. Only available on iOS (26.2+). | 0.5.0 | | **`Confirmed`** | `'CONFIRMED'` | The age range was set using a scrutinized method, like a credit card or government ID. Only available on iOS (26.5+). | 0.4.0 | | **`GovernmentIdChecked`** | `'GOVERNMENT_ID_CHECKED'` | The user set their own age range using a government ID. Only available on iOS (26.2+). | 0.5.0 | | **`GuardianCheckedByOtherMethod`** | `'GUARDIAN_CHECKED_BY_OTHER_METHOD'` | A parent or guardian set the age range using an unspecified method. Only available on iOS (26.2+). | 0.5.0 | | **`GuardianDeclared`** | `'GUARDIAN_DECLARED'` | A parent or guardian declared the age range without external verification. | 0.4.0 | | **`GuardianGovernmentIdChecked`** | `'GUARDIAN_GOVERNMENT_ID_CHECKED'` | A parent or guardian set the age range using a government ID. Only available on iOS (26.2+). | 0.5.0 | | **`GuardianPaymentChecked`** | `'GUARDIAN_PAYMENT_CHECKED'` | A parent or guardian set the age range using a payment method, like a credit card. Only available on iOS (26.2+). | 0.5.0 | | **`PaymentChecked`** | `'PAYMENT_CHECKED'` | The user set their own age range using a payment method, like a credit card. Only available on iOS (26.2+). | 0.5.0 | | **`SelfDeclared`** | `'SELF_DECLARED'` | The user declared their own age range without external verification. | 0.4.0 | #### AgeRangeSource | Members | Value | Description | Since | | ----------- | ---------- | ------------------------------------------------------------------------------------------------------------------------- | ----- | | **`TierA`** | `'TIER_A'` | The user has self-declared their age. | 0.5.0 | | **`TierB`** | `'TIER_B'` | The age of the user is managed by a parent or guardian. | 0.5.0 | | **`TierC`** | `'TIER_C'` | The age of the user was assessed using a credit card, an email address, a selfie assessment, a government ID or a tax ID. | 0.5.0 | | **`TierD`** | `'TIER_D'` | The age of the user was checked using a combination of a government ID and a selfie assessment, or using a digital ID. | 0.5.0 | #### SignificantChangeStatus | Members | Value | Description | Since | | -------------- | ------------ | --------------------------------------------------------------------------------------------------------- | ----- | | **`Approved`** | `'APPROVED'` | The parent or guardian has approved the most recent significant change and all prior significant changes. | 0.5.0 | | **`Declined`** | `'DECLINED'` | The parent or guardian has declined one or more significant changes. | 0.5.0 | | **`Pending`** | `'PENDING'` | The parent or guardian has not yet approved one or more significant changes. | 0.5.0 | #### RegulatoryFeature | Members | Value | Description | Since | | --------------------------------------------------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`DeclaredAgeRangeRequired`** | `'DECLARED_AGE_RANGE_REQUIRED'` | The user must share their age range with the app. | 0.5.0 | | **`SignificantAppChangeRequiresAdultNotification`** | `'SIGNIFICANT_APP_CHANGE_REQUIRES_ADULT_NOTIFICATION'` | An adult user must acknowledge a significant change of the app. Use `showSignificantUpdateAcknowledgment(...)` to show the system interface for the acknowledgment. | 0.5.0 | | **`SignificantAppChangeRequiresParentalConsent`** | `'SIGNIFICANT_APP_CHANGE_REQUIRES_PARENTAL_CONSENT'` | A parent or guardian must consent to a significant change of the app. This plugin does not implement the consent flow. Use Apple's PermissionKit framework to request the consent. | 0.5.0 | #### AgeRangeStatus | Members | Value | Description | Since | | -------------------------- | ------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`NotShared`** | `'NOT_SHARED'` | The user has not shared their age range with the app. | 0.5.0 | | **`Shared`** | `'SHARED'` | The user has shared their age range with the app. | 0.5.0 | | **`Unspecified`** | `'UNSPECIFIED'` | The system did not specify whether the user has shared their age range. Treat this value like `NOT_SHARED`. Google Play does not document when it is returned. Only available on Android. | 0.5.0 | | **`VerificationRequired`** | `'VERIFICATION_REQUIRED'` | The user must verify their age in the Google Play Store before the age range can be shared. Only available on Android. | 0.5.0 | #### ErrorCode | Members | Value | Description | Since | | ------------------------------------ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ApiNotAvailable`** | `'API_NOT_AVAILABLE'` | The age signals API is not available. On **Android**, the Play Store app version installed on the device might be old. On **iOS**, the system was unable to share the age range. | 0.0.1 | | **`AppNotOwned`** | `'APP_NOT_OWNED'` | The app was not installed by Google Play. Only available on Android. | 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. Only available on Android. | 0.0.1 | | **`ClientTransientError`** | `'CLIENT_TRANSIENT_ERROR'` | There was a transient error in the client device. Only available on Android. | 0.0.1 | | **`FakeManagerNotAllowed`** | `'FAKE_MANAGER_NOT_ALLOWED'` | The fake manager is not available, because the app is not debuggable. Only available on Android. | 0.5.0 | | **`FakeManagerNotEnabled`** | `'FAKE_MANAGER_NOT_ENABLED'` | The fake manager is not enabled. Only available on Android. | 0.5.0 | | **`InternalError`** | `'INTERNAL_ERROR'` | Unknown internal error. Only available on Android. | 0.0.1 | | **`InvalidRequest`** | `'INVALID_REQUEST'` | The request contains invalid parameters. On **iOS**, this is also returned if the requested age gates are rejected by the system. | 0.5.0 | | **`NetworkError`** | `'NETWORK_ERROR'` | No available network is found. Only available on Android. | 0.0.1 | | **`NotSupported`** | `'NOT_SUPPORTED'` | Age signals are not supported on this device. | 0.5.0 | | **`PlayServicesNotFound`** | `'PLAY_SERVICES_NOT_FOUND'` | Play Services is not available or its version is too old. Only available on Android. | 0.0.1 | | **`PlayServicesVersionOutdated`** | `'PLAY_SERVICES_VERSION_OUTDATED'` | Play Services needs to be updated. Only available on Android. | 0.0.1 | | **`PlayStoreNotFound`** | `'PLAY_STORE_NOT_FOUND'` | No Play Store app is found on the device. Only available on Android. | 0.0.1 | | **`PlayStoreVersionOutdated`** | `'PLAY_STORE_VERSION_OUTDATED'` | The Play Store app needs to be updated. Only available on Android. | 0.0.1 | | **`PresentationContextUnavailable`** | `'PRESENTATION_CONTEXT_UNAVAILABLE'` | No view controller was found to present the system interface. Only available on iOS. | 0.5.0 | | **`SdkVersionOutdated`** | `'SDK_VERSION_OUTDATED'` | The Age Signals SDK version is outdated. Only available on Android. | 0.4.0 | ## 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**: The fake manager is only available if your app is debuggable. In release builds, `setUseFakeManager(...)` rejects with `FAKE_MANAGER_NOT_ALLOWED`, because it would otherwise allow age signals to be forged from the web layer. **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 Self-Declared Adult User ``` import { AgeRangeSource, AgeRangeStatus, AgeSignals, } from '@capawesome/capacitor-age-signals'; // Enable the fake manager await AgeSignals.setUseFakeManager({ useFake: true }); // The user agrees to share their age range await AgeSignals.setNextAgeSignalsAccessResult({ status: AgeRangeStatus.Shared, }); // Set up an adult user await AgeSignals.setNextAgeSignalsResult({ ageLower: 18, ageRangeSource: AgeRangeSource.TierA, }); const result = await AgeSignals.requestAgeRange(); console.log(result.status); // 'SHARED' console.log(result.ageRange?.lowerBound); // 18 ``` #### Example: Testing a Supervised User (13-17 years old) ``` import { AgeRangeSource, AgeRangeStatus, AgeSignals, } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); await AgeSignals.setNextAgeSignalsAccessResult({ status: AgeRangeStatus.Shared, }); await AgeSignals.setNextAgeSignalsResult({ ageLower: 13, ageUpper: 17, ageRangeSource: AgeRangeSource.TierB, installId: 'fake_install_id', }); const result = await AgeSignals.requestAgeRange(); console.log(result.ageRange?.lowerBound); // 13 console.log(result.ageRange?.upperBound); // 17 console.log(result.installId); // 'fake_install_id' ``` #### Example: Testing Parental Approval Scenarios ``` import { AgeRangeSource, AgeRangeStatus, AgeSignals, SignificantChangeStatus, } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); await AgeSignals.setNextAgeSignalsAccessResult({ status: AgeRangeStatus.Shared, }); // Test pending approval await AgeSignals.setNextAgeSignalsResult({ ageLower: 13, ageUpper: 17, ageRangeSource: AgeRangeSource.TierB, significantChangeApprovalDate: '2026-02-01T00:00:00.000Z', significantChangeStatus: SignificantChangeStatus.Pending, }); const result = await AgeSignals.requestAgeRange(); console.log(result.significantChange?.status); // 'PENDING' console.log(result.significantChange?.approvalDate); // '2026-02-01T00:00:00.000Z' ``` #### Example: Testing a User Who Must Verify Their Age ``` import { AgeRangeStatus, AgeSignals } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); await AgeSignals.setNextAgeSignalsAccessResult({ status: AgeRangeStatus.VerificationRequired, }); const result = await AgeSignals.requestAgeRange(); console.log(result.status); // 'VERIFICATION_REQUIRED' console.log(result.ageRange); // undefined ``` #### Example: Testing Error Scenarios ``` import { AgeSignals, ErrorCode } from '@capawesome/capacitor-age-signals'; await AgeSignals.setUseFakeManager({ useFake: true }); // Simulate a network error while access is requested await AgeSignals.setNextRequestAgeSignalsAccessException({ errorCode: ErrorCode.NetworkError, }); try { await AgeSignals.requestAgeRange(); } catch (error) { console.log('Caught network error:', error); } // Simulate a network error while the age range is read await AgeSignals.setNextAgeSignalsException({ errorCode: ErrorCode.NetworkError, }); try { await AgeSignals.getAgeRange(); } 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.requestAgeRange(); ``` ## FAQ ### Which age verification APIs does this plugin use? On Android, the plugin uses the [Play Age Signals API](https://developer.android.com/google/play/age-signals/overview) to request age signals from Google Play. On iOS, it uses Apple's [DeclaredAgeRange](https://developer.apple.com/documentation/declaredagerange/) framework, which requires the `com.apple.developer.declared-age-range` entitlement (see [Installation](#installation)). ### What do the different statuses mean? The `status` property tells you whether the user shared their age range. `SHARED` means an age range is available, `NOT_SHARED` means the user declined or is not in an applicable region, and `VERIFICATION_REQUIRED` (Android only) means the user must resolve their status in the Google Play Store first. ### How do the Android age range sources map to the iOS age range declarations? Android reports how an age was established as a tier, while iOS reports a declaration method. They do not map one to one, so both values are exposed as separate properties. The following table shows the closest equivalents: | Android | iOS | | -------- | ------------------------------------------------------------------------------------------------------------ | | `TIER_A` | `SELF_DECLARED` | | `TIER_B` | `GUARDIAN_DECLARED` | | `TIER_C` | `PAYMENT_CHECKED`, `CHECKED_BY_OTHER_METHOD`, `GUARDIAN_PAYMENT_CHECKED`, `GUARDIAN_CHECKED_BY_OTHER_METHOD` | | `TIER_D` | `GOVERNMENT_ID_CHECKED`, `GUARDIAN_GOVERNMENT_ID_CHECKED` | On iOS 26.5 and later, the more granular declarations may be replaced by `CONFIRMED`, which covers both `TIER_C` and `TIER_D`. ### How can I test different age verification scenarios? On Android, the plugin integrates the `FakeAgeSignalsManager` API, which lets you simulate age signals without live responses from Google Play. Enable it with `setUseFakeManager(...)` and set the next results or exceptions with `setNextAgeSignalsAccessResult(...)`, `setNextAgeSignalsResult(...)`, `setNextRequestAgeSignalsAccessException(...)` and `setNextAgeSignalsException(...)`. The fake manager is only available if your app is debuggable. See the [Testing](#testing) section for complete examples. ### Why do I get a VerifyError in my Android unit tests? Due to a known issue in versions 0.0.1 and 0.0.2 of the Age Signals API, calling the builder method of `AgeSignalsResult` in unit tests may throw a `java.lang.VerifyError`. As a workaround, run your tests as Android instrumented tests within the `androidTest` source set. ### Why does the plugin reject with an error like API_NOT_AVAILABLE or APP_NOT_OWNED? The Play Age Signals API requires an up-to-date Play Store app and Play Services on the device, so errors like `API_NOT_AVAILABLE`, `PLAY_STORE_VERSION_OUTDATED`, or `PLAY_SERVICES_NOT_FOUND` usually indicate outdated or missing Google Play components. The `APP_NOT_OWNED` error means the app was not installed by Google Play. Check the `ErrorCode` enum in the [API](#api) section for the full list of error codes. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/): Verify app and device integrity using the Play Integrity API and App Attest. - [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/): Request user authorization via Apple's App Tracking Transparency framework. - [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/): Detect rooted and jailbroken devices. ## 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/). ## 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). # Capacitor Alarm Plugin Capacitor plugin to create system alarms and timers. ## Features - ⏰ **Real system alarms**: Create alarms that break through Silent Mode and Focus — unlike local notifications. - ⏲️ **Timers**: Create countdown timers in the system clock app on Android. - 📋 **Alarm management**: List and cancel the alarms created by your app on iOS. - 🍏 **AlarmKit**: One of the first plugins to support Apple's new AlarmKit framework on iOS 26. - 🔐 **Permissions**: Check and request the alarm authorization with a single call. - 🤝 **Compatibility**: Works alongside the [Background Task](https://capawesome.io/docs/sdks/capacitor/background-task/), [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/) and [Silent Mode](https://capawesome.io/docs/sdks/capacitor/silent-mode/) 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! ## Use Cases The Alarm plugin is typically used when the user must be reliably interrupted at a specific time, for example: - **Wake-up alarms**: Create alarms that break through Silent Mode and Focus, for example for morning routines in sleep or fitness apps. - **Recurring reminders**: Repeat alarms on specific weekdays, such as a medication reminder every Monday and Friday. - **Countdown timers**: Start timers in the system clock app on Android, for example for cooking or workout apps. - **Alarm management**: List and cancel the alarms created by your app on iOS to build your own alarm overview. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-alarm` 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-alarm npx cap sync ``` ### Android The plugin already declares the `com.android.alarm.permission.SET_ALARM` permission in its manifest. This is a normal permission that is granted automatically, so no additional configuration is required. ### iOS This plugin uses Apple's [AlarmKit](https://developer.apple.com/documentation/alarmkit) framework, which requires **iOS 26 or later**. On older versions, all methods except `isAvailable()` reject as unavailable. Add the `NSAlarmKitUsageDescription` key to the `ios/App/App/Info.plist` file, which tells the user why the app needs to schedule alarms: ``` NSAlarmKitUsageDescription The app uses alarms to notify you at the times you choose. ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check availability, check and request permissions, create alarms and timers, list and cancel alarms, and open the system clock app. ### Check if alarms are available Before creating an alarm, check if alarms are available on the device. On Android, this checks whether an app that can handle alarms is installed. On iOS, this returns `true` if the device runs iOS 26 or later: ``` import { Alarm } from '@capawesome/capacitor-alarm'; const isAvailable = async () => { const { available } = await Alarm.isAvailable(); return available; }; ``` ### Check and request permissions Check and request the permission to schedule alarms. On Android, both methods always return `granted` since alarms are created via the system clock app: ``` import { Alarm } from '@capawesome/capacitor-alarm'; const checkPermissions = async () => { const { alarms } = await Alarm.checkPermissions(); return alarms; }; const requestPermissions = async () => { const { alarms } = await Alarm.requestPermissions(); return alarms; }; ``` ### Create an alarm Create a one-time or repeating alarm by providing the hour, the minute, and optionally a label and the weekdays on which the alarm repeats. On Android, the alarm is created in the system clock app and the returned `id` is `null`. On iOS, the alarm is owned by your app and can be listed and canceled later: ``` import { Alarm, Weekday } from '@capawesome/capacitor-alarm'; const createAlarm = async () => { const { id } = await Alarm.createAlarm({ hour: 6, minute: 30, label: 'Wake up', days: [Weekday.Monday, Weekday.Friday], }); return id; }; ``` ### Create a timer Create a countdown timer in the system clock app by providing the duration in seconds. Only available on Android: ``` import { Alarm } from '@capawesome/capacitor-alarm'; const createTimer = async () => { await Alarm.createTimer({ duration: 300, label: 'Tea', }); }; ``` ### List and cancel alarms Get all alarms that were created by your app and cancel them by their identifier. Only available on iOS: ``` import { Alarm } from '@capawesome/capacitor-alarm'; const getAlarms = async () => { const { alarms } = await Alarm.getAlarms(); return alarms; }; const cancelAlarm = async (id: string) => { await Alarm.cancelAlarm({ id }); }; ``` ### Open the alarms in the system clock app Open the list of alarms in the system clock app, for example so the user can review or edit an alarm. Only available on Android: ``` import { Alarm } from '@capawesome/capacitor-alarm'; const openAlarms = async () => { await Alarm.openAlarms(); }; ``` ## API - [`cancelAlarm(...)`](#cancelalarm) - [`checkPermissions()`](#checkpermissions) - [`createAlarm(...)`](#createalarm) - [`createTimer(...)`](#createtimer) - [`getAlarms()`](#getalarms) - [`isAvailable()`](#isavailable) - [`openAlarms()`](#openalarms) - [`requestPermissions()`](#requestpermissions) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### cancelAlarm(...) ``` cancelAlarm(options: CancelAlarmOptions) => Promise ``` Cancel an alarm that was created by the app. Only available on iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `CancelAlarmOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permission to schedule alarms. On Android, this method always returns `granted` since alarms are created via the system clock app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### createAlarm(...) ``` createAlarm(options: CreateAlarmOptions) => Promise ``` Create a new alarm. On Android, the alarm is created in the system clock app. The app has no access to the created alarm afterwards. On iOS, the alarm is owned by the app and can be listed and canceled using the `getAlarms(...)` and `cancelAlarm(...)` methods. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `CreateAlarmOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### createTimer(...) ``` createTimer(options: CreateTimerOptions) => Promise ``` Create a new countdown timer in the system clock app. Only available on Android. | Param | Type | | ------------- | -------------------- | | **`options`** | `CreateTimerOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### getAlarms() ``` getAlarms() => Promise ``` Get all alarms that were created by the app. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if alarms are available on this device. On Android, this checks whether an app that can handle alarms is installed. On iOS, this returns `true` if the device runs iOS 26 or later. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### openAlarms() ``` openAlarms() => Promise ``` Open the list of alarms in the system clock app. Only available on Android. **Since:** 0.1.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to schedule alarms. On Android, this method always returns `granted` since alarms are created via the system clock app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### CancelAlarmOptions | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------------- | ----- | | **`id`** | `string` | The unique identifier of the alarm to cancel. | 0.1.0 | #### PermissionStatus | Prop | Type | Description | Since | | ------------ | ----------------- | -------------------------------------- | ----- | | **`alarms`** | `PermissionState` | Permission state of scheduling alarms. | 0.1.0 | #### CreateAlarmResult | Prop | Type | Description | Since | | -------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------- | | **`id`** | \`string | null\` | The unique identifier of the created alarm. On Android, this is always `null` since the alarm is created in the system clock app. | #### CreateAlarmOptions | Prop | Type | Description | Since | | ------------- | --------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`android`** | `CreateAlarmAndroidOptions` | Android-specific options. Only available on Android. | 0.1.0 | | **`days`** | `Weekday[]` | The days of the week on which the alarm repeats. If not provided, the alarm fires once at the next occurrence of the given time. | 0.1.0 | | **`hour`** | `number` | The hour of the alarm (0-23). | 0.1.0 | | **`label`** | `string` | The label of the alarm. | 0.1.0 | | **`minute`** | `number` | The minute of the alarm (0-59). | 0.1.0 | #### CreateAlarmAndroidOptions | Prop | Type | Description | Default | Since | | ------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`skipUi`** | `boolean` | Whether or not to create the alarm without showing the user interface of the system clock app. Only available on Android. | `false` | 0.1.0 | | **`vibrate`** | `boolean` | Whether or not the alarm should vibrate. If not provided, the default behavior of the system clock app is used. Only available on Android. | | 0.1.0 | #### CreateTimerOptions | Prop | Type | Description | Since | | -------------- | --------------------------- | ---------------------------------------------------- | ----- | | **`android`** | `CreateTimerAndroidOptions` | Android-specific options. Only available on Android. | 0.1.0 | | **`duration`** | `number` | The length of the timer in seconds (1-86400). | 0.1.0 | | **`label`** | `string` | The label of the timer. | 0.1.0 | #### CreateTimerAndroidOptions | Prop | Type | Description | Default | Since | | ------------ | --------- | ------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`skipUi`** | `boolean` | Whether or not to create the timer without showing the user interface of the system clock app. Only available on Android. | `false` | 0.1.0 | #### GetAlarmsResult | Prop | Type | Description | Since | | ------------ | ------------- | ---------------------------------------- | ----- | | **`alarms`** | `AlarmInfo[]` | The alarms that were created by the app. | 0.1.0 | #### AlarmInfo | Prop | Type | Description | Since | | ------------- | --------- | ----------------------------------- | ----------------------------------- | | **`enabled`** | \`boolean | null\` | Whether or not the alarm is active. | | **`hour`** | \`number | null\` | The hour of the alarm (0-23). | | **`id`** | `string` | The unique identifier of the alarm. | 0.1.0 | | **`label`** | \`string | null\` | The label of the alarm. | | **`minute`** | \`number | null\` | The minute of the alarm (0-59). | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | --------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not alarms are available on this device. | 0.1.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### Weekday | Members | Value | Since | | --------------- | ------------- | ----- | | **`Friday`** | `'FRIDAY'` | 0.1.0 | | **`Monday`** | `'MONDAY'` | 0.1.0 | | **`Saturday`** | `'SATURDAY'` | 0.1.0 | | **`Sunday`** | `'SUNDAY'` | 0.1.0 | | **`Thursday`** | `'THURSDAY'` | 0.1.0 | | **`Tuesday`** | `'TUESDAY'` | 0.1.0 | | **`Wednesday`** | `'WEDNESDAY'` | 0.1.0 | ## Alarms vs. Local Notifications Alarms are not the same as local notifications. Use an alarm when the user must be interrupted at a specific time, and a local notification for everything else. | | Alarm | Local Notification | | --------------------------- | ----- | ------------------ | | Breaks through Silent Mode | ✅ | ❌ | | Breaks through Focus modes | ✅ | ❌ | | Plays sound until dismissed | ✅ | ❌ | | Custom content and actions | ❌ | ✅ | ## Platform Support The two platforms have fundamentally different alarm models, which this plugin exposes honestly instead of hiding behind a false abstraction: - **Android**: Alarms and timers are created in the **system clock app** via the [`AlarmClock`](https://developer.android.com/reference/android/provider/AlarmClock) intent API. This is fire-and-forget: once created, the alarm belongs to the clock app and the OS offers no API to list or cancel it. The `createAlarm(...)` method therefore returns `null` as the alarm identifier, and `getAlarms()` and `cancelAlarm(...)` reject as unimplemented. - **iOS (26+)**: Alarms are created with [AlarmKit](https://developer.apple.com/documentation/alarmkit) and are **owned by your app**: you can list them with `getAlarms()` and cancel them with `cancelAlarm(...)`. Timers created with AlarmKit require a Live Activity widget extension in the app, which is why `createTimer(...)` is currently not supported on iOS. The `openAlarms()` method is not supported on iOS because the system clock app cannot be opened via a public API. | Method | Android | iOS (26+) | | ---------------------- | ------- | --------- | | `cancelAlarm(...)` | ❌ | ✅ | | `checkPermissions()` | ✅ | ✅ | | `createAlarm(...)` | ✅ | ✅ | | `createTimer(...)` | ✅ | ❌ | | `getAlarms()` | ❌ | ✅ | | `isAvailable()` | ✅ | ✅ | | `openAlarms()` | ✅ | ❌ | | `requestPermissions()` | ✅ | ✅ | ## FAQ ### How is this plugin different from other similar plugins? It creates real system alarms that break through Silent Mode and Focus and keep sounding until dismissed — one of the first plugins to adopt Apple's new AlarmKit on iOS 26 — with repeating weekday alarms, permission handling in a single call, timers via the system clock app on Android, and alarm listing and cancellation on iOS. It exposes each platform's fundamentally different alarm model honestly through a fully typed API and stays actively maintained against the latest Capacitor and OS versions. If a normal reminder is enough, a local notification is the simpler choice; if the user must be reliably interrupted at a set time, this plugin is built for that. ### Why does the plugin require iOS 26 or later? The plugin uses Apple's [AlarmKit](https://developer.apple.com/documentation/alarmkit) framework, which was introduced with iOS 26. On older iOS versions, all methods except `isAvailable()` reject as unavailable, so you can use `isAvailable()` to check for support at runtime. ### Why can't I list or cancel alarms on Android? On Android, alarms are created in the system clock app via the `AlarmClock` intent API, which is fire-and-forget: once created, the alarm belongs to the clock app and the OS offers no API to list or cancel it. That is why `createAlarm(...)` returns `null` as the alarm identifier on Android and `getAlarms()` and `cancelAlarm(...)` reject as unimplemented. See the [Platform Support](#platform-support) section for details. ### What is the difference between an alarm and a local notification? An alarm breaks through Silent Mode and Focus modes and plays a sound until it is dismissed, while a local notification does not. On the other hand, local notifications support custom content and actions. Use an alarm when the user must be interrupted at a specific time, and a local notification for everything else (see [Alarms vs. Local Notifications](#alarms-vs-local-notifications)). ### Why is creating timers not supported on iOS? Timers created with AlarmKit require a Live Activity widget extension in the app, which is why `createTimer(...)` is currently not supported on iOS. On Android, timers are created in the system clock app. ### Do I need any permissions to create alarms? On Android, the plugin already declares the `com.android.alarm.permission.SET_ALARM` permission in its manifest, which is granted automatically. On iOS, you must add the `NSAlarmKitUsageDescription` key to your `Info.plist` file (see [Installation](#installation)) and request the alarm authorization using `requestPermissions()`. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Background Task](https://capawesome.io/docs/sdks/capacitor/background-task/): Run background tasks in your app. - [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/): Let the user pick the alarm time with a native date and time picker. - [Silent Mode](https://capawesome.io/docs/sdks/capacitor/silent-mode/): Detect whether the device is in silent mode. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/alarm/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/alarm/LICENSE). # Capacitor Android Battery Optimization Plugin Capacitor plugin for Android to manage battery optimization settings, request exemptions, and enhance app performance under Doze and App Standby modes. ## Use Cases The Android Battery Optimization plugin is typically used by apps whose core function is affected by Doze and App Standby, for example: - **Background reliability checks**: Detect whether battery optimization is enabled and inform the user that background work may be deferred. - **Guided settings flow**: Open the battery optimization settings page so the user can manually exclude your app from battery optimization. - **Direct exemption requests**: Request the battery optimization ignore permission when the core function of your app is adversely affected by Power Management features. - **Troubleshooting support**: Help users understand why they receive delayed updates from your app by checking the battery optimization status. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | | ------- | | | ## 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. ## Usage The following examples show how to check whether battery optimization is enabled, open the battery optimization settings, and request an exemption from battery optimization. ### Check if battery optimization is enabled Find out whether battery optimization is enabled for the device, for example to inform the user that background work may be deferred. All methods of this plugin are only available on Android, so guard your calls with a platform check: ``` import { BatteryOptimization } from '@capawesome-team/capacitor-android-battery-optimization'; import { Capacitor } from '@capacitor/core'; const isBatteryOptimizationEnabled = async () => { if (Capacitor.getPlatform() !== 'android') { return false; } const { enabled } = await BatteryOptimization.isBatteryOptimizationEnabled(); return enabled; }; ``` ### Open the battery optimization settings Open the battery optimization settings page so the user can manually exclude your app from battery optimization: ``` import { BatteryOptimization } from '@capawesome-team/capacitor-android-battery-optimization'; import { Capacitor } from '@capacitor/core'; const openBatteryOptimizationSettings = async () => { if (Capacitor.getPlatform() !== 'android') { return; } await BatteryOptimization.openBatteryOptimizationSettings(); }; ``` ### Request an exemption from battery optimization Request the battery optimization ignore permission directly. This method requires the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` manifest permission (see [Installation](#installation)) and should only be used if your app meets an acceptable use case according to the Google Play policy: ``` import { BatteryOptimization } from '@capawesome-team/capacitor-android-battery-optimization'; import { Capacitor } from '@capacitor/core'; 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 | ## FAQ ### Does this plugin work on iOS or the Web? No, all methods of this plugin are only available on Android, since battery optimization with Doze and App Standby is an Android concept. On other platforms, guard your calls with a platform check using `Capacitor.getPlatform()` as shown in the [usage examples](#usage). ### What is the difference between opening the settings and requesting an exemption? The `openBatteryOptimizationSettings()` method opens the battery optimization settings page, where the user has to find and exclude your app manually. The `requestIgnoreBatteryOptimization()` method asks the user directly to exempt your app, but requires the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` manifest permission and is subject to Google Play policy restrictions. ### Do I need to add any permissions to my app? Only if you want to use `requestIgnoreBatteryOptimization()`. In that case, add the `REQUEST_IGNORE_BATTERY_OPTIMIZATIONS` permission to your `AndroidManifest.xml` as described in the [Installation](#installation) section. Checking the battery optimization status and opening the settings page do not require any permissions. ### Can requesting an exemption get my app rejected from Google Play? 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. Use `requestIgnoreBatteryOptimization()` only if your app meets an acceptable use case, or open the battery optimization settings with `openBatteryOptimizationSettings()` instead. See the [Android documentation](https://developer.android.com/training/monitoring-device-state/doze-standby.html#support_for_other_use_cases) for more information. ### Why is background work in my app delayed on Android? When battery optimization is enabled, Android defers background work under Doze and App Standby, which can delay tasks your app performs in the background. You can use `isBatteryOptimizationEnabled()` to detect this and guide the user to exclude your app from battery optimization. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/): Run a foreground service on Android. - [Background Task](https://capawesome.io/docs/sdks/capacitor/background-task/): Run background tasks in your app. - [Battery](https://capawesome.io/docs/sdks/capacitor/battery/): Access battery information. ## 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/). ## 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). # Capacitor Android Dark Mode Support Plugin Capacitor plugin for seamless Android dark mode support. Enhance user experience with `prefers-color-scheme` CSS media feature compatibility. ## Use Cases The Android Dark Mode Support plugin is typically used whenever an app should respect the system-wide dark mode setting on Android, for example: - **Automatic dark theme switching**: Apply dark styles with `prefers-color-scheme` CSS media queries that correctly reflect the Android system setting. - **Consistent theming**: Ensure the web view of your Capacitor app matches the user's system-wide dark mode preference without a manual in-app toggle. - **Framework theming**: Let UI frameworks whose dark themes rely on the `prefers-color-scheme` media feature switch themes correctly on Android. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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. ## 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. ## FAQ ### Why does `prefers-color-scheme` not work in my Capacitor app on Android? By default, the `prefers-color-scheme` CSS media feature may not correctly reflect the system dark mode setting in the Android web view. This plugin enables the correct behavior of the media feature so that your CSS dark mode styles are applied when the user has enabled dark mode on their device. ### Do I need to call any methods to use this plugin? No, the plugin only needs to be installed. Once installed, it enables the correct behavior of the `prefers-color-scheme` CSS media feature without any additional code. See the [Usage](#usage) section for more information. ### Does this plugin work on iOS or Web? No, this plugin only provides an Android implementation, as the name suggests. It exists to fix the dark mode behavior of the Android web view. ### Can I change the version of the `androidx.webkit` dependency? Yes, you can define the `$androidxWebkitVersion` project variable in your app's `variables.gradle` file to change the default version (`1.9.0`) of the `androidx.webkit:webkit` dependency. This can be useful if you encounter dependency conflicts with other plugins in your project. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Accessibility Preferences](https://capawesome.io/docs/sdks/capacitor/accessibility-preferences/): Read the user's system accessibility preferences. - [Android Edge-to-Edge Support](https://capawesome.io/docs/sdks/capacitor/android-edge-to-edge-support/): Support edge-to-edge display on Android and set the background color of the status bar and navigation bar. - [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/): Set the background color and button style of the navigation bar. ## 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/). ## 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). # Capacitor Android Edge-to-Edge Support Plugin 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. ## Use Cases The Android Edge-to-Edge Support plugin is typically used whenever an app needs to deal with Android's edge-to-edge display behavior, for example: - **Preserving traditional layouts**: Prevent Android's edge-to-edge changes from affecting apps that haven't been designed to support it by applying proper insets to the web view. - **Styling the system bars**: Set the background color of the status bar and navigation bar so they match your app's design. - **Switching between light and dark themes**: Update the system bar background colors together with the [SystemBars](https://capacitorjs.com/docs/apis/system-bars) plugin when the user changes the theme. - **Custom inset handling**: Read the insets that are currently applied to the web view to fine-tune your own layout. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | | 7.x.x | 7.x.x | Deprecated | ## Guides - [Capacitor Edge-to-Edge & Safe Areas: The Complete Guide](https://capawesome.io/blog/capacitor-edge-to-edge-and-safe-areas-guide/) ## 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 applies insets to the web view automatically once installed; the following examples show how to enable or disable the edge-to-edge mode, read the applied insets, and set the system bar colors for light and dark themes. ### Enable or disable the edge-to-edge mode Enable or disable the edge-to-edge mode at runtime. Only available on Android: ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; const enable = async () => { await EdgeToEdge.enable(); }; const disable = async () => { await EdgeToEdge.disable(); }; ``` ### Get the applied insets Read the insets that are currently applied to the web view, for example to fine-tune your own layout. Only available on Android: ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; const getInsets = async () => { const result = await EdgeToEdge.getInsets(); console.log('Insets:', result); }; ``` ### Set the system bar colors for light and dark themes Set the background color of the status bar and navigation bar, for example when the user switches between a light and a dark theme. It's recommended to combine this with the `setStyle(...)` method of the [SystemBars](https://capacitorjs.com/docs/apis/system-bars) plugin so the system bar icons remain readable: ``` import { EdgeToEdge } from '@capawesome/capacitor-android-edge-to-edge-support'; import { SystemBars, SystemBarsStyle } from '@capacitor/core'; 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. ### Does this plugin enable edge-to-edge mode by default? No, 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 web view, preventing Android's edge-to-edge changes from affecting apps that haven't been designed to support it. You can still enable or disable the edge-to-edge mode at runtime using the `enable()` and `disable()` methods. ### Why does my layout break when the keyboard is displayed? If you are using the [Capacitor Keyboard](https://capacitorjs.com/docs/apis/keyboard) plugin, make sure the `resizeOnFullScreen` property is set to `false` (the default) in your Capacitor configuration file. Otherwise, the web view will be resized to fit the screen, which may cause issues with this plugin. See the [Installation](#installation) section for more information. ### Does this plugin work on iOS or Web? No, this plugin only provides an Android implementation, as the name suggests. All methods are only available on Android since it addresses Android's edge-to-edge display behavior. ## Related Plugins - [Android Dark Mode Support](https://capawesome.io/docs/sdks/capacitor/android-dark-mode-support/): Support dark mode on Android via the `prefers-color-scheme` CSS media feature. - [Home Indicator](https://capawesome.io/docs/sdks/capacitor/home-indicator/): Hide and show the iOS home indicator. - [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/): Set the background color and button style of the navigation bar. ## 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/). ## 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). # Capacitor Android Foreground Service Plugin Capacitor plugin to run a foreground service on Android. ## Use Cases The Android Foreground Service plugin is typically used whenever an app needs to keep running a task while it is in the background, for example: - **Location tracking**: Run long-running use cases that require location access, such as navigation or location sharing, using the `Location` service type. - **Audio recording**: Continue microphone capture from the background, such as voice recorders or communication apps, using the `Microphone` service type. - **Long-running tasks with user awareness**: Show a persistent notification with a title, body, and icon while your app performs work in the background. - **Interactive notifications**: Add buttons to the foreground service notification and react to them via the `buttonClicked` event. - **Bringing the app back to the foreground**: Move the app to the foreground programmatically, for example in response to a notification button click. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Demo A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | | ------- | | | ## 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. ## Usage The following examples show how to start, update, and stop a foreground service and how to create and delete notification channels. ### Start a foreground service Start the foreground service with a persistent notification. You can optionally add buttons to the notification, which trigger the `buttonClicked` event when tapped. Only available on Android: ``` 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', }); }; ``` ### Update the foreground service notification Update the notification details of the running foreground service, for example to show the progress of a task. Only available on Android: ``` import { ForegroundService } from '@capawesome-team/capacitor-android-foreground-service'; const updateForegroundService = async () => { await ForegroundService.updateForegroundService({ id: 1, title: 'Title', body: 'Body', smallIcon: 'ic_stat_icon_config_sample', }); }; ``` ### Stop the foreground service Stop the running foreground service when the background work is done. Only available on Android: ``` import { ForegroundService } from '@capawesome-team/capacitor-android-foreground-service'; const stopForegroundService = async () => { await ForegroundService.stopForegroundService(); }; ``` ### Create and delete notification channels Create a notification channel for the foreground service notification. If not invoked, the plugin creates a channel with name and description set to "Default". Only available on Android (SDK 26+): ``` import { ForegroundService, Importance } from '@capawesome-team/capacitor-android-foreground-service'; 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). ### Which permissions does this plugin require? You need to add the `FOREGROUND_SERVICE` permission along with the permission for each foreground service type you use (for example `FOREGROUND_SERVICE_LOCATION`) to your `AndroidManifest.xml`, see the [Installation](#installation) section. On Android 13+, you should also use `checkPermissions()` and `requestPermissions()` to request the permission to display notifications. ### Why does `moveToForeground` not work? On Android SDK 23+, the user must grant the manage overlay permission before the app can be moved to the foreground. Use the `requestManageOverlayPermission()` method to request the permission and the `checkManageOverlayPermission()` method to check whether it has been granted. ### Which foreground service types are supported? The plugin currently supports the `Location` service type for long-running use cases that require location access and the `Microphone` service type for continued microphone capture from the background. Make sure to declare the matching permission and the `android:foregroundServiceType` attribute in your `AndroidManifest.xml`, see the [Installation](#installation) section. ### Does this plugin work on iOS or Web? No, this plugin only provides an Android implementation, as the name suggests. Foreground services are an Android concept and all methods are only available on Android. ## Related Plugins - [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/): Manage battery optimization settings and request exemptions on Android. - [Background Task](https://capawesome.io/docs/sdks/capacitor/background-task/): Run background tasks in your Capacitor app. - [Keep Awake](https://capawesome.io/docs/sdks/capacitor/keep-awake/): Keep the screen awake while your app is in use. ## 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/). ## 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). # Capacitor Android Intent Launcher Plugin Capacitor plugin to launch arbitrary Android intents. ## Features - 🚀 **Start activities**: Launch any Android activity via a custom intent and read its result. - 🔍 **Resolve activities**: Check whether an activity can handle an intent before launching it. - 🎯 **Explicit & implicit intents**: Target a specific component or let the system pick a handler. - 📦 **Extras & flags**: Attach primitive extras and intent flags. - 🤝 **Compatibility**: Works alongside the [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) and [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/) plugins. - 🔁 **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! ## Use Cases The Android Intent Launcher plugin is typically used whenever an app needs to interact with system screens or other apps that no dedicated plugin covers, for example: - **Opening system screens**: Open Android system screens such as the app settings screen via the `android.settings.APPLICATION_DETAILS_SETTINGS` action. - **Integrating with other apps**: Launch a specific activity of another app via an explicit intent and read its result code and result data once it finishes. - **Sharing and composing**: Share plain text via the `android.intent.action.SEND` action or compose an email via the `android.intent.action.SENDTO` action. - **Feature detection**: Check whether an activity exists that can handle an intent before launching it, for example to conditionally show a button in your UI. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-intent-launcher` 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-intent-launcher npx cap sync ``` This plugin is only available on **Android**. On iOS and Web, all methods reject as unimplemented. ### Android No additional configuration is required for this plugin. However, on Android 11 (API level 30) and higher, [package visibility](https://developer.android.com/training/package-visibility) restricts which apps and intents your app can see. To launch or resolve intents that target other apps, you may need to declare matching [``](https://developer.android.com/guide/topics/manifest/queries-element) entries in your app's `AndroidManifest.xml`. See [Package visibility](#package-visibility) below. ### iOS This plugin has **no iOS implementation**. Intents are an Android-only concept. All methods reject as unimplemented on iOS. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to start an activity for an intent and check whether an intent can be resolved. ### Start an activity Launch an activity for the given intent. The intent is started via the `startActivityForResult(...)` API, so the result code and result data of the launched activity are returned once it finishes. Only available on Android: ``` import { AndroidIntentLauncher } from '@capawesome/capacitor-android-intent-launcher'; const startActivity = async () => { const { resultCode } = await AndroidIntentLauncher.startActivity({ action: 'android.intent.action.VIEW', dataUri: 'https://capawesome.io', }); return resultCode; }; ``` ### Check whether an intent can be resolved Check whether an activity exists that can handle the given intent before launching it. On Android 11 (API level 30) and higher, the result is affected by [package visibility](#package-visibility). Only available on Android: ``` import { AndroidIntentLauncher } from '@capawesome/capacitor-android-intent-launcher'; const canResolveActivity = async () => { const { canResolve } = await AndroidIntentLauncher.canResolveActivity({ action: 'android.intent.action.VIEW', dataUri: 'https://capawesome.io', }); return canResolve; }; ``` ## API - [`canResolveActivity(...)`](#canresolveactivity) - [`startActivity(...)`](#startactivity) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### canResolveActivity(...) ``` canResolveActivity(options: CanResolveActivityOptions) => Promise ``` Check whether an activity exists that can handle the given intent. This is a wrapper around the `PackageManager.resolveActivity(...)` API. On Android 11 (API level 30) and higher, the result is affected by package visibility. Your app may need to declare matching `<queries>` entries in its `AndroidManifest.xml` for the intent to be resolved. Only available on Android. | Param | Type | | ------------- | ---------------------- | | **`options`** | `StartActivityOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startActivity(...) ``` startActivity(options: StartActivityOptions) => Promise ``` Launch an activity for the given intent. The intent is started via the `startActivityForResult(...)` API so the result code and result data of the launched activity are returned once it finishes. This is the power-user escape hatch for system screens and app integrations that no dedicated plugin covers. Prefer a typed plugin (such as [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) or [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/)) where one exists. Only available on Android. | Param | Type | | ------------- | ---------------------- | | **`options`** | `StartActivityOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### CanResolveActivityResult | Prop | Type | Description | Since | | ---------------- | --------- | ------------------------------------------------------------- | ----- | | **`canResolve`** | `boolean` | Whether or not an activity exists that can handle the intent. | 0.1.0 | #### StartActivityOptions | Prop | Type | Description | Since | | ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ | | **`action`** | `string` | The action of the intent. | 0.1.0 | | **`categories`** | `string[]` | The categories to add to the intent. | 0.1.0 | | **`className`** | `string` | The fully qualified class name of the component to launch. Must be used together with the `packageName` property to create an explicit intent that targets a specific component. | 0.1.0 | | **`dataUri`** | `string` | The data URI of the intent. | 0.1.0 | | **`extras`** | \`{ \[key: string\]: string | number | boolean; }\` | | **`flags`** | `number` | The flags to add to the intent. Multiple flags can be combined using the bitwise OR operator. | 0.1.0 | | **`packageName`** | `string` | The package name of the component to launch. If used without the `className` property, the intent is restricted to the given package. If used together with the `className` property, an explicit intent that targets a specific component is created. | 0.1.0 | | **`type`** | `string` | The MIME type of the intent data. | 0.1.0 | #### StartActivityResult | Prop | Type | Description | Since | | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- | | **`dataUri`** | \`string | null\` | The data URI returned by the launched activity. | | **`resultCode`** | `number` | The result code returned by the launched activity. The value is `-1` if the activity finished successfully (`RESULT_OK`), `0` if it was canceled (`RESULT_CANCELED`) or any other custom result code set by the launched activity. | 0.1.0 | ### Type Aliases #### CanResolveActivityOptions `StartActivityOptions` ## Common Intents The following table lists a few common intents to get you started. See the [Android documentation](https://developer.android.com/reference/android/content/Intent) for the full list of actions, categories, extras and flags. | Use case | `action` | `dataUri` | `type` | `extras` | | ----------------- | ----------------------------------------------- | ------------------------- | ------------ | ------------------------------------------------- | | Open a website | `android.intent.action.VIEW` | `https://capawesome.io` | – | – | | Open the dialer | `android.intent.action.DIAL` | `tel:+12025550123` | – | – | | Compose an email | `android.intent.action.SENDTO` | `mailto:hi@example.com` | – | `{ 'android.intent.extra.SUBJECT': 'Hi' }` | | Share plain text | `android.intent.action.SEND` | – | `text/plain` | `{ 'android.intent.extra.TEXT': 'Hello world!' }` | | Open app settings | `android.settings.APPLICATION_DETAILS_SETTINGS` | `package:com.example.app` | – | – | This plugin is intended as a **last resort** for system screens and app integrations that no dedicated plugin covers. Prefer a typed plugin where one exists — for example [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) for system settings screens or [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/) for opening URLs and other apps. ## Package Visibility On Android 11 (API level 30) and higher, [package visibility](https://developer.android.com/training/package-visibility) limits which other apps your app can interact with. This affects both `startActivity(...)` (an unresolvable intent rejects with the error code `ACTIVITY_NOT_FOUND`) and `canResolveActivity(...)` (which returns `false` for intents your app cannot see). If you launch or resolve intents that target other apps, declare the intents you query in your app's `AndroidManifest.xml`. The plugin cannot predeclare arbitrary intents on your behalf. ``` ``` ## FAQ ### How is this plugin different from other similar plugins? It launches arbitrary Android intents — explicit or implicit — so you can open system screens or hand off to other apps even when no dedicated plugin covers them, and read the activity result back. You can attach primitive extras and intent flags, and resolve whether an activity can handle an intent before launching it, all through a fully typed API kept current with the latest Capacitor version. When a focused plugin already exists for your target, such as Settings Launcher or App Launcher, that is a fine choice; when you need full control over the raw intent, this plugin is built for it. ### Why does `startActivity` reject with the error code `ACTIVITY_NOT_FOUND`? This happens when no activity exists that can handle the given intent. On Android 11 (API level 30) and higher, this can also be caused by [package visibility](#package-visibility), which limits which other apps your app can see. In that case, declare the intents you query in your app's `AndroidManifest.xml` using `` entries. ### Does this plugin work on iOS or Web? No, intents are an Android-only concept, so this plugin has no iOS or Web implementation. On iOS and Web, all methods reject as unimplemented. ### When should I use this plugin instead of a dedicated plugin? This plugin is intended as a last resort for system screens and app integrations that no dedicated plugin covers. Prefer a typed plugin where one exists, for example [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) for system settings screens or [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/) for opening URLs and other apps. ### Can I pass complex data as intent extras? No, only primitive values (string, number and boolean) are supported as intent extras. Intent flags can be attached as well and multiple flags can be combined using the bitwise OR operator. ### How do I know whether the launched activity was successful? The `startActivity(...)` method resolves with the result code returned by the launched activity once it finishes. The value is `-1` if the activity finished successfully (`RESULT_OK`), `0` if it was canceled (`RESULT_CANCELED`), or any other custom result code set by the launched activity. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/): Check if an app can be opened and open it. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open a file with the default application. - [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/): Open native settings screens with a typed API. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-intent-launcher/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-intent-launcher/LICENSE). # Capacitor Android SMS Retriever Plugin Capacitor plugin for OTP autofill on Android via the SMS User Consent and Phone Number Hint APIs. ## Features - 💬 **SMS User Consent**: Read an incoming verification SMS after a one-tap system consent dialog. - 📞 **Phone Number Hint**: Prefill the user's phone number via the system bottom sheet. - 🔒 **No SMS permissions**: Uses Play-policy-safe APIs that require no SMS permissions. - 🤝 **Compatibility**: Works alongside the [Password Autofill](https://capawesome.io/docs/sdks/capacitor/password-autofill/), [SIM](https://capawesome.io/docs/sdks/capacitor/sim/) and [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/) plugins. - 🔁 **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! ## Use Cases The Android SMS Retriever plugin is typically used in phone number verification flows, for example: - **OTP autofill**: Retrieve an incoming verification SMS after a one-tap system consent dialog and extract the one-time code, so the user doesn't have to type it manually. - **Phone number prefill**: Prefill the phone number input field of your sign-up or login form via the system bottom sheet. - **Play-policy-safe SMS verification**: Implement SMS-based verification without requesting any SMS permissions, using the SMS User Consent and Phone Number Hint APIs. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-sms-retriever` 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-sms-retriever npx cap sync ``` This plugin is only available on **Android**. On iOS and Web, all methods reject as unimplemented (see [iOS](#ios) below). ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$playServicesAuthVersion` version of `com.google.android.gms:play-services-auth` (default: `21.5.0`) - `$playServicesAuthApiPhoneVersion` version of `com.google.android.gms:play-services-auth-api-phone` (default: `18.3.0`) ### iOS This plugin has **no iOS implementation** and you don't need one. iOS already autofills one-time codes from incoming SMS messages in `WKWebView` when the input field uses `autocomplete="one-time-code"`: ``` ``` All methods reject as unimplemented on iOS. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to prefill the user's phone number and retrieve a verification SMS. ### Prefill the user's phone number Request the user's phone number via the Phone Number Hint API. A system bottom sheet is displayed that lets the user pick one of the phone numbers associated with the device, for example to prefill a phone number input field. Only available on Android: ``` import { AndroidSmsRetriever } from '@capawesome/capacitor-android-sms-retriever'; const requestPhoneNumber = async () => { const { phoneNumber } = await AndroidSmsRetriever.requestPhoneNumber(); return phoneNumber; }; ``` ### Retrieve a verification SMS Retrieve an incoming verification SMS via the SMS User Consent API. A system consent dialog is displayed when a matching SMS is received, and the promise resolves with the full message text once the user consents, so your app can extract the one-time code itself (see [Extracting the One-Time Code](#extracting-the-one-time-code)). Only available on Android: ``` import { AndroidSmsRetriever } from '@capawesome/capacitor-android-sms-retriever'; const retrieveSms = async () => { const { message } = await AndroidSmsRetriever.retrieveSms(); // Extract the one-time code from the message. const code = message.match(/\d{6}/)?.[0]; return code; }; ``` ## API - [`requestPhoneNumber()`](#requestphonenumber) - [`retrieveSms(...)`](#retrievesms) - [Interfaces](#interfaces) ### requestPhoneNumber() ``` requestPhoneNumber() => Promise ``` Request the user's phone number via the Phone Number Hint API. A system bottom sheet is displayed that lets the user pick one of the phone numbers associated with the device. The selected phone number is returned so it can be used to prefill a phone number input field. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### retrieveSms(...) ``` retrieveSms(options?: RetrieveSmsOptions | undefined) => Promise ``` Retrieve an incoming verification SMS via the SMS User Consent API. A system consent dialog is displayed when a matching SMS is received. The promise resolves with the full message text once the user consents, so the app can extract the one-time code itself. The underlying broadcast waits up to 5 minutes for a matching SMS. If no SMS is received within this time, the promise rejects with the error code `TIMEOUT`. Only available on Android. | Param | Type | | ------------- | -------------------- | | **`options`** | `RetrieveSmsOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### RequestPhoneNumberResult | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------- | ----- | | **`phoneNumber`** | `string` | The phone number selected by the user. | 0.1.0 | #### RetrieveSmsResult | Prop | Type | Description | Since | | ------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`message`** | `string` | The full text of the retrieved SMS message. The app is responsible for extracting the one-time code from the message. | 0.1.0 | #### RetrieveSmsOptions | Prop | Type | Description | Since | | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`senderPhoneNumber`** | `string` | The phone number of the sender to filter incoming messages by. If not provided, the SMS from any sender is retrieved. | 0.1.0 | ## Extracting the One-Time Code The `retrieveSms(...)` method resolves with the full text of the incoming SMS message. Your app is responsible for extracting the one-time code from the message, for example with a regular expression: ``` const { message } = await AndroidSmsRetriever.retrieveSms(); const code = message.match(/\d{6}/)?.[0]; ``` For the SMS User Consent API to detect a message, the SMS must: - contain a one-time code that the user sends back to your server to complete the verification, - be no longer than 140 bytes, - not originate from a phone number in the user's contacts. The underlying broadcast waits up to 5 minutes for a matching SMS. If no message is received within this time, the promise rejects with the error code `TIMEOUT`. ## FAQ ### How is this plugin different from other similar plugins? It implements OTP autofill on Android through the SMS User Consent and Phone Number Hint APIs, so you can read a verification SMS and prefill the user's phone number without requesting any SMS permissions and while staying within Google Play policy. It's a focused, fully typed API that handles the system consent dialog and bottom sheet for you, and it's actively maintained against the latest Capacitor and Android versions. On iOS you don't need it at all — the WebView already autofills one-time codes — and the README explains exactly how. ### Does this plugin require any SMS permissions? No, the plugin uses the SMS User Consent and Phone Number Hint APIs, which are Play-policy-safe and require no SMS permissions. Instead of reading SMS messages silently, the user explicitly consents via a one-tap system dialog before your app receives the message. ### Does this plugin work on iOS or Web? No, this plugin only provides an Android implementation and you don't need one on iOS. iOS already autofills one-time codes from incoming SMS messages in `WKWebView` when the input field uses `autocomplete="one-time-code"`. On iOS and Web, all methods reject as unimplemented. ### Why does `retrieveSms` reject with the error code `TIMEOUT`? The underlying broadcast waits up to 5 minutes for a matching SMS. If no matching message is received within this time, the promise rejects with the error code `TIMEOUT`. Make sure the SMS is sent while the broadcast is active and meets the requirements of the SMS User Consent API. ### Why is my SMS message not detected? For the SMS User Consent API to detect a message, the SMS must contain a one-time code that the user sends back to your server to complete the verification, be no longer than 140 bytes, and not originate from a phone number in the user's contacts. See [Extracting the One-Time Code](#extracting-the-one-time-code) for more information. ### Can I filter incoming messages by sender? Yes, you can pass the `senderPhoneNumber` option to the `retrieveSms(...)` method to only retrieve messages from a specific sender. If not provided, the SMS from any sender is retrieved. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Password Autofill](https://capawesome.io/docs/sdks/capacitor/password-autofill/): Save passwords to the platform credential store. - [SIM](https://capawesome.io/docs/sdks/capacitor/sim/): Read SIM card and carrier information. - [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/): Open the native SMS composer prefilled with recipients and a message body. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-sms-retriever/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/android-sms-retriever/LICENSE). # Capacitor App Icon Plugin Capacitor plugin to change the app icon at runtime. ## Features - 🎨 **Alternate icons**: Switch the home-screen icon between the icons your app declares. - 🔍 **Current icon**: Read the name of the icon that is currently in use. - 🔄 **Reset**: Restore the default icon at any time. - 🤝 **Compatibility**: Works alongside the [App Shortcuts](https://capawesome.io/docs/sdks/capacitor/app-shortcuts/) and [Badge](https://capawesome.io/docs/sdks/capacitor/badge/) 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! ## Use Cases The App Icon plugin is typically used to personalize or refresh the app's appearance on the home screen, for example: - **Seasonal campaigns**: Switch to a themed icon for events like Christmas and restore the default icon afterwards. - **Premium personalization**: Let paying users choose their favorite icon from a set of alternate icons. - **In-app icon picker**: Build a settings screen that lists all icons and highlights the one currently in use. - **Brand updates**: Roll out a new logo as an alternate icon and switch to it at runtime. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-icon` 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-icon npx cap sync ``` This plugin cannot add icons dynamically. Every icon you want to switch to must be **declared by the app beforehand**. The following sections describe how to declare alternate icons on each platform. ### Android On Android, each alternate icon is declared as an [``](https://developer.android.com/guide/topics/manifest/activity-alias-element) that points at the launcher activity. The plugin enables the requested alias and disables the others. Open your `AndroidManifest.xml` and remove the launcher `` from your `.MainActivity`. Then add one `` for the default icon and one for each alternate icon. Exactly one alias must be `android:enabled="true"` (the default icon); all others must be `android:enabled="false"`. ``` ``` The icon name passed to `setIcon(...)` is the alias name **without the leading dot** (e.g. `Christmas`). Add the referenced icon resources (e.g. `@mipmap/ic_launcher_christmas`) to your `res/mipmap-*` folders. > [!NOTE] The behavior after a change depends on the launcher. Some launchers apply the new icon only after the app's task is closed, and a few kill the app despite the plugin requesting otherwise. Shortcuts that were pinned to a now-disabled alias may stop working. ### iOS On iOS, alternate icons are declared as additional app icon sets in the asset catalog. Add one icon set per alternate icon (e.g. `Christmas` and `Halloween`) next to your primary `AppIcon` in `Assets.xcassets` and register the names (space-separated) in the build settings of your app target: ``` ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = "Christmas Halloween"; ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; ``` In Xcode, these settings are called **Alternate App Icon Sets** and **Include All App Icon Assets** in the *Asset Catalog Compiler* section of the build settings. The icon name passed to `setIcon(...)` is the name of the icon set (e.g. `Christmas`), case-sensitive. The primary icon (`AppIcon`) is restored with `resetIcon(...)`. > [!WARNING] Do **not** give an alternate icon set a name that starts with `AppIcon` (e.g. `AppIconChristmas`). In our testing on physical devices running iOS 26, alternate icons whose name shares the primary icon set's `AppIcon` prefix are not rendered on the home screen: `setIcon(...)` succeeds, but the system displays a blank placeholder icon instead of the alternate icon. The iOS Simulator is not affected, so make sure to test on a real device. This is an undocumented iOS behavior; using names without the `AppIcon` prefix is safe on all iOS versions. > > [!NOTE] The system shows a user-visible alert every time the icon changes, and the icon cannot be changed while the app is in the background. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check for alternate icon support, read the current icon, set an alternate icon, and reset to the default icon. ### Check if changing the app icon is supported Use `isAvailable()` to check whether the current device supports alternate icons. On Android, this always resolves to `true`; on iOS, it resolves to the value of `supportsAlternateIcons`. Only available on Android and iOS: ``` import { AppIcon } from '@capawesome/capacitor-app-icon'; const isAvailable = async () => { const { available } = await AppIcon.isAvailable(); return available; }; ``` ### Get the current icon Read the name of the icon that is currently in use, for example to highlight it in an icon picker. Returns `null` if the default icon is in use. Only available on Android and iOS: ``` import { AppIcon } from '@capawesome/capacitor-app-icon'; const getCurrentIcon = async () => { const { icon } = await AppIcon.getCurrentIcon(); return icon; }; ``` ### Set an alternate icon Change the app icon to an alternate icon that your app has declared beforehand (see [Installation](#installation)). Only available on Android and iOS: ``` import { AppIcon } from '@capawesome/capacitor-app-icon'; const setIcon = async () => { await AppIcon.setIcon({ icon: 'Christmas' }); }; ``` ### Reset to the default icon Restore the default app icon at any time. Only available on Android and iOS: ``` import { AppIcon } from '@capawesome/capacitor-app-icon'; const resetIcon = async () => { await AppIcon.resetIcon(); }; ``` ## API - [`getCurrentIcon()`](#getcurrenticon) - [`isAvailable()`](#isavailable) - [`resetIcon()`](#reseticon) - [`setIcon(...)`](#seticon) - [Interfaces](#interfaces) ### getCurrentIcon() ``` getCurrentIcon() => Promise ``` Get the name of the icon that is currently in use. Returns `null` if the default icon is in use. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if changing the app icon is supported on the current device. On Android, this always resolves to `true`. On iOS, this resolves to the value of `supportsAlternateIcons`. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### resetIcon() ``` resetIcon() => Promise ``` Restore the default app icon. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### setIcon(...) ``` setIcon(options: SetIconOptions) => Promise ``` Change the app icon to the alternate icon with the given name. The icon must be declared by the app beforehand. See the setup instructions for [Android](https://capawesome.io/docs/sdks/capacitor/app-icon/#android) and [iOS](https://capawesome.io/docs/sdks/capacitor/app-icon/#ios) for more information. Only available on Android and iOS. | Param | Type | | ------------- | ---------------- | | **`options`** | `SetIconOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetCurrentIconResult | Prop | Type | Description | Since | | ---------- | -------- | ----------- | -------------------------------------------------------------------------------------------- | | **`icon`** | \`string | null\` | The name of the icon that is currently in use. Returns `null` if the default icon is in use. | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------------------------ | ----- | | **`available`** | `boolean` | Whether or not changing the app icon is supported on the current device. | 0.1.0 | #### SetIconOptions | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`icon`** | `string` | The name of the alternate icon to use. On Android, this is the name of the `<activity-alias>` (without the leading dot). On iOS, this is the name of the alternate app icon set in the asset catalog. | 0.1.0 | ## FAQ ### How is this plugin different from other similar plugins? It changes the app icon at runtime on both Android and iOS through one fully typed API — switching to a declared alternate icon, reading the icon currently in use, resetting to the default, and checking whether the device supports alternate icons. It documents the platform realities honestly, from Android's launcher-dependent behavior to the system alert iOS shows on every change, so there are no surprises in production. The plugin is actively maintained against the latest Capacitor and OS versions. ### Can I add new app icons at runtime? No, this plugin cannot add icons dynamically. Every icon you want to switch to must be declared by the app beforehand, as an `` in the `AndroidManifest.xml` on Android and as an alternate app icon set in the asset catalog on iOS. See the [Installation](#installation) section for detailed setup instructions. ### What icon name do I pass to `setIcon`? On Android, it is the name of the `` without the leading dot (e.g. `Christmas`). On iOS, it is the name of the alternate app icon set in the asset catalog. ### Why is the new icon not applied immediately on Android? The behavior after a change depends on the launcher. Some launchers apply the new icon only after the app's task is closed, and a few even kill the app despite the plugin requesting otherwise. Also note that shortcuts pinned to a now-disabled alias may stop working. ### Why does iOS show an alert when the icon changes? The system shows a user-visible alert every time the icon changes. This is standard iOS behavior and cannot be suppressed. Also note that the icon cannot be changed while the app is in the background. ### How do I know if the device supports alternate icons? Call `isAvailable()` before offering an icon picker. On Android, it always resolves to `true`. On iOS, it resolves to the value of `supportsAlternateIcons`. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Shortcuts](https://capawesome.io/docs/sdks/capacitor/app-shortcuts/): Manage app shortcuts and quick actions on the home screen. - [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/): Assist your users with native app updates. - [Badge](https://capawesome.io/docs/sdks/capacitor/badge/): Access and update the badge number of the app icon. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-icon/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-icon/LICENSE). # Capacitor App Integrity Plugin Capacitor plugin to verify app and device integrity using the Play Integrity API (Android) and App Attest (iOS). ## Features - 🤖 **Play Integrity**: Request standard and classic integrity tokens from the Play Integrity API on Android. - 🍏 **App Attest**: Generate hardware-backed keys, attest them and generate assertions with App Attest on iOS. - 🛡️ **Server-verifiable**: Tokens and assertions are verified on your backend via Google and Apple, so they cannot be spoofed on the device. - 🤝 **Compatibility**: Works alongside the [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/) plugin for additional client-side checks. - 📦 **SPM**: Supports Swift Package Manager and CocoaPods 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! ## Use Cases The App Integrity plugin is typically used to protect your backend from requests that do not originate from your genuine, unmodified app, for example: - **API protection**: Attach integrity tokens or assertions to requests to sensitive endpoints so your server can reject tampered clients. - **Fraud prevention**: Verify high-value actions such as payments or account changes with a classic integrity request or an App Attest assertion. - **Account security**: Protect login and registration flows against bots and automated abuse. - **Gated content**: Grant access to protected resources only after your server has verified the integrity verdict. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-integrity` 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-integrity npx cap sync ``` ### Android The Play Integrity API must be set up in the Google Play Console before you can request integrity tokens. Follow the [official setup instructions](https://developer.android.com/google/play/integrity/setup) to link a Google Cloud project to your app. You need the **Google Cloud project number** of the linked project for the standard request flow. #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$playIntegrityVersion` version of `com.google.android.play:integrity` (default: `1.6.0`) ### iOS App Attest requires the **App Attest capability**. Add it to your app in Xcode under **Signing & Capabilities** or add the following entitlement to your `.entitlements` file: ``` com.apple.developer.devicecheck.appattest-environment development ``` Use the value `development` to attest against Apple's sandbox servers during development and `production` for App Store builds. Note that App Attest is not supported on simulators, so you need a real device for testing. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check for integrity attestation support, request an integrity token on Android, attest a key on iOS, and generate an assertion on iOS. ### Check if integrity attestation is available Use `isAvailable()` before requesting tokens or attestations. On Android, this checks whether Google Play Services is available; on iOS, it checks whether the App Attest service is supported (it is not supported on simulators): ``` import { AppIntegrity } from '@capawesome/capacitor-app-integrity'; const isAvailable = async () => { const { available } = await AppIntegrity.isAvailable(); return available; }; ``` ### Request an integrity token on Android For the recommended standard request flow, prepare the integrity token provider once (for example at app start) with your Google Cloud project number, since the preparation can take several seconds. Then request tokens with a `requestHash`. For infrequent, high-value actions you can use the classic request flow with a `nonce` instead. Only available on Android: ``` import { AppIntegrity } from '@capawesome/capacitor-app-integrity'; import { Capacitor } from '@capacitor/core'; const prepareIntegrityToken = async () => { // Only available on Android await AppIntegrity.prepareIntegrityToken({ cloudProjectNumber: 123456789012, }); }; const requestIntegrityToken = async () => { // Only available on Android if (Capacitor.getPlatform() === 'android') { // Standard request (recommended, requires a prior call to `prepareIntegrityToken(...)`) const { token } = await AppIntegrity.requestIntegrityToken({ requestHash: '2cp24z...', }); // Classic request const { token: classicToken } = await AppIntegrity.requestIntegrityToken({ nonce: 'R2Rra24fVm5xa2Mg', }); // Send the token to your server for verification } }; ``` ### Attest a key on iOS Generate a hardware-backed key pair and attest it with a one-time challenge received from your server. The attestation object must be sent to your server for verification with Apple. Only available on iOS: ``` import { AppIntegrity } from '@capawesome/capacitor-app-integrity'; import { Capacitor } from '@capacitor/core'; const attestKey = async () => { // Only available on iOS if (Capacitor.getPlatform() === 'ios') { // 1. Generate a new key pair and store the key identifier const { keyId } = await AppIntegrity.generateKey(); // 2. Attest the key with a one-time challenge received from your server const { attestationObject } = await AppIntegrity.attestKey({ keyId, challenge: 'dGhpc2lzYWNoYWxsZW5nZQ==', }); // 3. Send the attestation object to your server for verification } }; ``` ### Generate an assertion on iOS Once a key is attested, sign client data (usually a JSON payload that includes a one-time challenge from your server) to prove that a request comes from your genuine app. Only available on iOS: ``` import { AppIntegrity } from '@capawesome/capacitor-app-integrity'; import { Capacitor } from '@capacitor/core'; const generateAssertion = async () => { // Only available on iOS if (Capacitor.getPlatform() === 'ios') { const { assertion } = await AppIntegrity.generateAssertion({ keyId: 'Kh0DIEwVJTDJUyIRZ4M9BvJn/i4RSSGDkFvUZOaSm5g=', clientData: 'eyJjaGFsbGVuZ2UiOiJkR2hwYzJsellXTm9ZV3hzWlc1blpRPT0ifQ==', }); // Send the assertion to your server for verification } }; ``` ## API - [`attestKey(...)`](#attestkey) - [`generateAssertion(...)`](#generateassertion) - [`generateKey()`](#generatekey) - [`isAvailable()`](#isavailable) - [`prepareIntegrityToken(...)`](#prepareintegritytoken) - [`requestIntegrityToken(...)`](#requestintegritytoken) - [Interfaces](#interfaces) ### attestKey(...) ``` attestKey(options: AttestKeyOptions) => Promise ``` Attest a key generated with `generateKey()` using Apple's App Attest service. The returned attestation object must be sent to your server, which verifies it with Apple and stores the key identifier for future assertions. Only available on iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `AttestKeyOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### generateAssertion(...) ``` generateAssertion(options: GenerateAssertionOptions) => Promise ``` Generate an assertion for the given client data using an attested key. The returned assertion must be sent to your server along with the client data, where it is verified using the previously stored key identifier. Only available on iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `GenerateAssertionOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### generateKey() ``` generateKey() => Promise ``` Generate a new App Attest key pair. The private key is stored in the Secure Enclave. Store the returned key identifier in your app to attest the key and generate assertions later. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether app integrity attestation is available on this device. On Android, this checks whether Google Play Services is available. On iOS, this checks whether the App Attest service is supported. On iOS, the App Attest service is not supported on simulators. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### prepareIntegrityToken(...) ``` prepareIntegrityToken(options: PrepareIntegrityTokenOptions) => Promise ``` Prepare the integrity token provider for standard integrity token requests. Call this method once, for example at app start, before calling `requestIntegrityToken(...)` with a request hash. The preparation can take several seconds, so it should be done well ahead of the first request. Only available on Android. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `PrepareIntegrityTokenOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### requestIntegrityToken(...) ``` requestIntegrityToken(options: RequestIntegrityTokenOptions) => Promise ``` Request an integrity token from the Play Integrity API. Provide a `requestHash` for a standard request (recommended, requires a prior call to `prepareIntegrityToken(...)`) or a `nonce` for a classic request. The returned token must be sent to your server, which decrypts and verifies it via Google's servers. Only available on Android. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `RequestIntegrityTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### AttestKeyResult | Prop | Type | Description | Since | | ----------------------- | -------- | --------------------------------------------------------------------------------------------------------- | ----- | | **`attestationObject`** | `string` | The attestation object, encoded as a base64 string. Send this to your server for verification with Apple. | 0.1.0 | #### AttestKeyOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`challenge`** | `string` | The one-time challenge received from your server, encoded as a base64 string. The challenge is hashed with SHA-256 on the device before it is passed to the App Attest service. | 0.1.0 | | **`keyId`** | `string` | The identifier of the key to attest, as returned by `generateKey()`. | 0.1.0 | #### GenerateAssertionResult | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------------------------------------------------------- | ----- | | **`assertion`** | `string` | The assertion object, encoded as a base64 string. Send this to your server for verification. | 0.1.0 | #### GenerateAssertionOptions | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`clientData`** | `string` | The client data to sign, encoded as a base64 string. This is usually a JSON payload that includes a one-time challenge received from your server. The client data is hashed with SHA-256 on the device before it is passed to the App Attest service. | 0.1.0 | | **`keyId`** | `string` | The identifier of an attested key, as returned by `generateKey()`. | 0.1.0 | #### GenerateKeyResult | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------- | ----- | | **`keyId`** | `string` | The identifier of the generated key pair. | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether app integrity attestation is available on this device. | 0.1.0 | #### PrepareIntegrityTokenOptions | Prop | Type | Description | Since | | ------------------------ | -------- | ----------------------------------------------------------------------------------------------------- | ----- | | **`cloudProjectNumber`** | `number` | The Google Cloud project number of the project that is linked to your Play Console developer account. | 0.1.0 | #### RequestIntegrityTokenResult | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------- | ----- | | **`token`** | `string` | The integrity token. Send this to your server, which decrypts and verifies it via Google's servers. | 0.1.0 | #### RequestIntegrityTokenOptions | Prop | Type | Description | Since | | ------------------------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`cloudProjectNumber`** | `number` | The Google Cloud project number of the project that is linked to your Play Console developer account. Only used for classic requests. It is required if your app is distributed outside of Google Play. | 0.1.0 | | **`nonce`** | `string` | The one-time nonce for a classic request, encoded as a base64 web-safe no-wrap string. Provide either `nonce` or `requestHash`. | 0.1.0 | | **`requestHash`** | `string` | The request hash for a standard request. Requires a prior call to `prepareIntegrityToken(...)`. Provide either `nonce` or `requestHash`. | 0.1.0 | ## How Verification Works This plugin only provides the **client-side** part of the attestation flow. The returned tokens, attestation objects, and assertions are opaque to your app and **must** be verified on your own server. Never make security decisions based on the client-side result alone. The typical flow looks like this: 1. Your app requests a one-time challenge (or nonce) from your server. 1. Your app calls this plugin to obtain an integrity token (Android) or an attestation object/assertion (iOS) that is bound to the challenge. 1. Your app sends the result to your server. 1. Your server verifies the result: 1. **Android**: Decrypt and verify the integrity token via the [Play Integrity API](https://developer.android.com/google/play/integrity/verdicts) and evaluate the verdicts. 1. **iOS**: Validate the attestation object or assertion as described in [Validating Apps That Connect to Your Server](https://developer.apple.com/documentation/devicecheck/validating-apps-that-connect-to-your-server). 1. Your server grants or denies access based on the verification result. On Android, Google recommends the **standard request** flow (`prepareIntegrityToken(...)` + `requestIntegrityToken({ requestHash })`) for frequent integrity checks. Use the **classic request** flow (`requestIntegrityToken({ nonce })`) only for infrequent, high-value actions. ## FAQ ### How is this plugin different from other similar plugins? It brings both platforms' hardware-backed attestation under one fully typed API — the Play Integrity API on Android with standard and classic requests, and App Attest on iOS with key generation, attestation, and assertions. Every result is meant to be verified server-side via Google and Apple, so integrity decisions can't be spoofed on the device, and an `isAvailable()` check lets you adapt before you request. If client-side root and jailbreak signals are all you need, a lighter check may suffice; if you want tokens and assertions your backend can actually trust, this plugin is built for exactly that. ### Do I need my own server to use this plugin? Yes. This plugin only provides the client-side part of the attestation flow. The returned tokens, attestation objects, and assertions are opaque to your app and must be decrypted and verified on your own server via Google (Android) or Apple (iOS). Never make security decisions based on the client-side result alone. See the [How Verification Works](#how-verification-works) section for the typical flow. ### What is the difference between a standard and a classic request on Android? A standard request is the flow recommended by Google for frequent integrity checks: it requires a prior call to `prepareIntegrityToken(...)` and passes a `requestHash` to `requestIntegrityToken(...)`. A classic request passes a `nonce` instead, requires no preparation, and should only be used for infrequent, high-value actions. ### Why does `isAvailable` return `false` on the iOS Simulator? The App Attest service is not supported on simulators, so you need a real device for testing. On Android, `isAvailable()` checks whether Google Play Services is available. ### What do I need to set up before requesting integrity tokens on Android? The Play Integrity API must be set up in the Google Play Console by linking a Google Cloud project to your app. You need the Google Cloud project number of the linked project for the standard request flow. See the [Installation](#installation) section for more information. ### How is this plugin different from the Root Detection plugin? The [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/) plugin performs client-side checks for rooted and jailbroken devices, which can be helpful but can also be bypassed on a compromised device. The App Integrity plugin provides server-verifiable tokens and assertions that are verified via Google and Apple, so they cannot be spoofed on the device. The two plugins work well together as complementary layers. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Biometrics](https://capawesome.io/docs/sdks/capacitor/biometrics/): Request biometric authentication, such as face or fingerprint recognition. - [Google Play Services](https://capawesome.io/docs/sdks/capacitor/google-play-services/): Check whether Google Play Services is available on the device and prompt the user to install or update it. - [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/): Detect rooted and jailbroken devices with client-side checks. - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/): Securely store key/value pairs such as passwords, tokens or other sensitive information. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-integrity/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-integrity/LICENSE). # Capacitor App Language Plugin Capacitor plugin to manage the app's own language override, independent of the device language. ## Features - 🌐 **Language override**: Set the app's language independently of the device language (Android). - 🔎 **Read language**: Read the app's current language override. - ♻️ **Reset**: Clear the override so the app follows the device language again (Android). - ⚙️ **Settings**: Deep-link to the app's system settings page where the user can change the app language. - 🤝 **Compatibility**: Works alongside the [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugin, which reads the user's localization preferences. - 📦 **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! ## Use Cases The App Language plugin is typically used whenever an app offers its own language selection, for example: - **In-app language switcher**: Let users pick the app language from a settings screen instead of changing the device language (Android). - **Consistent native dialogs**: Make sure natively rendered strings like permission dialogs and notifications match the language of your web UI. - **Settings deep link**: Guide iOS users to the app's system settings page, where they can change the app language. - **System default option**: Offer a "system default" entry that clears the override so the app follows the device language again (Android). ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-language` 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-language npx cap sync ``` This plugin only affects **natively rendered** strings (e.g. permission dialogs, notifications, or plugin-presented sheets). The language of your web UI should be handled by your app's i18n library. ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$androidxAppCompatVersion` version of `androidx.appcompat:appcompat` (default: `1.7.1`) #### Locale Configuration To integrate the app language into the system settings (Android 13+), declare the supported locales in a `res/xml/locales_config.xml` file and reference it from the `` tag in your `AndroidManifest.xml`: ``` ``` ``` ``` To persist the selected language across app restarts on Android 12 and below, add the following `` inside the `` tag of your `AndroidManifest.xml`: ``` ``` > [!NOTE] Setting the language recreates the current activity, which reloads the web view. ### iOS On iOS, the app language can only be changed by the **user** in the system settings. The `setLanguage(...)` and `resetLanguage(...)` methods are therefore not available. Use `openSettings(...)` to deep-link the user to the app's settings page. The per-app language row is only shown in the system settings if the app bundle provides **more than one** localization. Declare the supported localizations in the `Info.plist` file of your app: ``` CFBundleLocalizations en de ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to get, set, and reset the app language override and open the app's settings page. ### Get the current language override Read the BCP 47 language tag of the app's current language override. Returns `null` if no override is set and the app follows the device language: ``` import { AppLanguage } from '@capawesome/capacitor-app-language'; const getLanguage = async () => { const { languageTag } = await AppLanguage.getLanguage(); return languageTag; }; ``` ### Set the app language Set the app's language override, independent of the device language. Note that this recreates the current activity, which reloads the web view. Only available on Android: ``` import { AppLanguage } from '@capawesome/capacitor-app-language'; const setLanguage = async () => { await AppLanguage.setLanguage({ languageTag: 'de-DE' }); }; ``` ### Reset the language override Clear the override so the app follows the device language again. Only available on Android: ``` import { AppLanguage } from '@capawesome/capacitor-app-language'; const resetLanguage = async () => { await AppLanguage.resetLanguage(); }; ``` ### Open the app's settings page Deep-link the user to the app's page in the system settings. On iOS, this is where the user can change the app language: ``` import { AppLanguage } from '@capawesome/capacitor-app-language'; const openSettings = async () => { await AppLanguage.openSettings(); }; ``` ## API - [`getLanguage()`](#getlanguage) - [`openSettings()`](#opensettings) - [`resetLanguage()`](#resetlanguage) - [`setLanguage(...)`](#setlanguage) - [Interfaces](#interfaces) ### getLanguage() ``` getLanguage() => Promise ``` Get the app's current language override. The language override is independent of the device language and only affects natively rendered strings (e.g. permission dialogs). **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the app's settings page in the system settings. On iOS, this is where the user can change the app's language (the language row is only shown if the app bundle provides more than one localization). **Since:** 0.1.0 ______________________________________________________________________ ### resetLanguage() ``` resetLanguage() => Promise ``` Reset the app's language override so the app follows the device language again. Resetting the language recreates the current activity, which reloads the web view. Only available on Android. **Since:** 0.1.0 ______________________________________________________________________ ### setLanguage(...) ``` setLanguage(options: SetLanguageOptions) => Promise ``` Set the app's language override. Setting the language recreates the current activity, which reloads the web view. On iOS, the language can only be changed by the user in the system settings (see the `openSettings(...)` method). Only available on Android. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetLanguageOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetLanguageResult | Prop | Type | Description | Since | | ----------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------- | | **`languageTag`** | \`string | null\` | The BCP 47 language tag of the app's language override. Returns `null` if no override is set and the app follows the device language. | #### SetLanguageOptions | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------------------- | ----- | | **`languageTag`** | `string` | The BCP 47 language tag to set as the app's language override. | 0.1.0 | ## Platform Behavior Setting the app language programmatically is only supported on Android. On iOS, the language can only be changed by the user in the system settings. | Method | Android | iOS | Web | | ------------------ | ------- | ---------------- | ---------------- | | `getLanguage()` | ✅ | ✅ | ❌ Unimplemented | | `setLanguage(...)` | ✅ | ❌ Unimplemented | ❌ Unimplemented | | `resetLanguage()` | ✅ | ❌ Unimplemented | ❌ Unimplemented | | `openSettings()` | ✅ | ✅ | ❌ Unimplemented | ## FAQ ### Why can't I set the app language programmatically on iOS? On iOS, the app language can only be changed by the user in the system settings, so the `setLanguage(...)` and `resetLanguage(...)` methods are not available there. Use `openSettings()` to deep-link the user to the app's settings page, where the language can be changed. ### Does this plugin translate my web UI? No. The language override only affects natively rendered strings, such as permission dialogs, notifications, or plugin-presented sheets. The language of your web UI should be handled by your app's i18n library. ### Why is the language row not shown in the iOS system settings? The per-app language row only appears if the app bundle provides more than one localization. Declare the supported localizations under the `CFBundleLocalizations` key in your app's `Info.plist`, as described in the [Installation](#installation) section. ### Why does my app reload after calling `setLanguage`? Setting or resetting the language recreates the current activity on Android, which reloads the web view. This is expected behavior and cannot be avoided. ### How does the selected language persist across app restarts on Android? On Android 13 and above, the override is integrated with the system settings via a `locales_config.xml` file. On Android 12 and below, you need to add the `AppLocalesMetadataHolderService` service to your `AndroidManifest.xml` to persist the selected language, as described in the [Installation](#installation) section. ### How is this plugin different from the Localization plugin? The [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugin reads the user's localization preferences, such as preferred locales, time zone, and regional formatting settings. The App Language plugin manages the app's own language override, independent of the device language. The two plugins work well together. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Localization](https://capawesome.io/docs/sdks/capacitor/localization/): Read the user's localization preferences, such as preferred locales, time zone, and regional formatting settings. - [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/): Open native settings screens. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-language/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-language/LICENSE). # Capacitor App Launcher Plugin Capacitor plugin to check if an app can be opened and to open it. ## Features - 🔗 **Open apps**: Open another app by its URL scheme or package name. - ✅ **Availability**: Check whether an app can be opened before trying to open it. - 🤖 **Package names**: Check and open apps by their package name on Android. - 🔒 **App Store safe**: Uses only official platform APIs. - 🤝 **Compatibility**: Works alongside the [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/) and [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) 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! ## Use Cases The App Launcher plugin is typically used to hand the user over to another app, for example: - **App-to-app navigation**: Open a companion or partner app directly from your app via its URL scheme or package name. - **Graceful fallbacks**: Check whether an app can be opened first and show an alternative if it is not installed. - **Composing emails**: Open the user's mail app with a `mailto:` URL. - **Launching Android apps by package name**: Open a specific app such as Gmail (`com.google.android.gm`) on Android. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-launcher` 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-launcher npx cap sync ``` ### Android Starting with Android 11 (API level 30), apps must declare the packages and URL schemes they want to check or open in the [``](https://developer.android.com/training/package-visibility) element of their `AndroidManifest.xml`. **If you do not add the corresponding `` entries, `canOpenUrl(...)` always resolves with `value: false` and `openUrl(...)` may resolve with `completed: false`.** Add an entry for **every** package name and URL scheme you pass to the plugin. For example, to check and open the Gmail app (package name `com.google.android.gm`) and any `mailto:` URL, add the following to your `android/app/src/main/AndroidManifest.xml` before or after the `application` tag: ``` ``` ### iOS On iOS, every URL scheme you want to check with `canOpenUrl(...)` must be declared in the [`LSApplicationQueriesSchemes`](https://developer.apple.com/documentation/bundleresources/information_property_list/lsapplicationqueriesschemes) key of your app's `Info.plist`. **If you do not add the corresponding entries, `canOpenUrl(...)` always resolves with `value: false`.** Add an entry for **every** URL scheme you pass to the plugin. For example, to check any `mailto:` URL, add the following to your `ios/App/App/Info.plist`: ``` LSApplicationQueriesSchemes mailto ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check whether an app can be opened and open another app. ### Check if an app can be opened Check whether an app can be opened before trying to open it. On iOS, the URL must be a URL scheme (e.g. `mailto:`); on Android, it can be a URL scheme or a package name (e.g. `com.google.android.gm`). Remember to declare every scheme and package name you check (see [Installation](#installation)), otherwise this method always resolves with `value: false`: ``` import { AppLauncher } from '@capawesome/capacitor-app-launcher'; const canOpenUrl = async () => { const { value } = await AppLauncher.canOpenUrl({ url: 'mailto:' }); return value; }; ``` ### Open an app Open another app with the given URL. On Android, the URL can also be a package name. The result tells you whether the app was opened successfully: ``` import { AppLauncher } from '@capawesome/capacitor-app-launcher'; const openUrl = async () => { const { completed } = await AppLauncher.openUrl({ url: 'mailto:' }); return completed; }; ``` ## API - [`canOpenUrl(...)`](#canopenurl) - [`openUrl(...)`](#openurl) - [Interfaces](#interfaces) ### canOpenUrl(...) ``` canOpenUrl(options: CanOpenUrlOptions) => Promise ``` Check if an app can be opened with the given URL. On **iOS**, every URL scheme you want to check must be declared in the `LSApplicationQueriesSchemes` key of your app's `Info.plist`. Otherwise this method always resolves with `value: false`. On **Android**, every package name or URL scheme you want to check must be declared in the `<queries>` element of your app's `AndroidManifest.xml`. Otherwise this method always resolves with `value: false`. | Param | Type | | ------------- | ------------------- | | **`options`** | `CanOpenUrlOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### openUrl(...) ``` openUrl(options: OpenUrlOptions) => Promise ``` Open an app with the given URL. | Param | Type | | ------------- | ---------------- | | **`options`** | `OpenUrlOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### CanOpenUrlResult | Prop | Type | Description | Since | | ----------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`value`** | `boolean` | Whether or not the app can be opened with the given URL. On the web, this is always `true` because the browser cannot determine whether a URL can be opened. | 0.1.0 | #### CanOpenUrlOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`url`** | `string` | The URL to check. On **iOS**, this must be a URL scheme (e.g. `mailto:`). On **Android**, this can be a URL scheme (e.g. `mailto:`) or a package name (e.g. `com.google.android.gm`). | 0.1.0 | #### OpenUrlResult | Prop | Type | Description | Since | | --------------- | --------- | ----------------------------------------------- | ----- | | **`completed`** | `boolean` | Whether or not the app was opened successfully. | 0.1.0 | #### OpenUrlOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`url`** | `string` | The URL to open. On **iOS**, this must be a URL scheme (e.g. `mailto:`). On **Android**, this can be a URL scheme (e.g. `mailto:`) or a package name (e.g. `com.google.android.gm`). | 0.1.0 | ## Migrating from `@capacitor/app-launcher` This plugin is a drop-in replacement for the official `@capacitor/app-launcher` plugin. The API is identical, so you only need to change the import and installation. | `@capacitor/app-launcher` | App Launcher | | --------------------------------- | --------------------------------- | | `AppLauncher.canOpenUrl({ url })` | `AppLauncher.canOpenUrl({ url })` | | `AppLauncher.openUrl({ url })` | `AppLauncher.openUrl({ url })` | On Android, the `url` may be a URL scheme or a package name for both `canOpenUrl(...)` and `openUrl(...)`. ## FAQ ### How is this plugin different from other similar plugins? It lets you both check whether an app can be opened and open it, and on Android the target can be a URL scheme or a package name, so you can launch a specific app like Gmail directly by its package. It uses only official platform APIs, keeping it safe for App Store and Google Play submissions, and pairs cleanly with the Maps Launcher and Settings Launcher plugins for a complete launching story. The API is fully typed, supports both CocoaPods and Swift Package Manager on iOS, and is actively maintained against the latest Capacitor and OS versions. ### Why does `canOpenUrl` always return `false`? This usually means the URL scheme or package name is not declared in your app. On Android 11 (API level 30) and above, every package name and URL scheme must be declared in the `` element of your `AndroidManifest.xml`. On iOS, every URL scheme must be declared in the `LSApplicationQueriesSchemes` key of your `Info.plist`. See the [Installation](#installation) section for examples. ### Can I open an app by its package name? Yes, but only on Android, where the `url` option accepts a URL scheme (e.g. `mailto:`) or a package name (e.g. `com.google.android.gm`). On iOS, only URL schemes are supported. ### How can I check if an app is installed? Use `canOpenUrl(...)` with the app's URL scheme or, on Android, its package name. Make sure the scheme or package name is declared as described in the [Installation](#installation) section. Note that on the Web, this method always resolves with `value: true` because the browser cannot determine whether a URL can be opened. ### How is this plugin different from the official `@capacitor/app-launcher` plugin? This plugin is a drop-in replacement with an identical API, so you only need to change the import and installation. In addition, on Android the `url` may also be a package name for both `canOpenUrl(...)` and `openUrl(...)`. See the [migration section](#migrating-from-capacitorapp-launcher) for details. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Android Intent Launcher](https://capawesome.io/docs/sdks/capacitor/android-intent-launcher/): Launch arbitrary Android intents. - [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/): Launch navigation apps with turn-by-turn directions. - [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/): Open the native phone dialer prefilled with a phone number. - [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/): Open native settings screens. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-launcher/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-launcher/LICENSE). # Capacitor App Review Plugin Capacitor plugin that allows users to submit app store reviews and ratings. ## Use Cases The App Review plugin is typically used to collect more app store ratings and reviews, for example: - **In-app review prompts**: Ask for a rating at a positive moment, such as after a completed task, without the user leaving the app. - **Rate this app button**: Open the app store page directly from a button in your settings or about screen. - **Feedback flows**: Route satisfied users from your own feedback dialog to the native review dialog or the app store review page. ## 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 The following examples show how to request an in-app review and open the app store page. ### Request an in-app review Show the native in-app review dialog so the user can rate your app without leaving it. Note that on iOS, review requests are limited to 3 requests per year. Only available on Android, and on iOS 14 and later: ``` import { AppReview } from '@capawesome/capacitor-app-review'; const requestReview = async () => { await AppReview.requestReview(); }; ``` ### Open the app store page Open the App Store page for the current app and, if possible, open the dialog to leave a review. On iOS, you can pass the Apple ID of your app via the `appId` option. Only available on Android and iOS: ``` import { AppReview } from '@capawesome/capacitor-app-review'; const openAppStore = async () => { await AppReview.openAppStore(); }; ``` ## 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) ## FAQ ### Why is the in-app review dialog not showing up? The operating system decides whether the review dialog is actually displayed. On iOS, review requests are limited to 3 requests per year, so the dialog may not appear every time you call `requestReview()`. Both platforms also have special requirements for testing, see the [Testing](#testing) section for the official instructions. ### What is the difference between `requestReview` and `openAppStore`? The `requestReview()` method shows the native in-app review dialog, so the user can rate your app without leaving it. The `openAppStore(...)` method opens the App Store page for the current app and, if possible, opens the dialog to leave a review there. Use `requestReview()` for gentle prompts and `openAppStore(...)` for an explicit "Rate this app" button. ### Do I need to provide an `appId`? Only on iOS, where the `appId` option of `openAppStore(...)` 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`). ### How can I test the in-app review functionality? You need to follow the instructions provided by the respective platform, since in-app reviews behave differently in development and production environments. The official testing guides for Android and iOS are linked in the [Testing](#testing) section. ### Which platforms are supported? The plugin supports Android and iOS. The `requestReview()` method requires iOS 14 or later, and the `openAppStore(...)` method is available on Android and iOS. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/): Assist your users with native app updates. - [Badge](https://capawesome.io/docs/sdks/capacitor/badge/): Access and update the badge number of the app icon. - [Install Referrer](https://capawesome.io/docs/sdks/capacitor/install-referrer/): Read install attribution data from the Play Install Referrer and Apple Ad Services. ## 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/). ## 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). # Capacitor App Shortcuts Plugin Capacitor plugin to manage app shortcuts and quick actions. ## Use Cases The App Shortcuts plugin is typically used to give users faster access to key parts of your app right from the home screen icon, for example: - **Quick actions**: Let users jump directly to frequently used screens such as search, compose, or feedback. - **Dynamic shortcuts**: Create or update shortcuts at runtime based on user behavior, for example a shortcut to the most recently opened item. - **Default shortcuts**: Define a set of default shortcuts in your Capacitor configuration without writing any code. - **Deep linking**: React to shortcut clicks with the `click` listener and navigate the user to the corresponding screen. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 8.x.x | >=8.x.x | Active support | ## Demo A working example can be found [here](https://github.com/capawesome-team/capacitor-plugins/tree/main/packages/app-shortcuts/example). | Android | iOS | | ------- | --- | | | | ## 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) + } ``` ## Usage The following examples show how to create, get, and remove app shortcuts and listen for shortcut clicks. ### Create or update app shortcuts Use the `set(...)` method to create or update the app shortcuts. Each shortcut needs a unique identifier and a title. Only available on Android and iOS: ``` import { AppShortcuts } from '@capawesome/capacitor-app-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', } ], }); }; ``` ### Get all app shortcuts Retrieve the list of app shortcuts that are currently set. Only available on Android and iOS: ``` import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const get = async () => { const result = await AppShortcuts.get(); return result.shortcuts; }; ``` ### Remove all app shortcuts Clear all app shortcuts, for example when the user signs out. Only available on Android and iOS: ``` import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const clear = async () => { await AppShortcuts.clear(); }; ``` ### Listen for shortcut clicks Add a listener for the `click` event to react when the user taps an app shortcut, for example to navigate to the corresponding screen. On iOS, this requires the changes to your `AppDelegate.swift` described in the [iOS configuration](#ios) instructions: ``` import { AppShortcuts } from '@capawesome/capacitor-app-shortcuts'; const addListener = async () => { AppShortcuts.addListener('click', (event) => { console.log('Shortcut clicked:', event.shortcutId); }); }; ``` ## 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 | ## FAQ ### Which platforms are supported by this plugin? App shortcuts are a native concept of the Android launcher and the iOS home screen, so the plugin is only available on Android and iOS. All plugin methods and the `click` event are marked as only available on Android and iOS in the [API](#api) section. ### Why is the click listener not called on iOS? On iOS, the plugin relies on changes to your app's `AppDelegate.swift` to forward shortcut items to the plugin. Make sure you have applied the code changes described in the [iOS configuration](#ios) instructions. Without them, the `click` event is never delivered. ### Can I define app shortcuts without writing any code? Yes, you can use the `shortcuts` configuration option to define a list of app shortcuts that are set by default. See the [Configuration](#configuration) section for examples in `capacitor.config.json` and `capacitor.config.ts`. ### Can I use different icons on Android and iOS? Yes, you can use the `androidIcon` and `iosIcon` properties to provide platform-specific icons, or the `icon` property for both platforms. On Android, drawable resources and base64 encoded images are supported, while on iOS you can use system symbol names, asset catalogue images, or `UIApplicationShortcutIcon` types. Note that on iOS, the icon and the description must be used together. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Icon](https://capawesome.io/docs/sdks/capacitor/app-icon/): Change the app icon at runtime. - [Badge](https://capawesome.io/docs/sdks/capacitor/badge/): Access and update the badge number of the app icon. ## 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/). ## 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). # Capacitor App Tracking Transparency Plugin Capacitor plugin for the [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency) framework. ## Features The Capacitor App Tracking Transparency plugin is one of the most complete tracking authorization solutions for Capacitor apps. Here are some of the key features: - 🔒 **Authorization status**: Read the current tracking authorization status. - 🙋 **Permission request**: Present the system tracking authorization prompt. - 🆔 **Advertising identifier**: Read the advertising identifier (IDFA) when authorized. - 🤝 **Compatibility**: Works alongside the [Permissions](https://capawesome.io/docs/sdks/capacitor/permissions/) plugin. - 📦 **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! ## Use Cases The App Tracking Transparency plugin is typically used whenever an iOS app collects data for tracking purposes, for example: - **Personalized ads**: Request tracking permission before serving personalized ads to the user. - **Ad attribution**: Read the advertising identifier (IDFA) to attribute app installs to ad campaigns once the user has authorized tracking. - **Conditional tracking**: Check the current tracking authorization status and only enable tracking-related features when the status is `authorized`. - **App Review compliance**: Present the system tracking authorization prompt as required by Apple for any app that tracks users. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-tracking-transparency` 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-tracking-transparency npx cap sync ``` This plugin is only available on **iOS**. On Android and Web, all methods reject as unimplemented. ### iOS #### Privacy Descriptions The `NSUserTrackingUsageDescription` key must be added to the `Info.plist` file of your app. Otherwise, the `requestPermission(...)` method will reject with an error. ``` NSUserTrackingUsageDescription Your data will be used to deliver personalized ads to you. ``` The purpose string must clearly explain why your app is requesting permission to track the user. See the [Apple documentation](https://developer.apple.com/documentation/bundleresources/information-property-list/nsusertrackingusagedescription) for more information. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check the tracking authorization status, request tracking permission, and read the advertising identifier. ### Check the tracking authorization status Read the current tracking authorization status, for example to decide whether tracking-related features should be enabled. Only available on iOS: ``` import { AppTrackingTransparency } from '@capawesome/capacitor-app-tracking-transparency'; const getStatus = async () => { const { status } = await AppTrackingTransparency.getStatus(); return status; }; ``` ### Request tracking permission Present the system tracking authorization prompt. The prompt is only shown once per install while the status is `notDetermined`; afterwards, the method resolves with the existing status. Only available on iOS: ``` import { AppTrackingTransparency } from '@capawesome/capacitor-app-tracking-transparency'; const requestPermission = async () => { const { status } = await AppTrackingTransparency.requestPermission(); return status; }; ``` ### Read the advertising identifier Read the advertising identifier (IDFA) of the device. It is only available if the tracking authorization status is `authorized`; otherwise, `null` is returned. Only available on iOS: ``` import { AppTrackingTransparency } from '@capawesome/capacitor-app-tracking-transparency'; const getAdvertisingIdentifier = async () => { const { advertisingIdentifier } = await AppTrackingTransparency.getAdvertisingIdentifier(); return advertisingIdentifier; }; ``` ## API - [`getAdvertisingIdentifier()`](#getadvertisingidentifier) - [`getStatus()`](#getstatus) - [`requestPermission()`](#requestpermission) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getAdvertisingIdentifier() ``` getAdvertisingIdentifier() => Promise ``` Get the advertising identifier (IDFA) of the device. The advertising identifier is only available if the tracking authorization status is `authorized`. Otherwise, `null` is returned. **Note**: The iOS Simulator always returns `null`, even if the tracking authorization status is `authorized`. Use a real device to test this method. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getStatus() ``` getStatus() => Promise ``` Get the current tracking authorization status. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### requestPermission() ``` requestPermission() => Promise ``` Request permission to track the user. This will present the system tracking authorization prompt if the status has not been determined yet. The prompt is only shown once per install. The `NSUserTrackingUsageDescription` key must be added to the `Info.plist` file of your app. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetAdvertisingIdentifierResult | Prop | Type | Description | Since | | --------------------------- | -------- | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`advertisingIdentifier`** | \`string | null\` | The advertising identifier (IDFA) of the device. Returns `null` if the tracking authorization status is not `authorized` or if no advertising identifier is available, which is always the case on the iOS Simulator. | #### GetStatusResult | Prop | Type | Description | Since | | ------------ | ---------------- | ------------------------------------------ | ----- | | **`status`** | `TrackingStatus` | The current tracking authorization status. | 0.1.0 | #### RequestPermissionResult | Prop | Type | Description | Since | | ------------ | ---------------- | ---------------------------------------------------- | ----- | | **`status`** | `TrackingStatus` | The tracking authorization status after the request. | 0.1.0 | ### Type Aliases #### TrackingStatus The tracking authorization status. - `authorized`: The user authorized access to app-related data for tracking. - `denied`: The user denied access to app-related data for tracking. - `notDetermined`: The user has not yet received a tracking authorization request. - `restricted`: Tracking authorization is restricted and cannot be changed by the user. `'authorized' | 'denied' | 'notDetermined' | 'restricted'` ## App Review Guidance Apple requires that any app that tracks users requests permission via the App Tracking Transparency framework. Keep the following guidelines in mind to avoid App Review rejections: - **Prompt timing**: Call `requestPermission(...)` only when the user is in a context where tracking makes sense. The system prompt is only shown once per install while the status is `notDetermined`. Calling the method again afterwards resolves with the existing status without showing the prompt. - **Purpose string**: The `NSUserTrackingUsageDescription` purpose string must accurately describe how the collected data is used. Vague or misleading descriptions are a common reason for rejection. - **Advertising identifier**: The advertising identifier (IDFA) is only available while the status is `authorized`. In all other cases, `getAdvertisingIdentifier(...)` returns `null`. - **Simulator**: The iOS Simulator never provides an advertising identifier, so `getAdvertisingIdentifier(...)` always returns `null` there, even if the status is `authorized`. Use a real device to test the advertising identifier. ## FAQ ### How is this plugin different from other similar plugins? It wraps Apple's App Tracking Transparency framework in a small, fully typed API: read the current authorization status, present the system prompt, and read the advertising identifier (IDFA) once the user has authorized tracking. It also ships focused App Review guidance on prompt timing, purpose strings, and simulator behavior — the details that most often decide whether a tracking-enabled app is approved. It supports CocoaPods and Swift Package Manager and is actively maintained against the latest Capacitor version. ### Does this plugin work on Android or Web? No, the App Tracking Transparency framework is an iOS-only concept. The plugin is only available on iOS; on Android and Web, all methods reject as unimplemented. ### Why does the requestPermission method reject with an error? The most common reason is a missing `NSUserTrackingUsageDescription` key in your app's `Info.plist` file. This key is required for the system tracking authorization prompt and its purpose string must clearly explain why your app is requesting permission to track the user. See the [Installation](#installation) section for details. ### Why is the tracking authorization prompt not shown? The system prompt is only shown once per install while the tracking authorization status is `notDetermined`. If the user has already responded to the prompt, calling `requestPermission()` again resolves with the existing status without showing the prompt. To test the prompt again, reinstall the app. ### Why does getAdvertisingIdentifier return null? The advertising identifier (IDFA) is only available if the tracking authorization status is `authorized`; in all other cases, `null` is returned. Additionally, the iOS Simulator never provides an advertising identifier, even if the status is `authorized`, so use a real device to test this method. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Permissions](https://capawesome.io/docs/sdks/capacitor/permissions/): Check and request device permissions with a unified API. - [Privacy Screen](https://capawesome.io/docs/sdks/capacitor/privacy-screen/): Hide sensitive app content in the app switcher and block screenshots. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-tracking-transparency/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/app-tracking-transparency/LICENSE). # Capacitor App Update Plugin 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/docs/sdks/capacitor/live-update/) plugin to update your app remotely in real-time without submitting a new version to the app store. 🚀 ## Features The Capacitor App Update plugin is one of the most complete native app update solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [Live Update](https://capawesome.io/docs/sdks/capacitor/live-update/) plugin. - 🔁 **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! ## Use Cases The App Update plugin is typically used to make sure users are running a recent version of your app, for example: - **Force updates**: Perform an immediate in-app update on Android when a critical new version must be installed before the app can be used. - **Optional update prompts**: Start a flexible in-app update on Android that downloads in the background and track its progress with the state change listener. - **Version checks**: Compare the current app version with the version available in the Play Store or App Store to decide whether to inform the user. - **Store redirects**: Open the app's Play Store or App Store entry so the user can update the app manually, for example on iOS where in-app updates are not available. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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. ## Usage The following examples show how to get app update information, open the app store entry, and perform immediate and flexible in-app updates. ### Get app update information Use `getAppUpdateInfo()` to retrieve the current and available app versions. On Android, versions are identified by the version code; on iOS, by the version name. Only available on Android and iOS: ``` import { AppUpdate } from '@capawesome/capacitor-app-update'; import { Capacitor } from '@capacitor/core'; 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; } }; ``` ### Open the app store entry Open the app's page in the Play Store (Android) or App Store (iOS) so the user can update the app manually. Only available on Android and iOS: ``` import { AppUpdate } from '@capawesome/capacitor-app-update'; const openAppStore = async () => { await AppUpdate.openAppStore(); }; ``` ### Perform an immediate update Perform an immediate in-app update if an update is available and an immediate update is allowed. Only available on Android: ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const performImmediateUpdate = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (result.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (result.immediateUpdateAllowed) { await AppUpdate.performImmediateUpdate(); } }; ``` ### Start a flexible update Start a flexible in-app update if an update is available and a flexible update is allowed. You can monitor the download progress with the `onFlexibleUpdateStateChange` listener. Only available on Android: ``` import { AppUpdate, AppUpdateAvailability } from '@capawesome/capacitor-app-update'; const startFlexibleUpdate = async () => { const result = await AppUpdate.getAppUpdateInfo(); if (result.updateAvailability !== AppUpdateAvailability.UPDATE_AVAILABLE) { return; } if (result.flexibleUpdateAllowed) { await AppUpdate.startFlexibleUpdate(); } }; ``` ### Complete a flexible update Complete a flexible in-app update by restarting the app. Only available on Android: ``` import { AppUpdate } from '@capawesome/capacitor-app-update'; 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`). **Attention**: This option is required on iOS. 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). ## FAQ ### What is the difference between this plugin and the Live Update plugin? The App Update plugin assists with native app updates that are distributed through the Play Store or App Store, for example by retrieving update information or performing in-app updates on Android. The [Capacitor Live Update](https://capawesome.io/docs/sdks/capacitor/live-update/) plugin, on the other hand, updates your app remotely in real-time without submitting a new version to the app store. ### Are in-app updates available on iOS? No, in-app updates are a feature of the Google Play Store and are therefore only available on Android. On iOS, you can use `getAppUpdateInfo(...)` to check whether an update is available and then call `openAppStore(...)` to open the app's App Store entry so the user can update manually. ### What is the difference between an immediate and a flexible update? An immediate update is performed in one step with `performImmediateUpdate()`. A flexible update is started with `startFlexibleUpdate()`, its download progress can be monitored with the `onFlexibleUpdateStateChange` listener, and it is completed by calling `completeFlexibleUpdate()`, which restarts the app. Both update types are only available on Android. ### Why does the update fail with the INFO_MISSING result code? The `INFO_MISSING` result code means that the app update information is missing. You must call `getAppUpdateInfo(...)` before requesting an update with `performImmediateUpdate()` or `startFlexibleUpdate()`. ### How can I test in-app updates on Android? The Android Developers documentation describes how to test in-app updates using internal app sharing. See the [Test with internal app-sharing](#test-with-internal-app-sharing) section for the relevant links. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Review](https://capawesome.io/docs/sdks/capacitor/app-review/): Let users submit app store reviews and ratings. - [Google Play Services](https://capawesome.io/docs/sdks/capacitor/google-play-services/): Check whether Google Play Services is available on the device and prompt the user to install or update it. - [Live Update](https://capawesome.io/docs/sdks/capacitor/live-update/): Update your app remotely in real-time without requiring users to download a new version from the app store. ## 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/). ## 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! # Capacitor Apple Sign-In Plugin Unofficial Capacitor plugin to sign-in with Apple.[1](#fn:1) ## Features The Capacitor Apple Sign-In plugin is one of the most complete Apple authentication solutions for Capacitor apps. 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/docs/sdks/capacitor/google-sign-in/) and [OAuth](https://capawesome.io/docs/sdks/capacitor/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! ## Use Cases The Apple Sign-In plugin is typically used to let users authenticate with their Apple ID, for example: - **User authentication**: Offer Sign in with Apple as a login option in your app on Android, iOS, and Web. - **Account creation**: Request the user's email address and full name during sign-up using the email and full name scopes. - **Backend integration**: Send the authorization code and ID token to your backend to verify the sign-in and create a user session. - **Secure sign-in flows**: Pass a nonce for replay protection and a state value for CSRF protection. ## 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 The following examples show how to initialize the plugin and sign in with Apple. ### Initialize the plugin On Android and Web, you must initialize the plugin with your Apple Service ID before calling `signIn(...)`. This method is only available on Android and Web: ``` import { AppleSignIn } from '@capawesome/capacitor-apple-sign-in'; const initialize = async () => { await AppleSignIn.initialize({ clientId: 'com.example.app.signin', }); }; ``` ### Sign in with Apple Start the sign-in flow and receive the authorization code and ID token. Use the `scopes` option to request the user's email address and full name, and pass a `nonce` for replay protection. The `redirectUrl` and `state` options are only available on Android and Web: ``` import { AppleSignIn, SignInScope } from '@capawesome/capacitor-apple-sign-in'; 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. ### Do I need to call initialize on iOS? No, the `initialize(...)` method is only available on Android and Web, where it must be called before `signIn(...)` to set the Apple Service ID. On iOS, the plugin uses the native AuthenticationServices framework and no initialization is required. ### Why are the email and name only returned on the first sign-in on iOS? On iOS, Apple only provides the user's email address, given name, and family name on the first sign-in; on subsequent sign-ins, these values are `null`. Make sure to persist this information, for example on your backend, when you receive it for the first time. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Facebook Sign-In](https://capawesome.io/docs/sdks/capacitor/facebook-sign-in/): Sign in with Facebook. - [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/): Sign in with Google. - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/): Communicate with OAuth 2.0 and OpenID Connect providers. - [Passkeys](https://capawesome.io/docs/sdks/capacitor/passkeys/): Create and authenticate with passkeys based on the WebAuthn standard. ## 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/). ## 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") # Capacitor Asset Manager Plugin Capacitor plugin to access native asset files. ## Features The Capacitor Asset Manager plugin is one of the most complete native asset access solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/), [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) and [Zip](https://capawesome.io/docs/sdks/capacitor/zip/) plugins. - 🔁 **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! ## Use Cases The Asset Manager plugin is typically used whenever an app ships files inside its native bundle that need to be accessed at runtime, for example: - **Prepopulated data**: Copy bundled files such as databases or sample data from the app bundle to the app's data directory on first launch. - **Configuration files**: Read bundled configuration files like `capacitor.config.json` using UTF-8 encoding. - **Asset discovery**: List the files in an asset directory to process them dynamically. - **Binary assets**: Read bundled binary files as Base64 encoded strings. ## 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 The following examples show how to copy an asset to the data directory, list asset files, and read an asset file. ### Copy an asset to the data directory Copy a file or directory from the app bundle to the app's data directory. You can generate the destination path using the `getUri(...)` method of the Capacitor Filesystem plugin. Only available on Android and iOS: ``` import { AssetManager } from '@capawesome/capacitor-asset-manager'; import { Directory, Filesystem } from '@capacitor/filesystem'; const copy = async () => { const { uri } = await Filesystem.getUri({ directory: Directory.Cache, path: 'index.html' }); await AssetManager.copy({ from: 'public/index.html', to: uri }); }; ``` ### List asset files List the files in an asset directory. If no path is specified, the root path is used. Only available on Android and iOS: ``` import { AssetManager } from '@capawesome/capacitor-asset-manager'; const list = async () => { await AssetManager.list({ path: 'public' }); }; ``` ### Read an asset file Read a file from the app bundle using Base64 (default) or UTF-8 encoding. Avoid reading large files this way, as it can cause out of memory issues; instead, copy them 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: ``` import { AssetManager, Encoding } from '@capawesome/capacitor-asset-manager'; 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 names of the files in the directory. The names are relative to the `path` that was listed. | 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 | ## FAQ ### Which platforms are supported by this plugin? The plugin provides access to files in the native app bundle and is therefore only available on Android and iOS. All plugin methods are marked as only available on Android and iOS in the [API](#api) section. ### Why does my app crash when reading large asset files? Reading large files with the `read(...)` method can cause out of memory (OOM) issues because the entire file content is returned as a string. Instead, copy the file to the app's data directory using the `copy(...)` method and read it from there using the Fetch API, as described in the [usage example](#read-an-asset-file) above. ### How is this plugin different from the Capacitor Filesystem plugin? The Capacitor Filesystem plugin reads and writes files in the app's filesystem directories, but it cannot access the files bundled inside the native app. The Asset Manager plugin fills this gap by copying, listing, and reading native asset files. The two plugins work well together: for example, you can generate the destination path for the `copy(...)` method using the `getUri(...)` method of the Filesystem plugin. ### Which encodings are supported when reading a file? The `read(...)` method supports Base64 and UTF-8 encoding. If no encoding is specified, Base64 is used by default. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open a file with the default application. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Select a file, directory, image, or video from the device's file system or gallery. - [Zip](https://capawesome.io/docs/sdks/capacitor/zip/): Zip and unzip files and directories with support for encryption. ## 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/). ## 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). # Capacitor Audio Player Plugin Capacitor plugin to play audio with background support. ## Features The Capacitor Audio Player plugin is one of the most complete audio playback solutions for Capacitor apps. 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. - 📋 **Playlist Mode**: Play multiple tracks sequentially with native track advancement, even in the background. - 🎛️ **Media Session**: Control the playback from the system's media controls (e.g. notification and lock screen), even while the app is in the background. - ⏪ **Seek Buttons**: Display seek backward and seek forward buttons with a configurable offset in the system's media controls. - 🎨 **Customizable Icons**: Configure the media notification icons on Android to match your app's branding. - 🔊 **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/docs/sdks/capacitor/audio-recorder/), [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/) and [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/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! ## Use Cases The Audio Player plugin is typically used whenever an app needs to play audio, for example: - **Music and podcast playback**: Play remote audio files and keep them playing while the app is in the background. - **Voice message playback**: Play voice messages recorded with the [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/) plugin in chat or support apps. - **Sound effects**: Play short sounds from your web assets with precise volume control and looping. - **Audiobooks and learning apps**: Let users adjust the playback speed and seek to specific positions. ## 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: [capawesome-team/capacitor-audio-player-demo](https://github.com/capawesome-team/capacitor-audio-player-demo) | Android | iOS | | ------- | --- | | | | ## Guides - [How to Play Audio in the Background in a Capacitor App](https://capawesome.io/blog/how-to-play-audio-in-the-background-in-capacitor/): Native playback, lock screen media controls, and playlists that keep running. ## 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 ``` ### Android #### Variables If needed, you can define the following project variables in your app's `variables.gradle` file to change the default versions of the dependencies: - `$androidxMedia3ExoPlayerVersion` version of `androidx.media3:media3-exoplayer` (default: `1.6.1`) - `$androidxMedia3SessionVersion` version of `androidx.media3:media3-session` (default: `1.6.1`) #### Media Session If you want to use the media session integration (see the `metadata` option of the `play(...)` method), add the following service to your `AndroidManifest.xml` inside the `application` tag: ``` ``` Also, add the following permissions before or after the `application` tag: ``` ``` #### Notification Icons If you want to use custom icons in the media notification (see [Configuration](#configuration)): 1. Add your icons to `android/app/src/main/res/drawable/` (e.g., `ic_notification.png`) 1. Icons 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: { AudioPlayer: { smallIcon: 'ic_notification', // Matches ic_notification.png seekBackwardIcon: 'ic_seek_backward_20', // Matches ic_seek_backward_20.png seekForwardIcon: 'ic_seek_forward_20', // Matches ic_seek_forward_20.png }, }, }; ``` 1. Run: `npx cap sync` On iOS, the playback controls on the lock screen and in the Control Center are rendered by the operating system and cannot be customized. ### 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. ## Configuration | Prop | Type | Description | Default | Since | | ---------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------- | ----- | | **`seekBackwardIcon`** | `string` | The name of the drawable resource to use as the icon for the seek backward button in the media notification. The button is only displayed if a seek offset has been provided (see the `seekBackwardOffset` and `seekForwardOffset` options of the `play(...)` method). The resource name should not include the `R.drawable.` prefix or file extension. For example, if you have `res/drawable/ic_seek_backward.png`, set this to `"ic_seek_backward"`. If the resource is not found, the default icon is used. **Attention:** The icon should be white pixels on a transparent background. Otherwise, a white square or circle may be displayed instead of the icon. Only available on Android. | `"media3_icon_skip_back" (Media3 built-in icon, with a number badge for an offset of 5, 10, 15 or 30 seconds)` | 8.5.0 | | **`seekForwardIcon`** | `string` | The name of the drawable resource to use as the icon for the seek forward button in the media notification. The button is only displayed if a seek offset has been provided (see the `seekBackwardOffset` and `seekForwardOffset` options of the `play(...)` method). The resource name should not include the `R.drawable.` prefix or file extension. For example, if you have `res/drawable/ic_seek_forward.png`, set this to `"ic_seek_forward"`. If the resource is not found, the default icon is used. **Attention:** The icon should be white pixels on a transparent background. Otherwise, a white square or circle may be displayed instead of the icon. Only available on Android. | `"media3_icon_skip_forward" (Media3 built-in icon, with a number badge for an offset of 5, 10, 15 or 30 seconds)` | 8.5.0 | | **`smallIcon`** | `string` | The name of the drawable resource to use as the small icon in the media notification. The resource name should not include the `R.drawable.` prefix or file extension. For example, if you have `res/drawable/ic_notification.png`, set this to `"ic_notification"`. If the resource is not found, the default icon is used. **Attention:** The icon should be white pixels on a transparent background. Otherwise, a white square or circle may be displayed instead of the icon. Only available on Android. | `"media3_notification_small_icon" (Media3 built-in icon)` | 8.5.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "AudioPlayer": { "seekBackwardIcon": "ic_seek_backward", "seekForwardIcon": "ic_seek_forward", "smallIcon": "ic_notification" } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { AudioPlayer: { seekBackwardIcon: "ic_seek_backward", seekForwardIcon: "ic_seek_forward", smallIcon: "ic_notification", }, }, }; export default config; ``` ## Usage The following examples show how to play audio from web assets, remote URLs, the file system, or a blob, how to play playlists, and how to control, seek, adjust the volume of, and inspect the playback. ### Play an audio file from your web assets or a remote URL Use the `src` option to play a web asset or a remote URL. Both are supported on all platforms: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const playFromWebAsset = async () => { await AudioPlayer.play({ src: '/assets/audio.mp3', loop: false, volume: 100, position: 0 }); }; ``` ### Play an audio file from the file system Use the `uri` option to play a file from the device's file system, for example one retrieved with the Capacitor Filesystem plugin. This option is only available on Android and iOS: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; import { Filesystem, FilesystemDirectory } from '@capacitor/filesystem'; const playFromNativeFile = async () => { const { uri } = await Filesystem.getUri({ directory: FilesystemDirectory.Documents, path: 'audio.mp3', }); await AudioPlayer.play({ uri, loop: false, volume: 100, position: 0 }); }; ``` ### Play an audio file from a blob Use the `blob` option to play a `Blob` instance, for example one fetched from a server. This option is only available on Web: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; 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 }); }; ``` ### Play a playlist Use the `tracks` option to play multiple tracks sequentially. The next track starts automatically when the current one ends, even in the background. Use the `startIndex` option to start playback from a specific track: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const playPlaylist = async () => { await AudioPlayer.play({ tracks: [ { src: '/assets/track1.mp3' }, { src: '/assets/track2.mp3' }, { src: '/assets/track3.mp3' }, ], startIndex: 0, }); }; const listenForTrackChanges = async () => { await AudioPlayer.addListener('trackChange', (event) => { console.log('Track changed to index:', event.index); }); }; ``` ### Navigate within a playlist Skip to the next or previous track, jump to a specific track, or retrieve the index of the current track: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const skipToNextTrack = async () => { await AudioPlayer.skipToNextTrack(); }; const skipToPreviousTrack = async () => { await AudioPlayer.skipToPreviousTrack(); }; const jumpToTrack = async () => { await AudioPlayer.seekTo({ index: 2, position: 0 }); }; const getCurrentTrackIndex = async () => { const { index } = await AudioPlayer.getCurrentTrackIndex(); console.log('Current track index:', index); }; ``` ### Modify the playlist Add tracks to the playlist or remove tracks from it while it is playing: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const addTracks = async () => { await AudioPlayer.addTracks({ tracks: [{ src: '/assets/track4.mp3' }], }); }; const removeTrack = async () => { await AudioPlayer.removeTrack({ index: 0 }); }; ``` ### Set the repeat mode Repeat the current track or the entire playlist: ``` import { AudioPlayer, RepeatMode } from '@capawesome-team/capacitor-audio-player'; const setRepeatMode = async () => { await AudioPlayer.setRepeatMode({ mode: RepeatMode.All }); }; ``` ### Control the playback from the system's media controls Provide the `metadata` option to display the playback in the system's media controls (e.g. notification and lock screen). The playback can then be controlled from there, even while the app is in the background. On Android, this requires additional manifest entries (see [Installation](#installation)): ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const playWithMediaSession = async () => { await AudioPlayer.play({ tracks: [ { src: 'https://example.com/track1.mp3', metadata: { album: 'The Dark Side of the Moon', artist: 'Pink Floyd', artworkSource: 'https://example.com/artwork.png', title: 'Time', }, }, { src: 'https://example.com/track2.mp3', metadata: { album: 'The Dark Side of the Moon', artist: 'Pink Floyd', artworkSource: 'https://example.com/artwork.png', title: 'Money', }, }, ], }); }; const listenForPlaybackStateChanges = async () => { await AudioPlayer.addListener('playbackStateChanged', (event) => { console.log('Playback state changed to:', event.state); }); }; ``` Provide the `seekBackwardOffset` and `seekForwardOffset` options to display seek buttons instead of the previous and next track buttons, for example for podcasts or audiobooks: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const playWithSeekButtons = async () => { await AudioPlayer.play({ src: 'https://example.com/episode.mp3', metadata: { artist: 'Capawesome', title: 'Episode 12', }, seekBackwardOffset: 15000, seekForwardOffset: 30000, }); }; ``` ### Pause, resume and stop the playback Pause the playback and resume it later, or stop it entirely: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const pause = async () => { await AudioPlayer.pause(); }; const resume = async () => { await AudioPlayer.resume(); }; const stop = async () => { await AudioPlayer.stop(); }; ``` ### Seek to a specific position Jump to a specific position in the audio playback, given in milliseconds: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const seekTo = async () => { await AudioPlayer.seekTo({ position: 30_000 }); // Seek to 30 seconds }; ``` ### Adjust the volume Set the volume level of the current playback session to a value between 0 and 100: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; const setVolume = async () => { await AudioPlayer.setVolume({ volume: 50 }); // Set volume to 50% }; ``` ### Get the current playback state Retrieve the current position and duration of the playback in milliseconds, and check whether the audio is currently playing: ``` import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; 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 - [`addTracks(...)`](#addtracks) - [`getCurrentPosition()`](#getcurrentposition) - [`getCurrentTrackIndex()`](#getcurrenttrackindex) - [`getDuration()`](#getduration) - [`isPlaying()`](#isplaying) - [`pause()`](#pause) - [`play(...)`](#play) - [`removeTrack(...)`](#removetrack) - [`resume()`](#resume) - [`seekTo(...)`](#seekto) - [`setRate(...)`](#setrate) - [`setRepeatMode(...)`](#setrepeatmode) - [`setVolume(...)`](#setvolume) - [`skipToNextTrack()`](#skiptonexttrack) - [`skipToPreviousTrack()`](#skiptoprevioustrack) - [`stop(...)`](#stop) - [`addListener('playbackStateChanged', ...)`](#addlistenerplaybackstatechanged-) - [`addListener('stop', ...)`](#addlistenerstop-) - [`addListener('trackChange', ...)`](#addlistenertrackchange-) - [Interfaces](#interfaces) - [Enums](#enums) ### addTracks(...) ``` addTracks(options: AddTracksOptions) => Promise ``` Add tracks to the currently loaded playlist. Only available if a playlist has been loaded via `play({ tracks })`. | Param | Type | | ------------- | ------------------ | | **`options`** | `AddTracksOptions` | **Since:** 8.4.0 ______________________________________________________________________ ### getCurrentPosition() ``` getCurrentPosition() => Promise ``` Get the current position of the audio playback in milliseconds. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getCurrentTrackIndex() ``` getCurrentTrackIndex() => Promise ``` Get the index of the currently playing track within the loaded playlist. The `index` is `undefined` if no playlist is loaded (e.g. single-track playback or nothing playing). **Returns:** `Promise` **Since:** 8.4.0 ______________________________________________________________________ ### 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 ______________________________________________________________________ ### removeTrack(...) ``` removeTrack(options: RemoveTrackOptions) => Promise ``` Remove a track from the currently loaded playlist. If the currently playing track is removed, playback continues with the track that takes its place, or stops if it was the last track. Only available if a playlist has been loaded via `play({ tracks })`. | Param | Type | | ------------- | -------------------- | | **`options`** | `RemoveTrackOptions` | **Since:** 8.4.0 ______________________________________________________________________ ### resume() ``` resume() => Promise ``` Resume the audio playback. **Since:** 0.0.1 ______________________________________________________________________ ### seekTo(...) ``` seekTo(options: SeekToOptions) => Promise ``` Seek to a specific position and/or track within the current playback. Provide `position` to seek within the current track, `index` to jump to a different track in the currently loaded playlist, or both to do both at once. If neither is provided, the call is a no-op. Play state is preserved: if the player was paused, it stays paused at the new location; call `resume()` afterwards to start 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, iOS and Web. | Param | Type | | ------------- | ---------------- | | **`options`** | `SetRateOptions` | **Since:** 8.2.0 ______________________________________________________________________ ### setRepeatMode(...) ``` setRepeatMode(options: SetRepeatModeOptions) => Promise ``` Set the repeat mode for the current playback. This only affects the current playback session and is not persisted. | Param | Type | | ------------- | ---------------------- | | **`options`** | `SetRepeatModeOptions` | **Since:** 8.4.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 ______________________________________________________________________ ### skipToNextTrack() ``` skipToNextTrack() => Promise ``` Skip to the next track in the currently loaded playlist. If the repeat mode is `ALL`, skipping past the last track wraps around to the first track. Only available if a playlist has been loaded via `play({ tracks })`. **Since:** 8.4.0 ______________________________________________________________________ ### skipToPreviousTrack() ``` skipToPreviousTrack() => Promise ``` Skip to the previous track in the currently loaded playlist. If the repeat mode is `ALL`, skipping before the first track wraps around to the last track. Only available if a playlist has been loaded via `play({ tracks })`. **Since:** 8.4.0 ______________________________________________________________________ ### stop(...) ``` stop(options?: StopOptions | undefined) => Promise ``` Stop the audio playback. | Param | Type | | ------------- | ------------- | | **`options`** | `StopOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### addListener('playbackStateChanged', ...) ``` addListener(eventName: 'playbackStateChanged', listenerFunc: (event: PlaybackStateChangedEvent) => void) => Promise ``` Called when the playback state changes, for example when the user pauses the playback via the media controls on the lock screen. | Param | Type | | ------------------ | -------------------------------------------- | | **`eventName`** | `'playbackStateChanged'` | | **`listenerFunc`** | `(event: PlaybackStateChangedEvent) => void` | **Returns:** `Promise` **Since:** 8.4.0 ______________________________________________________________________ ### 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 ______________________________________________________________________ ### addListener('trackChange', ...) ``` addListener(eventName: 'trackChange', listenerFunc: (event: TrackChangeEvent) => void) => Promise ``` Called when the current track changes during playlist playback. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'trackChange'` | | **`listenerFunc`** | `(event: TrackChangeEvent) => void` | **Returns:** `Promise` **Since:** 8.4.0 ______________________________________________________________________ ### Interfaces #### AddTracksOptions | Prop | Type | Description | Since | | ------------ | -------------- | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`index`** | `number` | The 0-based index at which to insert the tracks. If not provided, the tracks are appended to the end of the playlist. | 8.4.0 | | **`tracks`** | `AudioTrack[]` | The tracks to add to the playlist. | 8.4.0 | #### AudioTrack | Prop | Type | Description | Since | | -------------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`blob`** | `Blob` | The audio file to play. Only available on Web. | 8.4.0 | | **`metadata`** | `TrackMetadata` | The metadata of the track, displayed in the system's media controls (e.g. notification and lock screen). Providing metadata activates the media session integration so that the playback can be controlled from the system's media controls, even while the app is in the background. On Android, this requires additional manifest entries. See the [documentation](https://capawesome.io/docs/sdks/capacitor/audio-player/) for more information. | 8.4.0 | | **`src`** | `string` | The path to the web asset file or a remote URL. Both web assets and remote URLs are supported on all platforms. | 8.4.0 | | **`uri`** | `string` | The URI or path of the audio file to play. Only available on Android and iOS. | 8.4.0 | #### TrackMetadata | Prop | Type | Description | Since | | ------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`album`** | `string` | The album of the track. | 8.4.0 | | **`artist`** | `string` | The artist of the track. | 8.4.0 | | **`artworkSource`** | `string` | The source of the artwork of the track, displayed in the system's media controls. Both web assets and remote URLs are supported. | 8.4.0 | | **`title`** | `string` | The title of the track. | 8.4.0 | #### GetCurrentPositionResult | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------- | ----- | | **`position`** | `number` | The current position of the audio playback in milliseconds. | 0.0.1 | #### GetCurrentTrackIndexResult | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------ | ----- | | **`index`** | `number` | The 0-based index of the currently playing track within the loaded playlist. `undefined` if no playlist is loaded. | 8.4.0 | #### 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. This option is ignored when `tracks` is provided. | | 0.0.1 | | **`metadata`** | `TrackMetadata` | The metadata of the track, displayed in the system's media controls (e.g. notification and lock screen). Providing metadata activates the media session integration so that the playback can be controlled from the system's media controls, even while the app is in the background. On Android, this requires additional manifest entries. See the [documentation](https://capawesome.io/docs/sdks/capacitor/audio-player/) for more information. This option is ignored when `tracks` is provided. | | 8.4.0 | | **`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. Must be greater than `0`. Only available on Android, iOS and Web. | `1.0` | 8.2.0 | | **`seekBackwardOffset`** | `number` | The offset in milliseconds for the seek backward button in the system's media controls. If provided, the seek backward button is displayed instead of the previous track button, since the system's media controls only have one slot on each side of the play button. Must be greater than `0`. Only applies if the media session is active (see the `metadata` option). | | 8.5.0 | | **`seekForwardOffset`** | `number` | The offset in milliseconds for the seek forward button in the system's media controls. If provided, the seek forward button is displayed instead of the next track button, since the system's media controls only have one slot on each side of the play button. Must be greater than `0`. Only applies if the media session is active (see the `metadata` option). | | 8.5.0 | | **`startIndex`** | `number` | The 0-based index of the track to start playback from. Only meaningful when `tracks` is provided. | `0` | 8.4.0 | | **`tracks`** | `AudioTrack[]` | A list of audio tracks to play sequentially. When provided, `blob`, `src`, and `uri` are ignored. | | 8.4.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 | #### RemoveTrackOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------------------------- | ----- | | **`index`** | `number` | The 0-based index of the track to remove from the playlist. | 8.4.0 | #### SeekToOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`index`** | `number` | The 0-based index of the track to jump to within the currently loaded playlist. Only meaningful when a playlist has been loaded via `play({ tracks })`. | 8.4.0 | | **`position`** | `number` | The position to seek to (in milliseconds). When `index` is also provided, this is the position within the target track. | 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. Must be greater than `0`. | 8.2.0 | #### SetRepeatModeOptions | Prop | Type | Description | Since | | ---------- | ------------ | ----------------------- | ----- | | **`mode`** | `RepeatMode` | The repeat mode to set. | 8.4.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` | #### PlaybackStateChangedEvent | Prop | Type | Description | Since | | ----------- | --------------- | ----------------------- | ----- | | **`state`** | `PlaybackState` | The new playback state. | 8.4.0 | #### TrackChangeEvent | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------- | ----- | | **`index`** | `number` | The 0-based index of the new current track. | 8.4.0 | ### Enums #### RepeatMode | Members | Value | Description | Since | | ---------- | -------- | --------------------------- | ----- | | **`All`** | `'ALL'` | Repeat the entire playlist. | 8.4.0 | | **`None`** | `'NONE'` | Do not repeat. | 8.4.0 | | **`One`** | `'ONE'` | Repeat the current track. | 8.4.0 | #### PlaybackState | Members | Value | Description | Since | | ------------- | ----------- | ------------------------ | ----- | | **`Paused`** | `'PAUSED'` | The playback is paused. | 8.4.0 | | **`Playing`** | `'PLAYING'` | The playback is playing. | 8.4.0 | | **`Stopped`** | `'STOPPED'` | The playback is stopped. | 8.4.0 | ## 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 }); ``` ## FAQ ### Can I play audio while the app is in the background? Yes, the plugin supports background playback. On iOS, you need to enable the `Background Modes` capability with `Audio, AirPlay, and Picture in Picture` in your Xcode project, as described in the [Installation](#installation) section. ### Which audio sources can I play? You can play web assets and remote URLs via the `src` option on all platforms. On Android and iOS, you can also play files from the device's file system via the `uri` option. On Web, you can play `Blob` instances via the `blob` option. See the [Usage](#usage) section for examples. ### How can I change the playback speed? Use the `rate` option of the `play(...)` method or call `setRate(...)` during playback. Values between 0.5 and 2.0 are recommended, as other values may not be supported on all devices. The playback rate is adjusted with pitch preservation and is available on Android (SDK 23+), iOS and Web. ### Why does playback fail with a CoreMediaErrorDomain -16042 error on iOS? This can happen when `play()` is called shortly after `stop()`, because the audio session is deactivated by default when stopping. Set the `deactivateAudioSession` option of the `stop(...)` method to `false` if you intend to play audio again shortly after stopping. See the [Troubleshooting](#troubleshooting) section for more details. ### Can I use this plugin together with other audio plugins? Yes, the plugin is compatible with the [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/), [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/) and [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/) plugins. For example, you can play back a recording created with the Audio Recorder plugin. However, combining it with the [Media Session](https://capawesome.io/docs/sdks/capacitor/media-session/) plugin is not recommended. Use the built-in media session integration (see the `metadata` option of the `play(...)` method) instead, which handles the media controls natively, even when the web view is suspended while the app is in the background. ### How can I display seek buttons in the system's media controls? Provide the `seekBackwardOffset` and `seekForwardOffset` options of the `play(...)` method with the desired offsets in milliseconds. The seek buttons are displayed instead of the previous and next track buttons, since the system's media controls only have one slot on each side of the play button. The options belong to the playback, so a playlist started without them displays the previous and next track buttons again. See the [Usage](#usage) section for an example. ### Can I customize the notification icons on Android? Yes, use the `smallIcon`, `seekBackwardIcon` and `seekForwardIcon` configuration options to set the name of a drawable resource from your app's `res/drawable` directory. The icons should be single-color white with a transparent background for the best display. If a resource is not found, the default icon is used. The play, pause, previous track and next track buttons cannot be customized, as they are rendered by the operating system. See the [Configuration](#configuration) section for details. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/): Record audio using the device's microphone. - [Audio Session](https://capawesome.io/docs/sdks/capacitor/audio-session/): Configure and observe the iOS audio session. - [Media Session](https://capawesome.io/docs/sdks/capacitor/media-session/): Interact with media controllers, volume keys and media buttons. - [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/): Synthesize speech from text with voice selection, pitch, and rate control. ## 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/). ## 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). # Capacitor Audio Recorder Plugin Capacitor plugin for seamless audio recording using the device's microphone. Supports Android, iOS, and Web with advanced features and high performance. ## Features The Capacitor Audio Recorder plugin is one of the most advanced audio recording solutions for Capacitor apps. 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/docs/sdks/capacitor/audio-player/), [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/) and [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/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! ## Use Cases The Audio Recorder plugin is typically used whenever an app needs to capture audio with the device's microphone, for example: - **Voice messages**: Record voice messages in chat apps and play them back with the [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/) plugin. - **Voice notes and memos**: Let users record notes with full control to pause, resume, or cancel the recording. - **Dictation and transcription**: Capture spoken audio that is later transcribed, for example by a server-side speech-to-text service. - **Interviews and long sessions**: Record long audio sessions without performance issues, even while the app is in the background. ## 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 The following examples show how to manage permissions, start a recording, save it to a custom location, stop and play it back, pause, resume or cancel it, read the recording status, and listen for recording events. ### Check and request permissions Recording requires the microphone permission. Check and request it before starting a recording: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; 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); }; ``` ### Start a recording Start recording audio in AAC format. You can optionally configure the bitrate and sample rate (Android and iOS) as well as the audio session category options and mode (iOS only): ``` import { AudioRecorder, AudioSessionCategoryOption, AudioSessionMode } from '@capawesome-team/capacitor-audio-recorder'; const startRecording = async () => { await AudioRecorder.startRecording({ audioSessionCategoryOptions: [AudioSessionCategoryOption.DuckOthers], audioSessionMode: AudioSessionMode.Measurement, bitRate: 192000, sampleRate: 44100 }); }; ``` ### Save the recording to a custom location By default, the recording is saved to a temporary location in the cache directory. On Android and iOS, use the `uri` option to save it to a custom location instead, for example one generated with the Capacitor Filesystem plugin: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; import { Directory, Filesystem } from '@capacitor/filesystem'; const startRecordingToCustomLocation = async () => { const { uri } = await Filesystem.getUri({ directory: Directory.Data, path: 'recordings/my-recording.aac' }); await AudioRecorder.startRecording({ uri }); }; ``` ### Stop the recording and play it back Stop the recording to receive the recorded audio as a `Blob` (Web) or URI (Android and iOS), which you can then play back, for example with the [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/) plugin: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; import { AudioPlayer } from '@capawesome-team/capacitor-audio-player'; 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 }); } }; ``` ### Pause, resume or cancel the recording Pause a running recording and resume it later, or cancel it to discard the recorded audio. Pausing and resuming is only available on Android (SDK 24+), iOS and Web: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const pauseRecording = async () => { await AudioRecorder.pauseRecording(); }; const resumeRecording = async () => { await AudioRecorder.resumeRecording(); }; const cancelRecording = async () => { await AudioRecorder.cancelRecording(); }; ``` ### Get the current recording status Check whether a recording is currently inactive, active or paused: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; const getRecordingStatus = async () => { const { status } = await AudioRecorder.getRecordingStatus(); console.log('Recording status:', status); }; ``` ### Listen for recording events Get notified when the recording is paused (e.g. when it is interrupted by a phone call), when it is stopped, or when an error occurs. The `recordingError` event is only available on iOS: ``` import { AudioRecorder } from '@capawesome-team/capacitor-audio-recorder'; 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('recordingResumed', ...)`](#addlistenerrecordingresumed-) - [`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('recordingResumed', ...) ``` addListener(eventName: 'recordingResumed', listenerFunc: () => void) => Promise ``` Called when the recording is resumed (e.g. after an audio session interruption ends, when `autoResumeAfterInterruption` is enabled). | Param | Type | | ------------------ | -------------------- | | **`eventName`** | `'recordingResumed'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 8.2.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 | | --------------------------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----- | | **`autoResumeAfterInterruption`** | `boolean` | Whether the recording should automatically resume after an audio session interruption ends (e.g. after a phone call). The system only resumes the recording if it indicates that resuming is appropriate. Only available on iOS. | `false` | 8.2.0 | | **`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 | ## Troubleshooting ##### `recordingError` event fires with "An unknown error occurred." and the recording file is 0 bytes This typically happens when an incompatible combination of `bitRate` and `sampleRate` is passed to `startRecording(...)`. The AAC encoder accepts the settings without error at start, but silently fails to encode, producing an empty file and triggering the `recordingError` event on stop. For example, requesting `bitRate: 256000` with `sampleRate: 16000` (mono AAC) is invalid, because the requested bitrate is far higher than what the encoder can produce from the given sample rate. As a rule of thumb, AAC requires roughly `bitRate <= sampleRate * channels * 6`. Either lower the `bitRate` or raise the `sampleRate` (the default is `44100` Hz, which works with bitrates up to ~256 kbps): ``` await AudioRecorder.startRecording({ bitRate: 256_000, sampleRate: 44_100, }); ``` ## FAQ ### Which audio format is used for recordings? The plugin records audio in AAC format. You can configure the bitrate (default: 192000 bytes per second) and sample rate (default: 44100 Hz) via the options of the `startRecording(...)` method on Android and iOS. ### Can I record audio while the app is in the background? Yes, the plugin supports background recording. On iOS, you need to enable the `Background Modes` capability with `Audio, AirPlay, and Picture in Picture` in your Xcode project, as described in the [Installation](#installation) section. ### What permissions are required to record audio? The plugin needs access to the device's microphone, which you can check and request using the `checkPermissions()` and `requestPermissions()` methods. On iOS, you also need to add the `NSMicrophoneUsageDescription` key to your `Info.plist` file as described in the [Installation](#installation) section. ### Why is my recording file empty? This typically happens when an incompatible combination of `bitRate` and `sampleRate` is passed to `startRecording(...)`. The AAC encoder silently fails to encode, producing an empty file and triggering the `recordingError` event. Either lower the `bitRate` or raise the `sampleRate`. See the [Troubleshooting](#troubleshooting) section for more details. ### Where is the recorded audio saved? By default, the recording is saved to a temporary location in the cache directory on Android and iOS, and its URI is returned by the `stopRecording()` method. You can save the recording to a custom location instead by passing the `uri` option to `startRecording(...)`. On Web, the recorded audio is returned as a `Blob`. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/): Play back recorded audio with background support. - [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/): Transcribe speech into text (speech-to-text). - [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/): Synthesize speech from text (text-to-speech). ## 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/). ## 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). # Capacitor Audio Session Plugin Capacitor plugin to configure and observe the iOS [audio session](https://developer.apple.com/documentation/avfaudio/avaudiosession). ## Features The Capacitor Audio Session plugin is one of the most complete audio session management solutions for Capacitor apps. Here are some of the key features: - 🎛️ **Configuration**: Set the audio session category, mode and options. - 🔌 **Activation**: Activate and deactivate the audio session. - 🔊 **Output routing**: Read the current audio outputs and override the output port. - 📡 **Events**: Observe interruption and route change events. - 🤝 **Compatibility**: Works alongside the [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/) and [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/) 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! ## Use Cases The Audio Session plugin is typically used whenever an app needs fine-grained control over how audio behaves on iOS, for example: - **Media playback apps**: Configure the `playback` category so audio keeps playing while other apps are silenced. - **Voice and video chat**: Switch between the receiver and the built-in speaker using the output override. - **Mixing with other apps**: Let your audio play alongside (or duck) audio from other apps using the category options. - **Handling phone calls**: Pause playback when the session is interrupted and resume it when the interruption ends. - **Headphone awareness**: React when headphones or Bluetooth devices are plugged in or out by observing route changes. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-audio-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/capacitor-audio-session npx cap sync ``` This plugin is only available on **iOS**. On Android and Web, all methods reject as unimplemented. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to configure the audio session, activate or deactivate it, read the current audio outputs, route playback to the built-in speaker, listen for interruptions and route changes, and remove all listeners. ### Configure the audio session Set the audio session category, mode and options, for example for movie playback that interrupts audio from other apps. All methods of this plugin are only available on iOS: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const configure = async () => { await AudioSession.configure({ category: 'playback', mode: 'moviePlayback', options: { mixWithOthers: false, }, }); }; ``` ### Activate or deactivate the audio session Activate the audio session before playing or recording audio, and deactivate it when you are done so that other apps can resume their playback: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const setActive = async () => { await AudioSession.setActive({ active: true }); }; ``` ### Read the current audio outputs Get the audio outputs of the current audio route, for example to check whether headphones are connected: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const getCurrentOutputs = async () => { const { outputs } = await AudioSession.getCurrentOutputs(); return outputs; }; ``` ### Route playback to the built-in speaker Override the audio output port that is used for playback, for example to switch from the receiver to the loudspeaker during a call: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const overrideOutput = async () => { await AudioSession.overrideOutput({ type: 'speaker' }); }; ``` ### Listen for interruptions Get notified when the audio session is interrupted, e.g. by an incoming phone call. The `shouldResume` flag tells you whether playback should resume after the interruption ended: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const addInterruptionListener = async () => { await AudioSession.addListener('interruption', event => { console.log('Interruption:', event.type, event.shouldResume); }); }; ``` ### Listen for route changes Get notified when the audio route changes, e.g. when headphones are plugged in or out: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const addRouteChangeListener = async () => { await AudioSession.addListener('routeChange', event => { console.log('Route change:', event.reason, event.outputs); }); }; ``` ### Remove all listeners Remove all listeners that were registered for this plugin: ``` import { AudioSession } from '@capawesome/capacitor-audio-session'; const removeAllListeners = async () => { await AudioSession.removeAllListeners(); }; ``` ## API - [`configure(...)`](#configure) - [`getCurrentOutputs()`](#getcurrentoutputs) - [`overrideOutput(...)`](#overrideoutput) - [`setActive(...)`](#setactive) - [`addListener('interruption', ...)`](#addlistenerinterruption-) - [`addListener('routeChange', ...)`](#addlistenerroutechange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### configure(...) ``` configure(options: ConfigureOptions) => Promise ``` Configure the audio session category, mode and options. Only available on iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `ConfigureOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### getCurrentOutputs() ``` getCurrentOutputs() => Promise ``` Get the audio outputs of the current audio route. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### overrideOutput(...) ``` overrideOutput(options: OverrideOutputOptions) => Promise ``` Override the audio output port that is used for playback. Only available on iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `OverrideOutputOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setActive(...) ``` setActive(options: SetActiveOptions) => Promise ``` Activate or deactivate the audio session. Only available on iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetActiveOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addListener('interruption', ...) ``` addListener(eventName: 'interruption', listenerFunc: (event: InterruptionEvent) => void) => Promise ``` Called when the audio session is interrupted, e.g. by an incoming phone call. Only available on iOS. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'interruption'` | | **`listenerFunc`** | `(event: InterruptionEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('routeChange', ...) ``` addListener(eventName: 'routeChange', listenerFunc: (event: RouteChangeEvent) => void) => Promise ``` Called when the audio route changes, e.g. when headphones are plugged in or out. Only available on iOS. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'routeChange'` | | **`listenerFunc`** | `(event: RouteChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available on iOS. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ConfigureOptions | Prop | Type | Description | Default | Since | | -------------- | ----------------------------- | ----------------------------------- | ----------- | ----- | | **`category`** | `AudioSessionCategory` | The audio session category. | | 0.1.0 | | **`mode`** | `AudioSessionMode` | The audio session mode. | `'default'` | 0.1.0 | | **`options`** | `AudioSessionCategoryOptions` | The audio session category options. | | 0.1.0 | #### AudioSessionCategoryOptions | Prop | Type | Description | Default | Since | | ------------------------------------------ | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`allowAirPlay`** | `boolean` | Whether AirPlay devices can be used for output. | `false` | 0.1.0 | | **`allowBluetooth`** | `boolean` | Whether Bluetooth hands-free devices can be used for input and output. | `false` | 0.1.0 | | **`allowBluetoothA2DP`** | `boolean` | Whether Bluetooth A2DP devices can be used for output. | `false` | 0.1.0 | | **`defaultToSpeaker`** | `boolean` | Whether audio is routed to the built-in speaker instead of the receiver when no other audio route is connected. | `false` | 0.1.0 | | **`duckOthers`** | `boolean` | Whether audio from other sessions is reduced in volume (ducked) while audio from this session plays. | `false` | 0.1.0 | | **`interruptSpokenAudioAndMixWithOthers`** | `boolean` | Whether audio from other sessions using the `spokenAudio` mode is interrupted and audio from this session is mixed with the remaining audio. | `false` | 0.1.0 | | **`mixWithOthers`** | `boolean` | Whether audio from this session mixes with audio from other active sessions instead of interrupting them. | `false` | 0.1.0 | #### GetCurrentOutputsResult | Prop | Type | Description | Since | | ------------- | ---------------------- | --------------------------------------------- | ----- | | **`outputs`** | `AudioSessionOutput[]` | The audio outputs of the current audio route. | 0.1.0 | #### AudioSessionOutput | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------- | ----- | | **`portName`** | `string` | The human-readable name of the audio output port. | 0.1.0 | | **`portType`** | `string` | The type of the audio output port. | 0.1.0 | #### OverrideOutputOptions | Prop | Type | Description | Since | | ---------- | -------------------- | ------------------------------------------- | ----- | | **`type`** | `OverrideOutputType` | The audio output port to route playback to. | 0.1.0 | #### SetActiveOptions | Prop | Type | Description | Default | Since | | -------------------------------- | --------- | -------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`active`** | `boolean` | Whether the audio session should be activated (`true`) or deactivated (`false`). | | 0.1.0 | | **`notifyOthersOnDeactivation`** | `boolean` | Whether other audio sessions are notified when this session is deactivated, so they can resume playback. | `true` | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### InterruptionEvent | Prop | Type | Description | Since | | ------------------ | ------------------ | ---------------------------------------------------------------------------------------------- | ----- | | **`shouldResume`** | `boolean` | Whether playback should resume after the interruption ended. Only `true` if `type` is `ended`. | 0.1.0 | | **`type`** | `InterruptionType` | The type of the interruption. | 0.1.0 | #### RouteChangeEvent | Prop | Type | Description | Since | | ------------- | ---------------------- | ------------------------------------------------------ | ----- | | **`outputs`** | `AudioSessionOutput[]` | The audio outputs of the audio route after the change. | 0.1.0 | | **`reason`** | `RouteChangeReason` | The reason why the audio route changed. | 0.1.0 | ### Type Aliases #### AudioSessionCategory The audio session category. `'ambient' | 'multiRoute' | 'playAndRecord' | 'playback' | 'record' | 'soloAmbient'` #### AudioSessionMode The audio session mode. `'default' | 'gameChat' | 'measurement' | 'moviePlayback' | 'spokenAudio' | 'videoChat' | 'videoRecording' | 'voiceChat' | 'voicePrompt'` #### OverrideOutputType The audio output port to route playback to. `'default' | 'speaker'` #### InterruptionType The type of an audio session interruption. `'began' | 'ended'` #### RouteChangeReason The reason why the audio route changed. `'categoryChange' | 'newDeviceAvailable' | 'noSuitableRouteForCategory' | 'oldDeviceUnavailable' | 'override' | 'routeConfigurationChange' | 'unknown' | 'wakeFromSleep'` ## Session Ownership The audio session returned by [`AVAudioSession.sharedInstance()`](https://developer.apple.com/documentation/avfaudio/avaudiosession) is a **single, app-wide** object. This plugin gives you **manual** control over it, but so do other audio-related plugins (such as the [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/), [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/) and Speech Recognition plugins) and the underlying platform APIs they use. Because the session is shared, the **last write wins**: calling `configure(...)` may override the category, mode or options that another plugin has set, and vice versa. This plugin cannot prevent that. If you combine this plugin with other audio plugins, make sure to reconfigure the session whenever you switch between playback, recording and other audio scenarios. ## FAQ ### How is this plugin different from other similar plugins? It gives you fine-grained control over the iOS audio session through one fully typed API: set the category, mode and options, activate or deactivate the session, read the current outputs and override the output route, and observe interruption and route-change events. It documents the shared, app-wide nature of the audio session honestly, so you know exactly how it behaves alongside other audio code, and it's actively maintained against the latest Capacitor and iOS versions. If you only play a simple sound, a basic setup is enough; if you need precise session control, this plugin is designed for exactly that. ### Is this plugin available on Android or the Web? No, this plugin is only available on iOS since it controls the iOS-specific [audio session](https://developer.apple.com/documentation/avfaudio/avaudiosession). On Android and Web, all methods reject as unimplemented, so you can safely include the plugin in a cross-platform app. ### Why are my audio session settings overridden by other plugins? The audio session is a single, app-wide object that is shared with other audio-related plugins (such as the [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/) and [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/) plugins) and the underlying platform APIs they use. The last write wins, so another plugin may override the category, mode or options you have set. Reconfigure the session whenever you switch between playback, recording and other audio scenarios. See the [Session Ownership](#session-ownership) section for more details. ### How can my app play audio without interrupting other apps? Use the `mixWithOthers` category option when calling `configure(...)` to mix your audio with audio from other active sessions instead of interrupting them. Alternatively, use the `duckOthers` option to reduce the volume of other sessions while your audio plays. ### How do I resume playback after a phone call interrupted my app? Add a listener for the `interruption` event. When the event's `type` is `ended` and `shouldResume` is `true`, playback should be resumed. See [Listen for interruptions](#listen-for-interruptions) for an example. ### How do I route audio to the loudspeaker? Call `overrideOutput(...)` with the type `speaker` to route playback to the built-in speaker, and with `default` to restore the default route. If you want audio to be routed to the speaker by default when no other route is connected, use the `defaultToSpeaker` category option when calling `configure(...)`. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Audio Player](https://capawesome.io/docs/sdks/capacitor/audio-player/): Play audio with background support. - [Audio Recorder](https://capawesome.io/docs/sdks/capacitor/audio-recorder/): Record audio using the device's microphone. - [Media Session](https://capawesome.io/docs/sdks/capacitor/media-session/): Interact with media controllers, volume keys and media buttons. - [Volume](https://capawesome.io/docs/sdks/capacitor/volume/): Control the volume and observe hardware volume button presses. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-session/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/audio-session/LICENSE). # Capacitor Background Geolocation Plugin Capacitor plugin for reliable background geolocation tracking on Android and iOS. Provides a first-class permissions API, a correctly plumbed Android foreground service and fine-grained tuning options. ## Features The Capacitor Background Geolocation plugin provides everything you need for reliable location tracking, even while the app is in the background. Here are some of the key features: - 🛰️ **Background Tracking**: Keeps receiving position updates while the app is in the background. - 🔐 **Granular Permissions**: First-class permissions API including the two-step background location upgrade flow. - 📍 **One-Shot Position**: Get the current position with configurable accuracy, timeout and maximum age. - 🗄️ **Local Queue**: Persist every position in an on-device SQLite queue and read it out later, so nothing is lost while the web view is suspended. - ☁️ **HTTP Upload**: Upload queued positions to your own server in batches with automatic retries and at-least-once delivery. - 🧪 **Upload Testing**: Test the upload pipeline without your own server using the free [Background Geolocation Playground](https://background-geolocation-playground.capawesome.io) webpage. - 🛠️ **Real Tuning Knobs**: Configure accuracy, distance filter and update interval. - 🤖 **Foreground Service**: Correct Android 14+ foreground service with a fully configurable notification. - 📡 **Provider Choice**: Fused location provider by default with an escape hatch to the platform location manager for devices without Google Play services. - 🎯 **Full Accuracy Requests**: Request temporary full accuracy from users with reduced location accuracy on iOS. - 🕵️ **Mock Location Detection**: Every position reports whether it was delivered by a mock location provider on Android. - 🔋 **Battery Friendly**: Optional automatic pausing of position updates on iOS when the device is stationary. - 🔒 **Public APIs Only**: Built exclusively on public platform APIs, so it is safe for App Review and resilient to OS updates. - 🤝 **Compatibility**: Works hand in hand with the [Geofences](https://capawesome.io/docs/sdks/capacitor/geofences/) and [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/) 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! ## Use Cases The Background Geolocation plugin is typically used whenever an app needs to know where the device is over a period of time, for example: - **Fitness and activity tracking**: Record runs, rides and hikes even when the screen is off. - **Fleet and workforce management**: Track delivery drivers or field workers during their shift. - **Navigation**: Keep the position up to date while the user switches to another app. - **Safety**: Share the live location with family members or emergency contacts. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [Announcing the Capacitor Background Geolocation Plugin](https://capawesome.io/blog/announcing-the-capacitor-background-geolocation-plugin/): A tour of watch sessions, the native SQLite queue, and the HTTP upload pipeline. - [Why iOS Stops Sending Background Location Updates](https://capawesome.io/blog/why-ios-stops-background-location-updates/): Core Location's background rules mapped to the iOS options of this plugin, from the `location` background mode to automatic pausing. ## 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-background-geolocation` 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-background-geolocation npx cap sync ``` ### Android #### Permissions The plugin already declares the location and foreground service permissions in its own manifest. If you want to receive position updates while the app is in the **background**, you must additionally declare the `ACCESS_BACKGROUND_LOCATION` permission in the `AndroidManifest.xml` file of your app before or after the `application` tag: ``` ``` **Attention**: This permission is deliberately **not** declared by the plugin because Google Play requires a policy declaration and review for every app that requests background location access. Only declare it if your app really needs background tracking and make sure to complete the [location permissions declaration](https://support.google.com/googleplay/android-developer/answer/9799150) in the Google Play Console. The `INTERNET` permission that the [HTTP Upload](#http-upload) feature requires is already declared by every Capacitor app template, so no additional configuration is needed for it. #### 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: - `$playServicesLocationVersion` version of `com.google.android.gms:play-services-location` (default: `21.4.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS Add the `NSLocationWhenInUseUsageDescription` and `NSLocationAlwaysAndWhenInUseUsageDescription` keys to the `Info.plist` file of your app to explain why your app needs access to the location: ``` NSLocationWhenInUseUsageDescription The app needs access to your location to track your position. NSLocationAlwaysAndWhenInUseUsageDescription The app needs access to your location to track your position, even while the app is in the background. ``` If you want to receive position updates while the app is in the **background**, you must also enable the `location` background mode in the `Info.plist` file of your app: ``` UIBackgroundModes location ``` If you want to use the `requestTemporaryFullAccuracy(...)` method, you must also add the `NSLocationTemporaryUsageDescriptionDictionary` key to the `Info.plist` file of your app with one entry per purpose key: ``` NSLocationTemporaryUsageDescriptionDictionary navigation The app needs your precise location to provide turn-by-turn navigation. ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to get the current position, watch the position of the device, read queued positions, upload positions to a server, request temporary full accuracy, and check and request permissions. ### Get the current position Get a one-shot position of the device with a configurable accuracy, timeout and maximum age. Only available on Android and iOS: ``` import { Accuracy, BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const getCurrentPosition = async () => { const { position } = await BackgroundGeolocation.getCurrentPosition({ accuracy: Accuracy.High, timeout: 10000, }); return position; }; ``` ### Watch the position Start a watch session to receive position updates via the `positionChange` event, even while the app is in the background. Errors that occur during an active watch session are delivered via the `positionError` event. Only one watch session can be active at a time. Only available on Android and iOS: ``` import { Accuracy, ActivityType, BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const startWatching = async () => { // Enable the queue so that no position is lost while the web view is suspended. await BackgroundGeolocation.setConfig({ maxSize: 50000 }); await BackgroundGeolocation.addListener('positionChange', event => { console.log('New position: ', event.position); }); await BackgroundGeolocation.addListener('positionError', event => { console.error('Position error: ', event.code, event.message); }); await BackgroundGeolocation.startWatching({ accuracy: Accuracy.High, distanceFilter: 10, androidInterval: 5000, androidNotification: { title: 'Location Tracking', text: 'Your location is being tracked.', }, iosActivityType: ActivityType.Fitness, }); }; const stopWatching = async () => { await BackgroundGeolocation.stopWatching(); }; const isWatching = async () => { const { watching } = await BackgroundGeolocation.isWatching(); return watching; }; ``` ### Read queued positions The plugin can store every position of a watch session in a local queue that survives app restarts, so your app can read the positions it missed while the web view was suspended. Enable the queue with `setConfig(...)` and drain it whenever your app is in the foreground again. Only available on Android and iOS: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const enableQueue = async () => { await BackgroundGeolocation.setConfig({ maxSize: 50000 }); }; const drainQueue = async () => { let hasMore = true; while (hasMore) { const result = await BackgroundGeolocation.getQueuedPositions({ limit: 1000, }); if (!result.positions.length) { break; } await persist(result.positions); await BackgroundGeolocation.deleteQueuedPositions({ upToId: result.positions[result.positions.length - 1].id, }); hasMore = result.hasMore; } }; ``` See [Queue](#queue) for all options, the drain loop and the queue behavior. ### Upload positions to a server The plugin can upload every queued position to your own server without involving JavaScript, so that no position is lost while the device is offline. Provide the `url` option to enable it. Only available on Android and iOS: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const enableUpload = async () => { await BackgroundGeolocation.setConfig({ url: 'https://api.example.com/positions', headers: { Authorization: 'Bearer eyJhbGciOi...', }, }); }; ``` See [HTTP Upload](#http-upload) for all options, the server contract and the response handling. ### Request temporary full accuracy Ask users who have granted reduced location accuracy for full accuracy for the duration of the app session. The `NSLocationTemporaryUsageDescriptionDictionary` key must contain an entry for the given purpose key. Only available on iOS: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const requestTemporaryFullAccuracy = async () => { await BackgroundGeolocation.requestTemporaryFullAccuracy({ purposeKey: 'navigation', }); }; ``` ### Check and request permissions Check and request the permissions to access the location services. The background location permission must be requested in **two steps** on both platforms. Only available on Android and iOS: 1. Request the `location` (and optionally `notifications`) permission first. This displays the default system dialog. 1. Request the `backgroundLocation` permission in a **separate** call, ideally after explaining to the user why the app needs it: 1. On **Android 11+**, the user is taken to the location settings of the app where the `Allow all the time` option must be selected. Google recommends showing an in-app explanation before triggering this step. 1. On **iOS**, the operating system presents the upgrade prompt that asks the user to change the permission from `While Using the App` to `Always`. ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const checkPermissions = async () => { return BackgroundGeolocation.checkPermissions(); }; const requestPermissions = async () => { return BackgroundGeolocation.requestPermissions({ permissions: ['location', 'notifications'], }); }; const requestBackgroundLocationPermission = async () => { return BackgroundGeolocation.requestPermissions({ permissions: ['backgroundLocation'], }); }; ``` If a permission was permanently denied, you can take the user to the app settings to change it: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const openSettings = async () => { await BackgroundGeolocation.openSettings(); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`clearQueue()`](#clearqueue) - [`deleteQueuedPositions(...)`](#deletequeuedpositions) - [`getConfig()`](#getconfig) - [`getCurrentPosition(...)`](#getcurrentposition) - [`getQueuedPositions(...)`](#getqueuedpositions) - [`getQueueStatus()`](#getqueuestatus) - [`isWatching()`](#iswatching) - [`openSettings()`](#opensettings) - [`requestPermissions(...)`](#requestpermissions) - [`requestTemporaryFullAccuracy(...)`](#requesttemporaryfullaccuracy) - [`resetConfig()`](#resetconfig) - [`setConfig(...)`](#setconfig) - [`startWatching(...)`](#startwatching) - [`stopWatching()`](#stopwatching) - [`triggerUpload()`](#triggerupload) - [`addListener('positionChange', ...)`](#addlistenerpositionchange-) - [`addListener('positionError', ...)`](#addlistenerpositionerror-) - [`addListener('uploadFailed', ...)`](#addlisteneruploadfailed-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### checkPermissions() ``` checkPermissions() => Promise ``` Check the current permission status. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### clearQueue() ``` clearQueue() => Promise ``` Delete all positions from the queue and reset the dropped counter. This method can be called with or without an active watch session, for example to discard pending positions when the user signs out. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### deleteQueuedPositions(...) ``` deleteQueuedPositions(options: DeleteQueuedPositionsOptions) => Promise ``` Delete all queued positions up to and including the given id. Call this method after the positions returned by `getQueuedPositions()` have been persisted by your app. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `DeleteQueuedPositionsOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### getConfig() ``` getConfig() => Promise ``` Get the configuration that was last set via `setConfig(...)`. Only the properties that were provided are returned, so the result can be spread into `setConfig(...)` to change a single property: ``` const config = await BackgroundGeolocation.getConfig(); await BackgroundGeolocation.setConfig({ ...config, maxSize: 5000 }); ``` An empty object is returned if `setConfig(...)` has not been called yet. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### getCurrentPosition(...) ``` getCurrentPosition(options?: GetCurrentPositionOptions | undefined) => Promise ``` Get the current position of the device. On **Android**, the location permission is requested automatically if it has not been granted yet. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `GetCurrentPositionOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getQueuedPositions(...) ``` getQueuedPositions(options?: GetQueuedPositionsOptions | undefined) => Promise ``` Get the positions that are currently stored in the queue, oldest first. The positions are **not** deleted from the queue. Call `deleteQueuedPositions(...)` after the positions have been persisted by your app. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `GetQueuedPositionsOptions` | **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### getQueueStatus() ``` getQueueStatus() => Promise ``` Get the current status of the queue. This method can be called with or without an active watch session. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### isWatching() ``` isWatching() => Promise ``` Check whether or not a watch session is currently active. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the native app settings page to allow the user to grant the app the required permissions. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions. The `backgroundLocation` permission must be requested in a **second**, separate call after the `location` permission has been granted: - On **Android 11+**, the user is taken to the location settings of the app where the `Allow all the time` option must be selected. - On **iOS**, the operating system presents the upgrade prompt that asks the user to change the permission from `While Using the App` to `Always`. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestTemporaryFullAccuracy(...) ``` requestTemporaryFullAccuracy(options: RequestTemporaryFullAccuracyOptions) => Promise ``` Request temporary access to the full accuracy location of the device. Call this method if the user has granted the app reduced location accuracy to ask for full accuracy for the duration of the app session. The `NSLocationTemporaryUsageDescriptionDictionary` key must be defined in the `Info.plist` file of your app with an entry for the given `purposeKey`. Only available on iOS. | Param | Type | | ------------- | ------------------------------------- | | **`options`** | `RequestTemporaryFullAccuracyOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### resetConfig() ``` resetConfig() => Promise ``` Discard the configuration so that no positions are stored or uploaded. Positions that are already queued are kept, use `clearQueue()` to discard them. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### setConfig(...) ``` setConfig(options: SetConfigOptions) => Promise ``` Set the configuration of the position queue and of the upload. The configuration is persisted natively so that it keeps working when the operating system wakes your app without a web view. Positions are stored in the queue if `maxSize` or `url` is provided, and they are uploaded if `url` is provided. Use `resetConfig()` to stop storing and uploading positions. **This method replaces the whole configuration**, so every property you omit falls back to its default. `setConfig({ maxSize: 5000 })` therefore also stops the upload, because `url` is no longer set. Keep your configuration in one place and pass it whole: ``` await BackgroundGeolocation.setConfig({ maxSize: 5000, url }); ``` Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetConfigOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### startWatching(...) ``` startWatching(options?: StartWatchingOptions | undefined) => Promise ``` Start watching the position of the device. Position updates are delivered via the `positionChange` event. Errors that occur after the watch session has been started (e.g. the user disables the location services) are delivered via the `positionError` event. Only one watch session can be active at a time. The promise rejects with the `ALREADY_WATCHING` error code if a watch session is already active. On **Android**, a foreground service with a persistent notification is started so that the app keeps receiving position updates while it is in the background. For this reason, the `androidNotification` option must be provided. The location permission is requested automatically if it has not been granted yet. On **iOS**, position updates are delivered while the app is in the background if the `location` background mode is enabled in the app. If the queue is enabled via `setConfig(...)`, every position of the watch session is additionally stored in the local queue. Without the `backgroundLocation` permission, the watch session keeps working but position updates may be suspended while the app is in the background. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `StartWatchingOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### stopWatching() ``` stopWatching() => Promise ``` Stop the active watch session. On **Android**, this also stops the foreground service and removes the associated notification. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### triggerUpload() ``` triggerUpload() => Promise ``` Immediately attempt to upload all queued positions. Any pending retry backoff is cancelled and a new upload attempt is started right away. The promise resolves as soon as the attempt has been scheduled, not when the positions have been delivered. The promise rejects if no `url` has been set via `setConfig(...)`. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### addListener('positionChange', ...) ``` addListener(eventName: 'positionChange', listenerFunc: (event: PositionChangeEvent) => void) => Promise ``` Called when a new position is available during an active watch session. This event is only delivered while the web view is alive. If the queue is enabled, treat the queue as the single source of truth instead. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'positionChange'` | | **`listenerFunc`** | `(event: PositionChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('positionError', ...) ``` addListener(eventName: 'positionError', listenerFunc: (event: PositionErrorEvent) => void) => Promise ``` Called when an error occurs during an active watch session, for example when the user disables the location services or revokes the location permission. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'positionError'` | | **`listenerFunc`** | `(event: PositionErrorEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('uploadFailed', ...) ``` addListener(eventName: 'uploadFailed', listenerFunc: (event: UploadFailedEvent) => void) => Promise ``` Called when an upload attempt of queued positions fails. The affected positions remain in the queue and are retried automatically unless the server rejected them permanently. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'uploadFailed'` | | **`listenerFunc`** | `(event: UploadFailedEvent) => void` | **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Description | Since | | ------------------------ | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`backgroundLocation`** | `PermissionState` | Permission state for using the location services while the app is in the background. On **Android 9 and older**, this always mirrors the `location` permission state since no separate background location permission exists. | 0.0.1 | | **`location`** | `PermissionState` | Permission state for using the location services while the app is in use. | 0.0.1 | | **`notifications`** | `PermissionState` | Permission state for posting notifications. On **Android 12 and older** and on **iOS**, this is always `granted` since no notification permission is required. | 0.0.1 | #### DeleteQueuedPositionsOptions | Prop | Type | Description | Since | | ------------ | -------- | ----------------------------------------------------------------------------------------------------- | ----- | | **`upToId`** | `number` | The id of the newest position to delete. All queued positions with this id or a lower id are deleted. | 0.2.0 | #### SetConfigOptions | Prop | Type | Description | Default | Since | | ------------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------ | | **`batchSize`** | `number` | The maximum number of positions that are uploaded in a single request. Must be positive and must not be greater than `maxSize`. Set this option to `1` to upload each position immediately. | `100` | 0.2.0 | | **`extras`** | \`{ \[key: string\]: string | number | boolean; }\` | Static metadata that is attached to every upload request as the `extras` property of the request body. | | **`flushInterval`** | `number` | The maximum time in milliseconds before queued positions are uploaded, even if the batch size has not been reached yet. Must be positive. | `60000` | 0.2.0 | | **`headers`** | `{ [key: string]: string; }` | Static HTTP headers that are sent with every upload request, for example for authorization. | | 0.2.0 | | **`maxSize`** | `number` | The maximum number of positions that are stored in the queue. When the queue is full, the oldest positions are dropped first. A stored position occupies about `210` bytes, so the default of `50000` needs about `10` MB. Must be positive. Set this option to `1` to keep only the most recent position, for example when you upload every position immediately and only the latest one matters. | `50000` | 0.2.0 | | **`url`** | `string` | The URL the positions are uploaded to via HTTP `POST`. Providing this property enables the upload. Omit it to keep the positions on the device only. | | 0.2.0 | #### GetCurrentPositionResult | Prop | Type | Description | Since | | -------------- | ---------- | ----------------------------------- | ----- | | **`position`** | `Position` | The current position of the device. | 0.0.1 | #### Position | Prop | Type | Description | Since | | ---------------------- | --------- | ----------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`accuracy`** | `number` | The estimated horizontal accuracy radius of the position in meters. | 0.0.1 | | **`altitude`** | \`number | null\` | The altitude of the position in meters or `null` if the altitude is not available. | | **`altitudeAccuracy`** | \`number | null\` | The estimated accuracy of the altitude in meters or `null` if the altitude accuracy is not available. | | **`bearing`** | \`number | null\` | The direction in which the device is traveling in degrees relative to north or `null` if the bearing is not available. | | **`latitude`** | `number` | The latitude of the position in degrees. | 0.0.1 | | **`longitude`** | `number` | The longitude of the position in degrees. | 0.0.1 | | **`simulated`** | \`boolean | null\` | Whether or not the position was delivered by a mock location provider. On **iOS**, this is always `null` since the operating system does not provide this information. | | **`speed`** | \`number | null\` | The speed of the device in meters per second or `null` if the speed is not available. | | **`timestamp`** | `number` | The time at which the position was determined in milliseconds since the Unix epoch. | 0.0.1 | #### GetCurrentPositionOptions | Prop | Type | Description | Default | Since | | --------------------------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | ----- | | **`accuracy`** | `Accuracy` | The desired accuracy of the position. | `Accuracy.High` | 0.0.1 | | **`androidForceLocationManager`** | `boolean` | Whether or not to force the use of the platform location manager instead of the fused location provider, even if Google Play services is available. See the option of the same name of `startWatching(...)` for the cases in which this is useful. Only available on Android. | `false` | 0.2.0 | | **`maximumAge`** | `number` | The maximum age in milliseconds of a cached position that is accepted as the current position. If set to `0`, a fresh position is always fetched. | `0` | 0.0.1 | | **`timeout`** | `number` | The maximum time in milliseconds to wait for a position. The promise rejects with the `TIMEOUT` error code if no position is available within this time. | `10000` | 0.0.1 | #### GetQueuedPositionsResult | Prop | Type | Description | Since | | --------------- | ------------------ | --------------------------------------------------------------- | ----- | | **`hasMore`** | `boolean` | Whether or not more positions are available than were returned. | 0.2.0 | | **`positions`** | `QueuedPosition[]` | The queued positions, oldest first. | 0.2.0 | #### QueuedPosition | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `number` | The id of the position in the queue. The id is unique per app installation and strictly increasing. | 0.2.0 | #### GetQueuedPositionsOptions | Prop | Type | Description | Default | Since | | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`afterId`** | `number` | The id of the newest position that has already been read. Only positions with a higher id are returned. If not provided, the oldest positions in the queue are returned. | | 0.2.0 | | **`limit`** | `number` | The maximum number of positions to return. | `100` | 0.2.0 | #### GetQueueStatusResult | Prop | Type | Description | Since | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`droppedCount`** | `number` | The number of positions that were dropped without being read since the queue was last cleared, for example because the queue was full or the positions expired. | 0.2.0 | | **`lastUploadedAt`** | \`number | null\` | The time at which the last batch of positions was uploaded successfully in milliseconds since the Unix epoch or `null` if no batch has been uploaded successfully yet. | | **`pendingCount`** | `number` | The number of positions that are currently stored in the queue. | 0.2.0 | #### IsWatchingResult | Prop | Type | Description | Since | | -------------- | --------- | --------------------------------------------------- | ----- | | **`watching`** | `boolean` | Whether or not a watch session is currently active. | 0.0.1 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | ------------------ | --------------------------- | ------------------------------- | ----- | | **`permissions`** | `PermissionType[]` | The permissions to request. | `['location', 'notifications']` | 0.0.1 | #### RequestTemporaryFullAccuracyOptions | Prop | Type | Description | Since | | ---------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`purposeKey`** | `string` | The key of the entry in the `NSLocationTemporaryUsageDescriptionDictionary` dictionary of the `Info.plist` file of your app that describes why the app needs the full accuracy location. | 0.0.1 | #### StartWatchingOptions | Prop | Type | Description | Default | Since | | --------------------------------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------- | ----- | | **`accuracy`** | `Accuracy` | The desired accuracy of the position updates. | `Accuracy.High` | 0.0.1 | | **`androidForceLocationManager`** | `boolean` | Whether or not to force the use of the platform location manager instead of the fused location provider, even if Google Play services is available. Devices without Google Play services use the platform location manager anyway, so this option is not needed for them. Set it to `true` to receive the unmodified positions of a single provider instead of the fused ones, or if the fused location provider is unreliable on a device that reports Google Play services as available. This consumes more battery. Only available on Android. | `false` | 0.0.1 | | **`androidInterval`** | `number` | The interval in milliseconds at which position updates are requested. A position is requested on every interval, whether or not the device has moved, so use `distanceFilter` to skip the positions of a device that stands still. iOS has no equivalent option: the operating system decides how often it reports a position, which is far less often while the device is stationary. Only available on Android. | `5000` | 0.0.1 | | **`androidNotification`** | `AndroidNotificationOptions` | The configuration of the notification that is displayed while the watch session is active. This option must be provided on Android. Only available on Android. | | 0.0.1 | | **`distanceFilter`** | `number` | The minimum distance in meters that the device must move before a new position update is delivered. Set this option to `0` to receive every position, which also means that a device that stands still keeps reporting the same position. | `10` | 0.0.1 | | **`iosActivityType`** | `ActivityType` | The type of activity for which the position updates are used. This helps the operating system to decide when position updates may be paused automatically. Only available on iOS. | `ActivityType.Other` | 0.0.1 | | **`iosPausesAutomatically`** | `boolean` | Whether or not the operating system is allowed to pause position updates automatically when the device is unlikely to move (e.g. when the user is stationary for a longer period of time). This can significantly improve battery life. Only available on iOS. | `false` | 0.0.1 | | **`iosShowBackgroundIndicator`** | `boolean` | Whether or not the status bar indicator is displayed when the app uses the location services in the background. Only available on iOS. | `true` | 0.0.1 | #### AndroidNotificationOptions | Prop | Type | Description | Default | Since | | ----------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | ----- | | **`channelName`** | `string` | The name of the notification channel in which the notification is displayed. | `'Background Geolocation'` | 0.0.1 | | **`color`** | `string` | The color of the notification as a hex color code (e.g. `#42A5F5`). | | 0.0.1 | | **`icon`** | `string` | The name of the drawable resource that is displayed as the small icon of the notification (e.g. `ic_stat_location`). If not provided, the launcher icon of the app is used. | | 0.0.1 | | **`text`** | `string` | The body text of the notification. | | 0.0.1 | | **`title`** | `string` | The title of the notification. | | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### PositionChangeEvent | Prop | Type | Description | Since | | -------------- | ---------- | ------------------------------- | ----- | | **`position`** | `Position` | The new position of the device. | 0.0.1 | #### PositionErrorEvent | Prop | Type | Description | Since | | ------------- | ----------- | ------------------ | ----- | | **`code`** | `ErrorCode` | The error code. | 0.0.1 | | **`message`** | `string` | The error message. | 0.0.1 | #### UploadFailedEvent | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------------------------------------------------- | ----- | | **`message`** | `string` | The error message. | 0.0.1 | | **`statusCode`** | `number` | The HTTP status code of the response, if a response was received. | 0.0.1 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### GetConfigResult `SetConfigOptions` #### PermissionType The permissions that can be requested. `'backgroundLocation' | 'location' | 'notifications'` ### Enums #### Accuracy | Members | Value | Description | Since | | -------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Balanced`** | `'BALANCED'` | A balance between accuracy and power consumption. The position is accurate to within about a hundred meters. | 0.0.1 | | **`High`** | `'HIGH'` | The most accurate position that is available. This uses the most power and should only be used when a precise position is required. | 0.0.1 | | **`Low`** | `'LOW'` | A low accuracy position with minimal power consumption. The position is accurate to within about a kilometer. | 0.0.1 | #### ActivityType | Members | Value | Description | Since | | -------------------------- | ------------------------- | ---------------------------------------------------------------------------------------------- | ----- | | **`Airborne`** | `'AIRBORNE'` | [Position](#position) updates for activities in the air (e.g. flying). | 0.0.1 | | **`AutomotiveNavigation`** | `'AUTOMOTIVE_NAVIGATION'` | [Position](#position) updates for automotive navigation. | 0.0.1 | | **`Fitness`** | `'FITNESS'` | [Position](#position) updates for fitness activities (e.g. walking, running or cycling). | 0.0.1 | | **`Other`** | `'OTHER'` | [Position](#position) updates for activities that are not covered by the other activity types. | 0.0.1 | | **`OtherNavigation`** | `'OTHER_NAVIGATION'` | [Position](#position) updates for vehicular navigation that is not automotive (e.g. boating). | 0.0.1 | #### ErrorCode | Members | Value | Description | Since | | ------------------------------ | ------------------------------ | --------------------------------------------------- | ----- | | **`AlreadyWatching`** | `'ALREADY_WATCHING'` | A watch session is already active. | 0.0.1 | | **`LocationServicesDisabled`** | `'LOCATION_SERVICES_DISABLED'` | The location services are disabled on the device. | 0.0.1 | | **`PermissionDenied`** | `'PERMISSION_DENIED'` | The location permission has not been granted. | 0.0.1 | | **`PositionUnavailable`** | `'POSITION_UNAVAILABLE'` | The position is currently not available. | 0.0.1 | | **`Timeout`** | `'TIMEOUT'` | No position was available within the given timeout. | 0.0.1 | ## Queue The plugin can store every position of a watch session in a local SQLite queue. The queue is the durable record of a watch session: it is written natively, survives app restarts and does not depend on a running web view. The [`positionChange`](#addlistenerpositionchange-) event is only an ephemeral live feed on top of it. ### Enabling the queue The queue is **opt-in** because precise location history at rest is sensitive data. Enable it with `setConfig(...)`: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const enableQueue = async () => { await BackgroundGeolocation.setConfig({ maxSize: 50000, }); }; ``` The configuration is persisted natively so that the plugin keeps working when the operating system runs your app without a web view. Call `setConfig(...)` on every app start anyway: the stored copy is only a cache of your last call, and it is discarded whenever the plugin's configuration format changes in a future version. Queued positions are never discarded with it. **Attention**: The configuration you pass replaces the stored one entirely. Every property you omit falls back to its default, so read the stored configuration with `getConfig()` first if you only want to change a single property: ``` const config = await BackgroundGeolocation.getConfig(); await BackgroundGeolocation.setConfig({ ...config, maxSize: 10000 }); ``` Call `resetConfig()` to discard the configuration again. Positions that are already queued are kept, use `clearQueue()` to discard them: ``` await BackgroundGeolocation.resetConfig(); ``` ### Queue Options | Option | Default | Description | | --------- | ------- | ----------------------------------------------------------------------------------------------------------- | | `maxSize` | `50000` | The maximum number of queued positions. When the queue is full, the **oldest** positions are dropped first. | The default `maxSize` of `50000` records is about 10 MB on disk and about 100 hours of walking at the default `distanceFilter` of `10` meters, at which a device that stands still records nothing. It is a disaster ceiling, not a working set: an app that drains the queue whenever it is in the foreground sits close to zero. ### Draining the queue Reading and deleting are two separate operations, because the watch session keeps recording between them. Read a page of positions, persist them in your app, then delete everything up to the id of the last position you persisted: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const drainQueue = async () => { let hasMore = true; while (hasMore) { const result = await BackgroundGeolocation.getQueuedPositions({ limit: 1000, }); if (!result.positions.length) { break; } await persist(result.positions); await BackgroundGeolocation.deleteQueuedPositions({ upToId: result.positions[result.positions.length - 1].id, }); hasMore = result.hasMore; } }; ``` Every queued position is a [`Position`](#position) object with an additional `id` property. Ids are unique per app installation and strictly increasing, so `deleteQueuedPositions(...)` can never delete a position that is newer than the one you read. The `limit` option defaults to `100`, so a completely full queue is about 50 calls at `limit: 1000`. Use `getQueueStatus()` to inspect the queue and `clearQueue()` to discard everything, for example when the user signs out: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const getQueueStatus = async () => { const { pendingCount, droppedCount, lastUploadedAt } = await BackgroundGeolocation.getQueueStatus(); console.log(`${pendingCount} positions pending, ${droppedCount} dropped, last upload: ${lastUploadedAt}`); }; const clearQueue = async () => { await BackgroundGeolocation.clearQueue(); }; ``` ### Queue Behavior - **Persistence**: The queue is stored in a local SQLite database and survives app restarts and force-quits. Positions are only deleted when your app deletes them, the server acknowledged them or they were evicted. - **Capacity**: The queue holds at most `maxSize` positions. If the queue is full, the **oldest** positions are dropped first. - **Observability**: The `droppedCount` property of `getQueueStatus()` counts the positions that were dropped since the queue was last cleared. It is persisted and only reset by `clearQueue()`. - **Ordering**: Positions are returned oldest first and their ids are strictly increasing. - **Encryption at rest**: The queue database is not encrypted. It is stored in the sandboxed app storage of the operating system. ### Caveats - **Positions arrive twice**: While the web view is alive, every position is delivered via the `positionChange` event **and** stored in the queue. With the queue enabled, treat the queue as the single source of truth and use the event for live UI only, otherwise you process every position twice. - **The queue only fills while the watch session is alive**: If the user force-quits the app, the watch session ends and no further positions are recorded until it is started again. The already queued positions are not lost, but the gap in between cannot be filled. ## HTTP Upload The plugin can upload every queued position to your own server without involving JavaScript. Positions are uploaded in batches and only deleted from the [queue](#queue) after the server has acknowledged them. Since the whole pipeline runs natively, it keeps working while the web view is suspended. **Attention**: The upload drains the same queue that `getQueuedPositions(...)` reads. Positions are stored once and deleted once, so a position your app deletes with `deleteQueuedPositions(...)` is never uploaded, and a position the server acknowledged is no longer readable. Use either the upload or your own drain loop, not both. ### Options Provide the `url` option to enable the upload pipeline. It is persisted, so it stays active across app restarts until it is changed or discarded. Failed upload attempts are reported via the `uploadFailed` event: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const enableUpload = async () => { await BackgroundGeolocation.addListener('uploadFailed', event => { console.error('Upload failed: ', event.statusCode, event.message); }); await BackgroundGeolocation.setConfig({ url: 'https://api.example.com/positions', batchSize: 100, flushInterval: 60000, headers: { Authorization: 'Bearer eyJhbGciOi...', }, extras: { userId: 'abc', }, }); }; ``` Providing `url` also enables the queue, because the upload drains it. A rotated authorization header can be applied without stopping the watch session by calling `setConfig(...)` again with the updated configuration. Call `resetConfig()` to discard the configuration again. Queued positions are kept and are uploaded as soon as the upload is enabled again: ``` await BackgroundGeolocation.resetConfig(); ``` To stop the upload but keep storing positions, call `setConfig(...)` again without `url`: ``` const { url, ...config } = await BackgroundGeolocation.getConfig(); await BackgroundGeolocation.setConfig(config); ``` An upload attempt can be triggered manually at any time: ``` import { BackgroundGeolocation } from '@capawesome-team/capacitor-background-geolocation'; const triggerUpload = async () => { await BackgroundGeolocation.triggerUpload(); }; ``` ### Server Contract Positions are uploaded with an HTTP `POST` request and the `Content-Type: application/json; charset=utf-8` header. Your own `headers` are applied afterwards and may override it. The request body looks as follows: ``` { "positions": [ { "id": 4711, "latitude": 52.52, "longitude": 13.405, "accuracy": 5, "altitude": null, "altitudeAccuracy": null, "bearing": null, "speed": null, "simulated": false, "timestamp": 1723291200000 } ], "extras": { "userId": "abc" } } ``` Every entry of the `positions` array is a [`Position`](#position) object with an additional `id` property. The `extras` property is omitted entirely if the `extras` option was not provided. **Idempotency**: The `id` is unique per app installation and strictly increasing. Positions are delivered **at least once**, so the same position may be uploaded more than once, for example if the acknowledgment of the server is lost on the way back. Deduplicate the positions on the server by `id` per device to make the upload idempotent. ### Response Handling The response body is always ignored. Only the status code decides what happens to the uploaded batch: | Status Code | Behavior | | -------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | `2xx` | The batch is acknowledged and deleted from the queue. The next batch is uploaded immediately if more positions are pending. | | `401`, `408`, `429`, `5xx`, network error, timeout | The batch stays at the head of the queue and is retried with an exponential backoff. A `uploadFailed` event is emitted. | | Any other status code | The batch is **dropped permanently without being uploaded again** and a `uploadFailed` event is emitted. The upload continues with the next batch. | **Attention**: A batch that the server rejects with any status code other than `2xx`, `401`, `408`, `429` or `5xx` (for example `400 Bad Request` or `422 Unprocessable Entity`) is deleted from the queue and **lost**. This is intentional: a permanently rejected batch must never block the head of the queue forever. `401` is treated as retryable, because it usually means an expired token rather than an unacceptable batch — refresh the credentials with `setConfig(...)` and the queued positions are uploaded afterwards. Make sure your endpoint answers with a retryable status code (e.g. `503`) if it is temporarily unable to accept positions, and listen to the `uploadFailed` event to detect such drops. Dropped positions are also counted in the `droppedCount` property of `getQueueStatus()`. The request timeout is 30 seconds. Retries start with a delay of 5 seconds and double after every attempt up to a maximum of 10 minutes. The backoff is reset after every successful upload, whenever `setConfig(...)` is called with a `url` and whenever `triggerUpload()` is called. ### Upload Behavior - **Delivery window**: Positions are uploaded whenever the app process is alive and a `url` is configured, with or without an active watch session. Enabling the upload first deletes expired positions and then immediately uploads everything that is left over from previous sessions. - **Acknowledgment**: Positions are only deleted from the queue after the server acknowledged them or rejected them permanently. - **Shared queue**: The uploader and `getQueuedPositions(...)` drain the same queue. If your app reads and deletes positions itself, the uploader only sees what is left. - **Without a `url`**: The queue is left untouched. It is neither uploaded nor cleared. - **Observability**: Permanently rejected batches are counted in the `droppedCount` property of `getQueueStatus()` and `lastUploadedAt` reports the time of the last successful upload. Both values are persisted. ### Instant Upload There is no dedicated mode for uploading each position on its own. Set the `batchSize` option to `1` instead to upload every position as soon as it arrives: ``` await BackgroundGeolocation.setConfig({ url: 'https://api.example.com/positions', batchSize: 1, }); ``` ### Battery Consumption Every upload wakes up the cellular radio, which is one of the most expensive operations on a mobile device. Uploading one position at a time (see [Instant Upload](#instant-upload)) therefore has a noticeable impact on the battery life. Prefer a larger `batchSize` and a longer `flushInterval` whenever your use case allows it, so that many positions share a single radio wake-up. ### Testing The free [Background Geolocation Playground](https://background-geolocation-playground.capawesome.io) webpage is a ready-to-use upload target, so you can verify the upload pipeline before your own endpoint exists. Generate a session key on the webpage, use it as bearer token and every uploaded position appears in a table and on a map: ``` await BackgroundGeolocation.setConfig({ url: 'https://background-geolocation-playground.capawesome.io/v1/positions', headers: { Authorization: 'Bearer ', }, }); ``` **Attention**: Session keys and recorded positions are deleted after 14 days and everyone who knows the session key can view its positions, so use the webpage for debugging and testing only and never in production. ### Deliberately Not Supported The following features are intentionally not part of the upload pipeline: - **Network constraints**: Uploads cannot be restricted to Wi-Fi or unmetered networks. - **Response processing**: The response body of the server is always ignored, so the server cannot send commands back to the device. - **Upload after a force-quit**: Queued positions are not uploaded while the app is terminated. They are uploaded the next time the app is started. ## FAQ ### How is this plugin different from other similar plugins? It is built for reliable background location tracking with a correctly plumbed Android 14+ foreground service, a first-class permissions API that handles the two-step background upgrade flow, and real tuning knobs for accuracy, distance filtering and update interval. It also reports mock locations on Android, supports temporary full-accuracy requests on iOS, and falls back to the platform location manager on devices without Google Play services — all through one fully typed, actively maintained API with dedicated support. If you only need a single foreground position, a simpler geolocation setup may suffice; if you need dependable tracking that keeps running in the background, this plugin is made for it. ### Does the tracking continue when the user force-quits the app? No. On both platforms, the watch session ends when the user force-quits (terminates) the app. This is an operating system restriction. Only operating-system-managed APIs like geofencing can relaunch a terminated app. Take a look at the [Geofences](https://capawesome.io/docs/sdks/capacitor/geofences/) plugin if you need this capability. ### Why is the `androidNotification` option required? On Android, receiving position updates while the app is in the background requires a foreground service, and every foreground service must display a persistent notification. The plugin builds this notification from the `androidNotification` option so that you have full control over its content. ### Does the plugin work on devices without Google Play services? Yes. The plugin automatically falls back to the platform location manager if Google Play services is not available on the device (e.g. on certain Huawei devices). You can also force this behavior with the `androidForceLocationManager` option. ### Why does the plugin not declare the `ACCESS_BACKGROUND_LOCATION` permission? Google Play requires a policy declaration and review for every app that requests background location access. If the plugin declared this permission, every app using the plugin would be subject to this review, even if it only needs foreground location access. Therefore, the permission must be declared at the app level (see [Installation](#installation)). ### Can I start multiple watch sessions at the same time? No. Only one watch session can be active at a time. The native location engine delivers one stream of position updates, so multiple concurrent watchers would only be an illusion with a shared configuration. Call `stopWatching()` before starting a new watch session with different options. ### How can I reduce the battery consumption? The plugin deliberately does not include a motion-detection state machine that turns the GPS on and off based on accelerometer data. Instead, it exposes real tuning knobs: use a lower `accuracy` (e.g. `Accuracy.Balanced` instead of `Accuracy.High`), increase the `distanceFilter` to reduce the number of position updates and increase the `androidInterval` option on Android. On iOS, you can additionally enable the `iosPausesAutomatically` option so that the operating system pauses the position updates when the device is unlikely to move. Be aware of the trade-off: the operating system decides on its own when to pause and only resumes the position updates once the device has moved significantly again, so a watch session can stay silent for a long time. The plugin logs both the pause and the resume, but keeps the watch session active, which means that `isWatching()` still returns `true` while the position updates are paused. [Why iOS Stops Sending Background Location Updates](https://capawesome.io/blog/why-ios-stops-background-location-updates/) covers this and the other Core Location rules that stop background updates on iOS. ### What happens if the user grants only approximate location? On Android, the user can grant approximate instead of precise location. This is not reported separately but is reflected in the `accuracy` property of each position. On iOS, the user can grant reduced accuracy, which you can upgrade for the duration of the app session using `requestTemporaryFullAccuracy(...)`. ### Why are queued positions not uploaded after force-quit? Uploading requires a running app process. When the user force-quits (terminates) the app, no more requests can be sent. The queued positions are **not** lost though: they stay in the local queue and are uploaded as soon as the app is started again. See [Queue Behavior](#queue-behavior) for details. ### How do I secure the upload endpoint? Use the `headers` option to send a static credential (e.g. `Authorization: Bearer ...`) with every upload request and always use an `https://` URL, since the credential is otherwise sent in plain text. On iOS, App Transport Security blocks plain `http://` requests by default anyway. Call `setConfig(...)` again with the full configuration whenever the token is rotated, which applies the new header without interrupting the watch session. ### How can I test the upload endpoint locally? Point the `url` option to a local HTTP endpoint that logs the request body. A few lines of Node.js are enough: ``` // server.js import { createServer } from 'node:http'; createServer((request, response) => { let body = ''; request.on('data', chunk => (body += chunk)); request.on('end', () => { console.log(JSON.parse(body)); response.writeHead(200).end(); }); }).listen(3000); ``` Start it with `node server.js` and use the IP address of your development machine in the `url` option (e.g. `http://192.168.1.10:3000`). On the Android emulator, use `http://10.0.2.2:3000` instead. Plain `http://` requires an App Transport Security exception on iOS and a network security configuration that allows cleartext traffic on Android, so it is often easier to expose the local server via an HTTPS tunnel. Answer with a `503` status code to observe the retry behavior and with a `400` status code to observe a permanently dropped batch. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/): Convert the tracked positions into human-readable addresses. - [Geofences](https://capawesome.io/docs/sdks/capacitor/geofences/): Monitor circular regions with operating-system-managed geofencing, including delivery when the app is terminated. - [Settings Launcher](https://capawesome.io/docs/sdks/capacitor/settings-launcher/): Take the user to the app settings, for example to grant the background location permission. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/background-geolocation/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/background-geolocation/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/background-geolocation/LICENSE). # Capacitor Background Task Plugin Capacitor plugin for running background tasks. ## Use Cases The Background Task plugin is typically used to finish short-running work when the app moves to the background, for example: - **Finish network requests**: Complete an upload or API request that was started while the app was in the foreground. - **Save application state**: Persist unsaved user input or app state before the OS puts the app to sleep. - **Sync data**: Finish writing pending changes to your backend or local storage. - **Clean up resources**: Close connections or release resources gracefully before the app is suspended. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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. ## 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. ## FAQ ### How much time does my task have to run in the background? On iOS, the task started with `beforeExit(...)` should be finished in less than 30 seconds. As soon as your work is done, call `finish(...)` so the OS can put the app to sleep. Read more about the underlying background execution mechanism in the [Apple documentation](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). ### Is this plugin supported on Android? There is currently no ready implementation on Android, so the task is not extended there. Support for Android is planned for the future, see the [Quirks](#quirks) section. ### Can I run periodic or scheduled tasks with this plugin? No, this plugin is designed to extend the execution time of your app when it moves to the background, so you can finish a short-running task. It does not schedule tasks that run while the app is closed. ### How do I know when the app moves to the background? Use the `appStateChange` listener of the official `@capacitor/app` plugin, as shown in the [Usage](#usage) section. When `isActive` becomes `false`, call `beforeExit(...)` to start the background task. ### When do I have to call the `finish` method? Call `finish(...)` with the task ID returned by `beforeExit(...)` as soon as everything is done. This tells the OS that the task is complete so it can put the app to sleep. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Alarm](https://capawesome.io/docs/sdks/capacitor/alarm/): Create system alarms and timers. - [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/): Run a foreground service on Android for long-running work. - [Keep Awake](https://capawesome.io/docs/sdks/capacitor/keep-awake/): Keep the screen awake while your app is in the foreground. ## 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/). ## 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! # Capacitor Badge Plugin Capacitor plugin to access and update the badge number of the app icon. ## Features The Capacitor Badge plugin is one of the most complete app icon badge solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [App Icon](https://capawesome.io/docs/sdks/capacitor/app-icon/) and [Firebase Cloud Messaging](https://capawesome.io/docs/sdks/capacitor/firebase/cloud-messaging/) plugins. - 🔁 **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! ## Use Cases The Badge plugin is typically used to show a count on the app icon that reflects pending items, for example: - **Unread messages**: Show the number of unread chat messages or emails on the app icon. - **Pending notifications**: Keep the badge in sync with the notifications the user has not yet seen. - **Open tasks**: Display the number of open to-dos, reminders, or items in a shopping cart. - **Automatic reset**: Clear the counter automatically when the user resumes the app using the `autoClear` configuration option. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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; ``` ## Usage The following examples show how to get, set, increase, decrease, and clear the badge count, check whether badges are supported, and check and request permissions. ### Get the current badge count Read the current badge count. The count won't be lost after a reboot or app restart: ``` import { Badge } from '@capawesome/capacitor-badge'; const get = async () => { const result = await Badge.get(); return result.count; }; ``` ### Set the badge count Set the badge count to a specific value. On iOS, setting the count to `0` will remove the badge and also clear all notifications: ``` import { Badge } from '@capawesome/capacitor-badge'; const set = async (count: number) => { await Badge.set({ count }); }; ``` ### Increase or decrease the badge count Increment or decrement the badge count by one, for example when a new message arrives or is read: ``` import { Badge } from '@capawesome/capacitor-badge'; const increase = async () => { await Badge.increase(); }; const decrease = async () => { await Badge.decrease(); }; ``` ### Clear the badge count Remove the badge from the app icon. On iOS, this will also clear all notifications: ``` import { Badge } from '@capawesome/capacitor-badge'; const clear = async () => { await Badge.clear(); }; ``` ### Check if badges are supported Check whether the badge count is supported on the current device, for example since not every Android launcher supports badges: ``` import { Badge } from '@capawesome/capacitor-badge'; const isSupported = async () => { const result = await Badge.isSupported(); return result.isSupported; }; ``` ### Check and request permissions Check and request the permission to display the badge: ``` import { Badge } from '@capawesome/capacitor-badge'; 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** there is no official API to set a badge, so not all launchers support badges. This plugin uses [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger), which relies on vendor-specific mechanisms. All supported launchers are listed [there](https://github.com/leolin310148/ShortcutBadger#supported-launchers). On stock Android launchers, such as the Pixel Launcher, no badge is displayed. On **Web**, the app must run as an installed PWA (in the taskbar or dock). ## FAQ ### Why is the badge not showing on my Android device? Android provides no official API to set a badge on the launcher icon. This plugin therefore uses [ShortcutBadger](https://github.com/leolin310148/ShortcutBadger) under the hood, which relies on vendor-specific mechanisms offered by some manufacturers (such as Samsung, Xiaomi, HTC and Sony). Only the launchers listed as [supported launchers](https://github.com/leolin310148/ShortcutBadger#supported-launchers) can display the badge. On stock Android launchers, most notably the Pixel Launcher, no badge is displayed. You can use the `isSupported()` method to check whether badges are supported on the current device. ### Can the plugin display an Android notification badge (dot) instead? No. Since Android 8.0, the launcher displays a [notification badge](https://developer.android.com/develop/ui/views/notifications/badges) only while the app has an *active* notification. The badge is derived from that notification and disappears as soon as the notification is dismissed, so it cannot be set independently of one. On stock Android it is also a dot without a count. If your app already displays notifications, the badge is shown automatically without any plugin call. ### Does the badge count persist after a reboot or app restart? Yes, by default the badge count is restored after a reboot or app restart. You can disable this behavior by setting the `persist` configuration option to `false`, see the [Configuration](#configuration) section. ### How can I automatically clear the badge when the app is resumed? Set the `autoClear` configuration option to `true` to reset the counter after resuming the application. Note that on iOS, this will also clear all notifications. See the [Configuration](#configuration) section for an example. ### Why does `checkPermissions()` always return `granted` on Android and Web? Only iOS requires a permission to display a badge. On Android and Web no permission is needed, so the permission state is always `granted`. ### Why does clearing the badge also remove my notifications on iOS? On iOS, calling `clear()` or setting the badge count to `0` removes the badge and also clears all notifications. This is platform behavior and cannot be changed by the plugin. ### Does this plugin work on the Web? Yes, but the app must run as an installed PWA (in the taskbar or dock). Also make sure to check whether the browser supports badges using the `isSupported()` method. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Icon](https://capawesome.io/docs/sdks/capacitor/app-icon/): Change the app icon at runtime. - [App Shortcuts](https://capawesome.io/docs/sdks/capacitor/app-shortcuts/): Manage app shortcuts and quick actions. - [Firebase Cloud Messaging](https://capawesome.io/docs/sdks/capacitor/firebase/cloud-messaging/): Receive push notifications via Firebase Cloud Messaging. ## 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/). ## 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! # Capacitor Barcode Scanner Plugin Capacitor plugin for scanning barcodes and QR codes with a themeable fullscreen scanner or an embedded camera view on Android, iOS, and Web. ## Features The Capacitor Barcode Scanner plugin provides a premium barcode scanning experience with two presentation modes. Here are some of the key features: - 🖥️ **Ready-Made UI**: Fullscreen scanner with viewfinder, torch and flip camera buttons. - 🎨 **Themeable**: Customize the accent color, title, instructions, and buttons of the scanner UI. - 📦 **Batch Scanning**: Collect multiple barcodes in one session before returning them all at once. - 🖼️ **Embedded View**: Render the camera natively inside any frame of your app layout. - ⏯️ **Pause & Resume**: Pause and resume the barcode detection without stopping the camera. - 🔦 **Torch & Zoom**: Control the flashlight and the camera zoom during a scan session. - 🎯 **Detection Area**: Restrict the barcode detection to a region of interest. - 🔁 **Deduplication**: Configurable timeout before the same barcode is emitted again. - 🏷️ **13 Barcode Formats**: QR code, Aztec, Codabar, Code 39, Code 93, Code 128, Data Matrix, EAN-8, EAN-13, ITF, PDF417, UPC-A, and UPC-E. - 📷 **Image Reading**: Read barcodes from image files. - 🍎 **Zero Dependencies on iOS**: Built on AVFoundation and Vision only, so there are no third-party pods and no simulator issues. - 🤖 **Offline on Android**: Uses the bundled ML Kit model, so scanning works offline and without Google Play services. - 🌐 **Web Support**: Embedded scanning and image reading via the `BarcodeDetector` API. - 🤝 **Compatibility**: Works hand in hand with the [Document Scanner](https://capawesome.io/docs/sdks/capacitor/document-scanner/), [Torch](https://capawesome.io/docs/sdks/capacitor/torch/) and [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) 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! ## Use Cases The Barcode Scanner plugin is typically used whenever an app needs to capture barcode or QR code data, for example: - **Retail and inventory**: Scan product barcodes for stock management or price lookup. - **Ticketing and events**: Validate QR codes on tickets, also in batch mode at the entrance. - **Logistics**: Scan parcel labels (Code 128, ITF, PDF417) in a continuous embedded scanner. - **Payments and links**: Let users scan QR codes to open links or initiate payments. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## Guides - [Embedded Barcode Scanner with Camera Preview in Capacitor](https://capawesome.io/blog/capacitor-embedded-barcode-scanner/): Render the camera preview natively inside your app layout, without transparent web view hacks. ## 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-barcode-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 @capawesome-team/capacitor-barcode-scanner npx cap sync ``` ### Android The plugin declares the camera permission in its own manifest, so no manifest changes are required. #### 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 variables in your app’s `variables.gradle` file to change the default version of the dependencies: - `$androidxCameraVersion` version of the `androidx.camera` dependencies (default: `1.6.1`) - `$mlkitBarcodeScanningVersion` version of `com.google.mlkit:barcode-scanning` (default: `17.3.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. The plugin uses the **bundled** ML Kit barcode scanning model. Scanning works offline and does not require Google Play services, but the model increases your app size by a few megabytes. The default dependency versions are compatible with the 16 KB page size requirement of Google Play. ### iOS Add the `NSCameraUsageDescription` key to the `Info.plist` file of your app to explain why your app needs access to the camera: ``` NSCameraUsageDescription The app needs access to the camera to scan barcodes. ``` If the key is missing, `scan(...)` and `startScan(...)` reject with a clear error message. ### Web The web implementation is based on the [BarcodeDetector](https://developer.mozilla.org/en-US/docs/Web/API/BarcodeDetector) API, which is not available in all browsers. For browsers without built-in support, we recommend the [barcode-detector](https://www.npmjs.com/package/barcode-detector) polyfill: ``` import 'barcode-detector/side-effects'; ``` The polyfill is intentionally **not** bundled with this plugin, so you stay in control of your bundle size. ## Configuration No configuration required for this plugin. ## Usage The plugin offers two presentation modes: a ready-made, themeable native fullscreen scanner (`scan(...)`) and an embedded camera view that is rendered natively inside a frame of your app layout (`startScan(...)`). The following examples show how to check the availability and permissions, scan barcodes with both modes, and read barcodes from image files. ### Check the availability and permissions Check whether barcode scanning is available on the device and request the camera permission before you start a scan session: ``` import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner'; const isAvailable = async () => { const { available } = await BarcodeScanner.isAvailable(); return available; }; const requestPermissions = async () => { const { camera } = await BarcodeScanner.requestPermissions(); return camera; }; ``` Get the cameras available on the device, for example to hide or disable a camera flip button if the device has only one camera: ``` import { BarcodeScanner, LensFacing, } from '@capawesome-team/capacitor-barcode-scanner'; const canFlipCamera = async () => { const { lensFacings } = await BarcodeScanner.getAvailableCameras(); return ( lensFacings.includes(LensFacing.Back) && lensFacings.includes(LensFacing.Front) ); }; ``` If the user has denied the camera permission, open the native app settings page so that the user can grant it: ``` import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner'; const openSettings = async () => { await BarcodeScanner.openSettings(); }; ``` ### Scan a barcode with the fullscreen scanner Open the ready-made fullscreen scanner and customize its user interface. Only barcodes that are fully inside the viewfinder are detected. The promise resolves with the first detected barcode: ``` import { BarcodeFormat, BarcodeScanner, } from '@capawesome-team/capacitor-barcode-scanner'; const scanSingleBarcode = async () => { const { barcodes } = await BarcodeScanner.scan({ formats: [BarcodeFormat.QrCode, BarcodeFormat.Ean13], ui: { accentColor: '#59C7F9', instructions: 'Point your camera at a barcode.', title: 'Scan Barcode', }, }); return barcodes[0]; }; ``` ### Scan multiple barcodes in one session Enable the batch mode to let the user collect multiple barcodes. The promise resolves with all of them when the user taps the done button: ``` import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner'; const scanMultipleBarcodes = async () => { const { barcodes } = await BarcodeScanner.scan({ batch: true, }); return barcodes; }; ``` ### Scan barcodes with the embedded camera view Start an embedded scan session to render the camera preview inside a frame of your app layout. Detected barcodes are emitted continuously via the `barcodesScanned` event until you call `stopScan()`. The camera preview is a **native view**. By default, it is rendered **above** the web view, which means that HTML elements cannot overlap the camera preview. Use a placeholder element to reserve the space in your layout and to measure the frame: ``` import { BarcodeScanner, LensFacing, } from '@capawesome-team/capacitor-barcode-scanner'; const getScanFrame = () => { const rect = document.querySelector('#scanner').getBoundingClientRect(); return { x: rect.x, y: rect.y, width: rect.width, height: rect.height }; }; const startEmbeddedScan = async () => { await BarcodeScanner.addListener('barcodesScanned', (event) => { console.log('Scanned barcodes:', event.barcodes); }); await BarcodeScanner.startScan({ frame: getScanFrame(), lensFacing: LensFacing.Back, }); }; const stopEmbeddedScan = async () => { await BarcodeScanner.stopScan(); await BarcodeScanner.removeAllListeners(); }; ``` ### Overlay HTML elements over the embedded camera view Set `placement` to `PreviewPlacement.Behind` to render the camera preview **behind** the web view. This way, HTML elements can overlap the camera preview, for example to draw a viewfinder or detection markers. The camera preview is only visible where your app is transparent. The placeholder element used to measure the frame, all its ancestors and the `body` must have a transparent background over the frame area: ``` body, #scanner { background: transparent; } ``` With Ionic Framework UI components, also set `--background: transparent` on the surrounding `ion-content` element. ``` import { BarcodeScanner, PreviewPlacement, } from '@capawesome-team/capacitor-barcode-scanner'; const startEmbeddedScanBehindWebView = async () => { await BarcodeScanner.startScan({ frame: getScanFrame(), placement: PreviewPlacement.Behind, }); }; ``` ### Update the frame of the embedded camera view When the layout of your app changes (for example after an orientation change), update the frame of the active scan session: ``` import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner'; window.addEventListener('resize', async () => { await BarcodeScanner.setScanFrame({ frame: getScanFrame() }); }); ``` ### Read barcodes from an image Read barcodes from an image file, for example from an image the user picked with the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) plugin: ``` import { BarcodeScanner } from '@capawesome-team/capacitor-barcode-scanner'; const readBarcodesFromImage = async (path: string) => { const { barcodes } = await BarcodeScanner.readBarcodesFromImage({ path, }); return barcodes; }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`getAvailableCameras()`](#getavailablecameras) - [`getZoomRatioRange()`](#getzoomratiorange) - [`isAvailable()`](#isavailable) - [`openSettings()`](#opensettings) - [`pauseScan()`](#pausescan) - [`readBarcodesFromImage(...)`](#readbarcodesfromimage) - [`requestPermissions()`](#requestpermissions) - [`resumeScan()`](#resumescan) - [`scan(...)`](#scan) - [`setScanFrame(...)`](#setscanframe) - [`setTorchEnabled(...)`](#settorchenabled) - [`setZoomRatio(...)`](#setzoomratio) - [`startScan(...)`](#startscan) - [`stopScan()`](#stopscan) - [`addListener('barcodesScanned', ...)`](#addlistenerbarcodesscanned-) - [`addListener('scanError', ...)`](#addlistenerscanerror-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### checkPermissions() ``` checkPermissions() => Promise ``` Check the current camera permission status. On **Web**, the permission status is queried on a best-effort basis. Some browsers do not support the camera permission query and always return `prompt`. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getAvailableCameras() ``` getAvailableCameras() => Promise ``` Get the cameras available on the device. Use this, for example, to hide or disable a camera flip button if the device has only one camera. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.1 ______________________________________________________________________ ### getZoomRatioRange() ``` getZoomRatioRange() => Promise ``` Get the minimum and maximum zoom ratio supported by the camera. The promise rejects with the `NOT_SCANNING` error code if no scan session started with `startScan(...)` is active. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether barcode scanning is available on the device. On **Android** and **iOS**, this resolves to `true` if the device has a camera. On **Web**, this resolves to `true` if the browser supports camera access and the `BarcodeDetector` API is available (natively or via a polyfill). **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the native app settings page so that the user can grant the camera permission. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### pauseScan() ``` pauseScan() => Promise ``` Pause the barcode detection of the active scan session. The camera preview stays visible but no more `barcodesScanned` events are emitted until `resumeScan()` is called. The promise rejects with the `NOT_SCANNING` error code if no scan session is active. **Since:** 0.0.1 ______________________________________________________________________ ### readBarcodesFromImage(...) ``` readBarcodesFromImage(options: ReadBarcodesFromImageOptions) => Promise ``` Read barcodes from an image file. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `ReadBarcodesFromImageOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request the camera permission. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### resumeScan() ``` resumeScan() => Promise ``` Resume the barcode detection of the active scan session after it has been paused with `pauseScan()`. The promise rejects with the `NOT_SCANNING` error code if no scan session is active. **Since:** 0.0.1 ______________________________________________________________________ ### scan(...) ``` scan(options?: ScanOptions | undefined) => Promise ``` Open the ready-made fullscreen scanner user interface. In single-shot mode (default), the promise resolves with the first detected barcode. In batch mode, the user collects multiple barcodes and the promise resolves with all of them when the user taps the done button. Only barcodes that are fully inside the viewfinder are detected. The promise rejects with the `SCAN_CANCELED` error code if the user closes the scanner without a result. Only available on Android and iOS. | Param | Type | | ------------- | ------------- | | **`options`** | `ScanOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### setScanFrame(...) ``` setScanFrame(options: SetScanFrameOptions) => Promise ``` Update the frame of the active scan session, for example after an orientation change or when the layout of your app changes. The promise rejects with the `NOT_SCANNING` error code if no scan session is active. | Param | Type | | ------------- | --------------------- | | **`options`** | `SetScanFrameOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setTorchEnabled(...) ``` setTorchEnabled(options: SetTorchEnabledOptions) => Promise ``` Enable or disable the torch (flashlight) during an active scan session. The promise rejects with the `NOT_SCANNING` error code if no scan session started with `startScan(...)` is active. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `SetTorchEnabledOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### setZoomRatio(...) ``` setZoomRatio(options: SetZoomRatioOptions) => Promise ``` Set the zoom ratio of the camera during an active scan session. The promise rejects with the `NOT_SCANNING` error code if no scan session started with `startScan(...)` is active. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `SetZoomRatioOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### startScan(...) ``` startScan(options: StartScanOptions) => Promise ``` Start an embedded scan session. The camera preview is rendered natively in the given frame. Use a placeholder element to measure the frame (e.g. with `getBoundingClientRect()`) and reserve the space in your layout. By default, the camera preview is rendered **above** the web view so that HTML elements cannot overlap it. Set `placement` to `PreviewPlacement.Behind` to render the camera preview **behind** the web view so that HTML elements can overlap it (see the `placement` property for the requirements). Detected barcodes are emitted via the `barcodesScanned` event until `stopScan()` is called. The promise rejects with the `ALREADY_SCANNING` error code if a scan session is already active. | Param | Type | | ------------- | ------------------ | | **`options`** | `StartScanOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### stopScan() ``` stopScan() => Promise ``` Stop the active scan session and remove the camera preview. The promise rejects with the `NOT_SCANNING` error code if no scan session is active. **Since:** 0.0.1 ______________________________________________________________________ ### addListener('barcodesScanned', ...) ``` addListener(eventName: 'barcodesScanned', listenerFunc: (event: BarcodesScannedEvent) => void) => Promise ``` Called when barcodes are detected during an active scan session. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'barcodesScanned'` | | **`listenerFunc`** | `(event: BarcodesScannedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('scanError', ...) ``` addListener(eventName: 'scanError', listenerFunc: (event: ScanErrorEvent) => void) => Promise ``` Called when an error occurs during an active scan session. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'scanError'` | | **`listenerFunc`** | `(event: ScanErrorEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Description | Since | | ------------ | ----------------- | ---------------------------- | ----- | | **`camera`** | `PermissionState` | The camera permission state. | 0.0.1 | #### GetAvailableCamerasResult | Prop | Type | Description | Since | | ----------------- | -------------- | -------------------------------------------------------- | ----- | | **`lensFacings`** | `LensFacing[]` | The lens facings of the cameras available on the device. | 0.1.1 | #### GetZoomRatioRangeResult | Prop | Type | Description | Since | | --------- | -------- | ----------------------- | ----- | | **`max`** | `number` | The maximum zoom ratio. | 0.0.1 | | **`min`** | `number` | The minimum zoom ratio. | 0.0.1 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ----------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not barcode scanning is available on the device. | 0.0.1 | #### ReadBarcodesFromImageResult | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------- | ----- | | **`barcodes`** | `Barcode[]` | The detected barcodes. | 0.0.1 | #### Barcode | Prop | Type | Description | Since | | ------------------ | -------------------- | --------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`bytes`** | \`number[] | null\` | The raw bytes of the barcode. Only available on Android. On iOS and Web, this is always `null`. | | **`cornerPoints`** | \`[number, number][] | null\` | The four corner points of the barcode in clockwise order starting at the top-left corner. During an embedded scan session, the corner points are in CSS pixels relative to the top-left corner of the scan frame. During a fullscreen scan session, the corner points are in CSS pixels relative to the top-left corner of the screen. For `readBarcodesFromImage(...)`, the corner points are in pixels relative to the top-left corner of the image. This value might be `null` if the corner points cannot be determined. | | **`displayValue`** | `string` | The barcode value in a human-readable format. | 0.0.1 | | **`format`** | `BarcodeFormat` | The format of the barcode. | 0.0.1 | | **`rawValue`** | `string` | The barcode value as it was encoded in the barcode. | 0.0.1 | #### ReadBarcodesFromImageOptions | Prop | Type | Description | Since | | ------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`formats`** | `BarcodeFormat[]` | The barcode formats to detect. If not set, all supported formats are detected. Improve the performance by only setting the formats you need. | 0.0.1 | | **`path`** | `string` | The path of the image file to read the barcodes from. On **Web**, this must be a URL that can be fetched by the browser. | 0.0.1 | #### ScanResult | Prop | Type | Description | Since | | -------------- | ----------- | ----------------------------------------------------------------------------- | ----- | | **`barcodes`** | `Barcode[]` | The scanned barcodes. In single-shot mode, this contains exactly one element. | 0.0.1 | #### ScanOptions | Prop | Type | Description | Default | Since | | ---------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- | | **`batch`** | `boolean` | Whether or not to scan multiple barcodes in one session. If `true`, the scanner collects the detected barcodes and resolves when the user taps the done button. If `false`, the scanner resolves with the first detected barcode. | `false` | 0.0.1 | | **`formats`** | `BarcodeFormat[]` | The barcode formats to detect. If not set, all supported formats are detected. Improve the performance by only setting the formats you need. | | 0.0.1 | | **`lensFacing`** | `LensFacing` | The camera lens to use. | `LensFacing.Back` | 0.0.1 | | **`ui`** | `ScanUiOptions` | Options to customize the scanner user interface. | | 0.0.1 | #### ScanUiOptions | Prop | Type | Description | Default | Since | | -------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`accentColor`** | `string` | The accent color of the scanner user interface as a hex color code (e.g. `#59C7F9`). The accent color is applied to the viewfinder corners and the done button. | | 0.0.1 | | **`beep`** | `boolean` | Whether or not to play a beep sound when a barcode is detected. | `false` | 0.0.1 | | **`hapticFeedback`** | `boolean` | Whether or not to trigger a haptic feedback when a barcode is detected. | `true` | 0.0.1 | | **`instructions`** | `string` | The instructions text displayed below the title. | | 0.0.1 | | **`showFlipCameraButton`** | `boolean` | Whether or not to show the flip camera button. | `false` | 0.0.1 | | **`showTorchButton`** | `boolean` | Whether or not to show the torch button. | `true` | 0.0.1 | | **`title`** | `string` | The title displayed at the top of the scanner user interface. | | 0.0.1 | #### SetScanFrameOptions | Prop | Type | Description | Since | | ----------- | ----------- | ---------------------------------- | ----- | | **`frame`** | `ScanFrame` | The new frame of the scan session. | 0.0.1 | #### ScanFrame | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------------------------------------------------------------------- | ----- | | **`height`** | `number` | The height of the frame in CSS pixels. | 0.0.1 | | **`width`** | `number` | The width of the frame in CSS pixels. | 0.0.1 | | **`x`** | `number` | The x coordinate of the frame in CSS pixels relative to the top-left corner of the viewport. | 0.0.1 | | **`y`** | `number` | The y coordinate of the frame in CSS pixels relative to the top-left corner of the viewport. | 0.0.1 | #### SetTorchEnabledOptions | Prop | Type | Description | Since | | ------------- | --------- | ----------------------------------- | ----- | | **`enabled`** | `boolean` | Whether or not to enable the torch. | 0.0.1 | #### SetZoomRatioOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------------------------------ | ----- | | **`ratio`** | `number` | The zoom ratio to set. Must be a value within the range returned by `getZoomRatioRange()`. | 0.0.1 | #### StartScanOptions | Prop | Type | Description | Default | Since | | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ | ----- | | **`detectionArea`** | `DetectionArea` | The area within the scan frame in which barcodes are detected. Barcodes that are detected outside of this area are ignored. If not set, barcodes are detected in the entire scan frame. | | 0.0.1 | | **`duplicateTimeout`** | `number` | The time in milliseconds after which the same barcode is emitted again. | `1500` | 0.0.1 | | **`formats`** | `BarcodeFormat[]` | The barcode formats to detect. If not set, all supported formats are detected. Improve the performance by only setting the formats you need. | | 0.0.1 | | **`frame`** | `ScanFrame` | The frame in which the camera preview is rendered. The coordinates are in CSS pixels relative to the top-left corner of the viewport. | | 0.0.1 | | **`lensFacing`** | `LensFacing` | The camera lens to use. | `LensFacing.Back` | 0.0.1 | | **`placement`** | `PreviewPlacement` | Where the camera preview is rendered relative to the web view. With [`PreviewPlacement.Above`](#previewplacement), HTML elements cannot overlap the camera preview. With [`PreviewPlacement.Behind`](#previewplacement), HTML elements can overlap the camera preview, for example to draw a viewfinder or detection markers. The camera preview is only visible where your app is transparent. The placeholder element used to measure the frame, all its ancestors and the `body` must have a transparent background over the frame area. | `PreviewPlacement.Above` | 0.0.1 | #### DetectionArea | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------------------------------------------------------------------- | ----- | | **`height`** | `number` | The height of the detection area in CSS pixels. | 0.0.1 | | **`width`** | `number` | The width of the detection area in CSS pixels. | 0.0.1 | | **`x`** | `number` | The x coordinate of the detection area in CSS pixels relative to the top-left corner of the scan frame. | 0.0.1 | | **`y`** | `number` | The y coordinate of the detection area in CSS pixels relative to the top-left corner of the scan frame. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### BarcodesScannedEvent | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------- | ----- | | **`barcodes`** | `Barcode[]` | The detected barcodes. | 0.0.1 | #### ScanErrorEvent | Prop | Type | Description | Since | | ------------- | -------- | ------------------ | ----- | | **`message`** | `string` | The error message. | 0.0.1 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### LensFacing | Members | Value | Description | Since | | ----------- | --------- | ----------------- | ----- | | **`Back`** | `'BACK'` | The back camera. | 0.0.1 | | **`Front`** | `'FRONT'` | The front camera. | 0.0.1 | #### BarcodeFormat | Members | Value | Description | Since | | ---------------- | --------------- | ------------------------------------------------------------------------------------- | ----- | | **`Aztec`** | `'AZTEC'` | Aztec barcode. | 0.0.1 | | **`Codabar`** | `'CODABAR'` | Codabar barcode. On **iOS**, this format is only detected by the camera on iOS 15.4+. | 0.0.1 | | **`Code128`** | `'CODE_128'` | Code 128 barcode. | 0.0.1 | | **`Code39`** | `'CODE_39'` | Code 39 barcode. | 0.0.1 | | **`Code93`** | `'CODE_93'` | Code 93 barcode. | 0.0.1 | | **`DataMatrix`** | `'DATA_MATRIX'` | Data Matrix barcode. | 0.0.1 | | **`Ean13`** | `'EAN_13'` | EAN-13 barcode. | 0.0.1 | | **`Ean8`** | `'EAN_8'` | EAN-8 barcode. | 0.0.1 | | **`Itf`** | `'ITF'` | ITF (Interleaved 2 of 5) barcode. | 0.0.1 | | **`Pdf417`** | `'PDF_417'` | PDF417 barcode. | 0.0.1 | | **`QrCode`** | `'QR_CODE'` | QR code. | 0.0.1 | | **`UpcA`** | `'UPC_A'` | UPC-A barcode. | 0.0.1 | | **`UpcE`** | `'UPC_E'` | UPC-E barcode. | 0.0.1 | #### PreviewPlacement | Members | Value | Description | Since | | ------------ | ---------- | --------------------------------------------------- | ----- | | **`Above`** | `'ABOVE'` | The camera preview is rendered above the web view. | 0.0.1 | | **`Behind`** | `'BEHIND'` | The camera preview is rendered behind the web view. | 0.0.1 | ## Supported Barcode Formats | Format | Android | iOS | Web ¹ | | ------------- | ------- | ---- | ----- | | `AZTEC` | ✅ | ✅ | ✅ | | `CODABAR` | ✅ | ✅ ² | ✅ | | `CODE_39` | ✅ | ✅ | ✅ | | `CODE_93` | ✅ | ✅ | ✅ | | `CODE_128` | ✅ | ✅ | ✅ | | `DATA_MATRIX` | ✅ | ✅ | ✅ | | `EAN_8` | ✅ | ✅ | ✅ | | `EAN_13` | ✅ | ✅ | ✅ | | `ITF` | ✅ | ✅ | ✅ | | `PDF_417` | ✅ | ✅ | ✅ | | `QR_CODE` | ✅ | ✅ | ✅ | | `UPC_A` | ✅ | ✅ ³ | ✅ | | `UPC_E` | ✅ | ✅ | ✅ | ¹ On the web, the supported formats depend on the browser (or the polyfill). ² Camera scanning of Codabar barcodes requires iOS 15.4+. Reading Codabar barcodes from images works on all supported iOS versions. ³ Apple's frameworks report UPC-A barcodes as EAN-13 barcodes with a leading `0`. The plugin normalizes them to `UPC_A` (unless you explicitly request only `EAN_13`), so the behavior is consistent across platforms. ## Migration from ML Kit Barcode Scanning If you are migrating from the [ML Kit Barcode Scanning](https://capawesome.io/docs/sdks/capacitor/mlkit/barcode-scanning/) plugin, the following table maps the most important APIs: | `@capacitor-mlkit/barcode-scanning` | `@capawesome-team/capacitor-barcode-scanner` | | ----------------------------------------- | -------------------------------------------------- | | `scan()` | `scan()` (fullscreen UI, now also with batch mode) | | `startScan()` (transparent web view) | `startScan()` (embedded native view with `frame`) | | `stopScan()` | `stopScan()` | | — | `pauseScan()` / `resumeScan()` | | `readBarcodesFromImage()` | `readBarcodesFromImage()` | | `isSupported()` | `isAvailable()` | | `enableTorch()` / `disableTorch()` | `setTorchEnabled({ enabled })` | | `setZoomRatio()` / `getZoomRatio()` | `setZoomRatio()` / `getZoomRatioRange()` | | `getMinZoomRatio()` / `getMaxZoomRatio()` | `getZoomRatioRange()` | | `checkPermissions()` | `checkPermissions()` | | `requestPermissions()` | `requestPermissions()` | | `openSettings()` | `openSettings()` | | `isGoogleBarcodeScannerModuleAvailable()` | Not needed (the scanner UI is part of the plugin) | | `addListener('barcodesScanned')` | `addListener('barcodesScanned')` | | `addListener('scanError')` | `addListener('scanError')` | **Key differences**: - The camera preview is rendered inside a frame you provide, by default **above** the web view. You no longer need to make your app transparent (`hideBackground()`) during a scan, and HTML can no longer accidentally cover the camera. If you want to overlay HTML elements, opt in to rendering the camera preview behind the web view with the `placement` option. - The `barcode.cornerPoints` are relative to the scan frame (embedded mode) or the image (`readBarcodesFromImage(...)`), not to the screen. - Structured payload parsing (e.g. WiFi credentials, vCard contacts, driver licenses) is not yet supported. The `rawValue` is always returned, so you can parse the payload yourself. This feature is planned as a fast-follow. ## FAQ ### How is this plugin different from other similar plugins? It offers two presentation modes — a themeable, ready-made fullscreen scanner and a true embedded native camera view — plus batch scanning, pause/resume, torch and zoom control, a configurable detection area and reading barcodes from image files across 13 formats. On iOS it is built purely on AVFoundation and Vision, so there are no third-party pods, no Swift Package Manager limitations and no arm64 simulator issues. On Android it uses the bundled ML Kit model so scanning works offline, and on the web it uses the `BarcodeDetector` API — all through one fully typed, actively maintained API with dedicated support. If you only need a quick one-off scan, a minimal setup can be enough; if you want a polished UI, an embedded view and consistent behavior on Android, iOS and the Web, this plugin is designed for that. ### Why does the embedded camera view cover my HTML elements? By default, the embedded camera preview is a native view that is rendered above the web view. This is a deliberate design decision: it avoids the "camera not showing" issue class of transparent web view approaches. If you need buttons or overlays, place them outside the scan frame, use the fullscreen `scan(...)` mode which provides a themeable native UI, or set the `placement` option to `PreviewPlacement.Behind` to render the camera preview behind the web view. ### Is the fullscreen scanner available on the web? No. The `scan(...)` method as well as torch and zoom control are not available on the web. However, you can build your own web scanner UI on top of the embedded mode (`startScan(...)`), which is fully supported on the web via the `BarcodeDetector` API. ### Why is the `bytes` property always `null` on iOS and Web? Apple's AVFoundation and the web `BarcodeDetector` API only expose the string value of a barcode. The raw bytes are only available on Android. ### Does scanning work offline? Yes. On Android, the plugin uses the bundled ML Kit model. On iOS, it uses the built-in AVFoundation framework. No network connection or Google Play services module download is required. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Document Scanner](https://capawesome.io/docs/sdks/capacitor/document-scanner/): Scan documents with the native fullscreen scanner. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Pick images to read barcodes from. - [ML Kit Barcode Scanning](https://capawesome.io/docs/sdks/capacitor/mlkit/barcode-scanning/): Scan barcodes and QR codes with ML Kit Barcode Scanning. - [Torch](https://capawesome.io/docs/sdks/capacitor/torch/): Control the flashlight outside of a scan session. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barcode-scanner/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barcode-scanner/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/barcode-scanner/LICENSE). # Capacitor Barometer Plugin Capacitor plugin to obtain the static air pressure, which is measured in hectopascals (hPa). ## Features The Capacitor Barometer plugin is one of the most complete air pressure sensing solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/), [Compass](https://capawesome.io/docs/sdks/capacitor/compass/) and [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/) 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! ## Use Cases The Barometer plugin is typically used whenever an app needs to react to changes in air pressure, for example: - **Weather apps**: Show the current air pressure and track pressure trends to indicate weather changes. - **Altitude tracking**: Use the relative altitude on iOS to detect elevation changes, for example while hiking or climbing stairs. - **Outdoor and fitness apps**: Combine the pressure readings with their timestamps to analyze elevation profiles during activities. - **Sensor dashboards**: Display real-time barometer readings using continuous measurement updates. ## 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/) - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How to use pressure readings for faster-than-GPS elevation change detection in outdoor and hiking apps. ## 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 The following examples show how to check whether the barometer is available, request permissions, read the latest measurement, and listen for continuous measurement updates. ### Check if the barometer is available Check whether the device has a barometer sensor before using the other methods: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const isAvailable = async () => { const result = await Barometer.isAvailable(); console.log('Barometer is available:', result.isAvailable); }; ``` ### Check and request permissions Check and request permission to access the barometer sensor. On iOS, make sure you have added the `NSMotionUsageDescription` key to your `Info.plist` file (see [Installation](#installation)): ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; 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); }; ``` ### Get the latest measurement Get the most recent measurement from the barometer sensor. It contains the air pressure in hectopascals (hPa), the timestamp of the measurement and, on iOS, the relative altitude in meters: ``` 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)); }; ``` ### Listen for measurement updates Add a listener for the `measurement` event and start emitting measurements to monitor air pressure changes continuously. When you no longer need updates, stop them and remove your listeners: ``` import { Barometer } from '@capawesome-team/capacitor-barometer'; const startMeasurementUpdates = async () => { Barometer.addListener('measurement', (event) => { console.log('New measurement:', event); }); await Barometer.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Barometer.stopMeasurementUpdates(); Barometer.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 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` ## FAQ ### What unit is the air pressure measured in? The plugin returns the static air pressure in hectopascals (hPa). Each measurement also includes a timestamp in milliseconds since the epoch. ### How can I measure altitude with this plugin? On iOS, each measurement includes the relative altitude in meters in addition to the air pressure. On Android, only the air pressure is available. See the [usage example](#get-the-latest-measurement) above. ### What happens if the device does not have a barometer sensor? You can check whether the barometer sensor is available on the device using the `isAvailable()` method. It is recommended to call this method before using the other methods of the plugin. ### Do I need any permissions to use the barometer? The plugin provides the `checkPermissions()` and `requestPermissions()` methods to manage access to the barometer sensor. On iOS, you also need to add the `NSMotionUsageDescription` key to your `Info.plist` file as described in the [Installation](#installation) section. ### How do I monitor air pressure changes continuously? Add a listener for the `measurement` event and call `startMeasurementUpdates()` to start receiving measurements. When you no longer need updates, call `stopMeasurementUpdates()` and remove your listeners with `removeAllListeners()`. See the [usage example](#listen-for-measurement-updates) above. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/): Capture the acceleration force along the x, y, and z axes. - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/): Read the device's compass heading. - [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/): Read the device's gyroscope sensor. ## 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/). ## 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). # Capacitor Battery Plugin Capacitor plugin to access battery information. ## Features The Capacitor Battery plugin is one of the most complete battery monitoring solutions for Capacitor apps. Here are some of the key features: - 🔋 **Battery level**: Read the current battery level of the device. - ⚡ **Battery state**: Read whether the device is charging, full or unplugged. - 🪫 **Low power mode**: Read whether the low power mode is enabled. - 👂 **Change events**: Listen for changes to the battery level, state and low power mode. - 🌐 **Web support**: Read the battery level and state on supported browsers. - 🤝 **Compatibility**: Works alongside the [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/) plugin. - 📦 **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! ## Use Cases The Battery plugin is typically used whenever an app should adapt its behavior to the device's power situation, for example: - **Energy-aware features**: Reduce background work, animations, or sync frequency when the battery level is low. - **Low power mode handling**: Disable power-hungry features when the user has enabled low power mode. - **Charging-dependent tasks**: Only start heavy tasks such as large downloads while the device is charging. - **Status display**: Show the current battery level and charging state inside your app, for example in a kiosk or fleet app. - **Reacting to changes**: Warn the user when the battery level drops by listening for change events. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How battery state pairs with the Thermal State plugin to gate background work. ## 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-battery` 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-battery npx cap sync ``` This plugin does not require any additional configuration or permissions on Android or iOS. On the **Web**, the battery level and state are only available in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). The low power mode is not available on the Web. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to get the current battery level and state, check if low power mode is enabled, listen for battery changes, and remove all listeners. ### Get the current battery level Read the current battery level of the device as a value between `0.0` and `1.0`: ``` import { Battery } from '@capawesome/capacitor-battery'; const getBatteryLevel = async () => { const { level } = await Battery.getBatteryLevel(); return level; }; ``` ### Get the current battery state Check whether the device is charging, full or unplugged: ``` import { Battery } from '@capawesome/capacitor-battery'; const getBatteryState = async () => { const { state } = await Battery.getBatteryState(); return state; }; ``` ### Check if low power mode is enabled Read whether the low power mode (power saver mode on Android, Low Power Mode on iOS) is currently enabled. Only available on Android and iOS: ``` import { Battery } from '@capawesome/capacitor-battery'; const isLowPowerModeEnabled = async () => { const { enabled } = await Battery.isLowPowerModeEnabled(); return enabled; }; ``` ### Listen for battery changes Get notified when the battery level, battery state or low power mode changes. The device is only observed while at least one listener is attached. The `lowPowerModeChange` event is only available on Android and iOS: ``` import { Battery } from '@capawesome/capacitor-battery'; const addBatteryLevelChangeListener = async () => { await Battery.addListener('batteryLevelChange', event => { console.log('Battery level changed:', event.level); }); }; const addBatteryStateChangeListener = async () => { await Battery.addListener('batteryStateChange', event => { console.log('Battery state changed:', event.state); }); }; const addLowPowerModeChangeListener = async () => { await Battery.addListener('lowPowerModeChange', event => { console.log('Low power mode changed:', event.enabled); }); }; ``` ### Remove all listeners Remove all listeners that were registered for this plugin: ``` import { Battery } from '@capawesome/capacitor-battery'; const removeAllListeners = async () => { await Battery.removeAllListeners(); }; ``` ## API - [`getBatteryLevel()`](#getbatterylevel) - [`getBatteryState()`](#getbatterystate) - [`isLowPowerModeEnabled()`](#islowpowermodeenabled) - [`addListener('batteryLevelChange', ...)`](#addlistenerbatterylevelchange-) - [`addListener('batteryStateChange', ...)`](#addlistenerbatterystatechange-) - [`addListener('lowPowerModeChange', ...)`](#addlistenerlowpowermodechange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getBatteryLevel() ``` getBatteryLevel() => Promise ``` Get the current battery level of the device. On the web, this is only supported in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). Only available on Android, iOS and Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getBatteryState() ``` getBatteryState() => Promise ``` Get the current battery state of the device. On the web, this is only supported in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). Only available on Android, iOS and Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isLowPowerModeEnabled() ``` isLowPowerModeEnabled() => Promise ``` Get whether the low power mode is currently enabled. On Android, this refers to the power saver mode. On iOS, this refers to the Low Power Mode. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('batteryLevelChange', ...) ``` addListener(eventName: 'batteryLevelChange', listenerFunc: (event: BatteryLevelChangeEvent) => void) => Promise ``` Listen for changes to the battery level of the device. The device is only observed while at least one listener is attached. On the web, this is only supported in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). Only available on Android, iOS and Web. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'batteryLevelChange'` | | **`listenerFunc`** | `(event: BatteryLevelChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('batteryStateChange', ...) ``` addListener(eventName: 'batteryStateChange', listenerFunc: (event: BatteryStateChangeEvent) => void) => Promise ``` Listen for changes to the battery state of the device. The device is only observed while at least one listener is attached. On the web, this is only supported in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). Only available on Android, iOS and Web. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'batteryStateChange'` | | **`listenerFunc`** | `(event: BatteryStateChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('lowPowerModeChange', ...) ``` addListener(eventName: 'lowPowerModeChange', listenerFunc: (event: LowPowerModeChangeEvent) => void) => Promise ``` Listen for changes to the low power mode of the device. The device is only observed while at least one listener is attached. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'lowPowerModeChange'` | | **`listenerFunc`** | `(event: LowPowerModeChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetBatteryLevelResult | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------- | ----- | | **`level`** | `number` | The current battery level of the device as a value between `0.0` and `1.0`. | 0.1.0 | #### GetBatteryStateResult | Prop | Type | Description | Since | | ----------- | -------------- | ---------------------------------------- | ----- | | **`state`** | `BatteryState` | The current battery state of the device. | 0.1.0 | #### IsLowPowerModeEnabledResult | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------------------ | ----- | | **`enabled`** | `boolean` | Whether the low power mode is currently enabled. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### BatteryLevelChangeEvent | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------- | ----- | | **`level`** | `number` | The current battery level of the device as a value between `0.0` and `1.0`. | 0.1.0 | #### BatteryStateChangeEvent | Prop | Type | Description | Since | | ----------- | -------------- | ---------------------------------------- | ----- | | **`state`** | `BatteryState` | The current battery state of the device. | 0.1.0 | #### LowPowerModeChangeEvent | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------------------ | ----- | | **`enabled`** | `boolean` | Whether the low power mode is currently enabled. | 0.1.0 | ### Type Aliases #### BatteryState The battery state of the device. - `charging`: The device is plugged into power and the battery is charging. - `full`: The device is plugged into power and the battery is fully charged. - `unplugged`: The device is not plugged into power and the battery is discharging. - `unknown`: The battery state could not be determined. `'charging' | 'full' | 'unplugged' | 'unknown'` ## Battery Information Keep the following platform differences in mind when accessing battery information: - **Android**: The battery level and state are read from the sticky [`ACTION_BATTERY_CHANGED`](https://developer.android.com/reference/android/content/Intent#ACTION_BATTERY_CHANGED) broadcast. The low power mode reflects the [power saver mode]() of the device. - **iOS**: The battery level is not available on the iOS Simulator, so `getBatteryLevel()` rejects with an error there. Use a real device to test this method. The low power mode reflects the [Low Power Mode](https://support.apple.com/en-us/101604) of the device. - **Web**: The battery level and state are only available in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API) (Chromium-based browsers). The low power mode is not available on the Web. ## FAQ ### How is this plugin different from other similar plugins? It reports the full battery picture — level, charging state, and low power mode — plus change events that observe the device only while a listener is attached, all through a fully typed API that works across Android, iOS, and supported browsers on the Web. If you just need a one-off battery reading, that's a quick call; if you want to react to charging and low-power changes over time without wasting energy, this plugin is built for exactly that. ### Why does `getBatteryLevel` fail on the iOS Simulator? The battery level is not available on the iOS Simulator, so `getBatteryLevel()` rejects with an error there. Use a real device to test this method. ### Does this plugin work in all browsers? No, on the Web the battery level and state are only available in browsers that implement the [Battery Status API](https://developer.mozilla.org/en-US/docs/Web/API/Battery_Status_API), which are Chromium-based browsers. The low power mode is not available on the Web at all. ### What is the range of the battery level value? The battery level is returned as a value between `0.0` and `1.0`, where `0.0` means the battery is empty and `1.0` means it is fully charged. Multiply the value by 100 if you want to display it as a percentage. ### What does low power mode mean on Android and iOS? On Android, it refers to the [power saver mode]() of the device. On iOS, it refers to the [Low Power Mode](https://support.apple.com/en-us/101604). The `isLowPowerModeEnabled()` method and the `lowPowerModeChange` event are only available on Android and iOS. ### Does listening for battery changes drain the battery? The device is only observed while at least one listener is attached. As soon as you remove all listeners, for example with `removeAllListeners()`, the plugin stops observing the device. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/): Manage battery optimization settings and request exemptions on Android. - [Device Info](https://capawesome.io/docs/sdks/capacitor/device-info/): Read device information, such as the model, manufacturer, operating system, and memory. - [Thermal State](https://capawesome.io/docs/sdks/capacitor/thermal-state/): Read the device thermal state and react before the operating system throttles your app. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/battery/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/battery/LICENSE). # Capacitor Biometrics Plugin Capacitor plugin to request biometric authentication, such as using face recognition or fingerprint recognition. ## Features The Capacitor Biometrics plugin is one of the most complete biometric authentication solutions for Capacitor apps. 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/docs/sdks/capacitor/secure-preferences/) and [Vault](https://capawesome.io/docs/sdks/capacitor/vault/) 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! ## Use Cases The Biometrics plugin is typically used whenever an app needs to verify the identity of the user, for example: - **App lock**: Require fingerprint or face authentication before the app or sensitive screens can be opened. - **Confirming sensitive actions**: Ask the user to re-authenticate before critical actions such as payments or deleting data. - **Protecting stored credentials**: Combine biometric authentication with the [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/) or [Vault](https://capawesome.io/docs/sdks/capacitor/vault/) plugin to guard tokens and passwords. - **Device credential fallback**: Let users fall back to their device PIN or password when biometric authentication is not available or fails. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.5.x | >=8.x.x | Active support | ## Demo A working example can be found here: [capawesome-team/capacitor-biometrics-demo](https://github.com/capawesome-team/capacitor-biometrics-demo) ## 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. ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check whether biometric authentication can be used, authenticate and cancel the user, prompt for enrollment, and query the device's biometric capabilities. ### Check if biometric authentication can be used Before showing the authentication prompt, check whether biometrics is available on the device and has been configured by the current user. Only available on Android and iOS: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const isAvailable = async () => { const { isAvailable } = await Biometrics.isAvailable(); return isAvailable; }; const isEnrolled = async () => { const { isEnrolled } = await Biometrics.isEnrolled(); return isEnrolled; }; ``` ### Authenticate the user Show a prompt asking the user to authenticate with their biometrics, with a customizable title, subtitle, and button texts. If the authentication succeeds, the promise resolves; if the user cancels or an error occurs, the promise rejects with a detailed error code. Only available on Android and iOS: ``` 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); } } }; ``` ### Cancel an ongoing authentication Cancel the ongoing authentication session and dismiss the prompt. Only available on Android (SDK 29+) and iOS: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const cancelAuthentication = async () => { await Biometrics.cancelAuthentication(); }; ``` ### Prompt the user to enroll their biometrics If the user has not yet set up biometric authentication, you can prompt them to enroll. Only available on Android: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const enroll = async () => { await Biometrics.enroll(); }; ``` ### Check the device's biometric capabilities Query the supported biometric strength level (only available on Android) and check whether the user has set up a device credential such as a PIN or password: ``` import { Biometrics } from '@capawesome-team/capacitor-biometrics'; const getBiometricStrengthLevel = async () => { const { strengthLevel } = await Biometrics.getBiometricStrengthLevel(); return strengthLevel; }; const hasDeviceCredential = async () => { const { hasDeviceCredential } = await Biometrics.hasDeviceCredential(); return hasDeviceCredential; }; ``` ## API - [`authenticate(...)`](#authenticate) - [`cancelAuthentication()`](#cancelauthentication) - [`enroll()`](#enroll) - [`getAuthenticationType()`](#getauthenticationtype) - [`getBiometricStrengthLevel()`](#getbiometricstrengthlevel) - [`getBiometricType()`](#getbiometrictype) - [`getBiometricTypes()`](#getbiometrictypes) - [`hasDeviceCredential()`](#hasdevicecredential) - [`isAllowed()`](#isallowed) - [`isAvailable()`](#isavailable) - [`isEnrolled()`](#isenrolled) - [`isLockedOut()`](#islockedout) - [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. Rejects if another authentication is already in progress. 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 ______________________________________________________________________ ### getBiometricTypes() ``` getBiometricTypes() => Promise ``` Returns all biometric authentication types available on the device. On **iOS**, this returns at most a single-element array because the platform exposes only one biometric type per device. If no biometric authentication is available, an empty array is returned. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.5.4 ______________________________________________________________________ ### 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 ______________________________________________________________________ ### isAllowed() ``` isAllowed() => Promise ``` Check whether the user has granted permission to use biometric authentication. On **Android**, biometrics have no OS-level permission gate, so this always resolves to `true`. On **iOS**, this returns `false` only after the user has explicitly denied the Face ID permission prompt. Before the first `authenticate()` call it returns `true`, since iOS provides no API to distinguish the pre-prompt state from a granted state. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.5.4 ______________________________________________________________________ ### 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 ______________________________________________________________________ ### isLockedOut() ``` isLockedOut() => Promise ``` Check whether biometric authentication is currently locked out due to too many failed attempts. On **Android**, the platform exposes no queryable lockout status. This returns `true` only if the most recent `authenticate()` call in the current process failed with a lockout error; the state is cleared on the next successful authentication or process restart. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.5.4 ______________________________________________________________________ ### 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 `BiometricStrength.Weak` regardless of the value passed in if `allowDeviceCredential` is set to `true`. This is a known limitation. Only available on Android. | `BiometricStrength.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 | #### GetBiometricTypesResult | Prop | Type | Description | Since | | ----------- | ----------------- | -------------------------------------------------------------------------------------------------------------- | ----- | | **`types`** | `BiometricType[]` | The biometric authentication types available on the device. Empty if no biometric authentication is available. | 0.5.4 | #### 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 | #### IsAllowedResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------------------------------- | ----- | | **`isAllowed`** | `boolean` | Whether or not the user has granted permission to use biometric authentication. | 0.5.4 | #### 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 | #### IsLockedOutResult | Prop | Type | Description | Since | | ----------------- | --------- | ------------------------------------------------------------------------------------------------ | ----- | | **`isLockedOut`** | `boolean` | Whether or not biometric authentication is currently locked out due to too many failed attempts. | 0.5.4 | ### 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 | ## FAQ ### Which biometric authentication methods are supported? The plugin supports fingerprint, face, and iris recognition, where iris recognition is only available on Android. You can query the available methods on the device using the `getBiometricType()` and `getBiometricTypes()` methods. ### Can users authenticate with their device PIN or password instead? Yes, set the `allowDeviceCredential` option of the `authenticate(...)` method to `true` to allow the user to authenticate with their device credential (e.g. PIN or password) if biometric authentication is not available or fails. You can check whether a device credential has been set up using the `hasDeviceCredential()` method. ### How do I check if biometric authentication is available before showing the prompt? It is recommended to call the `isAvailable()` and `isEnrolled()` methods before calling `authenticate(...)`. This way you can check whether biometrics is available on the device and has been configured by the current user. See the [usage example](#check-if-biometric-authentication-can-be-used) above. ### How do I handle a failed or canceled authentication? If the user cancels the authentication or an error occurs, the promise returned by `authenticate(...)` rejects with a detailed error code such as `USER_CANCELED`, `NOT_ENROLLED`, or `NOT_AVAILABLE`. See the [usage example](#authenticate-the-user) above for how to handle these error codes. ### What configuration is required on iOS? You need to add the `NSFaceIDUsageDescription` key to your `Info.plist` file as described in the [Installation](#installation) section. The first time the user is prompted to authenticate, iOS will ask for permission to use biometrics; if the user denies it, the promise rejects with an error. ### How can I securely store credentials behind biometric authentication? The plugin is compatible with the [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/) and [Vault](https://capawesome.io/docs/sdks/capacitor/vault/) plugins, which can store sensitive key/value pairs. Check out the guide [How to Securely Store Credentials with Capacitor](https://capawesome.io/blog/how-to-securely-store-credentials-with-capacitor/) for a complete walkthrough. ## Related Plugins - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/): Communicate with OAuth 2.0 and OpenID Connect providers. - [Passkeys](https://capawesome.io/docs/sdks/capacitor/passkeys/): Create and authenticate with passkeys based on the WebAuthn standard. - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/): Securely store key/value pairs such as passwords, tokens or other sensitive information. - [Vault](https://capawesome.io/docs/sdks/capacitor/vault/): Securely store key/value pairs in lockable, biometric-protected vaults. ## 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/). ## 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). # Capacitor Bluetooth Low Energy Plugin Capacitor plugin for Bluetooth Low Energy (BLE) communication in the central and peripheral role with advanced features like headless tasks, foreground services, and more.[1](#fn:1)[2](#fn:2) ## Features The Capacitor Bluetooth Low Energy plugin is one of the most complete BLE communication solutions for Capacitor apps. 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. - 📡 **Extended Advertising**: Advertise larger payloads with BLE 5.0+ extended advertising. - 🦾 **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. - 🤝 **Compatibility**: Works alongside the [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/), [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/) and [NFC](https://capawesome.io/docs/sdks/capacitor/nfc/) 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! ## Use Cases The Bluetooth Low Energy plugin is typically used to communicate with nearby BLE hardware, for example: - **Connected hardware and IoT devices**: Connect to BLE peripherals such as sensors or wearables and exchange data by reading and writing characteristics. - **Health and fitness apps**: Receive live measurements from devices like heart rate monitors via characteristic notifications. - **Background data collection**: Keep the connection alive while the app is in the background using a foreground service on Android. - **Device-to-device communication**: Act as a BLE peripheral and advertise your own services to other central devices. - **Multi-device setups**: Connect to and communicate with multiple BLE devices at the same time. - **Tap-to-pair onboarding**: Read a device's identifier from an [NFC](https://capawesome.io/docs/sdks/capacitor/nfc/) tag, then connect to it over BLE — no manual scanning or pairing UI needed. ## 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 ## 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 The following examples show how to initialize the plugin, manage permissions, scan for and connect to devices, discover services, read and write characteristics and descriptors, act as a peripheral, and listen for Bluetooth Low Energy events. ### Initialize the plugin Initialize the plugin in the central or peripheral role and check whether Bluetooth Low Energy is available and enabled on the device: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const initialize = async () => { await BluetoothLowEnergy.initialize({ mode: 'central' }); }; const isAvailable = async () => { const result = await BluetoothLowEnergy.isAvailable(); return result.isAvailable; }; const isEnabled = async () => { const result = await BluetoothLowEnergy.isEnabled(); return result.enabled; }; ``` ### Check and request permissions Check and request the required Bluetooth permissions. Only available on Android: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const checkPermissions = async () => { const result = await BluetoothLowEnergy.checkPermissions(); return result; }; const requestPermissions = async () => { const result = await BluetoothLowEnergy.requestPermissions(); return result; }; ``` ### Scan for devices Start and stop scanning for nearby BLE devices. Scanned devices are delivered via the `deviceScanned` event (see [Listen for events](#listen-for-events)): ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const startScan = async () => { await BluetoothLowEnergy.startScan(); }; const stopScan = async () => { await BluetoothLowEnergy.stopScan(); }; ``` ### Connect to a device Connect to a BLE device by its ID, disconnect from it, and retrieve the currently connected devices: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const connect = async () => { await BluetoothLowEnergy.connect({ deviceId: '00:00:00:00:00:00' }); }; const disconnect = async () => { await BluetoothLowEnergy.disconnect({ deviceId: '00:00:00:00:00:00' }); }; const getConnectedDevices = async () => { const result = await BluetoothLowEnergy.getConnectedDevices(); return result.devices; }; ``` ### Bond with a device Create a bond with a BLE device and check whether a device is already bonded. Only available on Android: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const createBond = async () => { await BluetoothLowEnergy.createBond({ deviceId: '00:00:00:00:00:00' }); }; const isBonded = async () => { const result = await BluetoothLowEnergy.isBonded({ deviceId: '00:00:00:00:00:00' }); return result.bonded; }; ``` ### Discover services Discover the services of a connected device and retrieve them along with their characteristics and descriptors: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const discoverServices = async () => { await BluetoothLowEnergy.discoverServices({ deviceId: '00:00:00:00:00:00' }); }; const getServices = async () => { const result = await BluetoothLowEnergy.getServices({ deviceId: '00:00:00:00:00:00' }); return result.services; }; ``` ### Read and write characteristics Read the value of a characteristic or write a new value to it: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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 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], }); }; ``` ### Read and write descriptors Read the value of a descriptor or write a new value to it: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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 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], }); }; ``` ### Receive characteristic notifications Start and stop notifications for a characteristic to get notified when its value changes. The new values are delivered via the `characteristicChanged` event (see [Listen for events](#listen-for-events)): ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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 stopCharacteristicNotifications = async () => { await BluetoothLowEnergy.stopCharacteristicNotifications({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', deviceId: '00:00:00:00:00:00', serviceId: '00001800-0000-1000-8000-00805f9b34fb', }); }; ``` ### Tune the connection Read the signal strength (RSSI) of a connected device. On Android, you can also request a higher connection priority or a larger MTU for faster data transfers (`requestConnectionPriority(...)` and `requestMtu(...)` are only available on Android): ``` import { BluetoothLowEnergy, ConnectionPriority } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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, }); }; ``` ### Act as a peripheral Advertise your own services to nearby central devices in the peripheral role. Use `setCharacteristicValue(...)` to update the value of a characteristic (only available on Android): ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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 setCharacteristicValue = async () => { await BluetoothLowEnergy.setCharacteristicValue({ characteristicId: '00002a00-0000-1000-8000-00805f9b34fb', serviceId: '00001800-0000-1000-8000-00805f9b34fb', value: [1, 2, 3], }); }; const stopAdvertising = async () => { await BluetoothLowEnergy.stopAdvertising(); }; ``` ### Keep the connection alive in the background Start a foreground service to keep the connection alive while the app is in the background. Only available on Android (see [Installation](#installation) for the required service declaration and permissions): ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const startForegroundService = async () => { await BluetoothLowEnergy.startForegroundService({ body: 'Body', id: 1, smallIcon: 'smallIcon', title: 'Title', }); }; const stopForegroundService = async () => { await BluetoothLowEnergy.stopForegroundService(); }; ``` ### Open system settings Open the app settings, Bluetooth settings, or location settings so the user can grant permissions or enable Bluetooth. `openBluetoothSettings()` and `openLocationSettings()` are only available on Android: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; const openAppSettings = async () => { await BluetoothLowEnergy.openAppSettings(); }; const openBluetoothSettings = async () => { await BluetoothLowEnergy.openBluetoothSettings(); }; const openLocationSettings = async () => { await BluetoothLowEnergy.openLocationSettings(); }; ``` ### Listen for events Listen for plugin events such as scanned, connected, and disconnected devices or changed characteristic values, and remove the listeners when they are no longer needed: ``` import { BluetoothLowEnergy } from '@capawesome-team/capacitor-bluetooth-low-energy'; 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(); }; ``` ### Convert byte arrays Use the `BluetoothLowEnergyUtils` class to convert byte arrays to hexadecimal strings (see [Utils](#utils) for more information): ``` import { BluetoothLowEnergyUtils } from '@capawesome-team/capacitor-bluetooth-low-energy'; const convertBytesToHex = (bytes: number[]) => { return BluetoothLowEnergyUtils.convertBytesToHex({ bytes }); }; ``` ## API - [`connect(...)`](#connect) - [`createBond(...)`](#createbond) - [`disconnect(...)`](#disconnect) - [`discoverServices(...)`](#discoverservices) - [`getConnectedDevices()`](#getconnecteddevices) - [`getServices(...)`](#getservices) - [`initialize(...)`](#initialize) - [`isAvailable()`](#isavailable) - [`isBonded(...)`](#isbonded) - [`isEnabled()`](#isenabled) - [`isExtendedAdvertisingAvailable()`](#isextendedadvertisingavailable) - [`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) - [Type Aliases](#type-aliases) - [Enums](#enums) ### connect(...) ``` connect(options: ConnectOptions) => Promise ``` Connect to a BLE device. If the device is already connected, the promise resolves without reconnecting. 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 ______________________________________________________________________ ### isExtendedAdvertisingAvailable() ``` isExtendedAdvertisingAvailable() => Promise ``` Check if extended advertising is available on the device. Extended advertising (BLE 5.0+) allows for larger advertising payloads (up to ~250 bytes in a single packet or ~1650 bytes with chaining) compared to legacy advertising (~27-31 bytes). On **Android**, Bluetooth must be enabled; otherwise this always resolves to `false`, even if the device supports extended advertising. Use `isEnabled()` to check whether Bluetooth is enabled. On **iOS**, this always resolves to `false`, since extended advertisements cannot be transmitted via CoreBluetooth. **Returns:** `Promise` **Since:** 8.2.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. On iOS, reading a characteristic that is notifying also emits a `characteristicChanged` event. 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 and iOS. | 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'` | | **`showPowerAlert`** | `boolean` | Whether the system should display a warning dialog to the user if Bluetooth is powered off when the plugin is initialized. If not specified, the default value is `true` in `central` mode and `false` in `peripheral` mode. Only available on iOS. | | 8.1.0 | #### 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 | #### IsExtendedAdvertisingAvailableResult | Prop | Type | Description | Since | | ------------------------------ | --------- | ---------------------------------------------------------------------------------------------- | ----- | | **`isAvailable`** | `boolean` | Whether extended advertising is available on the device. | 8.2.0 | | **`maxAdvertisingDataLength`** | `number` | Maximum advertising data length in bytes. This is only available when `isAvailable` is `true`. | 8.2.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 | 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. On **Android**, for apps targeting `Build.VERSION_CODES.R` or lower, this requires the `BLUETOOTH_ADMIN` permission. For apps targeting `Build.VERSION_CODES.S` or higher, this requires the `BLUETOOTH_CONNECT` permission. Only available on Android and iOS. | 7.2.0 | | **`serviceData`** | `{ [key: string]: number[]; }` | [Service](#service) data to advertise (UUID -> byte array). Only available on Android and only when `settings.legacyMode` is `false`. | 8.2.0 | | **`services`** | `Service[]` | The services to advertise. | 7.2.0 | | **`settings`** | `AdvertisingSettings` | Extended advertising settings (BLE 5.0+). Only available on Android. | 8.2.0 | #### AdvertisingSettings | Prop | Type | Description | Default | Since | | ------------------ | ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------- | ----- | | **`legacyMode`** | `boolean` | Whether to use legacy advertising mode (BLE 4.x compatible). When `true`, uses legacy advertising which is compatible with all BLE devices but limited to ~27-31 bytes of advertising data. When `false`, uses extended advertising (BLE 5.0+) which supports larger payloads. | `true` | 8.2.0 | | **`connectable`** | `boolean` | Whether the advertisement is connectable. | `true` | 8.2.0 | | **`scannable`** | `boolean` | Whether the advertisement is scannable. Only applies when `legacyMode` is `false`. | `false` | 8.2.0 | | **`interval`** | `AdvertisingInterval` | Advertising interval. | `AdvertisingInterval.LOW_LATENCY` | 8.2.0 | | **`txPowerLevel`** | `AdvertisingTxPowerLevel` | TX power level. | `AdvertisingTxPowerLevel.HIGH` | 8.2.0 | | **`primaryPhy`** | `AdvertisingPhy` | Primary PHY for advertising. Only applies when `legacyMode` is `false`. | `AdvertisingPhy.PHY_LE_1M` | 8.2.0 | | **`secondaryPhy`** | `AdvertisingPhy` | Secondary PHY for advertising. Only applies when `legacyMode` is `false`. | `AdvertisingPhy.PHY_LE_1M` | 8.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 | Default | Since | | --------------------- | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------ | ----- | | **`allowDuplicates`** | `boolean` | Whether to emit the `deviceScanned` event for every advertisement packet received, instead of only the first advertisement of each device. This is required to receive manufacturer data or service data that is spread across multiple advertisement packets (e.g. when the value is delivered in the scan response packet on iOS). On iOS, this maps to `CBCentralManagerScanOptionAllowDuplicatesKey` and is only honored while the app is in the foreground. | `false` | 8.2.0 | | **`extended`** | `boolean` | Whether to use extended advertising scanning (BLE 5.0+) to also discover devices that use extended advertising. When enabled, both legacy and extended advertisements are reported. On **iOS**, extended advertisements are received automatically during a normal scan, so this option is not needed and has no effect. Only available on Android. | `false` | 8.2.0 | | **`phy`** | `ScanPhy` | The PHY to use for scanning. Only applies when `extended` is `true`. Only available on Android. | `ScanPhy.PHY_LE_ALL_SUPPORTED` | 8.2.0 | | **`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 | | **`localName`** | `string` | The local name of the scanned device from the advertisement data. In contrast to `name`, this always reflects the value from the current advertisement packet. | 8.2.0 | | **`manufacturerData`** | `{ [key: number]: number[]; }` | The manufacturer specific data from the advertisement data. The key is the 16-bit company identifier and the value is the payload bytes. | 8.2.0 | | **`name`** | `string` | The name of the scanned device. On iOS, this returns the cached GAP name and may differ from `localName` after a previous connection to the device. | 6.0.0 | | **`rawAdvertisement`** | `number[]` | The raw bytes of the advertisement data. Only available on Android. | 8.2.0 | | **`rssi`** | `number` | The RSSI value of the scanned device. | 6.0.0 | | **`serviceData`** | `{ [uuid: string]: number[]; }` | The service data from the advertisement data. The key is the service UUID and the value is the payload bytes. | 8.2.0 | | **`serviceUuids`** | `string[]` | The UUIDs of the services advertised by the device. | 8.2.0 | | **`txPower`** | `number` | The transmit power of the scanned device in dBm. | 8.2.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 | #### AdvertisingInterval | Members | Value | Description | Since | | ----------------- | --------------- | ------------------------------------------ | ----- | | **`LOW_LATENCY`** | `'LOW_LATENCY'` | Low latency advertising interval (~100ms). | 8.2.0 | | **`BALANCED`** | `'BALANCED'` | Balanced advertising interval (~250ms). | 8.2.0 | | **`LOW_POWER`** | `'LOW_POWER'` | Low power advertising interval (~1000ms). | 8.2.0 | #### AdvertisingTxPowerLevel | Members | Value | Description | Since | | --------------- | ------------- | ------------------------- | ----- | | **`ULTRA_LOW`** | `'ULTRA_LOW'` | Ultra-low TX power level. | 8.2.0 | | **`LOW`** | `'LOW'` | Low TX power level. | 8.2.0 | | **`MEDIUM`** | `'MEDIUM'` | Medium TX power level. | 8.2.0 | | **`HIGH`** | `'HIGH'` | High TX power level. | 8.2.0 | #### AdvertisingPhy | Members | Value | Description | Since | | ------------------ | ---------------- | --------------------------------------------- | ----- | | **`PHY_LE_1M`** | `'PHY_LE_1M'` | LE 1M PHY (default, compatible with BLE 4.x). | 8.2.0 | | **`PHY_LE_2M`** | `'PHY_LE_2M'` | LE 2M PHY (higher throughput, BLE 5.0+). | 8.2.0 | | **`PHY_LE_CODED`** | `'PHY_LE_CODED'` | LE Coded PHY (longer range, BLE 5.0+). | 8.2.0 | #### ScanPhy | Members | Value | Description | Since | | -------------------------- | ------------------------ | -------------------------------------- | ----- | | **`PHY_LE_1M`** | `'PHY_LE_1M'` | LE 1M PHY (compatible with BLE 4.x). | 8.2.0 | | **`PHY_LE_CODED`** | `'PHY_LE_CODED'` | LE Coded PHY (longer range, BLE 5.0+). | 8.2.0 | | **`PHY_LE_ALL_SUPPORTED`** | `'PHY_LE_ALL_SUPPORTED'` | Scan on all supported PHYs (default). | 8.2.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 '@capawesome-team/capacitor-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. ## Bluetooth 5.0 (BLE 5) support Several BLE 5 capabilities behave differently across platforms due to operating system constraints. These are platform limitations, not limitations of the plugin. | Capability | Android | iOS | | ----------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------ | | Extended advertising (large payloads, manufacturer/service data, PHY, interval, TX power) | ✅ Supported | ❌ Not supported (no CoreBluetooth API) | | Extended advertisement scanning | ✅ Supported and configurable (`extended`, `phy`) | ✅ Handled automatically, not configurable | | Coded PHY (LE Long Range) | ✅ Supported | ⚠️ Not reliably supported | ## FAQ ### Does the plugin work on the Web? No, the plugin supports Android and iOS. Web browsers do not provide the required APIs for the full feature set of this plugin, such as the peripheral role or foreground services. ### Which permissions do I need on Android? The required permissions depend on the features you use: `BLUETOOTH_SCAN` and `ACCESS_FINE_LOCATION` for scanning, `BLUETOOTH_CONNECT` for connecting to paired devices, `BLUETOOTH_ADVERTISE` for advertising as a peripheral, and the foreground service permissions if you want to start a foreground service. On Android 11 and below, the legacy `BLUETOOTH` and `BLUETOOTH_ADMIN` permissions are required instead. See [Installation](#installation) for the complete list and use `checkPermissions()` and `requestPermissions()` at runtime. ### How do I keep the BLE connection alive while the app is in the background? On Android, start a foreground service using the `startForegroundService(...)` method, which requires the service declaration and foreground service permissions described in the [Installation](#installation) section. On iOS, enable the `Background Modes` capability with `bluetooth-central` in your Xcode project. ### Can my app act as a BLE peripheral? Yes, the plugin supports the peripheral role in addition to the central role. Use `startAdvertising(...)` to advertise your own services to nearby central devices. Note that `setCharacteristicValue(...)` and the `characteristicWriteRequest` event are only available on Android. ### Can I connect to multiple devices at the same time? Yes, the plugin supports connections to multiple devices at the same time. Each method that operates on a device takes a `deviceId` parameter, and you can retrieve all currently connected devices using the `getConnectedDevices()` method. ### What is a headless task and when do I need one? A headless task lets you run your own native code when a specific Bluetooth Low Energy event occurs, for example when a characteristic value changes. For this, you create a Java class named `BluetoothLowEnergyHeadlessTask` in the same package as your `MainActivity` on Android, as described in the [Installation](#installation) section. This is useful if you need to react to events even when the web view is not running. ## Related Plugins - [Android Battery Optimization](https://capawesome.io/docs/sdks/capacitor/android-battery-optimization/): Manage battery optimization settings and request exemptions to keep background work running reliably. - [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/): Run a foreground service on Android. - [Network](https://capawesome.io/docs/sdks/capacitor/network/): Access network information such as connection status and type, and listen for network changes. - [NFC](https://capawesome.io/docs/sdks/capacitor/nfc/): Read, write, and emulate NFC tags with advanced features like HCE and raw command handling. - [Wi-Fi](https://capawesome.io/docs/sdks/capacitor/wifi/): Manage Wi-Fi connectivity, including adding, connecting, and disconnecting networks. ## 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/). ## 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). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Bluetooth SIG, Inc. or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") 1. `Bluetooth` is a registered trademark of Bluetooth SIG, Inc. [↩](#fnref:2 "Jump back to footnote 2 in the text") # Capacitor Calendar Plugin Capacitor plugin to manage calendars and events on Android and iOS. Create, read, update and delete calendars and events, work with recurring events, present the system event dialogs, and listen for calendar changes. ## Features The Capacitor Calendar plugin gives your app full access to the calendars and events on the device. Here are some of the key features: - 📅 **Calendars**: Create, delete and retrieve the calendars on the device, including the default calendar for new events. - 🗓️ **Events**: Create, read, update and delete events, and query all events in a time range with a single call. - 🔁 **Recurring Events**: Create recurring events with a readable recurrence rule — and read that rule back, instead of only being able to write it. - 🎯 **Single Occurrences**: Update or delete a single occurrence of a recurring event, or the occurrence and all future ones. - 📱 **System Dialogs**: Let the user create or edit an event in the system dialog, prefilled with your event data. - 🔔 **Change Listener**: Get notified when calendars or events change, including changes made by other apps. - 🔒 **Granular Permissions**: Separate read and write permissions, including write-only calendar access on iOS 17 and newer. - ⚠️ **Error Codes**: Every runtime failure rejects with a documented error code, so you can branch on it instead of parsing messages. - 🌍 **All-Day & Time Zones**: A documented all-day and time zone contract that behaves identically on both platforms — no off-by-one-day surprises. - 🤝 **Compatibility**: Works hand in hand with the [Contacts](https://capawesome.io/docs/sdks/capacitor/contacts/) and [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/) 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! ## Use Cases The Calendar plugin is typically used whenever an app needs to read from or write to the calendars on the device, for example: - **Booking and appointment apps**: Write a confirmed booking straight into the user's calendar, including an alert before the appointment, and update or remove it when the booking changes. - **Field service and scheduling**: Show the agenda of the device next to your own schedule so that technicians and sales reps see conflicts before they accept a job. - **Fitness and course apps**: Add recurring training sessions or course dates as a single recurring event, and let the user skip a single session without losing the series. - **Reminders before appointments**: Attach alerts to an event so that the operating system reminds the user, even when your app is not running. - **Calendar integrations**: Keep events in sync with your backend and react to changes that the user made in the calendar app. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor Calendar Plugin](https://capawesome.io/blog/announcing-the-capacitor-calendar-plugin/): Create, read, and update events with recurrence rules and a calendarChange listener. - [How to Manage Recurring Calendar Events in Capacitor](https://capawesome.io/blog/recurring-calendar-events-in-capacitor/): Write a recurrence rule, expand it into occurrences, and update or delete a single occurrence or every later one on Android and 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-calendar` 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-calendar npx cap sync ``` ### Android #### Permissions This API requires the following elements be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` Only declare the permissions that your app actually needs. Keep in mind that `createEvent(...)`, `updateEventById(...)` and `deleteEventById(...)` require the `READ_CALENDAR` permission in addition to the `WRITE_CALENDAR` permission, because they have to look up the calendar or event first. Only `createCalendar(...)` and `deleteCalendarById(...)` work with the `WRITE_CALENDAR` permission alone. #### 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 following keys to the `ios/App/App/Info.plist` file, which tell the user why your app needs access to the calendars: ``` NSCalendarsFullAccessUsageDescription The app needs access to your calendars to display and manage your events. NSCalendarsWriteOnlyAccessUsageDescription The app needs access to your calendars to add events for your bookings. NSCalendarsUsageDescription The app needs access to your calendars to display and manage your events. ``` Which keys you need depends on the access that your app requests: - `NSCalendarsFullAccessUsageDescription` is required on **iOS 17 and newer** whenever the `readCalendar` permission is requested and by every method that reads or modifies calendars or events. Modifying requires full access as well, because the plugin has to look up the calendar or event first. - `NSCalendarsWriteOnlyAccessUsageDescription` is only required on **iOS 17 and newer** if `requestPermissions(...)` is called with only the `writeCalendar` permission. Write-only access lets your app add events without seeing the events of the user, but is not sufficient for the methods of this plugin. - `NSCalendarsUsageDescription` is required on **iOS 16 and older**, which does not distinguish between read and write access. If a required key is missing, `requestPermissions(...)` rejects with a clear error message. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to request permissions, work with calendars and events, create and modify recurring events, present the system event dialogs, and listen for calendar changes. ### Request permissions Request read and write access to the calendars of the device. Pass the `permissions` option to request only a subset. On iOS 17 and newer, requesting only the `writeCalendar` permission requests write-only access, which does not give your app access to the existing events of the user. Methods such as `createEvent(...)` request full access when they are called, because they have to look up the calendar or event first: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const requestPermissions = async () => { const { readCalendar, writeCalendar } = await Calendar.requestPermissions(); return readCalendar === 'granted' && writeCalendar === 'granted'; }; const requestWriteOnlyPermission = async () => { const { writeCalendar } = await Calendar.requestPermissions({ permissions: ['writeCalendar'], }); return writeCalendar === 'granted'; }; ``` ### Get the calendars Retrieve all calendars on the device with `getCalendars()`, or only the calendar that the system uses for new events with `getDefaultCalendar()`. Use the `writable` property to filter out calendars that your app cannot write to, for example subscribed holiday calendars: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const getWritableCalendars = async () => { const { calendars } = await Calendar.getCalendars(); return calendars.filter(calendar => calendar.writable); }; const getDefaultCalendar = async () => { const { calendar } = await Calendar.getDefaultCalendar(); return calendar; }; ``` ### Create an event Create an event with `createEvent(...)`. Only `title` and `startDate` are required. If no `calendarId` is provided, the event is created in the default calendar. The `alerts` are offsets in minutes before the start of the event: ``` import { Calendar, EventAvailability } from '@capawesome-team/capacitor-calendar'; const createEvent = async (calendarId: string) => { const startDate = new Date('2026-09-01T10:00:00').getTime(); const { id } = await Calendar.createEvent({ event: { calendarId, title: 'Dentist appointment', startDate, endDate: startDate + 60 * 60 * 1000, location: 'Main Street 1, Springfield', description: 'Bring the insurance card.', availability: EventAvailability.Busy, alerts: [60, 15], }, }); return id; }; ``` ### Create a recurring event Add a `recurrence` rule to create a recurring event. The following example creates an event that repeats every week on Mondays and Wednesdays for ten occurrences: ``` import { Calendar, RecurrenceFrequency, Weekday, } from '@capawesome-team/capacitor-calendar'; const createRecurringEvent = async () => { const { id } = await Calendar.createEvent({ event: { title: 'Team stand-up', startDate: new Date('2026-09-01T09:00:00').getTime(), recurrence: { frequency: RecurrenceFrequency.Weekly, interval: 1, count: 10, daysOfWeek: [Weekday.Monday, Weekday.Wednesday], }, }, }); return id; }; ``` ### Get the events in a range Query all events that overlap a time range with `getEvents(...)`. Recurring events are expanded, so each occurrence is returned as a separate entry with its own `startDate`. Pass a `calendarId` to restrict the query to a single calendar: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const getEventsOfNextWeek = async () => { const from = Date.now(); const to = from + 7 * 24 * 60 * 60 * 1000; const { events } = await Calendar.getEvents({ from, to }); return events; }; ``` A single event can be retrieved by its identifier with `getEventById(...)`, which resolves with `null` if the event does not exist: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const getEventById = async (id: string) => { const { event } = await Calendar.getEventById({ id }); return event; }; ``` ### Update an event Update an event with `updateEventById(...)`. Only the properties that you pass are changed, all others keep their current values. Setting a property to `null` (or an array property to `[]`) removes it from the event: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const rescheduleEvent = async (id: string, startDate: number) => { await Calendar.updateEventById({ id, event: { startDate, endDate: startDate + 30 * 60 * 1000, }, }); }; const clearEventDetails = async (id: string) => { await Calendar.updateEventById({ id, event: { location: null, description: null, alerts: [], }, }); }; ``` ### Delete a single occurrence Pass the `instanceStartDate` of an occurrence, as returned by `getEvents(...)`, to apply an operation to a single occurrence of a recurring event instead of the whole series. The `span` option controls whether the operation affects only that occurrence or the occurrence and all future ones: ``` import { Calendar, EventSpan } from '@capawesome-team/capacitor-calendar'; const deleteOccurrence = async (id: string, instanceStartDate: number) => { await Calendar.deleteEventById({ id, instanceStartDate, span: EventSpan.ThisEvent, }); }; const deleteAllFutureOccurrences = async ( id: string, instanceStartDate: number, ) => { await Calendar.deleteEventById({ id, instanceStartDate, span: EventSpan.ThisAndFutureEvents, }); }; ``` Without `instanceStartDate`, the entire recurring event is deleted. ### Display the system event dialog Let the user create an event in the system dialog with `displayCreateEvent(...)`, optionally prefilled with your event data. On iOS, the identifier of the created event is returned if the user saved the event: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const displayCreateEvent = async () => { const { id } = await Calendar.displayCreateEvent({ event: { title: 'Lunch with Jane', startDate: new Date('2026-09-01T12:00:00').getTime(), location: 'Main Street 1, Springfield', }, }); return id; }; ``` Use `displayUpdateEventById(...)` to let the user edit an existing event. On iOS, the `action` describes what the user did in the dialog: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const displayUpdateEventById = async (id: string) => { const { action } = await Calendar.displayUpdateEventById({ id }); return action; }; ``` The system dialogs on Android do not report a result back to the app, so `id` and `action` are only available on iOS. On Android, only the event properties that are supported by the system intent are prefilled, and the remaining properties are silently ignored. ### Listen for calendar changes Register a listener for the `calendarChange` event to reload your data whenever calendars or events change, including changes that were made in the calendar app or by other apps. The event carries no payload, because the operating systems do not report which entities changed: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const addCalendarChangeListener = async () => { return Calendar.addListener('calendarChange', () => { console.log('The calendars or events on the device have changed.'); }); }; const removeAllListeners = async () => { await Calendar.removeAllListeners(); }; ``` ### Open the calendar app and the app settings Open the calendar app of the device at a specific date with `openCalendar(...)`, for example after an event was created. Use `openSettings()` to send the user to the settings of your app so that a previously denied permission can be granted: ``` import { Calendar } from '@capawesome-team/capacitor-calendar'; const openCalendar = async (date: number) => { await Calendar.openCalendar({ date }); }; const openSettings = async () => { await Calendar.openSettings(); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`createCalendar(...)`](#createcalendar) - [`createEvent(...)`](#createevent) - [`deleteCalendarById(...)`](#deletecalendarbyid) - [`deleteEventById(...)`](#deleteeventbyid) - [`displayCreateEvent(...)`](#displaycreateevent) - [`displayUpdateEventById(...)`](#displayupdateeventbyid) - [`getCalendars()`](#getcalendars) - [`getDefaultCalendar()`](#getdefaultcalendar) - [`getEventById(...)`](#geteventbyid) - [`getEvents(...)`](#getevents) - [`openCalendar(...)`](#opencalendar) - [`openSettings()`](#opensettings) - [`requestPermissions(...)`](#requestpermissions) - [`updateEventById(...)`](#updateeventbyid) - [`addListener('calendarChange', ...)`](#addlistenercalendarchange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions to access the device calendar. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### createCalendar(...) ``` createCalendar(options: CreateCalendarOptions) => Promise ``` Create a new calendar on the device. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------- | | **`options`** | `CreateCalendarOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### createEvent(...) ``` createEvent(options: CreateEventOptions) => Promise ``` Create a new event in a calendar. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `CreateEventOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### deleteCalendarById(...) ``` deleteCalendarById(options: DeleteCalendarByIdOptions) => Promise ``` Delete a calendar from the device. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `DeleteCalendarByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### deleteEventById(...) ``` deleteEventById(options: DeleteEventByIdOptions) => Promise ``` Delete an event from the device. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `DeleteEventByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### displayCreateEvent(...) ``` displayCreateEvent(options?: DisplayCreateEventOptions | undefined) => Promise ``` Display the system user interface to create a new event. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `DisplayCreateEventOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### displayUpdateEventById(...) ``` displayUpdateEventById(options: DisplayUpdateEventByIdOptions) => Promise ``` Display the system user interface to update an existing event. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `DisplayUpdateEventByIdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getCalendars() ``` getCalendars() => Promise ``` Get all calendars on the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getDefaultCalendar() ``` getDefaultCalendar() => Promise ``` Get the default calendar for new events. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getEventById(...) ``` getEventById(options: GetEventByIdOptions) => Promise ``` Get a single event by its identifier. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `GetEventByIdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getEvents(...) ``` getEvents(options: GetEventsOptions) => Promise ``` Get the events in a given time range. Returns all events that overlap the time range, including single occurrences of recurring events. Rejects with the error code `CALENDAR_NOT_FOUND` if a `calendarId` is provided but no calendar with that identifier exists. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `GetEventsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openCalendar(...) ``` openCalendar(options?: OpenCalendarOptions | undefined) => Promise ``` Open the calendar app of the device. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `OpenCalendarOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the settings of the app so that the user can grant or revoke permissions. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions to access the device calendar. On iOS 17+, requesting only the `writeCalendar` permission requests write-only access. Requesting the `readCalendar` permission requests full access. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### updateEventById(...) ``` updateEventById(options: UpdateEventByIdOptions) => Promise ``` Update an existing event. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `UpdateEventByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### addListener('calendarChange', ...) ``` addListener(eventName: 'calendarChange', listenerFunc: () => void) => Promise ``` Called when calendars or events are created, updated or deleted, including by other apps. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------ | | **`eventName`** | `'calendarChange'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Description | Since | | ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- | ----- | | **`readCalendar`** | `PermissionState` | The permission state for reading calendar data. On iOS, this is `prompt` as long as only write-only access has been granted. | 0.0.1 | | **`writeCalendar`** | `PermissionState` | The permission state for writing calendar data. | 0.0.1 | #### CreateCalendarResult | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------- | ----- | | **`id`** | `string` | The identifier of the created calendar. | 0.0.1 | #### CreateCalendarOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------------------------------------------ | ----- | | **`color`** | `string` | The color of the calendar as a hex string in the format `#RRGGBB`. | 0.0.1 | | **`title`** | `string` | The title of the calendar. | 0.0.1 | #### CreateEventResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------ | ----- | | **`id`** | `string` | The identifier of the created event. | 0.0.1 | #### CreateEventOptions | Prop | Type | Description | Since | | ----------- | ------------ | -------------------- | ----- | | **`event`** | `EventInput` | The event to create. | 0.0.1 | #### EventInput | Prop | Type | Description | Default | Since | | ------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- | ----- | | **`alerts`** | `number[]` | The alerts of the event as offsets in minutes before the start of the event. Negative values represent minutes after the start. | | 0.0.1 | | **`allDay`** | `boolean` | Whether the event is an all-day event. For all-day events, `startDate` and `endDate` are interpreted as midnight UTC of the respective calendar day. | `false` | 0.0.1 | | **`availability`** | `EventAvailability` | The availability of the event. | | 0.0.1 | | **`calendarId`** | `string` | The identifier of the calendar in which the event is created. | `The identifier of the default calendar.` | 0.0.1 | | **`description`** | `string` | The description of the event. | | 0.0.1 | | **`endDate`** | `number` | The end date of the event as a timestamp in milliseconds. If not provided, the event ends one hour after `startDate` (all-day events: on the same day as `startDate`). For all-day events, the end date is exclusive (midnight UTC of the day after the last day of the event). | | 0.0.1 | | **`location`** | `string` | The location of the event. | | 0.0.1 | | **`recurrence`** | `RecurrenceRule` | The recurrence rule of the event. | | 0.0.1 | | **`startDate`** | `number` | The start date of the event as a timestamp in milliseconds. | | 0.0.1 | | **`timezone`** | `string` | The time zone of the event as an IANA time zone identifier. | `The default time zone of the device.` | 0.0.1 | | **`title`** | `string` | The title of the event. | | 0.0.1 | #### RecurrenceRule | Prop | Type | Description | Default | Since | | ---------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`count`** | `number` | The number of occurrences after which the recurrence ends. Takes precedence over `until`. | | 0.0.1 | | **`daysOfWeek`** | `Weekday[]` | The days of the week on which the event recurs. | | 0.0.1 | | **`frequency`** | `RecurrenceFrequency` | The frequency of the recurrence. | | 0.0.1 | | **`interval`** | `number` | The interval between occurrences of the recurrence. For example, an interval of `2` with a `Weekly` frequency results in an event that recurs every two weeks. | `1` | 0.0.1 | | **`until`** | `number` | The date on which the recurrence ends as a timestamp in milliseconds. | | 0.0.1 | #### DeleteCalendarByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------------------- | ----- | | **`id`** | `string` | The identifier of the calendar to delete. | 0.0.1 | #### DeleteEventByIdOptions | Prop | Type | Description | Default | Since | | ----------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | ----- | | **`id`** | `string` | The identifier of the event to delete. | | 0.0.1 | | **`instanceStartDate`** | `number` | The start date of a single occurrence of a recurring event as a timestamp in milliseconds, as returned by `getEvents(...)`. If provided, only the given occurrence (or, depending on `span`, the given and all future occurrences) of the recurring event is deleted. If omitted, the entire recurring event is deleted. | | 0.0.1 | | **`span`** | `EventSpan` | The span of a recurring event to which the operation is applied. Only applied when `instanceStartDate` is provided. | `EventSpan.ThisEvent` | 0.0.1 | #### DisplayCreateEventResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The identifier of the created event. Only returned if the event was saved. Only available on iOS. | 0.0.1 | #### DisplayCreateEventOptions | Prop | Type | Description | Since | | ----------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`event`** | `Partial` | The event data with which the dialog is prefilled. On Android, only the properties supported by the system intent are applied. Unsupported properties are silently ignored. | 0.0.1 | #### DisplayUpdateEventByIdResult | Prop | Type | Description | Since | | ------------ | ----------------- | ------------------------------------------------------------------------ | ----- | | **`action`** | `EventEditAction` | The action that the user performed in the dialog. Only available on iOS. | 0.0.1 | #### DisplayUpdateEventByIdOptions | Prop | Type | Description | Since | | -------- | -------- | -------------------------------------- | ----- | | **`id`** | `string` | The identifier of the event to update. | 0.0.1 | #### GetCalendarsResult | Prop | Type | Description | Since | | --------------- | ------------ | ---------------------------- | ----- | | **`calendars`** | `Calendar[]` | The calendars on the device. | 0.0.1 | #### Calendar | Prop | Type | Description | Since | | -------------- | --------- | ------------------------------------------------------------------ | ----- | | **`color`** | `string` | The color of the calendar as a hex string in the format `#RRGGBB`. | 0.0.1 | | **`id`** | `string` | The identifier of the calendar. | 0.0.1 | | **`title`** | `string` | The title of the calendar. | 0.0.1 | | **`writable`** | `boolean` | Whether events can be added, updated, or deleted in this calendar. | 0.0.1 | #### GetDefaultCalendarResult | Prop | Type | Description | Since | | -------------- | ---------- | ----------- | --------------------------------------------------------------------------------------------- | | **`calendar`** | \`Calendar | null\` | The default calendar for new events. If no default calendar is available, `null` is returned. | #### GetEventByIdResult | Prop | Type | Description | Since | | ----------- | --------------- | ----------- | ------------------------------------------------------------------------------- | | **`event`** | \`CalendarEvent | null\` | The event with the given identifier. If no event was found, `null` is returned. | #### CalendarEvent | Prop | Type | Description | Since | | ------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`alerts`** | `number[]` | The alerts of the event as offsets in minutes before the start of the event. Negative values represent minutes after the start. | 0.0.1 | | **`allDay`** | `boolean` | Whether the event is an all-day event. For all-day events, `startDate` and `endDate` are returned as midnight UTC of the respective calendar day. | 0.0.1 | | **`availability`** | `EventAvailability` | The availability of the event. | 0.0.1 | | **`calendarId`** | `string` | The identifier of the calendar that the event belongs to. | 0.0.1 | | **`description`** | `string` | The description of the event. | 0.0.1 | | **`endDate`** | `number` | The end date of the event as a timestamp in milliseconds. For all-day events, the end date is exclusive (midnight UTC of the day after the last day of the event). | 0.0.1 | | **`id`** | `string` | The identifier of the event. All occurrences of a recurring event share the same identifier. | 0.0.1 | | **`location`** | `string` | The location of the event. | 0.0.1 | | **`recurrence`** | `RecurrenceRule` | The recurrence rule of the event. | 0.0.1 | | **`startDate`** | `number` | The start date of the event as a timestamp in milliseconds. | 0.0.1 | | **`status`** | `EventStatus` | The confirmation status of the event. | 0.0.1 | | **`timezone`** | `string` | The time zone of the event as an IANA time zone identifier. | 0.0.1 | | **`title`** | `string` | The title of the event. | 0.0.1 | #### GetEventByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ---------------------------- | ----- | | **`id`** | `string` | The identifier of the event. | 0.0.1 | #### GetEventsResult | Prop | Type | Description | Since | | ------------ | ----------------- | ----------------------------------- | ----- | | **`events`** | `CalendarEvent[]` | The events in the given time range. | 0.0.1 | #### GetEventsOptions | Prop | Type | Description | Since | | ---------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`calendarId`** | `string` | The identifier of the calendar to get the events from. If not provided, the events of all calendars are returned. | 0.0.1 | | **`from`** | `number` | The start of the time range as a timestamp in milliseconds. | 0.0.1 | | **`to`** | `number` | The end of the time range as a timestamp in milliseconds. On iOS, only the first 100 years after `from` are searched. | 0.0.1 | #### OpenCalendarOptions | Prop | Type | Description | Default | Since | | ---------- | -------- | ---------------------------------------------------------------------------- | ------------------- | ----- | | **`date`** | `number` | The date to which the calendar app is opened as a timestamp in milliseconds. | `The current time.` | 0.0.1 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | -------------------------- | --------------------------- | ----------------------------------- | ----- | | **`permissions`** | `CalendarPermissionType[]` | The permissions to request. | `['readCalendar', 'writeCalendar']` | 0.0.1 | #### UpdateEventByIdOptions | Prop | Type | Description | Default | Since | | ----------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | --------------------- | ----- | | **`event`** | `Nullable>` | The updated event data. Missing properties are ignored and keep their existing values. Properties explicitly set to `null` (or empty arrays `[]`) will be removed from the event. Properties that are required for the event structure (`allDay`, `calendarId`, `endDate`, `startDate`, `timezone`) must not be set to `null`. | | 0.0.1 | | **`id`** | `string` | The identifier of the event to update. | | 0.0.1 | | **`instanceStartDate`** | `number` | The start date of a single occurrence of a recurring event as a timestamp in milliseconds, as returned by `getEvents(...)`. If provided, only the given occurrence (or, depending on `span`, the given and all future occurrences) of the recurring event is updated. If omitted, the entire recurring event is updated. | | 0.0.1 | | **`span`** | `EventSpan` | The span of a recurring event to which the operation is applied. Only applied when `instanceStartDate` is provided. | `EventSpan.ThisEvent` | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### EventEditAction The action that the user performed in a system event dialog. `'canceled' | 'deleted' | 'saved'` #### EventStatus The confirmation status of an event. `'canceled' | 'confirmed' | 'tentative'` #### CalendarPermissionType The permissions to request when calling `requestPermissions(...)`. `'readCalendar' | 'writeCalendar'` #### Nullable `{ [K in keyof T]: T[K] | null }` ### Enums #### EventAvailability | Members | Value | Description | Since | | ----------------- | --------------- | ------------------------------------------------------------------------------------------- | ----- | | **`Busy`** | `'BUSY'` | The time of the event is marked as busy. | 0.0.1 | | **`Free`** | `'FREE'` | The time of the event is marked as free. | 0.0.1 | | **`Tentative`** | `'TENTATIVE'` | The time of the event is marked as tentative. | 0.0.1 | | **`Unavailable`** | `'UNAVAILABLE'` | The time of the event is marked as unavailable. On Android, this value is mapped to `Busy`. | 0.0.1 | #### Weekday | Members | Value | Description | Since | | --------------- | ------------- | ------------------------------- | ----- | | **`Friday`** | `'FRIDAY'` | The event recurs on Fridays. | 0.0.1 | | **`Monday`** | `'MONDAY'` | The event recurs on Mondays. | 0.0.1 | | **`Saturday`** | `'SATURDAY'` | The event recurs on Saturdays. | 0.0.1 | | **`Sunday`** | `'SUNDAY'` | The event recurs on Sundays. | 0.0.1 | | **`Thursday`** | `'THURSDAY'` | The event recurs on Thursdays. | 0.0.1 | | **`Tuesday`** | `'TUESDAY'` | The event recurs on Tuesdays. | 0.0.1 | | **`Wednesday`** | `'WEDNESDAY'` | The event recurs on Wednesdays. | 0.0.1 | #### RecurrenceFrequency | Members | Value | Description | Since | | ------------- | ----------- | ------------------------- | ----- | | **`Daily`** | `'DAILY'` | The event recurs daily. | 0.0.1 | | **`Monthly`** | `'MONTHLY'` | The event recurs monthly. | 0.0.1 | | **`Weekly`** | `'WEEKLY'` | The event recurs weekly. | 0.0.1 | | **`Yearly`** | `'YEARLY'` | The event recurs yearly. | 0.0.1 | #### EventSpan | Members | Value | Description | Since | | ------------------------- | -------------------------- | --------------------------------------------------------------------------------------------------- | ----- | | **`ThisAndFutureEvents`** | `'THIS_AND_FUTURE_EVENTS'` | The operation is applied to the given occurrence and all future occurrences of the recurring event. | 0.0.1 | | **`ThisEvent`** | `'THIS_EVENT'` | The operation is applied only to the given occurrence of the recurring event. | 0.0.1 | ## Recurring Events Recurring events are stored as a single event with a recurrence rule, but they are displayed to the user as many occurrences. The plugin makes both views available and keeps them consistent across Android and iOS. **Occurrence expansion**: `getEvents(...)` expands recurring events into their occurrences. Each occurrence is returned as a separate entry whose `startDate` and `endDate` describe that occurrence, while the `id` and the `recurrence` rule are the same for all occurrences of the same series. `getEventById(...)`, in contrast, always returns the series itself with its original start date. **Targeting a single occurrence**: `updateEventById(...)` and `deleteEventById(...)` operate on the whole series by default. Pass the `startDate` of an occurrence as `instanceStartDate` to target that occurrence instead, and use `span` to choose the scope: - `EventSpan.ThisEvent` (default) applies the change to the given occurrence only. The rest of the series remains untouched. - `EventSpan.ThisAndFutureEvents` applies the change to the given occurrence and all following ones, while past occurrences remain untouched. **Reading a rule back**: the `recurrence` property of an event is both written and read. A rule that uses parts which are not supported by the plugin — for example a rule created in another app — is read back as the closest supported subset, so `frequency` and `interval` are always correct even if a more exotic part is dropped. **All-day events and time zones**: an event with `allDay: true` has no time of day. Its `startDate` and `endDate` are therefore interpreted and returned as **midnight UTC** of the respective calendar day on both platforms, independent of the time zone of the device. The `endDate` is **exclusive**: it is midnight UTC of the day after the last day of the event, so a one-day all-day event on `2026-09-01` has `startDate = Date.UTC(2026, 8, 1)` and `endDate = Date.UTC(2026, 8, 2)`. An `endDate` that is missing or not on a midnight boundary is normalized: it is floored to midnight UTC of its day and raised to at least one day after `startDate`, so the event always spans at least one full day. Build these timestamps in UTC, for example with `Date.UTC(2026, 8, 1)`, and format them in UTC as well — using the local time zone instead is what makes an all-day event appear on the wrong day. For events that are not all-day, the `timezone` property defines the time zone in which the event takes place and defaults to the time zone of the device. ## FAQ ### How is this plugin different from other similar plugins? We focused on correctness instead of a long feature list. Every runtime failure rejects with a documented error code, so your app can react to a missing event or a read-only calendar programmatically. Recurrence rules can be read back, not just written, so a recurring event can round-trip through your app without losing information. Single occurrences of a recurring event can be updated and deleted, including all future occurrences. All-day events and time zones follow one documented contract on both platforms, and behavior that only one platform can provide is documented as such instead of being silently faked. On top of that, the plugin is covered by unit tests, is built from the ground up by the Capawesome Team, and comes with priority support. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Contacts](https://capawesome.io/docs/sdks/capacitor/contacts/): Read and write device contacts, for example to invite them to an event. - [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/): Let the user pick the date and time of an event natively. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/calendar/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/calendar/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/calendar/LICENSE). # Capacitor Clipboard Plugin Capacitor plugin to read from and write to the system clipboard. ## Features - 📋 **Read & Write**: Read from and write to the system clipboard. - 🖼️ **Images**: Copy and paste real images, not just data URLs as plain text. - 📝 **Rich Text**: Write HTML content with a plain-text fallback. - 🔗 **URLs**: Copy URLs as native URL clipboard items. - 🌐 **Cross-platform**: Works on Android, iOS, and the web. - 🔒 **App Store safe**: Uses only official platform APIs. - 🤝 **Compatibility**: Works alongside the [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/) and [Text Interaction](https://capawesome.io/docs/sdks/capacitor/text-interaction/) 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! ## Use Cases The Clipboard plugin is typically used whenever an app lets users copy or paste content, for example: - **Copy to clipboard buttons**: Let users copy coupon codes, invite links, or wallet addresses with one tap. - **Paste support**: Read text, URLs, or images from the clipboard into your app, for example into a form or editor. - **Sharing images**: Copy a generated image (such as a QR code or chart) so users can paste it into other apps. - **Rich text workflows**: Copy formatted HTML content with a plain-text fallback for apps that cannot handle HTML. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-clipboard` 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-clipboard npx cap sync ``` ### Android No additional configuration is required for this plugin. ### iOS No additional configuration is required for this plugin. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to write text, HTML, images, and URLs to the clipboard, and how to read its current content. ### Write text to the clipboard Copy plain text to the system clipboard: ``` import { Clipboard } from '@capawesome/capacitor-clipboard'; const writeText = async () => { await Clipboard.write({ text: 'Hello World' }); }; ``` ### Write HTML with a plain-text fallback Copy HTML content and combine it with `text` to provide a plain-text fallback for apps that cannot handle HTML content: ``` import { Clipboard } from '@capawesome/capacitor-clipboard'; const writeHtml = async () => { await Clipboard.write({ html: 'Hello World', text: 'Hello World', }); }; ``` ### Write an image to the clipboard Copy a real image (not just a data URL as plain text) by passing a Base64-encoded data URL: ``` import { Clipboard } from '@capawesome/capacitor-clipboard'; const writeImage = async () => { await Clipboard.write({ image: 'data:image/png;base64,iVBORw0KGgo...', }); }; ``` ### Write a URL to the clipboard Copy a URL as a native URL clipboard item: ``` import { Clipboard } from '@capawesome/capacitor-clipboard'; const writeUrl = async () => { await Clipboard.write({ url: 'https://capawesome.io' }); }; ``` ### Read the clipboard content Read the current content of the system clipboard. The result contains the content type (text, HTML, image, or URL) and the value. Note that on Android, reading the clipboard is only possible while the app is in the foreground, and on iOS, a system paste notification is displayed: ``` import { Clipboard } from '@capawesome/capacitor-clipboard'; const read = async () => { const { type, value } = await Clipboard.read(); console.log('Type:', type, 'Value:', value); }; ``` ## API - [`read()`](#read) - [`write(...)`](#write) - [Interfaces](#interfaces) - [Enums](#enums) ### read() ``` read() => Promise ``` Read the current content of the system clipboard. On Android, reading the clipboard is only possible while the app is in the foreground. On iOS, reading the clipboard displays a system paste notification. This is expected behavior and cannot be suppressed. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### write(...) ``` write(options: WriteOptions) => Promise ``` Write content to the system clipboard. Exactly one of `text`, `html`, `image` or `url` must be provided. The `html` property may additionally be combined with `text` to provide a plain-text fallback. | Param | Type | | ------------- | -------------- | | **`options`** | `WriteOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ReadResult | Prop | Type | Description | Since | | ----------- | ---------------------- | ----------------------------------------------------------------------------------------------- | ----- | | **`type`** | `ClipboardContentType` | The type of the content that was read from the clipboard. | 0.1.0 | | **`value`** | `string` | The content that was read from the clipboard. Images are returned as a Base64-encoded data URL. | 0.1.0 | #### WriteOptions | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`html`** | `string` | The HTML content to write to the clipboard. Combine this with `text` to provide a plain-text fallback for apps that cannot handle HTML content. | 0.1.0 | | **`image`** | `string` | The image to write to the clipboard as a Base64-encoded data URL. | 0.1.0 | | **`label`** | `string` | The label used to describe the clipboard content. Only available on Android. | 0.1.0 | | **`text`** | `string` | The plain text to write to the clipboard. | 0.1.0 | | **`url`** | `string` | The URL to write to the clipboard. | 0.1.0 | ### Enums #### ClipboardContentType | Members | Value | Description | Since | | ----------- | --------- | --------------------------------------------------------------- | ----- | | **`Html`** | `'HTML'` | The content is HTML. | 0.1.0 | | **`Image`** | `'IMAGE'` | The content is an image, returned as a Base64-encoded data URL. | 0.1.0 | | **`Text`** | `'TEXT'` | The content is plain text. | 0.1.0 | | **`Url`** | `'URL'` | The content is a URL. | 0.1.0 | ## Platform Behavior ### Android - On Android 10 (API level 29) and later, apps can only read the clipboard while they are in the foreground and have input focus. The `read(...)` method rejects otherwise. - On Android 12 (API level 31) and later, the system shows a toast message when an app reads the clipboard content that was written by another app. This is expected behavior and cannot be suppressed. ### iOS - On iOS 14 and later, the system shows a paste notification banner every time the clipboard is read. This is expected behavior and cannot be suppressed. ## Migrating from `@capacitor/clipboard` This plugin is a drop-in alternative to the official [`@capacitor/clipboard`](https://github.com/ionic-team/capacitor-plugins/tree/main/clipboard) plugin with real image support on Android and HTML support on all platforms. The following differences apply: | `@capacitor/clipboard` | `@capawesome/capacitor-clipboard` | | -------------------------------------------------- | --------------------------------------------------------- | | `write({ string: 'Hello' })` | `write({ text: 'Hello' })` | | `write({ image: dataUrl })` (text-only on Android) | `write({ image: dataUrl })` (real image on all platforms) | | — | `write({ html: 'Hello', text: 'Hello' })` | | `read() → { value, type: 'text/plain' }` | `read() → { value, type: 'TEXT' }` | ## FAQ ### How is this plugin different from other similar plugins? It supports far more than plain text — copy and paste images, HTML with a plain-text fallback, and native URL items, with reliable real image handling on Android, iOS, and the web. The API is fully typed and actively maintained against the latest OS and framework versions, and it uses only official platform APIs so it stays safe for App Store and Google Play submissions. If you only ever copy plain text, a simpler setup is perfectly fine; if you need rich content and consistent behavior across every platform, this plugin is designed for exactly that. ### Why is a notification shown when my app reads the clipboard? On iOS 14 and later, the system shows a paste notification banner every time the clipboard is read. On Android 12 (API level 31) and later, the system shows a toast message when an app reads clipboard content that was written by another app. Both are expected platform behavior and cannot be suppressed. ### Why does reading the clipboard fail on Android? On Android 10 (API level 29) and later, apps can only read the clipboard while they are in the foreground and have input focus. The `read()` method rejects otherwise, so make sure to only call it in response to a user interaction while the app is visible. ### Can I write multiple content types at once? No, exactly one of `text`, `html`, `image` or `url` must be provided when calling `write(...)`. The only exception is that `html` may additionally be combined with `text` to provide a plain-text fallback for apps that cannot handle HTML content. ### How are images handled by this plugin? Images are written to the clipboard as real images, not just data URLs as plain text, by passing a Base64-encoded data URL to `write(...)`. When reading, images are also returned as a Base64-encoded data URL. See the [usage examples](#write-an-image-to-the-clipboard) above. ### How is this plugin different from the official `@capacitor/clipboard` plugin? This plugin is a drop-in alternative to the official `@capacitor/clipboard` plugin with real image support on Android and HTML support on all platforms. Only a few method options differ, see the [migration table](#migrating-from-capacitorclipboard) above. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/): Receive content such as text, links, and files from other apps. - [Text Interaction](https://capawesome.io/docs/sdks/capacitor/text-interaction/): Enable and disable text interaction (selection, magnifier, callout menu) in the app's WebView. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/clipboard/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/clipboard/LICENSE). # Capacitor Cloudinary Plugin 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) - 🤝 **Compatibility**: Works alongside the [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/), [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) and [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) plugins. ## Use Cases The Cloudinary plugin is typically used whenever an app needs to exchange media files with Cloudinary, for example: - **User-generated content**: Upload photos and videos that users select from their device directly to your Cloudinary account. - **Large video uploads**: Upload large files in chunks via the native Cloudinary SDKs to avoid out-of-memory crashes in the WebView. - **Profile pictures**: Upload an image with a unique public identifier so it can be referenced and replaced later. - **Media downloads**: Download images, videos, or raw files from Cloudinary to the device for further processing. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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. ## Usage The following examples show how to initialize the plugin, upload a file to Cloudinary, and download a file from Cloudinary. ### Initialize the plugin Initialize the plugin with the cloud name of your app, which you can find in the Cloudinary Management Console. This method must be called once before all other methods: ``` import { Cloudinary } from '@capawesome/capacitor-cloudinary'; const initialize = async () => { await Cloudinary.initialize({ cloudName: 'my_cloud_name' }); }; ``` ### Upload a file to Cloudinary Upload an image, video, or raw file to Cloudinary. On Android and iOS, pass the path of the file to upload; on the Web, pass a `Blob` instead. Note that currently only unsigned uploads are supported, so you need an upload preset: ``` import { Cloudinary, ResourceType } from '@capawesome/capacitor-cloudinary'; 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', }); }; ``` ### Download a file from Cloudinary Download a resource by its URL. On Android and iOS, the result contains the path where the file was stored on the device; on the Web, it contains a `Blob`: ``` import { Cloudinary } from '@capawesome/capacitor-cloudinary'; 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). ## FAQ ### Do I need to call `initialize` before using the plugin? Yes, the `initialize(...)` method must be called once before all other methods. It only requires the cloud name of your app, which you can find in the Cloudinary Management Console. See the [usage example](#initialize-the-plugin) above. ### Are signed uploads supported? No, currently only unsigned uploads are supported. This means you need to create an unsigned upload preset in your Cloudinary account and pass its name via the `uploadPreset` option when calling `uploadResource(...)`. ### Where are downloaded files stored? On Android, the file is downloaded to the `Downloads` directory. On iOS, the file is downloaded to the temporary directory, so it is recommended to copy it to a permanent location for further processing. On the Web, the downloaded resource is returned as a `Blob` instead of a path. ### How do I upload a file on the Web? On the Web, pass the file as a `Blob` via the `blob` option of `uploadResource(...)`. The `path` option is only available on Android and iOS, where it points to a file on the device's filesystem. ### What do I need to configure on Android? You need to add the `DOWNLOAD_WITHOUT_NOTIFICATION` permission and the `DownloadBroadcastReceiver` receiver to your `AndroidManifest.xml`, as described in the [Installation](#installation) section. No additional configuration is required on iOS or the Web. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/): Compress images before uploading them. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open a downloaded file with the default application. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Let the user select a file, image, or video to upload to Cloudinary. ## 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/). ## 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") # Capacitor Compass Plugin Capacitor plugin for reading the device compass heading. ## Features - 🧭 **Heading**: Read the current compass heading on demand. - 🔄 **Live updates**: Listen for continuous heading changes. - 🌍 **True north**: Read the true (geographic) north heading on iOS. - 🤝 **Compatibility**: Works alongside the [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/), [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/) and [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/) 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! ## Use Cases The Compass plugin is typically used whenever an app needs to know which direction the device is pointing, for example: - **Compass apps**: Build a classic compass UI that rotates with live heading updates. - **Navigation**: Rotate a map or show the direction the user is currently facing. - **Outdoor activities**: Guide hikers or geocachers towards a target bearing. - **Points of interest**: Point the user towards a fixed geographic direction, such as a landmark or a prayer direction. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How heading data fits into navigation and wayfinding features. ## 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-compass` 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-compass npx cap sync ``` This plugin is only available on **Android** and **iOS**. On the Web, all methods reject as unimplemented. ### iOS #### Privacy Descriptions The magnetic heading is available without any permission. To also read the true (geographic) north heading, location services must be enabled and the `NSLocationWhenInUseUsageDescription` key must be added to the `Info.plist` file of your app. ``` NSLocationWhenInUseUsageDescription Your location is used to determine the true (geographic) north heading. ``` If the key is missing or location permission is not granted, the `trueHeading` value is `null`. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check if the compass sensor is available, read the current heading, and listen for heading changes. ### Check if the compass sensor is available Before reading the heading, you can check whether the device has a compass sensor. Only available on Android and iOS: ``` import { Compass } from '@capawesome/capacitor-compass'; const isAvailable = async () => { const { available } = await Compass.isAvailable(); return available; }; ``` ### Read the current heading Get the most recent heading reading from the device's compass sensor. The result contains the magnetic heading, the true (geographic) heading if available, and the accuracy. Only available on Android and iOS: ``` import { Compass } from '@capawesome/capacitor-compass'; const getHeading = async () => { const heading = await Compass.getHeading(); return heading; }; ``` ### Listen for heading changes Call `startHeadingUpdates()` to start emitting `headingChange` events and add a listener to receive continuous heading updates, for example to rotate a compass UI. Stop the updates when you no longer need them. Only available on Android and iOS: ``` import { Compass } from '@capawesome/capacitor-compass'; const startHeadingUpdates = async () => { await Compass.startHeadingUpdates(); }; const addHeadingChangeListener = async () => { await Compass.addListener('headingChange', heading => { console.log(heading); }); }; const stopHeadingUpdates = async () => { await Compass.stopHeadingUpdates(); }; const removeAllListeners = async () => { await Compass.removeAllListeners(); }; ``` ## API - [`getHeading()`](#getheading) - [`isAvailable()`](#isavailable) - [`startHeadingUpdates()`](#startheadingupdates) - [`stopHeadingUpdates()`](#stopheadingupdates) - [`addListener('headingChange', ...)`](#addlistenerheadingchange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getHeading() ``` getHeading() => Promise ``` Get the current device heading. This method returns the most recent heading reading from the device's compass sensor. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the compass sensor is available on the device. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startHeadingUpdates() ``` startHeadingUpdates() => Promise ``` Start emitting `headingChange` events. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### stopHeadingUpdates() ``` stopHeadingUpdates() => Promise ``` Stop emitting `headingChange` events. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### addListener('headingChange', ...) ``` addListener(eventName: 'headingChange', listenerFunc: (event: HeadingChangeEvent) => void) => Promise ``` Add a listener for heading changes. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------- | | **`eventName`** | `'headingChange'` | | **`listenerFunc`** | `(event: Heading) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### Heading | Prop | Type | Description | Since | | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`accuracy`** | \`number | null\` | The maximum deviation between the reported heading and the true heading in degrees. A negative value or `null` indicates that the accuracy is invalid or unknown. | | **`magneticHeading`** | `number` | The heading relative to magnetic north in degrees. The value ranges from `0` to `360`, where `0` means the device is pointing towards magnetic north. | 0.1.0 | | **`trueHeading`** | \`number | null\` | The heading relative to true (geographic) north in degrees. The value ranges from `0` to `360`, where `0` means the device is pointing towards true north. Returns `null` if the true heading cannot be determined. On Android, this value is always `null`. On iOS, this value requires location services to be enabled and the `NSLocationWhenInUseUsageDescription` key to be set. Otherwise, it is `null`. | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------ | ----- | | **`available`** | `boolean` | Whether the compass sensor is available on the device. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GetHeadingResult `Heading` #### HeadingChangeEvent `Heading` ## FAQ ### How is this plugin different from other similar plugins? It reads the device compass heading both on demand and as a continuous stream of updates, returning the magnetic heading, the accuracy, and — on iOS with location services — the true geographic heading. An `isAvailable()` check lets you confirm the sensor is present before you rely on it, and the whole surface is fully typed. It supports CocoaPods and Swift Package Manager on iOS and is actively maintained against the latest Capacitor version, giving you consistent heading data across Android and iOS. ### What is the difference between the magnetic heading and the true heading? The `magneticHeading` value is the heading relative to magnetic north, while the `trueHeading` value is the heading relative to true (geographic) north. Both range from `0` to `360` degrees. The magnetic heading is always available, whereas the true heading can only be determined on iOS and may be `null`. ### Why is the `trueHeading` value always `null`? On Android, the true heading is not supported and the value is always `null`. On iOS, reading the true heading requires location services to be enabled and the `NSLocationWhenInUseUsageDescription` key to be added to your app's `Info.plist` file, as described in the [Installation](#installation) section. If the key is missing or location permission is not granted, the value is `null`. ### Does this plugin work on the Web? No, this plugin is only available on Android and iOS. On the Web, all methods reject as unimplemented. You can use the `isAvailable()` method to check whether the compass sensor is available on the current device. ### Do I need any permissions to read the compass heading? The magnetic heading is available without any permission on both Android and iOS. Only if you also want to read the true (geographic) north heading on iOS, location services must be enabled and the `NSLocationWhenInUseUsageDescription` key must be present in your `Info.plist` file. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/): Capture the acceleration force along the x, y, and z axes. - [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/): Obtain the static air pressure measured in hectopascals. - [Gyroscope](https://capawesome.io/docs/sdks/capacitor/gyroscope/): Read the device's gyroscope sensor. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/compass/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/compass/LICENSE). # Capacitor Contacts Plugin 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 The Capacitor Contacts plugin is one of the most complete contact management solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [Mail Composer](https://capawesome.io/docs/sdks/capacitor/mail-composer/), [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/) and [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/) 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! ## Use Cases The Contacts plugin is typically used whenever an app needs to work with the device's address book, for example: - **Contact selection**: Let users pick one or more contacts from the device, for example to invite friends or share content. - **CRM and business apps**: Create, update, and delete contacts directly from your app, including photos, email addresses, phone numbers, and postal addresses. - **Contact syncing**: Read device contacts with pagination and keep them in sync with your backend. - **Contact organization**: Manage contact groups on iOS or add contacts to specific accounts on Android. - **Native contact forms**: Display, create, and update contacts in native modals without building your own UI. ## 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/) - [Google Play Contacts Policy 2027 for Capacitor](https://capawesome.io/blog/capacitor-google-play-contacts-policy-2027/) ## 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: ``` ``` `pickContacts(...)` requires the `READ_CONTACTS` permission below Android 17, because the contact picker grants access to the picked contact URI only, which does not expose any contact details. Pass the `property` option instead to let the user select a single phone number, email address or postal address, which never requires a permission. Apps that only need contact selection should use that option and omit `READ_CONTACTS`, as required by [Google Play's Contacts Permissions policy](https://support.google.com/googleplay/android-developer/answer/16926792). #### 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 The following examples show how to create, update, delete, pick, retrieve, and display contacts, manage contact groups and accounts, and check availability and permissions. ### Create a contact Create a new contact on the device with properties such as name, birthday, email addresses, phone numbers, and postal addresses. Only available on Android and iOS: ``` 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 } ] } }); }; ``` ### Update or delete a contact Update only specific fields of an existing contact without affecting others: missing properties are preserved, `null` values delete a field. You can also delete a contact by its ID. Only available on Android and iOS: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; 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 }); }; ``` ### Pick contacts Open the native contact picker and let the user select one or more contacts. Use the `fields` option to specify which contact properties should be returned and the `multiple` option to let the user select more than one contact: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const pickContacts = async () => { const { contacts } = await Contacts.pickContacts({ fields: [ 'id', 'givenName', 'familyName', 'emailAddresses', 'phoneNumbers', 'postalAddresses' ], multiple: true }); return contacts; }; ``` ### Retrieve contacts Fetch a single contact by its ID or retrieve a list of contacts. Use the `limit` and `offset` options to paginate through large address books and `countContacts()` to get the total number of contacts. Only available on Android and iOS: ``` import { Contacts } from '@capawesome-team/capacitor-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 countContacts = async () => { const { total } = await Contacts.countContacts(); return total; }; ``` ### Display contacts in native modals Show or edit a contact in the native contact modal instead of building your own UI. Only available on Android and iOS: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const displayContactById = async (id: string) => { await Contacts.displayContactById({ id }); }; const displayUpdateContactById = async (id: string) => { await Contacts.displayUpdateContactById({ id }); }; ``` ### Manage contact groups Create, retrieve, and delete contact groups. Only available on iOS: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const createGroup = async () => { return Contacts.createGroup({ group: { name: 'My Group' } }); }; const deleteGroupById = async (id: string) => { await Contacts.deleteGroupById({ id }); }; const getGroupById = async (id: string) => { const { group } = await Contacts.getGroupById({ id }); return group; }; const getGroups = async () => { const { groups } = await Contacts.getGroups(); return groups; }; ``` ### Retrieve accounts Retrieve the accounts that contacts can be added to. Only available on Android: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const getAccounts = async () => { const { accounts } = await Contacts.getAccounts(); return accounts; }; ``` ### Check availability Check whether the contacts API is supported and available on the current device before using it: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; const isAvailable = async () => { const { isAvailable } = await Contacts.isAvailable(); return isAvailable; }; const isSupported = async () => { const { isSupported } = await Contacts.isSupported(); return isSupported; }; ``` ### Check and request permissions Check and request permissions to access the device contacts. Only available on Android and iOS: ``` import { Contacts } from '@capawesome-team/capacitor-contacts'; 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. On Android, reading the selected contact requires the `READ_CONTACTS` permission below Android 17. Set the `property` option to select a single contact property instead, which never requires a permission. | 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 | | **`displayName`** | `string` | The formatted name of the contact as displayed by the device. This property is read-only and is ignored when creating or updating a contact. Only available on Android and iOS. | 8.1.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', 'displayName', '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. On Android 17 and newer, the contact picker only lists contacts that have at least one of the requested fields and never returns the `account` and `note` fields. Only available on Android and iOS. | `['birthday', 'displayName', 'emailAddresses', 'familyName', 'givenName', 'id', 'jobTitle', 'middleName', 'namePrefix', 'nameSuffix', 'organizationName', 'phoneNumbers', 'postalAddresses', 'urlAddresses']` | 7.4.0 | | **`multiple`** | `boolean` | Whether to allow selecting multiple contacts. Ignored if the `property` option is set. On Android, only supported on Android 17 and newer, where the user can select at most 100 contacts. | `false` | 7.0.0 | | **`property`** | `ContactProperty` | The contact property to select. If set, the user selects a single property instead of a whole contact. The result then only contains the `id`, the `displayName` and the selected property, but no permissions are required to read it. Only available on Android and iOS. | | 8.1.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 | #### ContactProperty | Members | Value | Description | Since | | ------------------- | ----------------- | --------------------------------------- | ----- | | **`EmailAddress`** | `'emailAddress'` | A single email address of the contact. | 8.1.0 | | **`PhoneNumber`** | `'phoneNumber'` | A single phone number of the contact. | 8.1.0 | | **`PostalAddress`** | `'postalAddress'` | A single postal address of the contact. | 8.1.0 | ## FAQ ### Do I need any permissions to access contacts? It depends. `displayCreateContact(...)` never needs a permission, and neither does `pickContacts(...)` on iOS. On Android, `pickContacts(...)` needs the `READ_CONTACTS` permission below Android 17, unless you pass the `property` option to select a single contact property instead. Every other method needs access to the address book: on Android, add the `READ_CONTACTS` and/or `WRITE_CONTACTS` permissions to your `AndroidManifest.xml` file (see [Installation](#installation)); on iOS, add the `NSContactsUsageDescription` key to your `Info.plist` file. At runtime, use `checkPermissions()` and `requestPermissions()` to manage the permission state, and `openSettings()` to send the user to the native app settings if the permission was denied. ### How do I update only specific fields of a contact? The `updateContactById(...)` method supports partial updates. Properties that are missing from the contact object are preserved, while properties explicitly set to `null` are deleted from the contact. See the [usage example](#update-or-delete-a-contact) above. ### Are contact groups and accounts available on all platforms? No. Contact groups (`createGroup(...)`, `getGroups()`, `getGroupById(...)`, and `deleteGroupById(...)`) are only available on iOS. Accounts (`getAccounts()`) are only available on Android, where you can add contacts to a specific account. ### How do I handle large address books without performance issues? Use the `limit` and `offset` options of `getContacts(...)` to paginate through the contacts instead of loading all of them at once. Additionally, only request the contact properties you actually need via the `fields` option, and use `countContacts()` to determine the total number of contacts. ### How is this plugin different from the Capacitor Community Contacts plugin? This plugin offers advanced features such as partial updates, contact groups on iOS, accounts on Android, pagination, native modals, and contact photos, and comes with priority support from the Capawesome Team. You can find a detailed comparison in the guide [Alternative to the Capacitor Community Contacts plugin](https://capawesome.io/blog/alternative-to-capacitor-community-contacts-plugin/). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Mail Composer](https://capawesome.io/docs/sdks/capacitor/mail-composer/): Open the native email composer. - [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/): Open the native phone dialer prefilled with a phone number. - [SIM](https://capawesome.io/docs/sdks/capacitor/sim/): Read SIM card and carrier information. - [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/): Open the native SMS composer prefilled with recipients and a message body. ## 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/). ## 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). # Capacitor Crisp Plugin Unofficial Capacitor plugin for [Crisp](https://crisp.chat/).[1](#fn:1) ## Features The Capacitor Crisp plugin is a comprehensive integration of the Crisp chat SDKs for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 💬 **Chatbox**: Open the Crisp chatbox with a single method call. - 👤 **User Identity**: Set the user's email, nickname, phone number, avatar, and company. - 🔐 **Identity Verification**: Verify the user's identity with an HMAC-SHA256 email signature. - 🧩 **Session Data**: Attach custom data, segments, and events to the session. - 📚 **Helpdesk**: Open the helpdesk search or a specific helpdesk article. - 🔔 **Push Notifications**: Interoperate with Firebase Cloud Messaging (Android) and APNs (iOS). - 📡 **Typed Events**: Listen for chat, message, and session events with typed listeners. - 🌐 **Typed Web SDK**: Uses the official `crisp-sdk-web` package on the web. - 🤝 **Compatibility**: Looking for a different chat SDK? Check out the [Intercom](https://capawesome.io/docs/sdks/capacitor/intercom/) plugin. - 📦 **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! ## Use Cases The Crisp plugin is typically used wherever you want to offer live chat and customer support inside your app, for example: - **Customer support**: Let users chat with your support team directly from within the app. - **User identification**: Attach the signed-in user's email, name, and company to every conversation. - **Self-service**: Point users to helpdesk articles without leaving the app. - **Contextual data**: Enrich conversations with session data, segments, and events based on user behavior. - **Re-engagement**: Notify users about new operator replies via push notifications. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-crisp` 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-crisp crisp-sdk-web npx cap sync ``` This plugin requires a [Crisp](https://crisp.chat/) account. You can find your **Website ID** in the Crisp dashboard under **Settings → Website Settings → Setup & Integrations**. ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$crispSdkVersion` version of `im.crisp:crisp-sdk` (default: `2.0.22`) - `$firebaseMessagingVersion` version of `com.google.firebase:firebase-messaging` (default: `24.1.1`) #### Minimum SDK Version The Crisp Android SDK requires a minimum SDK version of `24`. Make sure the `minSdkVersion` in your app's `variables.gradle` file is set to `24` or higher. #### Multidex The Crisp Android SDK requires [multidex](https://developer.android.com/build/multidex) to be enabled. Enable it in your app's `android/app/build.gradle` file: ``` android { defaultConfig { multiDexEnabled true } } ``` #### FileProvider The Crisp Android SDK ships its own `FileProvider` (used for chat attachments) with the authority `${applicationId}.im.crisp.client.uploadfileprovider`. This authority is merged automatically and does not conflict with your app's own `FileProvider`, so no additional configuration is required. #### Push Notifications Push notifications on Android are delivered through [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging). There are two ways to integrate them: **Option A — Crisp's own notification service (zero-config)** If your app does not use its own `FirebaseMessagingService`, add the following service to the `application` tag of your `android/app/src/main/AndroidManifest.xml` file: ``` ``` **Option B — Share an existing `FirebaseMessagingService`** If your app already receives push notifications (e.g. via [`@capacitor-firebase/messaging`](https://github.com/capawesome-team/capacitor-firebase)), forward incoming messages to Crisp from your JavaScript code: ``` import { FirebaseMessaging } from '@capacitor-firebase/messaging'; import { Crisp } from '@capawesome/capacitor-crisp'; FirebaseMessaging.addListener('notificationReceived', async ({ notification }) => { const data = notification.data ?? {}; const { crisp } = await Crisp.isCrispPushNotification({ data }); if (crisp) { await Crisp.handlePushNotification({ data }); } }); ``` You can enable or disable Crisp push notifications at runtime with `setNotificationsEnabled(...)`. ### iOS The Crisp iOS SDK can be integrated via Swift Package Manager (recommended) or CocoaPods. > [!NOTE] Crisp will stop publishing new CocoaPods releases in September 2026. We recommend using Swift Package Manager. If your app still uses CocoaPods, plan to migrate to Swift Package Manager before then. #### Info.plist The Crisp chatbox lets users send image and file attachments. Add the following keys to your `ios/App/App/Info.plist` file so users can attach photos: ``` NSCameraUsageDescription The app needs access to your camera to send photos in the chat. NSPhotoLibraryAddUsageDescription The app needs access to your photo library to send photos in the chat. ``` #### Push Notifications Push notifications on iOS are delivered through [Apple Push Notification service (APNs)](https://developer.apple.com/documentation/usernotifications): 1. Enable the **Push Notifications** capability for your app target in Xcode. 1. Register for remote notifications (e.g. via [`@capacitor-firebase/messaging`](https://github.com/capawesome-team/capacitor-firebase) or `@capacitor/push-notifications`). The plugin automatically forwards the APNs device token to Crisp once your app has registered for remote notifications, so no additional token handling is required. Use `setShouldPromptForNotificationPermission(...)` to control whether the Crisp SDK prompts the user for the notification permission. ### Web The web implementation loads the Crisp chatbox from Crisp's CDN at runtime. This requires an active network connection. If your app enforces a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), make sure to allow the Crisp domains (e.g. `client.crisp.chat`, `*.crisp.chat`, and `wss://*.relay.crisp.chat`). ## Configuration No configuration required for this plugin. ## Usage The following examples show how to use the plugin. ### Configure the plugin Call `configure(...)` once before all other methods: ``` import { Crisp } from '@capawesome/capacitor-crisp'; const configure = async () => { await Crisp.configure({ websiteId: 'YOUR_WEBSITE_ID', }); }; ``` ### Open the chat Open the Crisp chatbox: ``` import { Crisp } from '@capawesome/capacitor-crisp'; const openChat = async () => { await Crisp.openChat(); }; ``` ### Set the user Set the information of the current user, optionally with identity verification: ``` import { Crisp } from '@capawesome/capacitor-crisp'; const setUser = async () => { await Crisp.setUser({ email: 'jane.doe@example.com', emailSignature: 'YOUR_HMAC_SIGNATURE', nickname: 'Jane Doe', }); }; ``` ### Attach session data Attach custom data, segments, and events to the current session: ``` import { Crisp, SessionEventColor } from '@capawesome/capacitor-crisp'; const attachSessionData = async () => { await Crisp.setSessionString({ key: 'plan', value: 'pro' }); await Crisp.setSessionSegment({ segment: 'checkout' }); await Crisp.pushSessionEvent({ name: 'purchase', color: SessionEventColor.Green }); }; ``` ### Listen for events Listen for chat, message, and session events: ``` import { Crisp } from '@capawesome/capacitor-crisp'; const addListeners = async () => { await Crisp.addListener('sessionLoaded', ({ sessionId }) => { console.log('Session loaded:', sessionId); }); await Crisp.addListener('messageReceived', ({ content }) => { console.log('Message received:', content); }); }; ``` ## API - [`configure(...)`](#configure) - [`handlePushNotification(...)`](#handlepushnotification) - [`isCrispPushNotification(...)`](#iscrisppushnotification) - [`openChat()`](#openchat) - [`openHelpdeskArticle(...)`](#openhelpdeskarticle) - [`pushSessionEvent(...)`](#pushsessionevent) - [`resetSession()`](#resetsession) - [`searchHelpdesk()`](#searchhelpdesk) - [`setCompany(...)`](#setcompany) - [`setLocale(...)`](#setlocale) - [`setNotificationsEnabled(...)`](#setnotificationsenabled) - [`setSessionBool(...)`](#setsessionbool) - [`setSessionInt(...)`](#setsessionint) - [`setSessionSegment(...)`](#setsessionsegment) - [`setSessionString(...)`](#setsessionstring) - [`setShouldPromptForNotificationPermission(...)`](#setshouldpromptfornotificationpermission) - [`setTokenId(...)`](#settokenid) - [`setUser(...)`](#setuser) - [`addListener('chatClosed', ...)`](#addlistenerchatclosed-) - [`addListener('chatOpened', ...)`](#addlistenerchatopened-) - [`addListener('messageReceived', ...)`](#addlistenermessagereceived-) - [`addListener('messageSent', ...)`](#addlistenermessagesent-) - [`addListener('sessionLoaded', ...)`](#addlistenersessionloaded-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### configure(...) ``` configure(options: ConfigureOptions) => Promise ``` Configure the plugin with your Crisp website ID. This method must be called before any other method. | Param | Type | | ------------- | ------------------ | | **`options`** | `ConfigureOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### handlePushNotification(...) ``` handlePushNotification(options: HandlePushNotificationOptions) => Promise ``` Handle an incoming push notification that belongs to Crisp. Use `isCrispPushNotification(...)` to check whether the notification belongs to Crisp before calling this method. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `HandlePushNotificationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isCrispPushNotification(...) ``` isCrispPushNotification(options: IsCrispPushNotificationOptions) => Promise ``` Check whether an incoming push notification belongs to Crisp. Only available on Android. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `IsCrispPushNotificationOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### openChat() ``` openChat() => Promise ``` Open the chat. **Since:** 0.1.0 ______________________________________________________________________ ### openHelpdeskArticle(...) ``` openHelpdeskArticle(options: OpenHelpdeskArticleOptions) => Promise ``` Open a specific helpdesk article. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `OpenHelpdeskArticleOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### pushSessionEvent(...) ``` pushSessionEvent(options: PushSessionEventOptions) => Promise ``` Push a session event to the current session. | Param | Type | | ------------- | ------------------------- | | **`options`** | `PushSessionEventOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### resetSession() ``` resetSession() => Promise ``` Reset the current session. This clears the current session and starts a new one on the next interaction. **Since:** 0.1.0 ______________________________________________________________________ ### searchHelpdesk() ``` searchHelpdesk() => Promise ``` Open the helpdesk with the search view. **Since:** 0.1.0 ______________________________________________________________________ ### setCompany(...) ``` setCompany(options: SetCompanyOptions) => Promise ``` Set the company of the current user. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetCompanyOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setLocale(...) ``` setLocale(options: SetLocaleOptions) => Promise ``` Set the locale of the chatbox. On Android and iOS, the chatbox follows the language of the app. Only available on Web. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetLocaleOptions` | **Since:** 0.1.2 ______________________________________________________________________ ### setNotificationsEnabled(...) ``` setNotificationsEnabled(options: SetNotificationsEnabledOptions) => Promise ``` Enable or disable push notifications. Only available on Android. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SetNotificationsEnabledOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setSessionBool(...) ``` setSessionBool(options: SetSessionBoolOptions) => Promise ``` Set a boolean value for the given key in the current session. | Param | Type | | ------------- | ----------------------- | | **`options`** | `SetSessionBoolOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setSessionInt(...) ``` setSessionInt(options: SetSessionIntOptions) => Promise ``` Set an integer value for the given key in the current session. | Param | Type | | ------------- | ---------------------- | | **`options`** | `SetSessionIntOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setSessionSegment(...) ``` setSessionSegment(options: SetSessionSegmentOptions) => Promise ``` Set the segment of the current session. | Param | Type | | ------------- | -------------------------- | | **`options`** | `SetSessionSegmentOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setSessionString(...) ``` setSessionString(options: SetSessionStringOptions) => Promise ``` Set a string value for the given key in the current session. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SetSessionStringOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setShouldPromptForNotificationPermission(...) ``` setShouldPromptForNotificationPermission(options: SetShouldPromptForNotificationPermissionOptions) => Promise ``` Set whether the user should be prompted for the notification permission. Only available on iOS. | Param | Type | | ------------- | ------------------------------------------------- | | **`options`** | `SetShouldPromptForNotificationPermissionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setTokenId(...) ``` setTokenId(options: SetTokenIdOptions) => Promise ``` Set the token ID of the current user. The token ID is used to restore a session across devices and logins. You may also set it directly via `configure(...)`. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetTokenIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUser(...) ``` setUser(options: SetUserOptions) => Promise ``` Set the information of the current user. | Param | Type | | ------------- | ---------------- | | **`options`** | `SetUserOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addListener('chatClosed', ...) ``` addListener(eventName: 'chatClosed', listenerFunc: () => void) => Promise ``` Called when the chat is closed. | Param | Type | | ------------------ | -------------- | | **`eventName`** | `'chatClosed'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('chatOpened', ...) ``` addListener(eventName: 'chatOpened', listenerFunc: () => void) => Promise ``` Called when the chat is opened. | Param | Type | | ------------------ | -------------- | | **`eventName`** | `'chatOpened'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('messageReceived', ...) ``` addListener(eventName: 'messageReceived', listenerFunc: (event: MessageReceivedEvent) => void) => Promise ``` Called when a message is received from the operator. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'messageReceived'` | | **`listenerFunc`** | `(event: MessageReceivedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('messageSent', ...) ``` addListener(eventName: 'messageSent', listenerFunc: (event: MessageSentEvent) => void) => Promise ``` Called when a message is sent by the user. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'messageSent'` | | **`listenerFunc`** | `(event: MessageSentEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('sessionLoaded', ...) ``` addListener(eventName: 'sessionLoaded', listenerFunc: (event: SessionLoadedEvent) => void) => Promise ``` Called when the session is loaded. This is also the recommended way to get the session ID. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'sessionLoaded'` | | **`listenerFunc`** | `(event: SessionLoadedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ConfigureOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------- | ----- | | **`tokenId`** | `string` | The token ID of the current user. | 0.1.0 | | **`websiteId`** | `string` | The website ID of your Crisp website. | 0.1.0 | #### HandlePushNotificationOptions | Prop | Type | Description | Since | | ---------- | ------------------------- | ---------------------------------- | ----- | | **`data`** | `Record` | The data of the push notification. | 0.1.0 | #### IsCrispPushNotificationResult | Prop | Type | Description | Since | | ----------- | --------- | ----------------------------------------------- | ----- | | **`crisp`** | `boolean` | Whether the push notification belongs to Crisp. | 0.1.0 | #### IsCrispPushNotificationOptions | Prop | Type | Description | Since | | ---------- | ------------------------- | ---------------------------------- | ----- | | **`data`** | `Record` | The data of the push notification. | 0.1.0 | #### OpenHelpdeskArticleOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------- | ----- | | **`category`** | `string` | The category of the article. | 0.1.0 | | **`id`** | `string` | The ID (slug) of the article. | 0.1.0 | | **`locale`** | `string` | The locale of the article. | 0.1.0 | | **`title`** | `string` | The title of the article. | 0.1.0 | #### PushSessionEventOptions | Prop | Type | Description | Default | Since | | ----------- | ------------------- | ----------------------- | ------------------------ | ----- | | **`color`** | `SessionEventColor` | The color of the event. | `SessionEventColor.Blue` | 0.1.0 | | **`name`** | `string` | The name of the event. | | 0.1.0 | #### SetCompanyOptions | Prop | Type | Description | Since | | ----------------- | ----------------------- | ------------------------------------------------------ | ----- | | **`description`** | `string` | The description of the company. | 0.1.0 | | **`employment`** | `SetCompanyEmployment` | The employment information of the user at the company. | 0.1.0 | | **`geolocation`** | `SetCompanyGeolocation` | The geolocation of the company. | 0.1.0 | | **`name`** | `string` | The name of the company. | 0.1.0 | | **`url`** | `string` | The URL of the company. | 0.1.0 | #### SetCompanyEmployment | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------------- | ----- | | **`role`** | `string` | The role of the user at the company. | 0.1.0 | | **`title`** | `string` | The job title of the user at the company. | 0.1.0 | #### SetCompanyGeolocation | Prop | Type | Description | Since | | ------------- | -------- | --------------------------- | ----- | | **`city`** | `string` | The city of the company. | 0.1.0 | | **`country`** | `string` | The country of the company. | 0.1.0 | #### SetLocaleOptions | Prop | Type | Description | Since | | ------------ | -------- | ----------------------------------------------- | ----- | | **`locale`** | `string` | The locale of the chatbox as an ISO 639-1 code. | 0.1.2 | #### SetNotificationsEnabledOptions | Prop | Type | Description | Since | | ------------- | --------- | --------------------------------------------- | ----- | | **`enabled`** | `boolean` | Whether push notifications should be enabled. | 0.1.0 | #### SetSessionBoolOptions | Prop | Type | Description | Since | | ----------- | --------- | ------------------------------ | ----- | | **`key`** | `string` | The key of the session data. | 0.1.0 | | **`value`** | `boolean` | The value of the session data. | 0.1.0 | #### SetSessionIntOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------ | ----- | | **`key`** | `string` | The key of the session data. | 0.1.0 | | **`value`** | `number` | The value of the session data. | 0.1.0 | #### SetSessionSegmentOptions | Prop | Type | Description | Since | | ------------- | -------- | --------------------------- | ----- | | **`segment`** | `string` | The segment of the session. | 0.1.0 | #### SetSessionStringOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------ | ----- | | **`key`** | `string` | The key of the session data. | 0.1.0 | | **`value`** | `string` | The value of the session data. | 0.1.0 | #### SetShouldPromptForNotificationPermissionOptions | Prop | Type | Description | Since | | ------------- | --------- | -------------------------------------------------------------------- | ----- | | **`enabled`** | `boolean` | Whether the user should be prompted for the notification permission. | 0.1.0 | #### SetTokenIdOptions | Prop | Type | Description | Since | | ------------- | -------- | --------------------------------- | ----- | | **`tokenId`** | `string` | The token ID of the current user. | 0.1.0 | #### SetUserOptions | Prop | Type | Description | Since | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`avatarUrl`** | `string` | The URL of the user's avatar. | 0.1.0 | | **`email`** | `string` | The email address of the user. | 0.1.0 | | **`emailSignature`** | `string` | The HMAC-SHA256 signature of the user's email address for identity verification. The signature must be generated on your backend using your Crisp secret key. Never expose the secret key in your app. | 0.1.0 | | **`nickname`** | `string` | The nickname of the user. | 0.1.0 | | **`phone`** | `string` | The phone number of the user. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### MessageReceivedEvent | Prop | Type | Description | Since | | ------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | | **`content`** | \`string | null\` | The text content of the message. This is `null` if the message has no text content (e.g. a file or audio message). | #### MessageSentEvent | Prop | Type | Description | Since | | ------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------ | | **`content`** | \`string | null\` | The text content of the message. This is `null` if the message has no text content (e.g. a file or audio message). | #### SessionLoadedEvent | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------- | ----- | | **`sessionId`** | `string` | The ID of the loaded session. | 0.1.0 | ### Enums #### SessionEventColor | Members | Value | Since | | ------------ | ---------- | ----- | | **`Black`** | `'BLACK'` | 0.1.0 | | **`Blue`** | `'BLUE'` | 0.1.0 | | **`Brown`** | `'BROWN'` | 0.1.0 | | **`Green`** | `'GREEN'` | 0.1.0 | | **`Grey`** | `'GREY'` | 0.1.0 | | **`Orange`** | `'ORANGE'` | 0.1.0 | | **`Pink`** | `'PINK'` | 0.1.0 | | **`Purple`** | `'PURPLE'` | 0.1.0 | | **`Red`** | `'RED'` | 0.1.0 | | **`Yellow`** | `'YELLOW'` | 0.1.0 | ## Platform Support Not every Crisp SDK feature is available on all platforms. The following table lists the per-platform differences of the plugin's API: | Method | Android | iOS | Web | | ----------------------------------------------- | ------- | --- | --- | | `setSessionBool(...)` | ✅ | ✅ | ✅ | | `isCrispPushNotification(...)` | ✅ | ❌ | ❌ | | `handlePushNotification(...)` | ✅ | ❌ | ❌ | | `setNotificationsEnabled(...)` | ✅ | ❌ | ❌ | | `setShouldPromptForNotificationPermission(...)` | ❌ | ✅ | ❌ | | `setLocale(...)` | ❌ | ❌ | ✅ | On iOS, the Crisp SDK detects and handles its own push notifications internally once the APNs device token has been forwarded, so the JavaScript `isCrispPushNotification(...)` and `handlePushNotification(...)` methods are not available there. The Crisp Android and iOS SDKs do not provide a locale override, so the `setLocale(...)` method is only available on the web. The native chatbox follows the language of the app. To change it at runtime, change the language of the app, e.g. with the [App Language](https://capawesome.io/docs/sdks/capacitor/app-language/) plugin. The following features of the Crisp SDKs are intentionally not part of this plugin's initial release: unread message count, bot scenarios, and audio/video calls. [Open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) if you need any of them. ## FAQ ### Do I need a Crisp account to use this plugin? Yes. This plugin wraps the official Crisp SDKs, which require an active [Crisp](https://crisp.chat/) account and a website ID. ### How is this plugin different from other similar plugins? This plugin exposes the Crisp SDKs through a fully typed API, including a strongly typed listener for each event and dedicated helpdesk methods. Identity verification is built in through email-signature HMAC, and push notifications are handled entirely through Crisp's public API. It ships a typed web implementation alongside Android and iOS, supports Swift Package Manager ahead of the CocoaPods sunset, and stays unopinionated by never forcing an automatic show or hide of the chatbox. It is actively maintained and backed by dedicated support. ### How do I get the session ID? Listen for the `sessionLoaded` event. The event payload contains the `sessionId`. There is no synchronous getter, because the session is loaded asynchronously. ### How does identity verification work? Generate an HMAC-SHA256 signature of the user's email address on your backend using your Crisp secret key, then pass it as the `emailSignature` option of `setUser(...)`. Never expose your secret key in your app. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Formbricks](https://capawesome.io/docs/sdks/capacitor/formbricks/): Unofficial Capacitor plugin for Formbricks to run in-app surveys. - [Intercom](https://capawesome.io/docs/sdks/capacitor/intercom/): Unofficial Capacitor plugin for the Intercom live chat and customer support platform. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/): Unofficial Capacitor plugin for the PostHog product analytics platform. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/crisp/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/crisp/LICENSE). ## Third-Party Notices The Crisp Android and iOS SDKs are proprietary, closed-source binaries provided by Crisp IM SAS. This plugin only declares them as dependencies (via Maven, CocoaPods, and Swift Package Manager) and does not bundle or modify them. Using the plugin requires an active Crisp account and website ID. The MIT license of this plugin covers the wrapper code only, not the Crisp SDKs. ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Crisp IM SAS or any of its affiliates or subsidiaries. "Crisp" is a trademark of Crisp IM SAS. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Datetime Picker Plugin Capacitor plugin for seamless date and time selection with advanced features like localization, theming, and more. Available for Android and iOS. ## Features The Capacitor Datetime Picker plugin is one of the most feature-rich date and time selection solutions for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 📅 **Multiple modes**: Date, time, datetime, and month 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. - 🤝 **Compatibility**: Works alongside the [Action Sheet](https://capawesome.io/docs/sdks/capacitor/action-sheet/), [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/) and [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugins. - 🔁 **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! ## Use Cases The Datetime Picker plugin is typically used whenever an app needs the user to enter a date or time, for example: - **Appointment booking**: Let users pick a date and time for a reservation and restrict the selection with minimum and maximum constraints. - **Profile forms**: Ask for a date of birth with a native, localized date picker. - **Reminders and alarms**: Let users choose a time of day using the native time picker, optionally with a custom minute interval on iOS. - **Reports and statements**: Let users select a month and year only, for example to filter a monthly report, using the `month` mode. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | iOS | | ------- | --- | | | | ## 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. ## Usage The following examples show how to present a date, time, or datetime picker, and how to let the user pick a month and year only. ### Present a date, time, or datetime picker Open the native picker with the `present(...)` method. Use the `mode` option to choose between a date, time, or datetime picker, and customize the theme, locale, and button texts as needed. Only available on Android and iOS: ``` 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; }; ``` ### Let the user pick a month and year only Use the `month` mode to let the user pick a month and year without a day or time, for example to filter a monthly report. The returned value is the first day of the selected month: ``` import { DatetimePicker } from '@capawesome-team/capacitor-datetime-picker'; const presentMonth = async () => { const { value } = await DatetimePicker.present({ cancelButtonText: 'Cancel', doneButtonText: 'Ok', mode: 'month', value: '2026-05', format: 'yyyy-MM', 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. On Android, the picker is dismissed (the promise rejects with the `dismissed` error code) if the system language, font size or layout direction changes while it is open. 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'HH:​mm:ss.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' | 'month'\` | | **`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. On iOS 26 and later, the picker uses the system's Liquid Glass sheet, so the theme only selects its light or dark appearance. 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 | ## FAQ ### What happens when the user cancels the picker? The `present(...)` method throws an error if the input is canceled or dismissed by the user. Make sure to catch this error in your code, for example to keep the previous value. ### How can I close the picker programmatically? Use the `cancel()` method to cancel the currently active datetime picker, for example when your app is sent to the background. If there is no active picker, this method does nothing. ### How do I restrict which dates the user can select? Use the `min` and `max` options of the `present(...)` method to define the earliest and latest date and time to accept. The format of both values must match the value of the `format` parameter. ### How do I let the user pick only a month and year? Set the `mode` option to `month`. Unlike the other modes, the month picker uses a custom (non-native) UI on both platforms, because neither Android nor iOS provide a native month-only picker. The returned value is the first day of the selected month at 00:00:00 local time. See the [usage example](#let-the-user-pick-a-month-and-year-only) above. ### Can I change the language and theme of the picker? Yes, use the `locale` option with a BCP 47 language tag to define the language of the UI, and the `theme` option to choose between `auto`, `light`, and `dark`. With `auto`, the system theme is used. ### Does this plugin work on the Web? No, this plugin is only available on Android and iOS, where it uses platform-specific picker components. The `present(...)` and `cancel()` methods are not available on the Web. ## Related Plugins - [Action Sheet](https://capawesome.io/docs/sdks/capacitor/action-sheet/): Show native action sheets. - [Dialog](https://capawesome.io/docs/sdks/capacitor/dialog/): Display native alert, confirm, and prompt dialogs. - [Localization](https://capawesome.io/docs/sdks/capacitor/localization/): Read the user's preferred locales to localize the picker. - [Option Picker](https://capawesome.io/docs/sdks/capacitor/option-picker/): Let the user pick an option from a list with a native picker. ## 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). ## 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/). ## 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). # Capacitor Device Info Plugin Capacitor plugin to read device information, such as the model, manufacturer, operating system, memory, and a per-install identifier. ## Features - 🆔 **Identifier**: Read a unique per-install identifier for the device. - 📱 **Information**: Read the model, manufacturer, operating system, and more. - 🧠 **Memory**: Read the total memory of the device and the memory used by the app. - ⏱️ **Uptime**: Read how long the device has been running since its last boot. - 🖥️ **Cross-platform**: Support for Android, iOS, and Web. - 🤝 **Compatibility**: Works alongside the [Battery](https://capawesome.io/docs/sdks/capacitor/battery/) and [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) 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! ## Use Cases The Device Info plugin is typically used whenever an app needs to know more about the device it is running on, for example: - **Bug reports and support**: Attach the device model, manufacturer, operating system version, and WebView version to support tickets or crash reports. - **Device registration**: Use the per-install identifier to register a device on your backend without collecting personal data. - **Adaptive user interfaces**: Adjust your layout based on the device type, for example phone versus tablet. - **Feature gating**: Enable or disable features depending on the Android SDK version or the major iOS version. - **Diagnostics**: Monitor the memory used by your app or detect whether it is running on an emulator or simulator. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-device-info` 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-device-info npx cap sync ``` No additional permissions or configuration are required on any platform. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to read the device identifier, read device information, and read the device uptime. ### Read the device identifier Get a unique per-install identifier for the device, for example to register the device on your backend. See the [API documentation](#getid) for when the identifier is reset on each platform: ``` import { DeviceInfo } from '@capawesome/capacitor-device-info'; const getId = async () => { const { identifier } = await DeviceInfo.getId(); return identifier; }; ``` ### Read device information Get information about the device, such as the model, manufacturer, operating system, device type, and memory. Fields that a platform cannot determine are `null` (see [Platform Support](#platform-support)): ``` import { DeviceInfo } from '@capawesome/capacitor-device-info'; const getInfo = async () => { const info = await DeviceInfo.getInfo(); return info; }; ``` ### Read the device uptime Get the time the device has been running since its last boot, in milliseconds. Only available on Android and iOS: ``` import { DeviceInfo } from '@capawesome/capacitor-device-info'; const getUptime = async () => { const { uptime } = await DeviceInfo.getUptime(); return uptime; }; ``` ## API - [`getId()`](#getid) - [`getInfo()`](#getinfo) - [`getUptime()`](#getuptime) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getId() ``` getId() => Promise ``` Get a unique identifier for the device. On **Android**, the identifier is the `ANDROID_ID` value, which is unique per app-signing key, user, and device. It is reset when the app is reinstalled after the signing key changes or the device is factory reset. On **iOS**, the identifier is the `identifierForVendor` value, which is unique per vendor and device. It is reset when all apps from the vendor are uninstalled. On **Web**, the identifier is a random UUID that is persisted in the browser's `localStorage`. It is reset when the browser storage is cleared. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getInfo() ``` getInfo() => Promise ``` Get information about the device. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getUptime() ``` getUptime() => Promise ``` Get the time the device has been running since its last boot, in milliseconds. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetIdResult | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------ | ----- | | **`identifier`** | `string` | The unique identifier of the device. | 0.1.0 | #### GetInfoResult | Prop | Type | Description | Since | | ----------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`androidSdkVersion`** | \`number | null\` | The Android SDK version number (API level). Returns `null` on platforms other than Android. Only available on Android. | | **`deviceType`** | `DeviceType` | The type of the device. | 0.1.0 | | **`iosVersion`** | \`number | null\` | The major version number of iOS. Returns `null` on platforms other than iOS. Only available on iOS. | | **`isVirtual`** | `boolean` | Whether the app is running on a virtual device (simulator or emulator). | 0.1.0 | | **`manufacturer`** | `string` | The manufacturer of the device. | 0.1.0 | | **`model`** | `string` | The model identifier of the device. On **iOS**, this is the internal model identifier (e.g. `iPhone13,4`), not the marketing name. | 0.1.0 | | **`name`** | \`string | null\` | The name of the device. On **iOS 16 and newer**, a generic device name (e.g. `iPhone`) is returned unless the app has the required entitlement. Returns `null` if the name cannot be determined. | | **`operatingSystem`** | `OperatingSystem` | The operating system of the device. | 0.1.0 | | **`osVersion`** | `string` | The version of the operating system. | 0.1.0 | | **`platform`** | `Platform` | The platform the app is running on. | 0.1.0 | | **`totalMemory`** | \`number | null\` | The total amount of memory of the device, in bytes. Returns `null` if the total memory cannot be determined. Only available on Android and iOS. | | **`usedMemory`** | \`number | null\` | The amount of memory used by the app, in bytes. Returns `null` if the used memory cannot be determined. Only available on Android and iOS. | | **`webViewVersion`** | \`string | null\` | The version of the WebView that renders the app. On **iOS**, the version of the operating system is returned, because the WebKit version is tied to it. Returns `null` if the WebView version cannot be determined. | #### GetUptimeResult | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------------------------------------------------- | ----- | | **`uptime`** | `number` | The time the device has been running since its last boot, in milliseconds. | 0.1.0 | ### Type Aliases #### DeviceType The type of a device. - `phone`: A handheld phone-sized device. - `tablet`: A tablet-sized device. - `desktop`: A desktop or laptop computer. - `tv`: A television or set-top box. - `unknown`: The type could not be determined. `'phone' | 'tablet' | 'desktop' | 'tv' | 'unknown'` #### OperatingSystem The operating system of a device. `'android' | 'ios' | 'windows' | 'mac' | 'unknown'` #### Platform The platform an app is running on. `'android' | 'ios' | 'web'` ## Platform Support The plugin returns `null` for any field that a platform cannot determine. The following table shows which fields of the `getInfo()` result are available on each platform: | Property | Android | iOS | Web | | ------------------- | ------- | --- | ---------------- | | `androidSdkVersion` | ✅ | ❌ | ❌ | | `deviceType` | ✅ | ✅ | ✅ (best-effort) | | `iosVersion` | ❌ | ✅ | ❌ | | `isVirtual` | ✅ | ✅ | ✅ | | `manufacturer` | ✅ | ✅ | ❌ | | `model` | ✅ | ✅ | ✅ (best-effort) | | `name` | ✅ | ✅ | ❌ | | `operatingSystem` | ✅ | ✅ | ✅ (best-effort) | | `osVersion` | ✅ | ✅ | ✅ (best-effort) | | `platform` | ✅ | ✅ | ✅ | | `totalMemory` | ✅ | ✅ | ❌ | | `usedMemory` | ✅ | ✅ | ❌ | | `webViewVersion` | ✅ | ✅ | ✅ (best-effort) | The `getUptime()` method is only available on Android and iOS. ## Migration from `@capacitor/device` This plugin can be used as a replacement for the official [`@capacitor/device`](https://capacitorjs.com/docs/apis/device) plugin. The following table shows how the methods map: | `@capacitor/device` | `@capawesome/capacitor-device-info` | | ------------------- | -------------------------------------------------------------------------------------- | | `getId()` | `getId()` | | `getInfo()` | `getInfo()` | | `getBatteryInfo()` | Use the [Battery](https://capawesome.io/docs/sdks/capacitor/battery/) plugin | | `getLanguageCode()` | Use the [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugin | | `getLanguageTag()` | Use the [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugin | ## FAQ ### How is this plugin different from other similar plugins? It gathers a wide range of device details through one fully typed API — model, manufacturer, operating system, a per-install identifier, total and used memory, device type, virtual-device detection, WebView version, and uptime — with consistent behavior across Android, iOS, and the Web. Fields a platform cannot determine are returned as `null`, and the package is actively maintained against the latest Capacitor and OS versions. If you only need the platform name, a minimal setup is enough; if you want a complete, honest picture of the device, this plugin is built for exactly that. ### How stable is the identifier returned by `getId`? The identifier is unique per install. On Android, it is the `ANDROID_ID` value, which is reset when the app is reinstalled after the signing key changes or the device is factory reset. On iOS, it is the `identifierForVendor` value, which is reset when all apps from the vendor are uninstalled. On the Web, it is a random UUID persisted in the browser's `localStorage`, which is reset when the browser storage is cleared. ### Can this plugin replace the official `@capacitor/device` plugin? Yes, this plugin can be used as a replacement for the official `@capacitor/device` plugin. The `getId()` and `getInfo()` methods map directly, while battery information and language settings are covered by the [Battery](https://capawesome.io/docs/sdks/capacitor/battery/) and [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugins. See the [migration table](#migration-from-capacitordevice) above. ### Why is the device name always a generic value like `iPhone` on iOS? On iOS 16 and newer, a generic device name is returned unless the app has the required entitlement from Apple. If the name cannot be determined at all, the `name` property is `null`. ### Why are some fields `null` on my platform? The plugin returns `null` for any field that a platform cannot determine. For example, `totalMemory` and `usedMemory` are only available on Android and iOS, and `androidSdkVersion` is only available on Android. See the [Platform Support](#platform-support) section for a complete overview. ### Can I get the battery level or the device language with this plugin? No, this plugin focuses on device information such as the model, manufacturer, operating system, and memory. For battery information, use the [Battery](https://capawesome.io/docs/sdks/capacitor/battery/) plugin. For the user's language and locale preferences, use the [Localization](https://capawesome.io/docs/sdks/capacitor/localization/) plugin. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Battery](https://capawesome.io/docs/sdks/capacitor/battery/): Access battery information. - [Localization](https://capawesome.io/docs/sdks/capacitor/localization/): Read the user's localization preferences, such as preferred locales and time zone. - [Network](https://capawesome.io/docs/sdks/capacitor/network/): Access network information. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/device-info/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/device-info/LICENSE). # Capacitor Dialog Plugin Capacitor plugin for native alert, confirm, and prompt dialogs. ## Features - 💬 **Alert**: Display a native alert dialog with a single button. - ❓ **Confirm**: Ask the user to confirm or cancel an action. - ⌨️ **Prompt**: Request text input from the user. - 🌐 **Cross-platform**: Works on Android, iOS, and the web. - 🔒 **App Store safe**: Uses only official platform APIs. - 🤝 **Compatibility**: Works alongside the [Action Sheet](https://capawesome.io/docs/sdks/capacitor/action-sheet/), [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/) and [Toast](https://capawesome.io/docs/sdks/capacitor/toast/) 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! ## Use Cases The Dialog plugin is typically used whenever an app needs a quick native interaction with the user, for example: - **Confirmations**: Ask the user to confirm a destructive action, such as deleting an item, before executing it. - **Notices**: Inform the user about the result of an action, such as saved changes or an error, with a simple alert. - **Quick text input**: Ask the user for a short text value, such as a name, without building a custom form. - **Native look and feel**: Replace the browser's `window.alert`, `window.confirm`, and `window.prompt` with native dialogs on Android and iOS. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-dialog` 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-dialog npx cap sync ``` ### Android No additional configuration is required for this plugin. ### iOS No additional configuration is required for this plugin. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to display an alert dialog, ask the user for confirmation, and request text input from the user. ### Display an alert dialog Show a message with a single button, for example to inform the user about the result of an action: ``` import { Dialog } from '@capawesome/capacitor-dialog'; const alert = async () => { await Dialog.alert({ title: 'Success', message: 'Your changes have been saved.', }); }; ``` ### Ask the user for confirmation Show a confirmation dialog with two buttons. The result tells you whether the user confirmed the dialog: ``` import { Dialog } from '@capawesome/capacitor-dialog'; const confirm = async () => { const { value } = await Dialog.confirm({ title: 'Confirm', message: 'Do you want to delete this item?', }); console.log('Confirmed:', value); }; ``` ### Request text input from the user Show a prompt dialog with a text input, a confirm and a cancel button. The result contains the entered value. If the user cancels the dialog, the promise is rejected with the `CANCELED` error code: ``` import { Dialog, ErrorCode } from '@capawesome/capacitor-dialog'; const prompt = async () => { try { const { value } = await Dialog.prompt({ title: 'Name', message: 'What is your name?', inputPlaceholder: 'Enter your name', }); console.log('Value:', value); } catch (error) { if (error.code === ErrorCode.Canceled) { console.log('The user canceled the dialog.'); } } }; ``` ## API - [`alert(...)`](#alert) - [`confirm(...)`](#confirm) - [`prompt(...)`](#prompt) - [Interfaces](#interfaces) ### alert(...) ``` alert(options: AlertOptions) => Promise ``` Display an alert dialog with a single button. | Param | Type | | ------------- | -------------- | | **`options`** | `AlertOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### confirm(...) ``` confirm(options: ConfirmOptions) => Promise ``` Display a confirmation dialog with two buttons. | Param | Type | | ------------- | ---------------- | | **`options`** | `ConfirmOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### prompt(...) ``` prompt(options: PromptOptions) => Promise ``` Display a prompt dialog with a text input, a confirm and a cancel button. If the user selects the cancel button or dismisses the dialog, the promise is rejected with the `CANCELED` error code. | Param | Type | | ------------- | --------------- | | **`options`** | `PromptOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### AlertOptions | Prop | Type | Description | Default | Since | | ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`buttonTitle`** | `string` | The title of the button that confirms the dialog. On the web, the button title cannot be customized and is ignored. | `'OK'` | 0.1.0 | | **`message`** | `string` | The message to display in the dialog. | | 0.1.0 | | **`title`** | `string` | The title of the dialog. On the web, the title cannot be customized and is ignored. | | 0.1.0 | #### ConfirmResult | Prop | Type | Description | Since | | ----------- | --------- | -------------------------------------- | ----- | | **`value`** | `boolean` | Whether the user confirmed the dialog. | 0.1.0 | #### ConfirmOptions | Prop | Type | Description | Default | Since | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ----- | | **`cancelButtonTitle`** | `string` | The title of the button that cancels the dialog. On the web, the button title cannot be customized and is ignored. | `'Cancel'` | 0.1.0 | | **`message`** | `string` | The message to display in the dialog. | | 0.1.0 | | **`okButtonTitle`** | `string` | The title of the button that confirms the dialog. On the web, the button title cannot be customized and is ignored. | `'OK'` | 0.1.0 | | **`title`** | `string` | The title of the dialog. On the web, the title cannot be customized and is ignored. | | 0.1.0 | #### PromptResult | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------- | ----- | | **`value`** | `string` | The value of the text input. | 0.1.0 | #### PromptOptions | Prop | Type | Description | Default | Since | | ----------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- | ---------- | ----- | | **`cancelButtonTitle`** | `string` | The title of the button that cancels the dialog. On the web, the button title cannot be customized and is ignored. | `'Cancel'` | 0.1.0 | | **`inputPlaceholder`** | `string` | The placeholder of the text input. On the web, the placeholder cannot be customized and is ignored. | | 0.1.0 | | **`inputText`** | `string` | The initial value of the text input. | | 0.1.0 | | **`message`** | `string` | The message to display in the dialog. | | 0.1.0 | | **`okButtonTitle`** | `string` | The title of the button that confirms the dialog. On the web, the button title cannot be customized and is ignored. | `'OK'` | 0.1.0 | | **`title`** | `string` | The title of the dialog. On the web, the title cannot be customized and is ignored. | | 0.1.0 | ## Migrating from `@capacitor/dialog` This plugin is largely API-compatible with the official [`@capacitor/dialog`](https://github.com/ionic-team/capacitor-plugins/tree/main/dialog) plugin, with a single difference: if the user cancels a prompt dialog, `prompt(...)` rejects with the `CANCELED` error code instead of resolving with a `cancelled` flag. | `@capacitor/dialog` | `@capawesome/capacitor-dialog` | | ---------------------------------------- | ---------------------------------------------------------------- | | `alert({ title, message, buttonTitle })` | `alert({ title, message, buttonTitle })` | | `confirm({ ... }) → { value }` | `confirm({ ... }) → { value }` | | `prompt({ ... }) → { value, cancelled }` | `prompt({ ... }) → { value }`, rejects with `CANCELED` on cancel | ## FAQ ### How is this plugin different from other similar plugins? It brings native alert, confirm, and prompt dialogs to Android, iOS, and the web through a single, fully typed API, using only official platform APIs so it stays safe for App Store and Google Play submissions. It supports both CocoaPods and Swift Package Manager on iOS and is actively maintained against the latest Capacitor and OS versions, with customizable button titles on Android and iOS. If you only need a quick native message, it is refreshingly simple to drop in; if you need consistent alert, confirm, and prompt behavior across every platform, it is built for exactly that. ### How is this plugin different from the official `@capacitor/dialog` plugin? This plugin is largely API-compatible with the official `@capacitor/dialog` plugin, with a single difference: if the user cancels a prompt dialog, `prompt(...)` rejects with the `CANCELED` error code instead of resolving with a `cancelled` flag. See the [migration table](#migrating-from-capacitordialog) above for the complete method mapping. ### Can I customize the dialog buttons? Yes, on Android and iOS you can customize the button titles using the `buttonTitle`, `okButtonTitle`, and `cancelButtonTitle` options. On the web, the button titles cannot be customized and are ignored, because the browser's built-in dialogs are used. ### How do I know whether the user canceled a prompt? If the user cancels the dialog, the `prompt(...)` method rejects with the `CANCELED` error code, so catch the error and compare `error.code` with `ErrorCode.Canceled`. Otherwise, the result contains the entered text in the `value` property. For confirmation dialogs, the `value` property of the `confirm(...)` result tells you whether the user confirmed the dialog. ### Why is the dialog title not displayed on the web? On the web, the title and button titles cannot be customized and are ignored, because the plugin uses the browser's built-in dialogs. Only the message is displayed. On Android and iOS, the title is fully supported. ### Is this plugin safe to use for App Store submissions? Yes, the plugin uses only official platform APIs to display the dialogs, so it is safe to use in apps submitted to the Apple App Store and Google Play Store. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Action Sheet](https://capawesome.io/docs/sdks/capacitor/action-sheet/): Show native action sheets. - [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/): Let the user pick a date and time with a native picker. - [Toast](https://capawesome.io/docs/sdks/capacitor/toast/): Show native toast notifications. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/dialog/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/dialog/LICENSE). # Capacitor Document Scanner Plugin Capacitor plugin for scanning documents with the native, full-screen scanner on Android and iOS. Returns perspective-corrected images and an optional combined PDF. ## Features The Capacitor Document Scanner plugin brings the platform's own document scanning experience to your Capacitor app. Here are some of the key features: - 📱 **Native UI**: Uses the platform's built-in scanner (Google's ML Kit flow on Android, VisionKit on iOS). - ✂️ **Perspective Correction**: Automatically detects edges and corrects the perspective of captured pages. - 📄 **Multi-Page**: Capture multiple pages in a single scan. - 🖼️ **JPEG Output**: Returns the scanned pages as JPEG files in the cache directory. - 📑 **PDF Output**: Optionally generates a combined PDF document from the scanned pages. - 🔒 **Public APIs Only**: Built exclusively on public platform APIs, so it is safe for App Review and resilient to OS updates. - 🤝 **Compatibility**: Works hand in hand with the [PDF Viewer](https://capawesome.io/docs/sdks/capacitor/pdf-viewer/), [Printer](https://capawesome.io/docs/sdks/capacitor/printer/) and [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) 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! ## Use Cases The Document Scanner plugin is typically used whenever an app needs to digitize a physical document, for example: - **Receipts and invoices**: Let users scan receipts for expense tracking. - **Contracts and forms**: Capture signed documents as multi-page PDFs. - **ID and cards**: Scan identity documents or business cards. - **Notes and whiteboards**: Digitize handwritten notes or whiteboard content. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor Document Scanner Plugin](https://capawesome.io/blog/announcing-the-capacitor-document-scanner-plugin/): Scan documents with the native scanner UIs and get perspective-corrected pages and a combined PDF in two API calls. ## 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-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 @capawesome-team/capacitor-document-scanner npx cap sync ``` ### Android The Google-provided scanner activity requests the camera permission itself, so no manifest changes are required. #### 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: - `$playServicesMlkitDocumentScannerVersion` 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. The scanner is delivered as an on-demand Google Play services module. It is **not** bundled with your app and is downloaded automatically the first time `scanDocument(...)` is called. ### iOS Add the `NSCameraUsageDescription` key to the `Info.plist` file of your app to explain why your app needs access to the camera: ``` NSCameraUsageDescription The app needs access to the camera to scan documents. ``` If the key is missing, `scanDocument(...)` rejects with a clear error message. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check whether document scanning is available, scan a document, and generate a PDF document from the scanned pages. ### Check the availability Check whether document scanning is available on the device before starting a scan. Only available on Android and iOS: ``` import { DocumentScanner } from '@capawesome-team/capacitor-document-scanner'; const isAvailable = async () => { const { available } = await DocumentScanner.isAvailable(); return available; }; ``` ### Scan a document Open the native scanner user interface to capture one or more pages. On both platforms, the scanner detects the edges of the document, corrects the perspective, and offers cropping, rotation and a review step before the pages are returned as JPEG files in the cache directory. On Android, you can change the editing capabilities with the `androidScannerMode` option and allow the user to import an existing image with the `androidGalleryImportAllowed` option. On iOS, image cleaning is applied automatically by the system. The promise rejects with the `SCAN_CANCELED` error code if the user cancels the scan. Only available on Android and iOS: ``` import { DocumentScanner } from '@capawesome-team/capacitor-document-scanner'; const scanDocument = async () => { const { scannedImages } = await DocumentScanner.scanDocument({ imageQuality: 80, pageLimit: 5, }); return scannedImages; }; ``` ### Generate a PDF document Set the `generatePdf` option to `true` to receive a combined PDF document of all scanned pages in the `pdf` property of the result. On Android, the PDF is generated by ML Kit. On iOS, it is composed from the scanned page images. Only available on Android and iOS: ``` import { DocumentScanner } from '@capawesome-team/capacitor-document-scanner'; const scanDocumentAsPdf = async () => { const { pdf } = await DocumentScanner.scanDocument({ generatePdf: true, }); return pdf; }; ``` You can pass the resulting `pdf` path to the [PDF Viewer](https://capawesome.io/docs/sdks/capacitor/pdf-viewer/) plugin to display it, to the [Printer](https://capawesome.io/docs/sdks/capacitor/printer/) plugin to print it, or to the [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) plugin to open it in another app. ## API - [`isAvailable()`](#isavailable) - [`scanDocument(...)`](#scandocument) - [Interfaces](#interfaces) - [Enums](#enums) ### isAvailable() ``` isAvailable() => Promise ``` Check whether document scanning is available on the device. On **Android**, this resolves to `true` if Google Play services is available. The scanner module is downloaded on demand the first time `scanDocument(...)` is called. On **iOS**, this resolves to `true` if the device supports document scanning (i.e. it has a camera and runs a supported iOS version). Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### scanDocument(...) ``` scanDocument(options?: ScanDocumentOptions | undefined) => Promise ``` Open the native document scanner user interface. The scanner captures one or more pages, applies perspective correction, and returns the file paths of the scanned images and, optionally, a combined PDF document. The scanner user interface is provided by the platform (Google's ML Kit flow on Android, VisionKit on iOS) and cannot be customized. The promise rejects with the `SCAN_CANCELED` error code if the user cancels the scan. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ScanDocumentOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------------ | ----- | | **`available`** | `boolean` | Whether or not document scanning is available on the device. | 0.0.1 | #### ScanDocumentResult | Prop | Type | Description | Since | | ------------------- | ---------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | **`pdf`** | \`string | null\` | The path of the generated PDF document. Only set if the `generatePdf` option is set to `true`. Otherwise, `null`. | | **`scannedImages`** | `string[]` | The paths of the scanned images as JPEG files in the cache directory. | 0.0.1 | #### ScanDocumentOptions | Prop | Type | Description | Default | Since | | --------------------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ----- | | **`androidGalleryImportAllowed`** | `boolean` | Whether or not the user is allowed to import an existing image from the gallery instead of capturing a new one with the camera. Only available on Android. | `false` | 0.0.1 | | **`androidScannerMode`** | `ScannerMode` | The mode of the scanner user interface. Only available on Android. | `ScannerMode.Full` | 0.0.1 | | **`generatePdf`** | `boolean` | Whether or not to generate a combined PDF document from the scanned pages. The path of the generated PDF is returned in the `pdf` property of the result. | `false` | 0.0.1 | | **`imageQuality`** | `number` | The JPEG quality of the scanned images. Must be a value between `0` (lowest quality) and `100` (highest quality). | `100` | 0.0.1 | | **`pageLimit`** | `number` | The maximum number of pages that can be scanned. Must be greater than or equal to `1`. On **iOS**, the scanner cannot be limited to a specific number of pages. Instead, the returned pages are truncated to this value after scanning. | `10` | 0.0.1 | ### Enums #### ScannerMode | Members | Value | Description | Since | | -------------------- | -------------------- | --------------------------------------------------------------------------------------------------------------------- | ----- | | **`Base`** | `'BASE'` | Basic editing capabilities (crop and rotate). | 0.0.1 | | **`BaseWithFilter`** | `'BASE_WITH_FILTER'` | Adds image filters (grayscale and automatic image enhancement) on top of the `Base` mode. | 0.0.1 | | **`Full`** | `'FULL'` | Adds ML-based image cleaning capabilities (removes stains, fingers, and shadows) on top of the `BaseWithFilter` mode. | 0.0.1 | ## FAQ ### How is this plugin different from other similar plugins? It brings the platform's native scanning experience to both Android (ML Kit) and iOS (VisionKit) through a single, unified API, with automatic edge detection and perspective correction, multi-page capture, JPEG output and an optional combined PDF. Everything is fully typed, built exclusively on public platform APIs so it stays safe for App Review, actively maintained against the latest OS and Capacitor versions, and backed by dedicated support. If you only need to scan on a single platform, a simpler setup can be enough; if you want consistent cross-platform scanning with PDF output, this plugin is designed for that. ### Is document scanning available on the web? No. The `isAvailable(...)` and `scanDocument(...)` methods are only available on Android and iOS. On the web, both methods reject with an unimplemented error. ### Can I customize the scanner UI? No. The plugin uses the scanner user interface of the platform (Google's ML Kit flow on Android, VisionKit on iOS) and does not draw any UI itself. Labels, buttons, icons and the scanning flow are defined by Google and Apple and therefore differ between the platforms. On Android, you can change the editing capabilities with the `androidScannerMode` option and allow the user to import an existing image with the `androidGalleryImportAllowed` option. On iOS, no options are available. If you want to suggest changes to the scanner UI, please report them to [Google](https://developers.google.com/ml-kit/community) or [Apple](https://feedbackassistant.apple.com/) directly. ### Why can't I limit the number of pages on iOS? Apple's VisionKit does not expose a public API to stop the scanner after a specific number of pages. To avoid using private APIs (which would put your app at risk during App Review), the plugin truncates the returned pages to `pageLimit` after scanning instead. ### Where are the scanned files stored? The scanned images and the generated PDF are stored in the app's cache directory. Stale files created by the plugin are cleaned up automatically when the plugin is loaded. Copy the files to a persistent location if you need to keep them. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open the scanned files in another app. - [ML Kit Document Scanner](https://www.npmjs.com/package/@capacitor-mlkit/document-scanner): Scan documents with ML Kit Document Scanning on Android. - [PDF Viewer](https://capawesome.io/docs/sdks/capacitor/pdf-viewer/): Display PDF documents in a fullscreen native viewer. - [Photo Manipulator](https://capawesome.io/docs/sdks/capacitor/photo-manipulator/): Apply additional edits such as brightness or contrast adjustments. - [Printer](https://capawesome.io/docs/sdks/capacitor/printer/): Print the scanned images or the generated PDF. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/document-scanner/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/document-scanner/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/document-scanner/LICENSE). # Capacitor Electron Platform Capacitor platform to build, run, and package desktop apps for macOS, Windows, and Linux with Electron.[1](#fn:1) ## Features The Capacitor Electron platform brings your web app and your Capacitor plugins to the desktop. Here are some of the key features: - 🖥️ **Cross-platform**: Build desktop apps for macOS, Windows, and Linux from one codebase. - ⚡ **Familiar workflow**: `cap add`, `cap sync`, `cap run` — the same commands as iOS and Android. - 🔌 **Plugin support**: Capacitor plugins with an Electron implementation work out of the box; plugins with a web implementation work automatically via fallback. - ⚡ **Live Updates**: Deliver web-bundle updates over the air with the [Capacitor Live Update plugin](https://capawesome.io/docs/sdks/capacitor/live-update/) and Capawesome Cloud — the same workflow as on Android and iOS. - 🔒 **Security-first**: Sandboxed renderer, context isolation, strict Content-Security-Policy, and validated IPC — enabled by default and not configurable. - 🔗 **Deep links**: Custom URL schemes delivered through the standard `@capacitor/app` plugin's `appUrlOpen` event. - 📱 **App lifecycle**: `appStateChange`, `pause`, and `resume` events, exactly like on mobile. - ♻️ **Live reload**: Develop against your web dev server with full HMR. - 📦 **Packaging**: Create installers with electron-builder, including automatic dependency vendoring. - 🪟 **Window management**: Typed window options, state persistence, and hooks for trays and menus. - 🧩 **Minimal scaffold**: You own a handful of small, stable files; all platform logic ships in the package and updates via `npm update`. - 🔁 **Up-to-date**: Always supports the latest Capacitor and Electron versions. - ⭐️ **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-electron/issues) and we'll take a look! ## Use Cases The Electron platform is typically used to bring an existing Capacitor app to the desktop, for example: - **Desktop companion apps**: Ship your mobile app's functionality to macOS, Windows, and Linux without a rewrite. - **Offline-first desktop tools**: Combine the platform with plugins like [SQLite](https://capawesome.io/docs/sdks/capacitor/sqlite/) for fully offline desktop applications. - **Instant desktop updates**: Ship fixes to the web layer without building and distributing a new installer, using [Live Updates](https://capawesome.io/docs/cloud/live-updates/electron/). - **Internal business tools**: Distribute apps directly to your team without app stores. - **Kiosk and point-of-sale apps**: Run your web app full screen on dedicated desktop hardware. - **Deep-link driven workflows**: Handle custom URL schemes on the desktop exactly like on mobile. ## Compatibility | Platform Version | Capacitor Version | Electron Version | Status | | ---------------- | ----------------- | ---------------- | -------------- | | 0.x | >=6.x.x | >=28.x.x | Active support | > [!NOTE] On Capacitor 6 and 7, the Capacitor CLI ignores the exit code of platform hooks, so a failing `npx cap sync` still reports success — check the log output for `[capacitor-electron]` errors. Capacitor 8 fails the command properly. ## Guides - [Capacitor Electron Platform: Build Desktop Apps](https://capawesome.io/blog/announcing-the-capacitor-electron-platform/) ## Installation You can use our **AI-Assisted Setup** to add the platform. 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-platforms ``` Then use the following prompt: ``` Use the `capacitor-platforms` skill from `capawesome-team/skills` to add the `@capawesome/capacitor-electron` platform to my project. ``` If you prefer **Manual Setup**, add the platform by running the following commands: ``` npm install @capawesome/capacitor-electron npx cap add @capawesome/capacitor-electron cd electron && npm install && cd .. ``` We recommend adding a `postinstall` script to your root `package.json` so the Electron dependencies are always installed together with your app dependencies: ``` { "scripts": { "postinstall": "cd electron && npm ci && cd .." } } ``` The initial `cd electron && npm install` generates `electron/package-lock.json` — commit it so that `npm ci` works for every future install. > [!NOTE] Always use the full package name with Capacitor CLI commands (e.g. `npx cap sync @capawesome/capacitor-electron`). A bare `npx cap sync electron` resolves to the `electron` npm package and silently does nothing. The scaffolded `electron/` project contains only files you own: | File | Purpose | | ------------------------------ | ------------------------------------------------------- | | `main.ts` | ~5 lines: imports the runtime and starts the app | | `capacitor.electron.config.ts` | Typed platform options (window, CSP, deep links, hooks) | | `electron-builder.config.js` | Packaging configuration | | `assets/` | App icons | Everything with logic lives in the versioned npm package and updates via `npm update` — your platform project never rots. ## Configuration Platform options are configured in `electron/capacitor.electron.config.ts` with full type safety: ``` import { defineConfig } from '@capawesome/capacitor-electron/config'; export default defineConfig({ window: { width: 1200, height: 800, minWidth: 800, minHeight: 600, }, deepLinks: { scheme: 'myapp', }, }); ``` Extension happens through typed options and hooks (`windowFactory`, `beforeReady`, `onWindowCreated`, CSP overrides) — never by owning runtime code. ### Plugin Configuration Some plugins support platform-specific configuration on Android and iOS — for example, the Capacitor Live Update plugin reads a default channel from an Android string resource or an iOS `Info.plist` key so the build system can inject a value derived from the app version. The `plugins` section of `electron/capacitor.electron.config.ts` fills that role on Electron — generically, for every plugin. Because the file is executable TypeScript evaluated in the main process, values can be computed in code — no template syntax required. It is merged over the `plugins` section of the Capacitor config **shallowly, per plugin key, and this section wins**: for each plugin, its keys override the matching keys in the Capacitor config while unmentioned keys survive; plugins present in only one of the two configs pass through unchanged. Everything outside `plugins` is untouched. The merge runs once at startup, before plugins receive their config. ``` import { defineConfig } from '@capawesome/capacitor-electron/config'; import packageJson from './package.json'; export default defineConfig({ plugins: { LiveUpdate: { defaultChannel: `production-${packageJson.version}`, }, }, }); ``` > Importing `./package.json` requires `resolveJsonModule` in `electron/tsconfig.json` (already enabled in the scaffold). ## Demo A working example can be found here: [capawesome-team/capacitor-electron](https://github.com/capawesome-team/capacitor-electron/tree/main/example) ## Usage ``` # Copy web assets + regenerate the plugin manifest npx cap sync @capawesome/capacitor-electron # Run the app (uses server.url for live reload when configured) npx cap run @capawesome/capacitor-electron # Open the built app npx cap open @capawesome/capacitor-electron ``` ### Live Reload Set `server.url` in your Capacitor config to your dev server and run the app — the window loads the dev server with full HMR, and the plugin bridge works exactly as in production: ``` // capacitor.config.ts const config: CapacitorConfig = { // ... server: { url: 'http://localhost:5173', }, }; ``` ``` npx vite & # your web dev server npx cap run @capawesome/capacitor-electron # dev mode ``` In dev mode a documented, dev-only CSP relaxation is applied (inline scripts, eval, websockets — required by HMR runtimes), and the window automatically reconnects when the dev server restarts. Remove `server.url` (or use a production config) to serve the built web assets from the app bundle again. ### Deep Links Declare the custom URL scheme in the platform configuration (see [Configuration](#configuration)) and listen to the standard `@capacitor/app` event: ``` import { App } from '@capacitor/app'; await App.addListener('appUrlOpen', ({ url }) => { console.log('App opened with URL:', url); }); ``` Deep links opened while the app is running are routed to the running instance (single instance is enforced by default); the URL that launched the app is available via `App.getLaunchUrl()`. ### Splash Screen Booting a desktop app is not instant: the platform runs every plugin's `load()` lifecycle hook (e.g. the [Live Update](https://capawesome.io/plugins/live-update/) plugin verifying and activating a bundle) *before* the main window is shown. A splash screen covers that gap so the app never appears frozen or blank. A splash screen is shown automatically when a splash file exists in the electron app directory — no configuration required. Two files are looked up, in order: 1. `electron/assets/splash.html` 1. `electron/assets/splash.png` The scaffold ships a neutral, theme-aware `assets/splash.html` by default. Migrating from [`@capacitor-community/electron`](https://github.com/capacitor-community/electron)? Its `assets/splash.png` is picked up unchanged. Configure the splash screen in `electron/capacitor.electron.config.ts`: ``` import { defineConfig } from '@capawesome/capacitor-electron/config'; export default defineConfig({ splashScreen: { // Custom file, relative to the electron app directory. Either an `.html` // file or an image (`.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp`). path: 'assets/splash.html', width: 400, height: 300, backgroundColor: '#ffffff', // Keep the splash visible for at least this long, even on fast startups. minimumDurationMs: 0, }, }); ``` | Option | Type | Default | Description | | ------------------- | --------- | ----------------------------------------- | ------------------------------------------------------------------------------------------ | | `enabled` | `boolean` | shown when a splash file exists | Set `false` to disable. Set `true` to require a splash file — boot fails if none is found. | | `path` | `string` | `assets/splash.html`, `assets/splash.png` | Splash file relative to the electron app directory. | | `width` | `number` | `400` | Window width in pixels. | | `height` | `number` | `300` | Window height in pixels. | | `backgroundColor` | `string` | `'#ffffff'` | Window background and the canvas behind an image splash. | | `minimumDurationMs` | `number` | `0` | Minimum time the splash stays visible. | **HTML vs. image:** an `.html` file is loaded directly, so you get full control over layout, fonts, and animation. An image is centered (`object-fit: contain`) on a `backgroundColor` canvas — convenient for a logo, but static. The splash window is deliberately kept outside the plugin bridge: it is frameless, sandboxed, has no preload and no access to the app scheme, and navigation is blocked. > **Packaging note:** the splash files live under `assets/`, which is also the electron-builder `buildResources` directory. The scaffolded `electron-builder.config.js` includes `assets/**/*` in `files` so the splash ships inside the packaged app (`app.asar`). If you replace the config, keep that entry — otherwise the splash works in development but silently disappears from packaged binaries. ### Debugging The platform keeps Electron's default application menu, so the Chromium DevTools can be opened at any time via *View → Toggle Developer Tools* or the keyboard shortcut: | Operating System | Shortcut | | ---------------- | ---------------------- | | macOS | `Cmd` + `Option` + `I` | | Windows | `Ctrl` + `Shift` + `I` | | Linux | `Ctrl` + `Shift` + `I` | To open the DevTools automatically on launch (e.g. to catch logs from early app startup), use the `onWindowCreated` hook in `electron/capacitor.electron.config.ts`: ``` import { defineConfig } from '@capawesome/capacitor-electron/config'; export default defineConfig({ // ... hooks: { onWindowCreated: window => { window.webContents.openDevTools(); }, }, }); ``` ### Tray Icon Use the `onWindowCreated` hook together with Electron's [`Tray`](https://www.electronjs.org/docs/latest/api/tray) and [`Menu`](https://www.electronjs.org/docs/latest/api/menu) APIs to add a tray icon and context menu: ``` import { defineConfig } from '@capawesome/capacitor-electron/config'; import { app, Menu, Tray } from 'electron'; import { join } from 'path'; export default defineConfig({ // ... hooks: { onWindowCreated: window => { const tray = new Tray(join(app.getAppPath(), 'assets', 'tray-icon.png')); tray.setContextMenu( Menu.buildFromTemplate([ { label: 'Show', click: () => window.show() }, { label: 'Hide', click: () => window.hide() }, { type: 'separator' }, { label: 'Quit', click: () => app.quit() }, ]), ); }, }, }); ``` Place the tray icon under `electron/assets/`, which already ships inside the packaged app (see the [Splash Screen](#splash-screen) packaging note). To start the app minimized to the tray, set [`window.showOnLaunch: false`](#configuration) so the main window is created hidden until the user picks *Show*. > **Migrating from [`@capacitor-community/electron`](https://github.com/capacitor-community/electron)?** This recipe replaces its `trayIconAndMenuEnabled` and `hideMainWindowOnLaunch` options — build the tray via the hook above and use `window.showOnLaunch: false` for start-in-tray behavior. ## Migration You can use our **AI-Assisted Migration** to migrate from [`@capacitor-community/electron`](https://github.com/capacitor-community/electron). 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-platforms ``` Then use the following prompt: ``` Use the `capacitor-platforms` skill from `capawesome-team/skills` to migrate my project from `@capacitor-community/electron` to `@capawesome/capacitor-electron`. ``` If you prefer **Manual Migration**, perform the following steps: 1. Back up anything you customized in your existing `electron/` directory (icons, electron-builder configuration, and any code you added to the generated runtime files). 1. Remove the old platform and its `electron/` directory: ``` npm uninstall @capacitor-community/electron rm -rf electron ``` 1. Add this platform: ``` npm install @capawesome/capacitor-electron npx cap add @capawesome/capacitor-electron cd electron && npm install && cd .. ``` 1. Re-apply your customizations through the typed options in `electron/capacitor.electron.config.ts` (window options, deep-link scheme, CSP overrides, tray/menu via hooks) instead of editing runtime code, and restore your icons to `electron/assets/` and your electron-builder settings in `electron/electron-builder.config.js`. 1. Sync and run (always with the full package name): ``` npx cap sync @capawesome/capacitor-electron npx cap run @capawesome/capacitor-electron ``` Notes: - Deep links no longer require hand-written runtime code — declare the scheme in the platform config and listen to `@capacitor/app`'s `appUrlOpen` event. - Splash screens are picked up automatically from `electron/assets/`. Keep `assets/splash.png` and it just works; if you used a custom `splashScreenImageName: 'x.gif'`, either rename it to `assets/splash.png` or point the config at it via `splashScreen: { path: 'assets/x.gif' }` (see [Splash Screen](#splash-screen)). - Plugins must provide an electron implementation for this platform's contract (see [Plugin Development](#plugin-development)); implementations written for the old platform are not loaded. Plugins whose web implementation is sufficient continue to work unchanged via the automatic fallback. ## Plugin Development Plugins declare their electron implementation via `package.json`: ``` { "capacitor": { "electron": { "src": "electron" } } } ``` `src` is optional and defaults to `electron`, like `android`/`ios` in the Capacitor CLI. The implementation is an ES module at `/dist/plugin.mjs` exporting plugin classes. A plugin class declares its Capacitor registration name and its public API via static metadata — the static property is the contract, so a build-time dependency on this package is not required. ### Recommended: extend `ElectronPlugin` Mirroring how Android/iOS plugins extend Capacitor's `Plugin` and override `load()`, the recommended path is to extend the `ElectronPlugin` base class. Add `@capawesome/capacitor-electron` as a **devDependency** (for the types) and an **optional peerDependency** (for the runtime value), then: ``` import { ElectronPlugin, defineElectronPlugin } from '@capawesome/capacitor-electron/plugin'; class SqliteImpl extends ElectronPlugin { // `this.context` (config, services, notifyListeners) is stored by the base constructor. // Optional lifecycle hook. Awaited by the platform before the first window loads. async load() { ... } async open(options) { ... } async query(options) { ... } // Not declared below, therefore never bridged. resolvePath(path) { ... } } export const Sqlite = defineElectronPlugin( { name: 'Sqlite', methods: ['open', 'query'] }, SqliteImpl, ); ``` ### Zero-dependency: marker-only The base class is optional sugar — the discovery contract is the static `__capacitorElectronPlugin` metadata, and the lifecycle hook is detected structurally (never via `instanceof`, which would break across duplicated copies of this package). So a plugin can ship with **no dependency on this package at all**, implementing a structural `load()` if it needs the hook: ``` class SqliteImpl { constructor({ config, services, notifyListeners }) { ... } // Optional. Structural lifecycle hook, detected by name. async load() { ... } async open(options) { ... } async query(options) { ... } // Not declared below, therefore never bridged. resolvePath(path) { ... } } // Equivalent to defineElectronPlugin, without importing this package. SqliteImpl.__capacitorElectronPlugin = { name: 'Sqlite', methods: ['open', 'query'], }; export { SqliteImpl as Sqlite }; ``` The declared `methods` array is the plugin's entire bridged surface: anything not listed stays main-process-internal, and a declared method that is missing on the class fails loudly at boot. Each class is instantiated once in the main process (full Node and Electron API access) and exposed under its registration name through Capacitor's native plugin path — `registerPlugin('Sqlite', { web: ... })` just works, with the web implementation as the automatic fallback for platforms the plugin doesn't cover. No `electron` key in the plugin's `registerPlugin` wiring is needed. The constructor context (`this.context` on an `ElectronPlugin` subclass, or the constructor argument otherwise) provides: - `config` — the app's Capacitor configuration. - `notifyListeners(eventName, data)` — emits a plugin event, mirroring Capacitor's native `notifyListeners`. Web listeners use the standard `addListener(eventName, callback)` / `PluginListenerHandle` API. - `services` — platform primitives (currently `services.bundles`: switch the served web-bundle directory and reload the app windows). ### The `load()` lifecycle hook `load()` runs once after the plugin is constructed and is **awaited before the first application window loads**, so async setup — including repointing the active bundle via `services.bundles.setActiveBundle()` — takes effect on first paint (no default-bundle flash). It is a lifecycle hook, **not** a bridged method: `load` is reserved and is never bridged to the renderer. It must **not** be listed in `methods` — doing so is rejected at boot, because bridging it would let web content invoke the lifecycle hook arbitrarily. A rejected or thrown `load()` fails the app boot loudly, the same way a declared-but-missing method does. On an `ElectronPlugin` subclass the default `load()` is a no-op, so overriding it is optional. At sync time the platform statically scans the app's dependencies and generates a plugin manifest — no plugin code runs outside Electron. Results, thrown `Error`s, and their `code` properties cross the bridge with Capacitor semantics. ## Packaging The scaffolded `electron/` project packages with electron-builder: ``` cd electron && npm run pack ``` The `pack` script runs three steps: compile (`tsc`), **vendor**, and `electron-builder`. The vendor step (`capacitor-electron vendor`) copies the platform runtime, every plugin's electron implementation, and their dependency closure (production, peer, and installed optional dependencies) into `electron/vendor/`, which the scaffolded electron-builder config maps to `node_modules` inside the packaged app — so module resolution works identically in development (from your app root) and in the package, without a second `npm install` and without version drift. Code signing, notarization, targets (dmg/msi/nsis/AppImage/deb), and icons are standard electron-builder configuration in the user-owned `electron-builder.config.js` — see https://www.electron.build. Electron Forge is a supported alternative: run `capacitor-electron vendor` before packaging and include `vendor/node_modules` as the app's `node_modules`. ### App Updates Two independent update layers, matching mobile: - **Binary updates** (Electron itself, the runtime, native modules): use [electron-updater](https://www.electron.build/auto-update) — the desktop analog of an app-store update. Wire it in your `main.ts`; it operates on the packaged artifacts produced above. - **Web-bundle updates**: use the [Capacitor Live Update plugin](https://capawesome.io/docs/sdks/capacitor/live-update/) with [Capawesome Cloud](https://capawesome.io/docs/cloud/live-updates/electron/) — download, verification, channels, and failed-boot rollback, exactly like on mobile. The platform itself only ships the serving primitive the plugin builds on: `services.bundles` (`setActiveBundle(dir | null)` switches the served directory in memory and reloads the app windows; `getActiveBundlePath()` returns it). The platform persists nothing and never rolls back; the plugin re-applies its active bundle in its `load()` hook. ## Electron Support Policy - **Floor**: Electron 28 (required for the runtime's serving APIs). No ceiling — the scaffold uses a caret range you control, and the platform releases only when Electron actually breaks an API it uses. - **Tested**: CI runs the example app against the latest stable Electron and the floor on every release. - Electron ships a new major roughly every 8 weeks. This platform's wide peer range means you can adopt new Electron majors immediately, without waiting for a platform release. ## Limitations Read this before committing to the platform — these are inherent trade-offs, not bugs: - **Binary size and memory.** Every app ships its own Chromium and Node: expect roughly 80–150 MB installed and a matching memory footprint. If minimal footprint is the priority, Electron is the wrong tool. - **Plugins need an electron implementation.** Only Capacitor plugins that ship an `electron` implementation (or whose web implementation is sufficient — the automatic fallback) work. iOS/Android native code does not translate. - **Native Node addons are not rebuilt automatically.** If a plugin's electron implementation depends on native Node modules, `capacitor-electron vendor` detects and reports them, but you must rebuild them against Electron's ABI yourself (e.g. `@electron/rebuild`) before packaging. - **No web-bundle OTA product included.** The platform provides the serving primitive (`services.bundles`) only; update delivery, channels, and verification are a separate product layer. - **Always pass the platform name to the Capacitor CLI.** A bare `npx cap sync` only processes iOS/Android/web; `npx cap sync electron` resolves to the `electron` npm package and silently does nothing. Use the full package name (see the note under Installation). - **Desktop is not mobile.** APIs like geolocation permission prompts, status bars, or app-store review flows have no desktop equivalent; plugins whose web implementation assumes a mobile browser may behave differently on desktop Chromium. ## FAQ ### Can I use any Capacitor plugin with this platform? Plugins with a dedicated Electron implementation or a sufficient web implementation work. Plugins that only ship iOS/Android native code do not. ### How do I update Electron in my app? Update the `electron` version in `electron/package.json` and run `npm install` there. The platform supports Electron 28 and later with no upper bound. ### Does `Capacitor.getPlatform()` return `electron`? Yes. `Capacitor.getPlatform()` returns `'electron'` and `Capacitor.isNativePlatform()` returns `true`, so you can branch platform-specific code the same way as on iOS and Android. ### Can I use Live Updates on Electron? Yes. The [Capacitor Live Update plugin](https://capawesome.io/docs/sdks/capacitor/live-update/) (8.5.0 or later) has a native Electron implementation and uses the same Capawesome Cloud app as your mobile app. See [Update Electron Apps](https://capawesome.io/docs/cloud/live-updates/electron/). ## Related Plugins - [Capacitor Live Update plugin](https://capawesome.io/docs/sdks/capacitor/live-update/) — over-the-air web-bundle updates with Capawesome Cloud, with a native Electron implementation. - [Capacitor SQLite plugin](https://capawesome.io/docs/sdks/capacitor/sqlite/) — local SQL database with a native Electron implementation. ## 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://capawesome.io/newsletter/). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-electron/blob/main/BREAKING.md). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-electron/blob/main/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-electron/blob/main/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by the OpenJS Foundation or any of its affiliates. `Electron` is a trademark of the OpenJS Foundation. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Exif Plugin Capacitor plugin to read, write and remove EXIF metadata from image files. ## Features - 📖 **Read**: Read typed EXIF tags like GPS position, camera model, and capture date. - ✍️ **Write**: Update EXIF tags in place without touching the rest of the file. - 🕵️ **Privacy**: Strip GPS positions and other sensitive metadata before uploading images. - 💎 **Lossless**: The pixel data is never re-encoded, so the image quality is not affected. - 🖼️ **HEIC support**: Read and write HEIC metadata natively, which JavaScript libraries can not do. - ⚡ **Native performance**: Files are processed natively and never loaded into the WebView memory. - 🔒 **App Store safe**: Uses only official platform APIs. - 🤝 **Compatibility**: Works alongside the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/), [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/) and [Photo Editor](https://capawesome.io/docs/sdks/capacitor/photo-editor/) 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! ## Use Cases The Exif plugin is typically used whenever an app needs to work with photo metadata, for example: - **Privacy protection**: Strip GPS positions and other sensitive metadata from photos before uploading them to a server. - **Photo organization**: Read the capture date and GPS position to sort and group photos. - **Metadata correction**: Fix a wrong capture date or add a missing GPS position without re-encoding the image. - **Camera details**: Display camera and lens information such as ISO, aperture, and exposure time in a photo detail view. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [How to Take and Edit Photos in a Capacitor App](https://capawesome.io/blog/how-to-take-and-edit-photos-in-a-capacitor-app/) ## 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-exif` 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-exif npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app’s `variables.gradle` file): - `$androidxExifInterfaceVersion` version of `androidx.exifinterface:exifinterface` (default: `1.4.2`) ### iOS No additional configuration is required for this plugin. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to read the EXIF metadata of an image, write EXIF tags to an image, and remove the EXIF metadata from an image. ### Read the EXIF metadata of an image Read typed EXIF tags such as the GPS position, camera model, and capture date from an image file. Only available on Android and iOS: ``` import { Exif } from '@capawesome/capacitor-exif'; const readExif = async () => { const { tags } = await Exif.readExif({ path: 'file:///path/to/photo.jpg', }); console.log('GPS position:', tags.gpsLatitude, tags.gpsLongitude); }; ``` ### Write EXIF tags to an image Update EXIF tags in place. Only the provided tags are updated and the pixel data is never re-encoded. Only available on Android and iOS: ``` import { Exif } from '@capawesome/capacitor-exif'; const writeExif = async () => { await Exif.writeExif({ path: 'file:///path/to/photo.jpg', tags: { dateTimeOriginal: '2026:01:15 10:30:00', gpsLatitude: 51.503364, gpsLongitude: -0.127625, }, }); }; ``` ### Remove the EXIF metadata from an image Strip all EXIF metadata from an image file in place, for example to protect the privacy of your users before an upload. By default, the orientation tag is kept so that the image is not suddenly displayed rotated. Only available on Android and iOS: ``` import { Exif } from '@capawesome/capacitor-exif'; const removeExif = async () => { await Exif.removeExif({ path: 'file:///path/to/photo.jpg', }); }; ``` ## API - [`readExif(...)`](#readexif) - [`removeExif(...)`](#removeexif) - [`writeExif(...)`](#writeexif) - [Interfaces](#interfaces) ### readExif(...) ``` readExif(options: ReadExifOptions) => Promise ``` Read the EXIF metadata of an image file. Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `ReadExifOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeExif(...) ``` removeExif(options: RemoveExifOptions) => Promise ``` Remove the EXIF metadata from an image file in place. The pixel data is never re-encoded, so the image quality is not affected. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `RemoveExifOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### writeExif(...) ``` writeExif(options: WriteExifOptions) => Promise ``` Write EXIF metadata to an image file in place. Only the provided tags are updated. All other tags remain unchanged. The pixel data is never re-encoded, so the image quality is not affected. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `WriteExifOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ReadExifResult | Prop | Type | Description | Since | | ---------- | ---------- | ------------------------------------------------- | ----- | | **`tags`** | `ExifTags` | The EXIF tags that were read from the image file. | 0.1.0 | #### ExifTags The EXIF tags of an image file. Every property is optional and only present if the tag exists in the file. | Prop | Type | Description | Since | | ---------------------- | --------- | ------------------------------------------------------------------------------------------------------------------ | ----- | | **`dateTimeOriginal`** | `string` | The date and time when the image was originally captured in the EXIF date format `YYYY:MM:DD HH:MM:SS`. | 0.1.0 | | **`exposureTime`** | `number` | The exposure time in seconds. | 0.1.0 | | **`fNumber`** | `number` | The F number (aperture) of the lens. | 0.1.0 | | **`flash`** | `boolean` | Whether or not the flash fired when the image was captured. | 0.1.0 | | **`focalLength`** | `number` | The focal length of the lens in millimeters. | 0.1.0 | | **`gpsAltitude`** | `number` | The altitude in meters above (positive) or below (negative) sea level. | 0.1.0 | | **`gpsLatitude`** | `number` | The latitude in signed decimal degrees. Positive values are north of the equator, negative values are south. | 0.1.0 | | **`gpsLongitude`** | `number` | The longitude in signed decimal degrees. Positive values are east of the prime meridian, negative values are west. | 0.1.0 | | **`iso`** | `number` | The ISO speed rating (photographic sensitivity). | 0.1.0 | | **`lensModel`** | `string` | The model of the lens. | 0.1.0 | | **`make`** | `string` | The manufacturer of the camera. | 0.1.0 | | **`model`** | `string` | The model of the camera. | 0.1.0 | | **`orientation`** | `number` | The orientation of the image as defined by the EXIF specification (1-8). | 0.1.0 | | **`pixelHeight`** | `number` | The height of the image in pixels. | 0.1.0 | | **`pixelWidth`** | `number` | The width of the image in pixels. | 0.1.0 | | **`software`** | `string` | The name of the software that was used to create or edit the image. | 0.1.0 | #### ReadExifOptions | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path to the image file to read the EXIF metadata from. Local file paths and `file://` URIs are supported. | 0.1.0 | #### RemoveExifOptions | Prop | Type | Description | Default | Since | | --------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`keepOrientation`** | `boolean` | Whether or not to keep the orientation tag when removing the EXIF metadata. This is enabled by default so that images are not suddenly displayed rotated after the metadata has been removed. | `true` | 0.1.0 | | **`path`** | `string` | The path to the image file to remove the EXIF metadata from. Local file paths and `file://` URIs are supported. | | 0.1.0 | #### WriteExifOptions | Prop | Type | Description | Since | | ---------- | ------------------ | -------------------------------------------------------------------------------------------------------------- | ----- | | **`path`** | `string` | The path to the image file to write the EXIF metadata to. Local file paths and `file://` URIs are supported. | 0.1.0 | | **`tags`** | `WritableExifTags` | The EXIF tags to write to the image file. Only the provided tags are updated. All other tags remain unchanged. | 0.1.0 | #### WritableExifTags The writable subset of the EXIF tags. | Prop | Type | Description | Since | | ---------------------- | --------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`dateTimeOriginal`** | `string` | The date and time when the image was originally captured in the EXIF date format `YYYY:MM:DD HH:MM:SS`. | 0.1.0 | | **`exposureTime`** | `number` | The exposure time in seconds. | 0.1.0 | | **`fNumber`** | `number` | The F number (aperture) of the lens. | 0.1.0 | | **`flash`** | `boolean` | Whether or not the flash fired when the image was captured. | 0.1.0 | | **`focalLength`** | `number` | The focal length of the lens in millimeters. | 0.1.0 | | **`gpsAltitude`** | `number` | The altitude in meters above (positive) or below (negative) sea level. | 0.1.0 | | **`gpsLatitude`** | `number` | The latitude in signed decimal degrees. Positive values are north of the equator, negative values are south. Must be provided together with `gpsLongitude`. | 0.1.0 | | **`gpsLongitude`** | `number` | The longitude in signed decimal degrees. Positive values are east of the prime meridian, negative values are west. Must be provided together with `gpsLatitude`. | 0.1.0 | | **`iso`** | `number` | The ISO speed rating (photographic sensitivity). | 0.1.0 | | **`lensModel`** | `string` | The model of the lens. | 0.1.0 | | **`make`** | `string` | The manufacturer of the camera. | 0.1.0 | | **`model`** | `string` | The model of the camera. | 0.1.0 | | **`orientation`** | `number` | The orientation of the image as defined by the EXIF specification (1-8). | 0.1.0 | | **`software`** | `string` | The name of the software that was used to create or edit the image. | 0.1.0 | ## Format Support The plugin can read EXIF metadata from any image format that the platform supports. Writing and removing EXIF metadata is a lossless in-place operation that is only supported for the following formats: | Format | Read (Android) | Read (iOS) | Write & Remove (Android) | Write & Remove (iOS) | | --------- | -------------- | ---------- | ------------------------ | -------------------- | | JPEG | ✅ | ✅ | ✅ | ✅ | | PNG | ✅ | ✅ | ✅ | ❌ | | WebP | ✅ | ✅ | ✅ | ❌ | | HEIC/HEIF | ✅ | ✅ | ❌ | ✅ | | DNG/RAW | ✅ | ✅ | ❌ | ❌ | Calling `writeExif(...)` or `removeExif(...)` with an unsupported format rejects with the `UNSUPPORTED_FORMAT` error code. ## Lossless Metadata Writes A metadata plugin that degrades the image quality is broken. This plugin therefore guarantees that the pixel data is never decoded or re-encoded: - On **Android**, the [`ExifInterface`](https://developer.android.com/reference/androidx/exifinterface/media/ExifInterface) library rewrites only the metadata segments of the file in place. - On **iOS**, the [`CGImageDestinationCopyImageSource`]() API copies the compressed image data unchanged and only replaces the metadata. ## Privacy: Strip Metadata Before Upload Images taken with a smartphone usually contain the exact GPS position, the device model, and the capture date. To protect the privacy of your users, remove this metadata before uploading images to a server: ``` import { Exif } from '@capawesome/capacitor-exif'; const prepareUpload = async (path: string) => { // Remove the GPS position and all other EXIF metadata. // The orientation is kept so that the image is not displayed rotated. await Exif.removeExif({ path }); }; ``` ## Limitations ### Android - Vendor-specific maker notes and other unknown tags may survive `removeExif(...)` since the `ExifInterface` library can only remove known tags. ### iOS - HEIC files store the orientation at the container level, so the `orientation` tag can not be changed and is always kept, even with `keepOrientation` set to `false`. - HEIC files may keep the `software` and `dateTimeOriginal` tags after `removeExif(...)`. GPS data and device identity tags (`make`, `model`, `lensModel`) are always removed. ## FAQ ### How is this plugin different from other similar plugins? It reads, writes and removes EXIF metadata directly on the image file on Android and iOS, updating tags in place so the pixel data is never re-encoded and the image quality is untouched. It reads and writes HEIC metadata natively, processes files natively without loading them into WebView memory, and uses only official platform APIs, so it stays App Store safe. The API is fully typed, ships for both CocoaPods and Swift Package Manager, and is kept current with the latest Capacitor version. ### Which platforms are supported by this plugin? The plugin supports Android and iOS. All methods (`readExif`, `writeExif` and `removeExif`) are only available on these two platforms, since the files are processed natively and never loaded into the WebView memory. ### Does writing or removing EXIF metadata affect the image quality? No. Writing and removing EXIF metadata is a lossless in-place operation. The pixel data is never decoded or re-encoded, so the image quality is not affected. See [Lossless Metadata Writes](#lossless-metadata-writes) for details on how this is implemented on each platform. ### Which image formats are supported? Reading EXIF metadata works with any image format that the platform supports, including JPEG, PNG, WebP, HEIC/HEIF, and DNG/RAW. Writing and removing metadata is only supported for certain formats, which differ between Android and iOS (see [Format Support](#format-support)). Calling `writeExif(...)` or `removeExif(...)` with an unsupported format rejects with the `UNSUPPORTED_FORMAT` error code. ### Why is the orientation tag still present after calling `removeExif`? The `keepOrientation` option is enabled by default so that images are not suddenly displayed rotated after the metadata has been removed. You can set it to `false` to also remove the orientation tag. Note that HEIC files on iOS store the orientation at the container level, so it is always kept there. ### How do I remove the GPS position from a photo before uploading it? Call `removeExif(...)` with the path to the image file. This strips the GPS position along with all other EXIF metadata in place, without affecting the image quality. See [Privacy: Strip Metadata Before Upload](#privacy-strip-metadata-before-upload) for an example. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/): Compress images in formats like PNG, JPEG, and WebP. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Pick the image files whose metadata you want to read or edit. - [Photo Editor](https://capawesome.io/docs/sdks/capacitor/photo-editor/): Let the user edit a photo. - [Photo Manipulator](https://capawesome.io/docs/sdks/capacitor/photo-manipulator/): Headless image transforms like crop, resize, rotate, and format conversion. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/exif/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/exif/LICENSE). # Capacitor Facebook Sign-In Plugin Unofficial Capacitor plugin to sign-in with Facebook.[1](#fn:1) ## Features The Capacitor Facebook Sign-In plugin is a comprehensive Facebook authentication solution for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 🔐 **Authentication**: Sign in users with their Facebook account. - 🔑 **Access Token**: Retrieve the access token for the Facebook Graph API. - 👤 **User Profile**: Retrieve the user's email, display name, profile picture, and more. - 🕵️ **Limited Login**: Support for Limited Login on iOS to sign in users without tracking. - 🛡️ **Nonce Support**: Prevent replay attacks with a custom nonce on iOS. - 🪶 **Lightweight**: Just a single dependency and zero unnecessary bloat. - 🚨 **Error Codes**: Provides detailed error codes for better error handling. - 🤝 **Compatibility**: Works alongside the [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/) and [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/) 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! ## Use Cases The Facebook Sign-In plugin is typically used wherever users should sign in with their existing Facebook account, for example: - **Social login**: Let users sign in to your app with their Facebook account instead of creating a new password. - **Backend authentication**: Send the authentication token (JWT) to your backend to verify the user's identity. - **Graph API access**: Use the access token to request data from the Facebook Graph API on behalf of the user. - **Privacy-friendly sign-in on iOS**: Use Limited Login to sign in users without tracking and without App Tracking Transparency consent. - **Profile pre-filling**: Use the user's email, display name, and profile picture to pre-fill their profile in your app. ## 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-facebook-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-facebook-sign-in npx cap sync ``` This plugin requires a Facebook app. If you don't have one yet, create it in the [Meta App Dashboard](https://developers.facebook.com/apps/) and add the **Facebook Login** product. You can find your **App ID** and **Client Token** in the app settings. ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$facebookLoginVersion` version of `com.facebook.android:facebook-login` (default: `18.3.0`) #### Resources Add the following string resources to the `android/app/src/main/res/values/strings.xml` file: ``` YOUR_APP_ID YOUR_CLIENT_TOKEN ``` Replace `YOUR_APP_ID` with your Facebook App ID and `YOUR_CLIENT_TOKEN` with your Facebook Client Token. #### Metadata Add the following elements inside the `application` tag of the `android/app/src/main/AndroidManifest.xml` file: ``` ``` ### iOS Add the following keys to the `ios/App/App/Info.plist` file: ``` FacebookAppID YOUR_APP_ID FacebookClientToken YOUR_CLIENT_TOKEN FacebookDisplayName YOUR_APP_NAME LSApplicationQueriesSchemes fbapi fb-messenger-share-api ``` Replace `YOUR_APP_ID` with your Facebook App ID, `YOUR_CLIENT_TOKEN` with your Facebook Client Token, and `YOUR_APP_NAME` with the display name of your Facebook app. You also need to add the URL scheme for your Facebook App ID to the `ios/App/App/Info.plist` file: ``` CFBundleURLTypes CFBundleURLSchemes fbYOUR_APP_ID ``` Replace `YOUR_APP_ID` with your Facebook App ID, so that the URL scheme is your App ID prefixed with `fb` (e.g. `fb1234567890123456`). ## Configuration No configuration required for this plugin. ## Usage The following examples show how to initialize the plugin, sign in a user, sign in with Limited Login, get the current access token, and sign out a user. ### Initialize the plugin Call `initialize(...)` once before all other methods. On Android and iOS, the App ID is usually read from the native configuration, so the `appId` option is only required on Web: ``` import { FacebookSignIn } from '@capawesome/capacitor-facebook-sign-in'; const initialize = async () => { await FacebookSignIn.initialize({ appId: '1234567890123456', }); }; ``` ### Sign in a user Start the Facebook Sign-In flow and retrieve the access token and the user's profile: ``` import { FacebookSignIn } from '@capawesome/capacitor-facebook-sign-in'; const signIn = async () => { const result = await FacebookSignIn.signIn(); console.log(result.accessToken?.token); console.log(result.profile.id); console.log(result.profile.name); console.log(result.profile.email); }; ``` ### Sign in with Limited Login Use Limited Login to sign in users without tracking. Instead of an access token, an authentication token (JWT) is returned that can be verified on your backend. Provide a nonce to prevent replay attacks. Only available on iOS: ``` import { FacebookSignIn } from '@capawesome/capacitor-facebook-sign-in'; const signInWithLimitedLogin = async () => { const result = await FacebookSignIn.signIn({ limitedLogin: true, nonce: 'YOUR_NONCE', }); console.log(result.authenticationToken); }; ``` ### Get the current access token Retrieve the current access token, for example to check whether a user is still signed in. The result is `null` if no user is signed in or the access token has expired: ``` import { FacebookSignIn } from '@capawesome/capacitor-facebook-sign-in'; const getCurrentAccessToken = async () => { const { accessToken } = await FacebookSignIn.getCurrentAccessToken(); console.log(accessToken?.token); }; ``` ### Sign out a user Sign out the current user: ``` import { FacebookSignIn } from '@capawesome/capacitor-facebook-sign-in'; const signOut = async () => { await FacebookSignIn.signOut(); }; ``` ## API - [`getCurrentAccessToken()`](#getcurrentaccesstoken) - [`initialize(...)`](#initialize) - [`signIn(...)`](#signin) - [`signOut()`](#signout) - [Interfaces](#interfaces) ### getCurrentAccessToken() ``` getCurrentAccessToken() => Promise ``` Get the current access token. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Initialize the Facebook 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 Facebook Sign-In flow. | Param | Type | | ------------- | --------------- | | **`options`** | `SignInOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### signOut() ``` signOut() => Promise ``` Sign out the current user. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetCurrentAccessTokenResult | Prop | Type | Description | Since | | ----------------- | ------------- | ----------- | ------------------------------------------------------------------------------------------------- | | **`accessToken`** | \`AccessToken | null\` | The current access token. This is `null` if no user is signed in or the access token has expired. | #### AccessToken | Prop | Type | Description | Since | | ----------------- | ---------- | ----------------------------------------------------------------------------------- | ----- | | **`expiresAt`** | `number` | The timestamp (in milliseconds since the Unix epoch) when the access token expires. | 0.1.0 | | **`permissions`** | `string[]` | The permissions granted to the access token. | 0.1.0 | | **`token`** | `string` | The access token string. | 0.1.0 | | **`userId`** | `string` | The unique identifier of the user's Facebook account. | 0.1.0 | #### InitializeOptions | Prop | Type | Description | Since | | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`appId`** | `string` | The Facebook App ID from the Meta App Dashboard. On Android and iOS, this overrides the value from the native configuration and is usually not needed. **Attention**: This option is required on Web. | 0.1.0 | | **`clientToken`** | `string` | The Facebook Client Token from the Meta App Dashboard. This overrides the value from the native configuration and is usually not needed. Only available on Android and iOS. | 0.1.0 | #### SignInResult | Prop | Type | Description | Since | | ------------------------- | ------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | | **`accessToken`** | \`AccessToken | null\` | The access token for accessing the Facebook Graph API. This is `null` on iOS when Limited Login is used. | | **`authenticationToken`** | \`string | null\` | The authentication token (JWT) returned by Facebook. This token can be sent to your backend for verification. Only available on Android and iOS. | | **`profile`** | `Profile` | The profile of the signed-in user. | 0.1.0 | #### Profile | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------- | ----------------------------------------------------------------------------------- | | **`email`** | \`string | null\` | The user's email address. This is `null` if the `email` permission was not granted. | | **`id`** | `string` | The unique identifier of the user's Facebook account. | 0.1.0 | | **`imageUrl`** | \`string | null\` | The URL of the user's profile picture. | | **`name`** | \`string | null\` | The user's display name (full name). | #### SignInOptions | Prop | Type | Description | Default | Since | | ------------------ | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- | ----- | | **`limitedLogin`** | `boolean` | Whether or not to use [Limited Login](https://developers.facebook.com/docs/facebook-login/limited-login/) instead of classic login. With Limited Login, no access token is returned. Instead, an authentication token (JWT) is returned that can be verified on your backend. Only available on iOS. | `false` | 0.1.0 | | **`nonce`** | `string` | A nonce to prevent replay attacks. The nonce is included in the authentication token and can be verified on your backend. Only available on iOS. | | 0.1.0 | | **`permissions`** | `string[]` | The permissions to request from the user. | `["public_profile", "email"]` | 0.1.0 | ## Limited Login On iOS, the Facebook SDK distinguishes between classic login and [Limited Login](https://developers.facebook.com/docs/facebook-login/limited-login/). With Limited Login, no data is shared with Meta that could be used for tracking, so no App Tracking Transparency consent is required. However, no access token for the Facebook Graph API is returned. Instead, an authentication token (JWT) is returned, which can be verified on your backend. Keep the following in mind: - Set the `limitedLogin` option to `true` to use Limited Login. - If the user has not granted the App Tracking Transparency permission, the Facebook SDK may fall back to Limited Login even if classic login was requested. In this case, the `accessToken` property in the sign-in result is `null`. - Provide a `nonce` option and verify it on your backend to prevent replay attacks. ## Security This plugin handles the sign-in flow and returns tokens to your app. To keep your integration secure, be aware of the following: - **Server-side token verification is required.** The `authenticationToken` (JWT) is **not** verified client-side. Your backend **must** verify the JWT signature using [Facebook's public keys](https://www.facebook.com/.well-known/oauth/openid/jwks/) before trusting any claims. Never use client-side token data for authorization decisions without server-side verification. - **Validate the access token on your backend.** If you use the `accessToken`, your backend should validate it with the [Facebook Graph API](https://developers.facebook.com/docs/facebook-login/guides/advanced/manual-flow#checktoken) before trusting it. ## FAQ ### How is this plugin different from other similar plugins? It covers the full Facebook sign-in flow across Android, iOS, and the Web, returning both the access token for the Graph API and the signed-in user's profile through a fully typed API. On iOS it supports Limited Login with nonce verification, so you can sign users in without tracking and without App Tracking Transparency consent, and it works alongside the Apple and Google Sign-In plugins when you want to offer several options. Detailed error codes and support for the latest Capacitor version round it out. If you only ever need a single provider, a lighter setup can be enough; when you need Facebook handled thoroughly, this plugin is built for exactly that. ### Do I need a Facebook app to use this plugin? Yes, this plugin requires a Facebook app. You can create one in the [Meta App Dashboard](https://developers.facebook.com/apps/) and add the **Facebook Login** product to it. The **App ID** and **Client Token** from the app settings are needed for the platform-specific configuration, see the [Installation](#installation) section. ### What is Limited Login and when should I use it? Limited Login is an alternative sign-in mode of the Facebook SDK on iOS. With Limited Login, no data is shared with Meta that could be used for tracking, so no App Tracking Transparency consent is required. However, no access token for the Facebook Graph API is returned. Instead, an authentication token (JWT) is returned, which can be verified on your backend. Set the `limitedLogin` option to `true` to use it. ### Why is the `accessToken` property `null` after signing in on iOS? This happens when Limited Login is used. If the user has not granted the App Tracking Transparency permission, the Facebook SDK may fall back to Limited Login even if classic login was requested. In this case, use the `authenticationToken` (JWT) instead and verify it on your backend. ### How do I verify the sign-in on my backend? Your backend must verify the JWT signature of the `authenticationToken` using [Facebook's public keys](https://www.facebook.com/.well-known/oauth/openid/jwks/) before trusting any claims. If you use the `accessToken`, your backend should validate it with the Facebook Graph API before trusting it. See the [Security](#security) section for more details. ### Can I use this plugin together with other sign-in plugins? Yes, the plugin works alongside the [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/) and [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/) plugins, so you can offer multiple sign-in options in the same app. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/): Sign in users with their Apple account. - [Biometrics](https://capawesome.io/docs/sdks/capacitor/biometrics/): Request biometric authentication such as face or fingerprint recognition. - [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/): Sign in users with their Google account. - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/): Communicate with any OAuth 2.0 and OpenID Connect provider. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/facebook-sign-in/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/facebook-sign-in/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Meta Platforms, Inc. or any of its affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor File Compressor Plugin Capacitor plugin for efficient file compression with support for image formats like PNG, JPEG, and WebP. ## Features The Capacitor File Compressor plugin offers one of the most complete file compression solutions for Capacitor apps. 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/docs/sdks/capacitor/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! ## Use Cases The File Compressor plugin is typically used to shrink images before they leave the device, for example: - **Image uploads**: Compress photos before uploading them to a server to save bandwidth and speed up uploads. - **Storage savings**: Reduce the size of images before storing them on the device or in the cloud. - **Thumbnails and previews**: Use the `width` and `height` options to create smaller versions of images for lists and galleries. - **Format conversion**: Convert PNG images to smaller JPEG or WebP files using the `mimeType` option. ## 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 The following example shows how to compress an image while controlling its quality and dimensions. ### Compress an image Compress a PNG, JPEG, or WebP image by passing its path (Android and iOS) or blob (Web). Use the `quality` option to control the compression level and the optional `width` and `height` options to resize the image: ``` 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 | ## FAQ ### Which image formats are supported? The plugin can compress PNG, JPEG, and WebP images. The output format is controlled by the `mimeType` option: on Android and Web, `image/jpeg` and `image/webp` are supported, while on iOS only `image/jpeg` is supported. The default output format is `image/jpeg`. ### How do I control the compression quality? Use the `quality` option, which accepts a value from `0.0` to `1.0`. The value `0.0` represents the maximum compression (or lowest quality) while `1.0` represents the least compression (or best quality). The default is `0.6`. You can additionally resize the image with the `width` and `height` options to reduce the file size even further. ### Does the plugin preserve EXIF metadata? No, the EXIF data of the image is lost during compression. If you need the metadata, read it before compressing the image and store it separately. ### What does `compressImage` return on each platform? On Android and iOS, the result contains the `path` of the compressed file. On Web, the result contains a `Blob` instance instead. Take this into account if you share code across platforms, for example when uploading the compressed image to a server. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Let the user select the images to compress from the file system or gallery. - [Photo Manipulator](https://capawesome.io/docs/sdks/capacitor/photo-manipulator/): Headless image transforms like crop, resize, rotate, flip and format conversion. - [Zip](https://capawesome.io/docs/sdks/capacitor/zip/): Zip and unzip files and directories with support for encryption. ## 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/). ## 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). # Capacitor File Manager Plugin Capacitor plugin to manage files and directories on Android, iOS and Web. Persist access to user-picked folders, run copy, move and delete operations with progress and cancellation, calculate checksums, and inspect device and app storage. ## Features The Capacitor File Manager plugin works with the user's own storage, not just your app's sandbox. Here are some of the key features: - 📂 **Persistent Directory Access**: Let the user pick a folder once with the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) plugin and keep read and write access to it across app launches — Play-policy-compliant on Android, sandbox-correct on iOS. - 🔗 **URI-based API**: Every method works directly with file URIs. Construct them with `getUri(...)` from well-known directories or any parent URI. - 📄 **Files**: Read, write, append, truncate, copy, move and delete files with constant memory usage, including random access reads and writes for large files. - 🗂️ **Directories**: Create, read, copy, move, delete and clear directories, and calculate their total size — with rich metadata and paging. - 📊 **Progress & Cancellation**: Directory operations report progress events and can be canceled at any time. - 🔢 **Checksums**: Calculate MD5, SHA-1 and SHA-256 checksums of files of any size with constant memory usage. - 🧲 **Blob Reads**: Read large files as a `Blob` without base64 overhead via `readFileAsBlob(...)`. - 💾 **Storage Info**: Query the total and free storage space of the device, the storage usage of your app, and clear the app's cache. - 🌐 **Web Support**: A real web implementation on top of the Origin Private File System with actual directories, streaming and random access — not base64 blobs in IndexedDB. - ⚠️ **Error Codes**: Every runtime failure rejects with a documented error code, so you can branch on it instead of parsing messages. - 🤝 **Compatibility**: Works hand in hand with the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/), [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) and [File Transfer](https://capawesome.io/docs/sdks/capacitor/file-transfer/) 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! ## Use Cases The File Manager plugin is typically used whenever an app needs durable access to user-picked folders or manager-grade file operations, for example: - **Document management apps**: Let the user pick a folder once and organize, rename and sort documents in it across app launches. - **Download and media managers**: Check the free storage space before a download, verify the result with a checksum, and move it into a user-visible folder. - **Backup and export**: Export app data into a folder the user picked, with progress reporting and cancellation for large exports. - **Large file processing**: Read file headers with random access, stream large files into your web code as a `Blob`, and truncate or append without rewriting the whole file. - **Cache management**: Show the user how much space your app occupies and offer a one-tap cache cleanup. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [Announcing the Capacitor File Manager Plugin](https://capawesome.io/blog/announcing-the-capacitor-file-manager-plugin/): What this plugin adds on top of `@capacitor/filesystem`: persisted folder access, directory operations with progress and cancellation, checksums, and a web implementation on the Origin Private File System. - [Android Scoped Storage in Capacitor Apps, Explained](https://capawesome.io/blog/android-scoped-storage-in-capacitor-apps/): Which directories still work without permissions, and how this plugin wraps the Storage Access Framework in persisted folder access. ## 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-manager` 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-manager npx cap sync ``` ### Android This plugin requires no storage permissions. The app's sandbox directories are accessible without any permission, and user-visible folders are accessed through persisted directories, which are granted by the user in the system file picker. #### 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.** { *; } ``` ### Web On the web platform, files are stored in the [Origin Private File System](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API/Origin_private_file_system). Persisted directories and checksums are not available on the web. #### Data persistence The Origin Private File System is subject to the browser's storage eviction rules. By default, storage is granted on a best-effort basis: Chromium-based browsers and Firefox only evict data under storage pressure, but Safari deletes all script-writable storage after seven days without user interaction if the app runs in a browser tab. Web apps added to the home screen are exempt from this rule. To reduce the risk of eviction, request persistent storage with [`navigator.storage.persist()`](https://developer.mozilla.org/en-US/docs/Web/API/StorageManager/persist) at a moment of meaningful user engagement (for example, after sign-in or after the user has saved data for the first time). Note that persistence is granted at the browser's discretion and reduces the risk of eviction, but is not an absolute guarantee. ## Configuration No configuration required for this plugin. ## Usage ### Write and read a file in the app's sandbox ``` import { Directory, Encoding, FileManager } from '@capawesome-team/capacitor-file-manager'; const writeAndReadFile = async () => { const { uri } = await FileManager.getUri({ path: 'notes/todo.txt', directory: Directory.Data, }); await FileManager.writeFile({ uri, data: 'Hello, World!', encoding: Encoding.Utf8, recursive: true, }); const { data } = await FileManager.readFile({ uri, encoding: Encoding.Utf8 }); return data; }; ``` ### Persist access to a picked directory This requires version `8.1.0` or later of the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) plugin, since `pickDirectory()` only returns a bookmark from that version on. ``` import { FileManager } from '@capawesome-team/capacitor-file-manager'; import { FilePicker } from '@capawesome/capacitor-file-picker'; const persistDirectoryAccess = async () => { const result = await FilePicker.pickDirectory(); const { directory } = await FileManager.persistDirectoryAccess({ uri: result.path, bookmark: result.bookmark, }); return directory; }; const getPersistedDirectories = async () => { // Call this on app start and use the returned URIs // instead of storing them yourself. const { directories } = await FileManager.getPersistedDirectories(); return directories; }; ``` ### Work inside a persisted directory ``` import { Directory, FileManager } from '@capawesome-team/capacitor-file-manager'; const exportIntoPickedDirectory = async (directoryUri: string) => { // Construct the URI of a file inside the picked directory. const { uri: targetUri } = await FileManager.getUri({ path: 'exports/report.pdf', parentUri: directoryUri, }); // Copy a file from the app's sandbox into the picked directory. const { uri: sourceUri } = await FileManager.getUri({ path: 'report.pdf', directory: Directory.Cache, }); const { uri } = await FileManager.copyFile({ uri: sourceUri, toUri: targetUri }); return uri; }; const listPickedDirectory = async (directoryUri: string) => { const { entries } = await FileManager.readDirectory({ uri: directoryUri }); return entries; }; ``` ### Copy a directory with progress and cancellation ``` import { FileManager } from '@capawesome-team/capacitor-file-manager'; const copyDirectoryWithProgress = async (uri: string, toUri: string) => { await FileManager.addListener('operationProgress', (event) => { console.log(`Processed ${event.processedFiles} of ${event.totalFiles} files`); }); await FileManager.copyDirectory({ uri, toUri, id: 'my-copy-operation' }); }; const cancelCopy = async () => { await FileManager.cancelOperationById({ id: 'my-copy-operation' }); }; ``` ### Read a large file as a Blob ``` import { FileManager } from '@capawesome-team/capacitor-file-manager'; const readLargeFile = async (uri: string) => { // Streams the file without base64 overhead. const { blob } = await FileManager.readFileAsBlob({ uri }); return blob; }; ``` ### Verify a file with a checksum ``` import { ChecksumAlgorithm, FileManager } from '@capawesome-team/capacitor-file-manager'; const verifyFile = async (uri: string, expectedChecksum: string) => { const { checksum } = await FileManager.getFileChecksum({ uri, algorithm: ChecksumAlgorithm.Sha256, }); return checksum === expectedChecksum; }; ``` ### Check the storage space ``` import { FileManager } from '@capawesome-team/capacitor-file-manager'; const checkStorage = async () => { const { freeBytes } = await FileManager.getDeviceStorageInfo(); const { cacheBytes } = await FileManager.getAppStorageInfo(); if (cacheBytes > 100_000_000) { await FileManager.clearCache(); } return freeBytes; }; ``` ## API - [`appendFile(...)`](#appendfile) - [`cancelOperationById(...)`](#canceloperationbyid) - [`clearCache()`](#clearcache) - [`clearDirectory(...)`](#cleardirectory) - [`copyDirectory(...)`](#copydirectory) - [`copyFile(...)`](#copyfile) - [`createDirectory(...)`](#createdirectory) - [`deleteDirectory(...)`](#deletedirectory) - [`deleteFile(...)`](#deletefile) - [`exists(...)`](#exists) - [`getAppStorageInfo()`](#getappstorageinfo) - [`getDeviceStorageInfo()`](#getdevicestorageinfo) - [`getDirectorySize(...)`](#getdirectorysize) - [`getFileChecksum(...)`](#getfilechecksum) - [`getMetadata(...)`](#getmetadata) - [`getPersistedDirectories()`](#getpersisteddirectories) - [`getUri(...)`](#geturi) - [`moveDirectory(...)`](#movedirectory) - [`moveFile(...)`](#movefile) - [`persistDirectoryAccess(...)`](#persistdirectoryaccess) - [`readDirectory(...)`](#readdirectory) - [`readFile(...)`](#readfile) - [`readFileAsBlob(...)`](#readfileasblob) - [`releaseDirectoryAccess(...)`](#releasedirectoryaccess) - [`truncateFile(...)`](#truncatefile) - [`writeFile(...)`](#writefile) - [`addListener('operationProgress', ...)`](#addlisteneroperationprogress-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Enums](#enums) ### appendFile(...) ``` appendFile(options: AppendFileOptions) => Promise ``` Append data to a file. If the file does not exist, it is created. | Param | Type | | ------------- | ------------------- | | **`options`** | `AppendFileOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### cancelOperationById(...) ``` cancelOperationById(options: CancelOperationByIdOptions) => Promise ``` Cancel a running directory operation. The promise of the canceled operation is rejected with the `OPERATION_CANCELED` error code. Files that were already processed remain in place. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `CancelOperationByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### clearCache() ``` clearCache() => Promise ``` Delete the contents of the app's cache directory. **Since:** 0.0.1 ______________________________________________________________________ ### clearDirectory(...) ``` clearDirectory(options: ClearDirectoryOptions) => Promise ``` Delete the contents of a directory but keep the directory itself. Unlike deleting and recreating the directory, this preserves persisted access to the directory. | Param | Type | | ------------- | ----------------------- | | **`options`** | `ClearDirectoryOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### copyDirectory(...) ``` copyDirectory(options: CopyDirectoryOptions) => Promise ``` Copy a directory recursively. Reports progress via the `operationProgress` event and can be canceled with `cancelOperationById(...)`. On **Android**, the operation fails with the `WRITE_FAILED` error code if a document provider returns an entry name that cannot be used as a file name (e.g. a name containing `/`). | Param | Type | | ------------- | ---------------------- | | **`options`** | `CopyDirectoryOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### copyFile(...) ``` copyFile(options: CopyFileOptions) => Promise ``` Copy a file. The file is copied natively with constant memory usage. | Param | Type | | ------------- | ----------------- | | **`options`** | `CopyFileOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### createDirectory(...) ``` createDirectory(options: CreateDirectoryOptions) => Promise ``` Create a new directory. | Param | Type | | ------------- | ------------------------ | | **`options`** | `CreateDirectoryOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### deleteDirectory(...) ``` deleteDirectory(options: DeleteDirectoryOptions) => Promise ``` Delete a directory. Reports progress via the `operationProgress` event and can be canceled with `cancelOperationById(...)`. | Param | Type | | ------------- | ------------------------ | | **`options`** | `DeleteDirectoryOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### deleteFile(...) ``` deleteFile(options: DeleteFileOptions) => Promise ``` Delete a file. | Param | Type | | ------------- | ------------------- | | **`options`** | `DeleteFileOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### exists(...) ``` exists(options: ExistsOptions) => Promise ``` Check whether a file or directory exists. | Param | Type | | ------------- | --------------- | | **`options`** | `ExistsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getAppStorageInfo() ``` getAppStorageInfo() => Promise ``` Get the storage usage of the app itself. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getDeviceStorageInfo() ``` getDeviceStorageInfo() => Promise ``` Get the total and free storage space of the device. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getDirectorySize(...) ``` getDirectorySize(options: GetDirectorySizeOptions) => Promise ``` Get the total size of a directory and all of its contents. | Param | Type | | ------------- | ------------------------- | | **`options`** | `GetDirectorySizeOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getFileChecksum(...) ``` getFileChecksum(options: GetFileChecksumOptions) => Promise ``` Calculate the checksum of a file. The file is processed in a streaming manner with constant memory usage, so it works for files of any size. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `GetFileChecksumOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getMetadata(...) ``` getMetadata(options: GetMetadataOptions) => Promise ``` Get the metadata of a file or directory. | Param | Type | | ------------- | -------------------- | | **`options`** | `GetMetadataOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getPersistedDirectories() ``` getPersistedDirectories() => Promise ``` Get all directories with persisted access. Stale entries are refreshed and directories whose document no longer exists are released automatically. Call this method on app start and use the returned URIs instead of storing them yourself, since they may change between app launches. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getUri(...) ``` getUri(options: GetUriOptions) => Promise ``` Construct the URI of a file or directory. Provide either `directory` to resolve the path in a well-known sandbox directory or `parentUri` to resolve the path relative to any directory URI. The file or directory does not need to exist. | Param | Type | | ------------- | --------------- | | **`options`** | `GetUriOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### moveDirectory(...) ``` moveDirectory(options: MoveDirectoryOptions) => Promise ``` Move a directory. Reports progress via the `operationProgress` event and can be canceled with `cancelOperationById(...)`. On **Android**, the operation fails with the `WRITE_FAILED` error code if a document provider returns an entry name that cannot be used as a file name (e.g. a name containing `/`). | Param | Type | | ------------- | ---------------------- | | **`options`** | `MoveDirectoryOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### moveFile(...) ``` moveFile(options: MoveFileOptions) => Promise ``` Move a file. A move within the same directory is performed as a rename. | Param | Type | | ------------- | ----------------- | | **`options`** | `MoveFileOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### persistDirectoryAccess(...) ``` persistDirectoryAccess(options: PersistDirectoryAccessOptions) => Promise ``` Persist access to a directory across app launches. Pass the values returned by `FilePicker.pickDirectory()` of the Capawesome File Picker plugin. Afterwards, the URIs of the persisted directory and its contents are accepted by every method of this plugin. Requires version `8.1.0` or later of the Capawesome File Picker plugin. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `PersistDirectoryAccessOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### readDirectory(...) ``` readDirectory(options: ReadDirectoryOptions) => Promise ``` Read the entries of a directory. | Param | Type | | ------------- | ---------------------- | | **`options`** | `ReadDirectoryOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### readFile(...) ``` readFile(options: ReadFileOptions) => Promise ``` Read data from a file. Use `offset` and `length` to read a byte range of a large file. **Attention**: To read a whole large file, use `readFileAsBlob(...)` instead, since it streams the file and avoids out-of-memory (OOM) issues. The `readFile(...)` method reads the whole file into memory and encodes it as base64. | Param | Type | | ------------- | ----------------- | | **`options`** | `ReadFileOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### readFileAsBlob(...) ``` readFileAsBlob(options: ReadFileAsBlobOptions) => Promise ``` Read a file as a `Blob`. This is the recommended way to read large files. The file is streamed without base64 encoding overhead. | Param | Type | | ------------- | ----------------------- | | **`options`** | `ReadFileAsBlobOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### releaseDirectoryAccess(...) ``` releaseDirectoryAccess(options: ReleaseDirectoryAccessOptions) => Promise ``` Release persisted access to a directory. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `ReleaseDirectoryAccessOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### truncateFile(...) ``` truncateFile(options: TruncateFileOptions) => Promise ``` Truncate a file to a given size. | Param | Type | | ------------- | --------------------- | | **`options`** | `TruncateFileOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### writeFile(...) ``` writeFile(options: WriteFileOptions) => Promise ``` Write data to a file. If the file does not exist, it is created. If no `position` is provided, the existing content is overwritten. | Param | Type | | ------------- | ------------------ | | **`options`** | `WriteFileOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('operationProgress', ...) ``` addListener(eventName: 'operationProgress', listenerFunc: (event: OperationProgressEvent) => void) => Promise ``` Called when a directory operation reports progress. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'operationProgress'` | | **`listenerFunc`** | `(event: OperationProgressEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### AppendFileOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | ---------------------------------- | ----------------- | ----- | | **`data`** | `string` | The data to append to the file. | | 0.0.1 | | **`encoding`** | `Encoding` | The encoding of the `data` option. | `Encoding.Base64` | 0.0.1 | | **`uri`** | `string` | The URI of the file. | | 0.0.1 | #### CancelOperationByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------ | ----- | | **`id`** | `string` | The identifier of the operation to cancel. | 0.0.1 | #### ClearDirectoryOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------- | ----- | | **`uri`** | `string` | The URI of the directory. | 0.0.1 | #### CopyDirectoryResult | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`uri`** | `string` | The URI of the created directory. | 0.0.1 | #### CopyDirectoryOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | A unique identifier for this operation. It can be used to cancel the operation and to correlate `operationProgress` events. | 0.0.1 | | **`toUri`** | `string` | The URI of the directory to copy to. | 0.0.1 | | **`uri`** | `string` | The URI of the directory to copy. | 0.0.1 | #### CopyFileResult | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`uri`** | `string` | The URI of the created file. This may differ from the requested URI, for example if the document provider had to rename the file to avoid a collision. | 0.0.1 | #### CopyFileOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------- | ----- | | **`toUri`** | `string` | The URI of the file to copy to. | 0.0.1 | | **`uri`** | `string` | The URI of the file to copy. | 0.0.1 | #### CreateDirectoryResult | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`uri`** | `string` | The URI of the created directory. | 0.0.1 | #### CreateDirectoryOptions | Prop | Type | Description | Default | Since | | --------------- | --------- | ---------------------------------------------------- | ------- | ----- | | **`recursive`** | `boolean` | Whether or not to create missing parent directories. | `true` | 0.0.1 | | **`uri`** | `string` | The URI of the directory to create. | | 0.0.1 | #### DeleteDirectoryOptions | Prop | Type | Description | Default | Since | | --------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`id`** | `string` | A unique identifier for this operation. It can be used to cancel the operation and to correlate `operationProgress` events. | | 0.0.1 | | **`recursive`** | `boolean` | Whether or not to delete the contents of the directory. If `false`, the call is rejected with the `NOT_EMPTY` error code if the directory is not empty. | `false` | 0.0.1 | | **`uri`** | `string` | The URI of the directory to delete. | | 0.0.1 | #### DeleteFileOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------ | ----- | | **`uri`** | `string` | The URI of the file to delete. | 0.0.1 | #### ExistsResult | Prop | Type | Description | Since | | ------------ | --------- | -------------------------------------------- | ----- | | **`exists`** | `boolean` | Whether or not the file or directory exists. | 0.0.1 | #### ExistsOptions | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`uri`** | `string` | The URI of the file or directory. | 0.0.1 | #### GetAppStorageInfoResult | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------- | --------------------------------------------------------------------------------- | | **`cacheBytes`** | `number` | The size of the app's cache in bytes. | 0.0.1 | | **`dataBytes`** | \`number | null\` | The size of the app's data in bytes. Is `null` if the platform cannot provide it. | #### GetDeviceStorageInfoResult | Prop | Type | Description | Since | | ---------------------- | -------- | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`allocatableBytes`** | \`number | null\` | The number of bytes the app is allowed to allocate, including space the system can free up for the app (e.g. by deleting caches). Is `null` if the platform cannot provide it. | | **`freeBytes`** | `number` | The number of free bytes. | 0.0.1 | | **`totalBytes`** | `number` | The number of total bytes. | 0.0.1 | #### GetDirectorySizeResult | Prop | Type | Description | Since | | ---------- | -------- | ----------------------------------------------------------------- | ----- | | **`size`** | `number` | The total size of the directory and all of its contents in bytes. | 0.0.1 | #### GetDirectorySizeOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------- | ----- | | **`uri`** | `string` | The URI of the directory. | 0.0.1 | #### GetFileChecksumResult | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------------- | ----- | | **`checksum`** | `string` | The checksum of the file as a lowercase hexadecimal string. | 0.0.1 | #### GetFileChecksumOptions | Prop | Type | Description | Since | | --------------- | ------------------- | -------------------------------------------------- | ----- | | **`algorithm`** | `ChecksumAlgorithm` | The algorithm to use for the checksum calculation. | 0.0.1 | | **`uri`** | `string` | The URI of the file. | 0.0.1 | #### GetMetadataResult | Prop | Type | Description | Since | | ---------------- | ----------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **`createdAt`** | \`number | null\` | The timestamp when the file or directory was created, in milliseconds since the epoch. Is `null` if the platform does not provide it. | | **`mimeType`** | \`string | null\` | The MIME type of the file. Is `null` for directories or if the type is unknown. | | **`modifiedAt`** | \`number | null\` | The timestamp when the file or directory was last modified, in milliseconds since the epoch. Is `null` if the platform does not provide it. | | **`name`** | `string` | The name of the file or directory. | 0.0.1 | | **`size`** | `number` | The size of the file in bytes. Is always `0` for directories. | 0.0.1 | | **`type`** | `EntryType` | The type of the entry. | 0.0.1 | | **`uri`** | `string` | The canonical URI of the file or directory. | 0.0.1 | #### GetMetadataOptions | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`uri`** | `string` | The URI of the file or directory. | 0.0.1 | #### GetPersistedDirectoriesResult | Prop | Type | Description | Since | | ----------------- | ---------------------- | -------------------------------------- | ----- | | **`directories`** | `PersistedDirectory[]` | The directories with persisted access. | 0.0.1 | #### PersistedDirectory | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`name`** | `string` | The name of the directory. | 0.0.1 | | **`uri`** | `string` | The URI of the directory. The URI identifies the persisted directory in all methods of this plugin. It may change between app launches, so do not store it yourself but retrieve it via `getPersistedDirectories()`. | 0.0.1 | #### GetUriResult | Prop | Type | Description | Since | | --------- | -------- | --------------------------------- | ----- | | **`uri`** | `string` | The URI of the file or directory. | 0.0.1 | #### GetUriOptions | Prop | Type | Description | Since | | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`directory`** | `Directory` | The well-known sandbox directory to resolve the path in. Either this option or `parentUri` must be provided. | 0.0.1 | | **`parentUri`** | `string` | The URI of the directory to resolve the path in. Either this option or `directory` must be provided. On Android, constructing the URI of a non-existing entry inside a persisted directory is only supported for path-structured document providers such as the local device storage. | 0.0.1 | | **`path`** | `string` | The path to resolve. | 0.0.1 | #### MoveDirectoryResult | Prop | Type | Description | Since | | --------- | -------- | ------------------------------- | ----- | | **`uri`** | `string` | The URI of the moved directory. | 0.0.1 | #### MoveDirectoryOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | A unique identifier for this operation. It can be used to cancel the operation and to correlate `operationProgress` events. | 0.0.1 | | **`toUri`** | `string` | The URI of the directory to move to. | 0.0.1 | | **`uri`** | `string` | The URI of the directory to move. | 0.0.1 | #### MoveFileResult | Prop | Type | Description | Since | | --------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`uri`** | `string` | The URI of the moved file. This may differ from the requested URI, for example if the document provider had to rename the file to avoid a collision. | 0.0.1 | #### MoveFileOptions | Prop | Type | Description | Since | | ----------- | -------- | ------------------------------- | ----- | | **`toUri`** | `string` | The URI of the file to move to. | 0.0.1 | | **`uri`** | `string` | The URI of the file to move. | 0.0.1 | #### PersistDirectoryAccessResult | Prop | Type | Description | Since | | --------------- | -------------------- | ------------------------ | ----- | | **`directory`** | `PersistedDirectory` | The persisted directory. | 0.0.1 | #### PersistDirectoryAccessOptions | Prop | Type | Description | Since | | -------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bookmark`** | `string` | The base64-encoded security-scoped bookmark of the directory as returned by `FilePicker.pickDirectory()`. Only returned by version `8.1.0` or later of the Capawesome File Picker plugin. Only available on iOS. | 0.0.1 | | **`uri`** | `string` | The URI of the directory as returned by `FilePicker.pickDirectory()` (named `path` in its result). | 0.0.1 | #### ReadDirectoryResult | Prop | Type | Description | Since | | ------------- | ------------------ | ----------------------------- | ----- | | **`entries`** | `DirectoryEntry[]` | The entries of the directory. | 0.0.1 | #### DirectoryEntry | Prop | Type | Description | Since | | ---------------- | ----------- | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | | **`createdAt`** | \`number | null\` | The timestamp when the entry was created, in milliseconds since the epoch. Is `null` if the platform does not provide it. | | **`mimeType`** | \`string | null\` | The MIME type of the entry. Is `null` for directories or if the type is unknown. | | **`modifiedAt`** | \`number | null\` | The timestamp when the entry was last modified, in milliseconds since the epoch. Is `null` if the platform does not provide it. | | **`name`** | `string` | The name of the entry. | 0.0.1 | | **`size`** | `number` | The size of the entry in bytes. Is always `0` for directories. | 0.0.1 | | **`type`** | `EntryType` | The type of the entry. | 0.0.1 | | **`uri`** | `string` | The URI of the entry. | 0.0.1 | #### ReadDirectoryOptions | Prop | Type | Description | Default | Since | | ------------ | -------- | ------------------------------------------------------------------------------ | ------- | ----- | | **`limit`** | `number` | The maximum number of entries to return. By default, all entries are returned. | | 0.0.1 | | **`offset`** | `number` | The number of entries to skip. | `0` | 0.0.1 | | **`uri`** | `string` | The URI of the directory to read. | | 0.0.1 | #### ReadFileResult | Prop | Type | Description | Since | | ---------- | -------- | ---------------------------------------------------------------------------- | ----- | | **`data`** | `string` | The data read from the file, encoded as requested via the `encoding` option. | 0.0.1 | #### ReadFileOptions | Prop | Type | Description | Default | Since | | -------------- | ---------- | ----------------------------------------------------------------------------- | ----------------- | ----- | | **`encoding`** | `Encoding` | The encoding of the returned data. | `Encoding.Base64` | 0.0.1 | | **`length`** | `number` | The maximum number of bytes to read. By default, the file is read to the end. | | 0.0.1 | | **`offset`** | `number` | The zero-based byte offset at which to start reading. | `0` | 0.0.1 | | **`uri`** | `string` | The URI of the file to read. | | 0.0.1 | #### ReadFileAsBlobResult | Prop | Type | Description | Since | | ---------- | ------ | ------------------------------------- | ----- | | **`blob`** | `Blob` | The contents of the file as a `Blob`. | 0.0.1 | #### ReadFileAsBlobOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`uri`** | `string` | The URI of the file to read. | 0.0.1 | #### ReleaseDirectoryAccessOptions | Prop | Type | Description | Since | | --------- | -------- | ----------------------------------- | ----- | | **`uri`** | `string` | The URI of the persisted directory. | 0.0.1 | #### TruncateFileOptions | Prop | Type | Description | Default | Since | | ---------- | -------- | ------------------------------------------ | ------- | ----- | | **`size`** | `number` | The size in bytes to truncate the file to. | `0` | 0.0.1 | | **`uri`** | `string` | The URI of the file to truncate. | | 0.0.1 | #### WriteFileResult | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`uri`** | `string` | The URI of the written file. This may differ from the requested URI, for example if the document provider had to rename the file to avoid a collision. | 0.0.1 | #### WriteFileOptions | Prop | Type | Description | Default | Since | | --------------- | ---------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- | | **`data`** | `string` | The data to write to the file. | | 0.0.1 | | **`encoding`** | `Encoding` | The encoding of the `data` option. | `Encoding.Base64` | 0.0.1 | | **`position`** | `number` | The zero-based byte offset at which to start writing. Existing content outside of the written range is preserved. If not provided, the file is overwritten. | | 0.0.1 | | **`recursive`** | `boolean` | Whether or not to create missing parent directories. | `false` | 0.0.1 | | **`uri`** | `string` | The URI of the file to write. | | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### OperationProgressEvent | Prop | Type | Description | Since | | -------------------- | --------------- | ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | **`id`** | \`string | null\` | The identifier of the operation as provided in the options of the operation. Is `null` if no identifier was provided. | | **`operationType`** | `OperationType` | The type of the operation. | 0.0.1 | | **`processedBytes`** | `number` | The number of bytes that have been processed so far. | 0.0.1 | | **`processedFiles`** | `number` | The number of files that have been processed so far. | 0.0.1 | | **`totalBytes`** | \`number | null\` | The total number of bytes to process. Is `null` if the total is not yet known. | | **`totalFiles`** | \`number | null\` | The total number of files to process. Is `null` if the total is not yet known. | ### Enums #### Encoding | Members | Value | Description | Since | | ------------ | ---------- | ---------------- | ----- | | **`Base64`** | `'BASE64'` | Base64 encoding. | 0.0.1 | | **`Utf8`** | `'UTF8'` | UTF-8 encoding. | 0.0.1 | #### ChecksumAlgorithm | Members | Value | Description | Since | | ------------ | ---------- | ----------- | ----- | | **`Md5`** | `'MD5'` | MD5. | 0.0.1 | | **`Sha1`** | `'SHA1'` | SHA-1. | 0.0.1 | | **`Sha256`** | `'SHA256'` | SHA-256. | 0.0.1 | #### EntryType | Members | Value | Description | Since | | --------------- | ------------- | ------------ | ----- | | **`Directory`** | `'DIRECTORY'` | A directory. | 0.0.1 | | **`File`** | `'FILE'` | A file. | 0.0.1 | #### Directory | Members | Value | Description | Since | | -------------------- | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`Cache`** | `'CACHE'` | The cache directory. The system may delete this directory to free up disk space. On Android, it maps to `getCacheDir()`. On iOS, it maps to `Library/Caches`. On Web, it maps to `opfs://cache`. | 0.0.1 | | **`Data`** | `'DATA'` | The directory holding application files. On Android, it maps to `getFilesDir()`. On iOS, it maps to `Documents`. On Web, it maps to `opfs://data`. | 0.0.1 | | **`Documents`** | `'DOCUMENTS'` | The documents directory. On Android, it maps to the app-specific documents directory (`getExternalFilesDir(DIRECTORY_DOCUMENTS)`). Unlike `@capacitor/filesystem`, no storage permissions are required. Use persisted directories to access user-visible folders. On iOS, it maps to `Documents`. On Web, it maps to `opfs://documents`. | 0.0.1 | | **`External`** | `'EXTERNAL'` | The external directory. On Android, it maps to `getExternalFilesDir(null)`. On iOS, it maps to `Documents`. On Web, it maps to `opfs://external`. | 0.0.1 | | **`ExternalCache`** | `'EXTERNAL_CACHE'` | The external cache directory. On Android, it maps to `getExternalCacheDir()`. On iOS, it maps to `Library/Caches`. On Web, it maps to `opfs://external-cache`. | 0.0.1 | | **`Library`** | `'LIBRARY'` | The library directory. On Android, it maps to `getFilesDir()`. On iOS, it maps to `Library`. On Web, it maps to `opfs://library`. | 0.0.1 | | **`LibraryNoCloud`** | `'LIBRARY_NO_CLOUD'` | The library directory without cloud backup. On Android, it maps to `getFilesDir()`. On iOS, it maps to `Library/NoCloud`. On Web, it maps to `opfs://library-no-cloud`. | 0.0.1 | | **`Temporary`** | `'TEMPORARY'` | The temporary directory. On Android, it maps to `getCacheDir()`. On iOS, it maps to `tmp`. On Web, it maps to `opfs://temporary`. | 0.0.1 | #### OperationType | Members | Value | Description | Since | | ------------ | ---------- | ------------------- | ----- | | **`Copy`** | `'COPY'` | A copy operation. | 0.0.1 | | **`Delete`** | `'DELETE'` | A delete operation. | 0.0.1 | | **`Move`** | `'MOVE'` | A move operation. | 0.0.1 | ## Migration from `@capacitor/filesystem` This plugin is a replacement for the sandbox operations of `@capacitor/filesystem`. Instead of passing `path` and `directory` to every method, construct the URI once with `getUri(...)` and pass it to any method: ``` // Before (@capacitor/filesystem) const result = await Filesystem.readFile({ path: 'text.txt', directory: Directory.Data }); // After (@capawesome-team/capacitor-file-manager) const { uri } = await FileManager.getUri({ path: 'text.txt', directory: Directory.Data }); const result = await FileManager.readFile({ uri }); ``` The `Directory` enum members have the same names and values as in `@capacitor/filesystem`. | `@capacitor/filesystem` | `@capawesome-team/capacitor-file-manager` | | ----------------------------------- | -------------------------------------------------------------------------------------------- | | `appendFile(...)` | `getUri(...)` + `appendFile(...)` | | `checkPermissions()` | Not needed. Persisted directories replace the legacy storage permissions. | | `copy(...)` | `getUri(...)` + `copyFile(...)` or `copyDirectory(...)` | | `deleteFile(...)` | `getUri(...)` + `deleteFile(...)` | | `downloadFile(...)` | [File Transfer](https://capawesome.io/docs/sdks/capacitor/file-transfer/) plugin | | `getUri(...)` | `getUri(...)` | | `mkdir(...)` | `getUri(...)` + `createDirectory(...)` | | `readdir(...)` | `getUri(...)` + `readDirectory(...)` | | `readFile(...)` | `getUri(...)` + `readFile(...)` | | `readFileInChunks(...)` | `readFileAsBlob(...)` | | `rename(...)` | `getUri(...)` + `moveFile(...)` or `moveDirectory(...)` | | `requestPermissions()` | Not needed. Persisted directories replace the legacy storage permissions. | | `rmdir(...)` | `getUri(...)` + `deleteDirectory(...)` | | `stat(...)` | `getUri(...)` + `getMetadata(...)` | | `writeFile(...)` | `getUri(...)` + `writeFile(...)` | | `Directory.ExternalStorage` | Persisted directories. Direct access to the shared storage is blocked on Android 11+ anyway. | | `Encoding.ASCII` / `Encoding.UTF16` | Dropped. Use `Encoding.Utf8` or `Encoding.Base64`. | ## Working with other plugins - **Sandbox URIs** (`file://`): Fully compatible with all file-related Capawesome plugins (File Opener, File Compressor, Zip, ...) — pass the URI wherever a `path` is expected. - **Persisted directory URIs** (`content://` on Android, security-scoped `file://` on iOS): Not accepted by plugins that only support plain file paths. To bridge, copy the file into a sandbox directory (for example `Directory.Cache`) with `copyFile(...)`, operate on it there, and optionally copy it back. - **Web URIs** (`opfs://`): Internal to this plugin. Use `readFileAsBlob(...)` to hand the data to other web APIs. ## FAQ ### How is this plugin different from other similar plugins? It works with the user's own storage, not just your app's sandbox: pick a folder once and keep read and write access to it across app launches on Android and iOS, then run manager-grade operations there — recursive copy, move and delete with progress, checksums, and directory sizing. It also reports device and app storage usage, can clear the cache, and streams large files into your web code without base64 overhead. On the web, files live in the Origin Private File System with real directories and random access instead of base64 blobs in IndexedDB. If you only need to read and write files inside your app's own sandbox, a simpler approach is enough; if you need durable access to user-picked folders and integrity-checked file operations, this plugin is built for it. Backed by dedicated support. ### How do I read a whole large file? Use `readFileAsBlob(...)`, which streams the file into a `Blob` without base64 overhead. Do not read a large file by calling `readFile(...)` with an increasing `offset` in a loop — on some storage providers every call has to skip to the offset again, which makes the loop drastically slower with every iteration. The `offset` and `length` options are intended for targeted reads, like a file header or a byte range. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Let the user pick the files and directories that this plugin operates on. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open files in the matching app on the device. - [File Transfer](https://capawesome.io/docs/sdks/capacitor/file-transfer/): Download and upload files, even while the app is in the background. - [Zip](https://capawesome.io/docs/sdks/capacitor/zip/): Compress and decompress files and directories. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-manager/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-manager/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-manager/LICENSE). # Capacitor File Opener Plugin Capacitor plugin to open a file with the default application. ## Features The Capacitor File Opener plugin is one of the most complete file opening solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/), [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) and [Printer](https://capawesome.io/docs/sdks/capacitor/printer/) plugins. - 🔁 **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! ## Use Cases The File Opener plugin is typically used whenever an app hands a file over to another application, for example: - **Document viewing**: Open downloaded documents such as PDFs, invoices, or reports with the device's default viewer app. - **Post-download handling**: Let users open a file right after your app has downloaded or generated it. - **Media playback**: Open images, videos, or audio files with the app the user has associated with that file type. - **Web downloads**: Open files from `Blob` objects on the Web without saving them first. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) | Android | iOS | | ------- | --- | | | | ## Guides - [How to Take and Edit Photos in a Capacitor App](https://capawesome.io/blog/how-to-take-and-edit-photos-in-a-capacitor-app/) ## 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. ## Usage The following example shows how to open a file with the default application. ### Open a file with the default application On Android and iOS, pass the path of the file. On Android, both file paths and content URIs are supported. The mime type is determined automatically, but you can also specify it with the `mimeType` option. On Web, pass a `Blob` instance instead: ``` 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 | ## FAQ ### Which file types can I open with this plugin? The plugin can open various file types with their default applications. On Android and iOS, it uses the system file associations, so any file type for which the user has a suitable app installed can be opened. The mime type is determined automatically, but you can also specify it manually with the `mimeType` option. ### Why can't a file be opened on Android? Make sure that the directory containing the file is declared in the `file_paths.xml` file of your Android project, as described in the [Installation](#installation) section. Without this configuration, the file can not be shared with the app that should open it. ### How do I open a file on the Web? On the Web, pass a `Blob` instance to the `openFile(...)` method using the `blob` option. The `path` and `mimeType` options are only available on Android and iOS. ### Do I need to specify the mime type of the file? No, if the `mimeType` option is not specified, the mime type will be determined automatically. You only need to set it if you want to override the detected type. This option is only available on Android and iOS. ### How is this plugin different from the File Picker plugin? The [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/) plugin lets the user select a file from the device and returns its path and metadata. The File Opener plugin does the opposite: it takes a file your app already has and opens it with the default application. Both plugins work well together, for example to pick a file and then open it. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/): Compress files with support for image formats like PNG, JPEG, and WebP. - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Let the user select a file, directory, image, or video from the device. - [Printer](https://capawesome.io/docs/sdks/capacitor/printer/): Print files, HTML, PDFs, and web views on Android and iOS. - [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/): Receive text, links, and files shared from other apps. ## 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/). ## 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). # Capacitor File Picker Plugin Capacitor plugin that allows the user to select a file, directory, image, or video from the device's file system or gallery. ## Features The Capacitor File Picker plugin is one of the most complete file selection solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/), [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) and [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/) 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! ## Use Cases The File Picker plugin is typically used whenever an app needs the user to hand over a file, for example: - **File uploads**: Let users attach documents such as PDFs or spreadsheets to a form and upload them to a server. - **Profile and cover pictures**: Let users choose an existing photo from their gallery. - **Media attachments**: Add images and videos to chat messages, posts, or support tickets. - **Data imports**: Import CSV, JSON, or backup files into your app. ## 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 | ## Guides - [The File Handling Guide for Capacitor](https://capawesome.io/blog/capacitor-file-handling-guide/) ## 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 #### Variables This plugin will use the following project variables (defined in your app’s `variables.gradle` file): - `$androidxActivityVersion` version of `androidx.activity:activity` (default: `1.13.0`) #### 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 The following examples show how to pick files, images, videos, and directories, and how to process the selected files. ### Pick one or more files Open the system file picker and let the user select one or more files of any type. The result contains the metadata (name, size, mime type, last modified timestamp) and, on Android and iOS, the path of each selected file: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const pickFiles = async () => { const result = await FilePicker.pickFiles(); const file = result.files[0]; }; ``` ### Restrict the picker to specific file types Use the `types` option to only accept certain [IANA media types](https://www.iana.org/assignments/media-types/media-types.xhtml), for example PDF documents: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const pickPdfFiles = async () => { const result = await FilePicker.pickFiles({ types: ['application/pdf'], }); }; ``` On Android, the system file picker can only offer files whose media type the device derives from the file extension. On Android 9 and older, `.json` files are not mapped to `application/json` and are reported as `application/octet-stream`. Third-party document providers may behave the same on any version. Add `application/octet-stream` to `types` if such files must be selectable. ### Pick images or videos from the gallery Use `pickImages(...)`, `pickVideos(...)` or `pickMedia(...)` to open the photo gallery instead of the file picker. These methods are only available on Android and iOS: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const pickImages = async () => { const result = await FilePicker.pickImages(); }; const pickVideos = async () => { const result = await FilePicker.pickVideos(); }; const pickMedia = async () => { // Pick both images and videos const result = await FilePicker.pickMedia({ limit: 3 }); }; ``` ### Display a picked image in the web view Use the `webPath` property to load a picked file in the web view, for example as the `src` of an `` element: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const displayImage = async () => { const { files } = await FilePicker.pickImages({ limit: 1 }); const image = document.createElement('img'); image.src = files[0].webPath!; document.body.appendChild(image); }; ``` ### Pick a directory Let the user select a directory, for example to import all files it contains. Only available on Android and iOS: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const pickDirectory = async () => { const { path } = await FilePicker.pickDirectory(); }; ``` ### Upload a picked file to a server On the Web, the picked file contains a `Blob` instance. On Android and iOS, load the file as a blob using the [Fetch API](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) and the file's `webPath`. You can then append the blob to a `FormData` object and upload it: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const uploadFile = async () => { const result = await FilePicker.pickFiles({ limit: 1 }); const file = result.files[0]; let blob: Blob; if (file.blob) { // Web blob = file.blob; } else { // Android and iOS const response = await fetch(file.webPath!); blob = await response.blob(); } const formData = new FormData(); formData.append('file', blob, file.name); await fetch('https://example.com/upload', { method: 'POST', body: formData, }); }; ``` **Attention**: Avoid the `readData` option for large files. It loads the entire file into memory as a Base64 string, which can crash your app. The fetch-based approach above streams the file instead. ### Convert a HEIC image to JPEG On iOS, photos are often stored in the HEIC format, which many servers and browsers cannot display. Use `convertHeicToJpeg(...)` to convert them. Only available on iOS: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const convertHeicToJpeg = async () => { const { path } = await FilePicker.convertHeicToJpeg({ path: 'path/to/image.heic', }); }; ``` ### Convert a RAW image to JPEG RAW images (e.g. DNG) are not transcoded when they are picked. Use `convertRawToJpeg(...)` to convert them. Only available on iOS: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const convertRawToJpeg = async () => { const { path } = await FilePicker.convertRawToJpeg({ path: 'path/to/image.dng', }); }; ``` ### Check and request permissions Picking files does not require any permissions since the operating system presents the picker. However, if you need the `ACCESS_MEDIA_LOCATION` or `READ_EXTERNAL_STORAGE` permission on Android (see [Installation](#installation)), you can check and request them: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const checkPermissions = async () => { const result = await FilePicker.checkPermissions(); }; const requestPermissions = async () => { const result = await FilePicker.requestPermissions(); }; ``` ### Listen for the picker being dismissed On iOS, you can be notified when the user closes the picker without selecting anything: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const addPickerDismissedListener = async () => { await FilePicker.addListener('pickerDismissed', () => { console.log('Picker was dismissed'); }); }; ``` ### Copy a file Copy a picked file to a new location, for example into your app's data directory: ``` import { FilePicker } from '@capawesome/capacitor-file-picker'; const copyFile = async () => { await FilePicker.copyFile({ from: 'path/to/file', to: 'path/to/destination', }); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`convertHeicToJpeg(...)`](#convertheictojpeg) - [`convertRawToJpeg(...)`](#convertrawtojpeg) - [`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 ______________________________________________________________________ ### convertRawToJpeg(...) ``` convertRawToJpeg(options: ConvertRawToJpegOptions) => Promise ``` Convert a RAW image to JPEG. Only available on iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `ConvertRawToJpegOptions` | **Returns:** `Promise` **Since:** 8.1.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 | #### ConvertRawToJpegResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------- | ----- | | **`path`** | `string` | The path of the converted JPEG image. | 8.1.0 | #### ConvertRawToJpegOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------- | ----- | | **`path`** | `string` | The path of the RAW image. | 8.1.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. | | | **`webPath`** | `string` | The path of the file that can be used to load it in the web view, for example as the `src` of an `<img>` element. On the web, this is an object URL. Call `URL.revokeObjectURL(...)` when it is no longer needed. | 8.1.0 | | **`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. Wildcards such as `image/*` are supported. On Android, the system file picker can only offer files whose media type the device derives from the file extension. On Android 9 and older, `.json` files are not mapped to `application/json` and are reported as `application/octet-stream`. Third-party document providers may behave the same on any version. Add `application/octet-stream` to `types` if such files must be selectable. | | | | **`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/capacitor-file-handling-guide/#read-a-file). | `false` | | #### PickDirectoryResult | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bookmark`** | `string` | The base64-encoded security-scoped bookmark of the selected directory. It can be used to retain access to the directory across app launches. Only available on iOS. | 8.1.0 | | **`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'` ## FAQ ### How do I upload a picked file to a server? On the Web, the picked file already contains a `Blob` instance that you can append to a `FormData` object. On Android and iOS, load the file as a blob using the Fetch API and the file's path, then upload it the same way. See the [usage example](#upload-a-picked-file-to-a-server) above and [The File Handling Guide for Capacitor](https://capawesome.io/blog/capacitor-file-handling-guide/) for a complete walkthrough. ### Why does my app crash when picking large files? This usually happens when the `readData` option is enabled. It reads the entire file into memory as a Base64 string, which can exceed the available memory for large files. Keep `readData` disabled (the default) and load the file as a blob using the Fetch API instead, as shown in the [usage example](#upload-a-picked-file-to-a-server) above. ### What is the difference between `pickFiles`, `pickImages`, `pickMedia` and `pickVideos`? The `pickFiles(...)` method opens the system file picker and supports any file type on Android, iOS and Web. The `pickImages(...)`, `pickVideos(...)` and `pickMedia(...)` methods open the photo gallery instead, which provides a more familiar experience for selecting photos and videos, and are only available on Android and iOS. ### Do I need any runtime permissions to pick files? No, picking files itself does not require any runtime permissions because the operating system presents the picker on behalf of your app. On Android, the `ACCESS_MEDIA_LOCATION` permission is only needed to retrieve unredacted EXIF metadata from photos, and `READ_EXTERNAL_STORAGE` is only needed to read files from external storage. On iOS, no privacy descriptions are required. ### How is this plugin different from the Capacitor Camera and Filesystem plugins? The Capacitor Camera plugin takes a photo with the camera or picks images from the gallery, but it does not support other file types. The Capacitor Filesystem plugin reads and writes files at known paths, but it does not provide any user interface for selecting them. The File Picker plugin fills this gap: it lets the user select any file, directory, image, or video and returns its path and metadata, which you can then process with other plugins. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/): Compress images before uploading them. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open a picked file with the default application. - [Share Target](https://capawesome.io/docs/sdks/capacitor/share-target/): Receive files shared from other apps. - [Zip](https://capawesome.io/docs/sdks/capacitor/zip/): Zip and unzip files and directories. ## 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/). ## 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! # Capacitor File Transfer Plugin Capacitor plugin for reliable background file uploads and downloads that survive the app being backgrounded. Task-based transfers with pause/resume, progress events, retries, and a persisted task store. ## Features The Capacitor File Transfer plugin brings robust, task-based file transfers to your Capacitor app. Here are some of the key features: - ⬆️ **Uploads**: Binary (raw body, e.g. S3 presigned URLs) and `multipart/form-data` uploads. - ⬇️ **Downloads**: Streamed downloads directly to a file on the device. - 🔋 **Background Continuation**: Transfers keep running while the app is in the background — a background `URLSession` on iOS and a `dataSync` foreground service driven by our own OkHttp engine on Android. - ⏯️ **Pause & Resume**: Downloads can be paused and resumed, even after the process was killed (via resume data on iOS and HTTP `Range` requests on Android). - 📊 **Progress Events**: Throttled progress events with transferred and total bytes. - 💾 **Persisted Tasks**: Transfers are persisted and can be queried after an app restart. - 🔁 **Retries**: Automatic retries with backoff on network errors. - 🔒 **Honest Semantics**: No fake pause and no hidden private APIs — documented behavior across foreground, background, and force-quit. - 🤝 **Compatibility**: Works hand in hand with the [File Manager](https://capawesome.io/docs/sdks/capacitor/file-manager/), [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) and [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/) 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! ## Use Cases The File Transfer plugin is typically used whenever an app needs to move large files between the device and a server, for example: - **Offline-first content**: Download large assets such as videos, maps, or course material for offline use and let the transfer continue while the user leaves the app. - **Media uploads**: Upload photos and videos captured in your app to your own backend or directly to an S3 presigned URL, without blocking the user interface. - **Resumable downloads**: Let users pause and resume large downloads, even after the app process was killed, and retry automatically after network errors. - **Transfer manager UI**: Build a download or upload manager with live progress bars, using the throttled progress events and the persisted task store. - **Wi-Fi-only transfers**: Restrict large transfers to unmetered networks so that users don't consume their mobile data plan. - **Document synchronization**: Keep documents in sync with your backend in the background, then open or compress them with the [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/) and [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/) plugins. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor File Transfer Plugin](https://capawesome.io/blog/announcing-the-capacitor-file-transfer-plugin/): Task-based background transfers with pause and resume, retries, and a persisted task store. - [An Alternative to cordova-plugin-file-transfer](https://capawesome.io/blog/alternative-to-cordova-plugin-file-transfer/): Compares the XHR advice, this plugin, and its background transfer 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-transfer` 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-transfer npx cap sync ``` ### Android The plugin declares the `INTERNET`, `FOREGROUND_SERVICE`, `FOREGROUND_SERVICE_DATA_SYNC` and `POST_NOTIFICATIONS` permissions and the `dataSync` foreground service in its own manifest, so no manifest changes are required. On Android 13 (API level 33) and higher, the progress notification is only shown if the user has granted the `POST_NOTIFICATIONS` runtime permission. Transfers still run without it — only the notification is hidden. Call `requestPermissions()` before starting a transfer if you want the notification to appear: ``` const { notifications } = await FileTransfer.requestPermissions(); if (notifications !== 'granted') { // The transfer will run, but no progress notification is shown. } ``` The foreground service notification itself is shown while a transfer runs — Android requires a foreground service to have one. On Android 12+, a transfer that starts while the app is in the background (e.g. after waiting for an unmetered network) runs without the foreground service, since Android does not allow starting one from the background. The per-transfer progress notification is opt-in and off by default: ``` await FileTransfer.startDownload({ url, path, androidNotification: { title: 'Downloading', progress: true }, }); ``` #### 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.** { *; } -keep class okhttp3.** { *; } -keep class okio.** { *; } ``` #### 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: - `$okhttpVersion` version of `com.squareup.okhttp3:okhttp` (default: `4.12.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS The plugin uses a background `URLSession` so that transfers continue while your app is suspended. When a transfer finishes while the app is not running, iOS relaunches the app in the background and needs the transfer's completion handler to be forwarded to the plugin. Add the following method to your app's `AppDelegate.swift` to forward the handler: ``` import Foundation extension AppDelegate { func application( _ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void ) { NotificationCenter.default.post( name: Notification.Name("io.capawesome.capacitorjs.plugins.filetransfer.handleEventsForBackgroundURLSession"), object: completionHandler ) } } ``` Without this hook, background transfers still complete, but iOS may not be able to wake your app to deliver the final events promptly. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to download and upload files, listen for transfer events, pause, resume, and cancel transfers, and retrieve known transfers. ### Download a file Start a background download with `startDownload(...)`. The method resolves immediately with the identifier of the transfer, while the download itself continues in the background: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const startDownload = async () => { const { id } = await FileTransfer.startDownload({ url: 'https://example.com/file.zip', path: '/path/to/destination/file.zip', headers: { Authorization: 'Bearer ', }, network: 'unmetered', maxRetries: 3, androidNotification: { title: 'Downloading file', text: 'The file is being downloaded.', }, }); return id; }; ``` ### Upload a file Upload a file from the device with `startUpload(...)`. By default, the file is sent as a `multipart/form-data` request, where `fileField` defines the name of the form field that contains the file and `formFields` adds further fields to the request: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const startUpload = async () => { const { id } = await FileTransfer.startUpload({ url: 'https://example.com/upload', path: '/path/to/source/file.jpg', uploadType: 'multipart', fileField: 'file', mimeType: 'image/jpeg', formFields: { albumId: '42', }, }); return id; }; ``` ### Upload to an S3 presigned URL Presigned URLs expect the raw file as the request body. Use `uploadType: 'binary'` and the `PUT` method: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const uploadToPresignedUrl = async () => { const { id } = await FileTransfer.startUpload({ url: 'https://example.com/presigned-url', path: '/path/to/source/file.jpg', method: 'PUT', uploadType: 'binary', mimeType: 'image/jpeg', }); return id; }; ``` ### Listen for transfer events Transfers report their state through events: `transferProgress` is emitted repeatedly while a transfer is running, `transferCompleted` when it succeeds, and `transferFailed` when it fails. Completed and failed events that occur while no listener is registered are retained and delivered as soon as a listener is added: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const addTransferListeners = async () => { await FileTransfer.addListener('transferProgress', event => { console.log(`Transfer ${event.id}: ${event.bytes}/${event.totalBytes}`); }); await FileTransfer.addListener('transferCompleted', event => { console.log(`Transfer ${event.id} completed: `, event.path); }); await FileTransfer.addListener('transferFailed', event => { console.error(`Transfer ${event.id} failed: `, event.errorCode, event.message); }); }; ``` ### Pause and resume a transfer Pause a running download with `pauseTransferById(...)` and continue it later with `resumeTransferById(...)`, using the identifier returned by `startDownload(...)`. Paused downloads survive process death and can therefore also be resumed after an app restart. Uploads cannot be paused in this version: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const pauseTransferById = async (id: string) => { await FileTransfer.pauseTransferById({ id }); }; const resumeTransferById = async (id: string) => { await FileTransfer.resumeTransferById({ id }); }; ``` ### Cancel a transfer Cancel a running or paused transfer with `cancelTransferById(...)`. Any partially transferred data is deleted, so the transfer cannot be resumed afterwards: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const cancelTransferById = async (id: string) => { await FileTransfer.cancelTransferById({ id }); }; ``` ### Retrieve transfers Get a single transfer with `getTransferById(...)` or all known transfers with `getTransfers()`. Transfers are persisted, so both methods also return transfers that were restored after an app restart, for example to rebuild a transfer list on app start: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const getTransferById = async (id: string) => { const { transfer } = await FileTransfer.getTransferById({ id }); return transfer; }; const getRunningTransfers = async () => { const { transfers } = await FileTransfer.getTransfers(); return transfers.filter(transfer => transfer.state === 'running'); }; ``` ### Remove all listeners Remove all listeners that your app has registered, for example when a view is destroyed: ``` import { FileTransfer } from '@capawesome-team/capacitor-file-transfer'; const removeAllListeners = async () => { await FileTransfer.removeAllListeners(); }; ``` ## API - [`cancelTransferById(...)`](#canceltransferbyid) - [`checkPermissions()`](#checkpermissions) - [`getTransferById(...)`](#gettransferbyid) - [`getTransfers()`](#gettransfers) - [`pauseTransferById(...)`](#pausetransferbyid) - [`requestPermissions()`](#requestpermissions) - [`resumeTransferById(...)`](#resumetransferbyid) - [`startDownload(...)`](#startdownload) - [`startUpload(...)`](#startupload) - [`addListener('transferCompleted', ...)`](#addlistenertransfercompleted-) - [`addListener('transferFailed', ...)`](#addlistenertransferfailed-) - [`addListener('transferProgress', ...)`](#addlistenertransferprogress-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### cancelTransferById(...) ``` cancelTransferById(options: CancelTransferByIdOptions) => Promise ``` Cancel a running or paused transfer and delete any partially transferred data. | Param | Type | | ------------- | --------------------------- | | **`options`** | `CancelTransferByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check the current permission status. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getTransferById(...) ``` getTransferById(options: GetTransferByIdOptions) => Promise ``` Get a single transfer by its identifier. | Param | Type | | ------------- | ------------------------ | | **`options`** | `GetTransferByIdOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getTransfers() ``` getTransfers() => Promise ``` Get all known transfers, including transfers that were restored after an app restart. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### pauseTransferById(...) ``` pauseTransferById(options: PauseTransferByIdOptions) => Promise ``` Pause a running transfer. Downloads are paused so that they survive process death and can be resumed later. Uploads and downloads that are not resumable cannot be paused and are rejected with the `TRANSFER_NOT_PAUSABLE` error code. | Param | Type | | ------------- | -------------------------- | | **`options`** | `PauseTransferByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to post the progress notification. On **Android 12 and older**, on **iOS** and on the **web**, this resolves without prompting since no notification permission is required. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### resumeTransferById(...) ``` resumeTransferById(options: ResumeTransferByIdOptions) => Promise ``` Resume a paused or failed transfer. Only transfers in the `paused` or `failed` state are resumed. For a transfer in any other state, this resolves without effect. | Param | Type | | ------------- | --------------------------- | | **`options`** | `ResumeTransferByIdOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### startDownload(...) ``` startDownload(options: StartDownloadOptions) => Promise ``` Start a background download and immediately return its identifier. The download continues while the app is in the background. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `StartDownloadOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### startUpload(...) ``` startUpload(options: StartUploadOptions) => Promise ``` Start a background upload and immediately return its identifier. The upload continues while the app is in the background. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `StartUploadOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('transferCompleted', ...) ``` addListener(eventName: 'transferCompleted', listenerFunc: (event: TransferCompletedEvent) => void) => Promise ``` Called when a transfer completes successfully. Completed events that occur while no listener is registered are retained and delivered once a listener is added. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'transferCompleted'` | | **`listenerFunc`** | `(event: TransferCompletedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('transferFailed', ...) ``` addListener(eventName: 'transferFailed', listenerFunc: (event: TransferFailedEvent) => void) => Promise ``` Called when a transfer fails. Failed events that occur while no listener is registered are retained and delivered once a listener is added. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'transferFailed'` | | **`listenerFunc`** | `(event: TransferFailedEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('transferProgress', ...) ``` addListener(eventName: 'transferProgress', listenerFunc: (event: TransferProgressEvent) => void) => Promise ``` Called repeatedly while a transfer is running to report its progress. The event is throttled to roughly one emission every 100 milliseconds per transfer. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'transferProgress'` | | **`listenerFunc`** | `(event: TransferProgressEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### CancelTransferByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------------------- | ----- | | **`id`** | `string` | The identifier of the transfer to cancel. | 0.0.1 | #### PermissionStatus | Prop | Type | Description | Since | | ------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`notifications`** | `PermissionState` | Permission state for posting notifications. The progress notification is only shown if this is `granted`. Transfers still run without it. On **Android 12 and older**, on **iOS** and on the **web**, this is always `granted` since no notification permission is required. | 0.0.1 | #### GetTransferByIdResult | Prop | Type | Description | Since | | -------------- | ---------- | --------------------------------------- | ----- | | **`transfer`** | `Transfer` | The transfer with the given identifier. | 0.0.1 | #### Transfer | Prop | Type | Description | Since | | ---------------- | --------------- | ------------------------------------------------------ | ------------------------------------------------------------ | | **`id`** | `string` | The identifier of the transfer. | 0.0.1 | | **`type`** | `TransferType` | The direction of the transfer. | 0.0.1 | | **`state`** | `TransferState` | The current state of the transfer. | 0.0.1 | | **`url`** | `string` | The URL of the transfer. | 0.0.1 | | **`path`** | `string` | The path on the device of the transfer. | 0.0.1 | | **`bytes`** | `number` | The number of bytes that have been transferred so far. | 0.0.1 | | **`totalBytes`** | \`number | null\` | The total number of bytes to transfer, or `null` if unknown. | #### GetTransferByIdOptions | Prop | Type | Description | Since | | -------- | -------- | -------------------------------------- | ----- | | **`id`** | `string` | The identifier of the transfer to get. | 0.0.1 | #### GetTransfersResult | Prop | Type | Description | Since | | --------------- | ------------ | -------------------------------- | ----- | | **`transfers`** | `Transfer[]` | The list of all known transfers. | 0.0.1 | #### PauseTransferByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ---------------------------------------- | ----- | | **`id`** | `string` | The identifier of the transfer to pause. | 0.0.1 | #### ResumeTransferByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------------------- | ----- | | **`id`** | `string` | The identifier of the transfer to resume. | 0.0.1 | #### StartDownloadResult | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------- | ----- | | **`id`** | `string` | The identifier of the started transfer. | 0.0.1 | #### StartDownloadOptions | Prop | Type | Description | Default | Since | | ------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ------- | | **`androidNotification`** | `TransferNotificationOptions` | The configuration of the foreground service notification. Only available on Android. | | 0.0.1 | | **`url`** | `string` | The URL to download the file from. | | 0.0.1 | | **`path`** | `string` | The path on the device where the downloaded file should be stored. | | 0.0.1 | | **`headers`** | `Record` | The HTTP headers to send with the request. | | 0.0.1 | | **`method`** | \`'GET' | 'POST'\` | The HTTP method to use for the request. | `'GET'` | | **`network`** | `TransferNetwork` | The network type that is required for the transfer to run. Use `'unmetered'` to only run the transfer on unmetered networks (e.g. Wi-Fi). The transfer waits until such a network is available. On **Android**, it stays in the `pending` state while waiting. | `'any'` | 0.0.1 | | **`resumable`** | `boolean` | Whether the download may be resumed after a pause or an interruption. Requires the server to support the HTTP `Range` header. | `true` | 0.0.1 | | **`maxRetries`** | `number` | The maximum number of times the transfer is retried after a network error. | `0` | 0.0.1 | #### TransferNotificationOptions The configuration of the Android foreground service notification. On **Android 12+**, a transfer that starts while the app is in the background (e.g. after waiting for an unmetered network) runs without the foreground service and its notification, since Android does not allow starting one from the background. Only available on Android. | Prop | Type | Description | Default | Since | | ----------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----- | | **`title`** | `string` | The title of the notification. | | 0.0.1 | | **`progress`** | `boolean` | Whether to show a separate notification with a progress bar for this transfer. The foreground service notification is shown while a transfer runs, because Android requires it. This option only adds the per-transfer progress notification. | `false` | 0.0.1 | | **`text`** | `string` | The text of the notification. | | 0.0.1 | | **`channelName`** | `string` | The name of the notification channel. | `'File Transfer'` | 0.0.1 | #### StartUploadResult | Prop | Type | Description | Since | | -------- | -------- | --------------------------------------- | ----- | | **`id`** | `string` | The identifier of the started transfer. | 0.0.1 | #### StartUploadOptions | Prop | Type | Description | Default | Since | | ------------------------- | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | -------- | | **`androidNotification`** | `TransferNotificationOptions` | The configuration of the foreground service notification. Only available on Android. | | 0.0.1 | | **`url`** | `string` | The URL to upload the file to. | | 0.0.1 | | **`path`** | `string` | The path on the device of the file to upload. | | 0.0.1 | | **`method`** | \`'POST' | 'PUT'\` | The HTTP method to use for the request. | `'POST'` | | **`uploadType`** | `TransferUploadType` | The type of upload to perform. Use `'binary'` to send the raw file as the request body (e.g. for S3 presigned URLs) or `'multipart'` to send a `multipart/form-data` request. | `'multipart'` | 0.0.1 | | **`fileField`** | `string` | The name of the form field that contains the file. Only used when `uploadType` is `'multipart'`. | `'file'` | 0.0.1 | | **`mimeType`** | `string` | The MIME type of the file to upload. | | 0.0.1 | | **`formFields`** | `Record` | The additional form fields to send with the request. Only used when `uploadType` is `'multipart'`. | | 0.0.1 | | **`headers`** | `Record` | The HTTP headers to send with the request. | | 0.0.1 | | **`network`** | `TransferNetwork` | The network type that is required for the transfer to run. Use `'unmetered'` to only run the transfer on unmetered networks (e.g. Wi-Fi). The transfer waits until such a network is available. On **Android**, it stays in the `pending` state while waiting. | `'any'` | 0.0.1 | | **`maxRetries`** | `number` | The maximum number of times the transfer is retried after a network error. | `0` | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### TransferCompletedEvent | Prop | Type | Description | Since | | ------------------ | -------- | ------------------------------- | --------------------------------------------------------------------------------- | | **`id`** | `string` | The identifier of the transfer. | 0.0.1 | | **`path`** | \`string | null\` | The path on the device of the transferred file, or `null` for uploads. | | **`responseCode`** | \`number | null\` | The HTTP status code of the response, or `null` if not available. | | **`responseBody`** | \`string | null\` | The body of the response, or `null` if not available. Only available for uploads. | #### TransferFailedEvent | Prop | Type | Description | Since | | ------------------ | -------- | --------------------------------- | ----------------------------------------------------------------- | | **`id`** | `string` | The identifier of the transfer. | 0.0.1 | | **`errorCode`** | `string` | The error code of the failure. | 0.0.1 | | **`message`** | `string` | The error message of the failure. | 0.0.1 | | **`responseCode`** | \`number | null\` | The HTTP status code of the response, or `null` if not available. | #### TransferProgressEvent | Prop | Type | Description | Since | | ---------------- | -------- | ------------------------------------------------------ | ---------------------------------------------------------------------------------- | | **`id`** | `string` | The identifier of the transfer. | 0.0.1 | | **`bytes`** | `number` | The number of bytes that have been transferred so far. | 0.0.1 | | **`totalBytes`** | \`number | null\` | The total number of bytes to transfer, or `null` if unknown. | | **`progress`** | \`number | null\` | The progress of the transfer as a value between `0` and `1`, or `null` if unknown. | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### TransferType The direction of a transfer. `'download' | 'upload'` #### TransferState The current state of a transfer. `'pending' | 'running' | 'paused' | 'completed' | 'failed' | 'canceled'` #### TransferNetwork The network type that is required for a transfer to run. `'any' | 'unmetered'` #### TransferUploadType The type of upload to perform. `'binary' | 'multipart'` ## Migration from `@capacitor/file-transfer` | `@capacitor/file-transfer` | `@capawesome-team/capacitor-file-transfer` | | ------------------------------ | -------------------------------------------------------------------------------------------------- | | `downloadFile({ url, path })` | `startDownload({ url, path })` → returns `{ id }` and continues in the background | | `uploadFile({ url, path })` | `startUpload({ url, path })` → returns `{ id }` and continues in the background | | `addListener('progress', ...)` | `addListener('transferProgress', ...)` | | *No equivalent* | `pauseTransferById`, `resumeTransferById`, `cancelTransferById`, `getTransferById`, `getTransfers` | | *No equivalent* | `transferCompleted` and `transferFailed` events | Transfers are asynchronous: `startDownload(...)` and `startUpload(...)` resolve immediately with a transfer `id`, and you observe completion through the `transferCompleted` and `transferFailed` events. ## FAQ ### Do transfers continue when the app is in the background? Yes. On Android, transfers keep running in a `dataSync` foreground service, and on iOS, in a background `URLSession`. The following table summarizes what happens to a transfer in each app state: | App State | Android | iOS | | ----------------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Foreground | Runs. | Runs. | | Backgrounded | Runs (via the `dataSync` foreground service). | Runs (via the background `URLSession`). | | Killed by the OS (low memory) | Interrupted; restored as `failed`, downloads resumable. | Continued by the OS and delivered on relaunch. | | Force-quit by the user | Interrupted; restored as `failed`, downloads resumable. | Canceled by the OS (documented OS behavior); on relaunch retried when `maxRetries > 0`, otherwise restored as `failed` with a `transferFailed` event. | Resuming an interrupted download requires the server to support the HTTP `Range` header. ### Can I pause an upload? Not in this version. Plain HTTP uploads have no standard resume mechanism, so `pauseTransferById(...)` rejects for uploads instead of faking it with a suspend. Downloads, on the other hand, can be paused and resumed at any time, even after the app process was killed. ### Is this available on the web? No. Transfers require a native background API, so `startDownload(...)` and `startUpload(...)` reject as unavailable on the web, as do `pauseTransferById(...)` and `resumeTransferById(...)`. The remaining methods are implemented and simply report that no transfer exists. ### How is this plugin different from the `@capacitor/file-transfer` plugin? This plugin offers advanced features such as background continuation on Android and iOS, pause and resume that survives process death, a task-based API with a persisted task store, throttled progress events, automatic retries, and network constraints, and comes with priority support from the Capawesome Team. See the [Migration from `@capacitor/file-transfer`](#migration-from-capacitorfile-transfer) section for a side-by-side overview of both APIs. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [File Compressor](https://capawesome.io/docs/sdks/capacitor/file-compressor/): Compress files before uploading them. - [File Manager](https://capawesome.io/docs/sdks/capacitor/file-manager/): Access and manage the user's storage natively. - [File Opener](https://capawesome.io/docs/sdks/capacitor/file-opener/): Open the transferred files in another app. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-transfer/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-transfer/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/file-transfer/LICENSE). # Capacitor Flic Plugin Unofficial Capacitor plugin for [Flic](https://flic.io/) smart buttons.[1](#fn:1) ## Features - 🔍 **Pairing wizard**: Pair new Flic buttons with a single call using the integrated scan wizard. - 🔗 **Connection management**: Connect, disconnect and forget buttons at any time. - 🖱️ **Button events**: Listen for single click, double click, hold, down and up events. - 📥 **Queued events**: Receive events that occurred while the button was disconnected. - 🔋 **Battery level**: Read the last known battery voltage of each button. - 🔐 **Permissions**: Check and request the required permissions with a single call. - 📦 **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! ## Use Cases The Flic plugin is typically used to trigger app actions with a physical button, for example: - **Smart home control**: Toggle lights, scenes or other devices with a single press. - **Safety buttons**: Send an alert or start a call when the user presses the button. - **Time tracking**: Start and stop timers without opening the app. - **Accessibility**: Provide a simple physical trigger for essential app actions. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-flic` 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-flic npx cap sync ``` ### Android The plugin already declares all required Bluetooth permissions in its manifest, so no additional configuration is required. The [flic2lib-android](https://github.com/50ButtonsEach/flic2lib-android) library is resolved from [JitPack](https://jitpack.io/). The plugin already declares the JitPack repository in its `build.gradle` file. If your project restricts repository declarations to the settings file (e.g. via `dependencyResolutionManagement` with `FAIL_ON_PROJECT_REPOS`), add the JitPack repository to your `settings.gradle` file: ``` dependencyResolutionManagement { repositories { maven { url 'https://jitpack.io' } } } ``` #### Variables This plugin will use the following project variables (defined in your app’s `variables.gradle` file): - `$flic2libVersion` version of `com.github.50ButtonsEach:flic2lib-android` (default: `2.0.1`) ### iOS Add the `NSBluetoothAlwaysUsageDescription` and `NSBluetoothPeripheralUsageDescription` keys to the `ios/App/App/Info.plist` file, which tells the user why the app needs access to Bluetooth: ``` NSBluetoothAlwaysUsageDescription The app needs access to Bluetooth to communicate with your Flic buttons. NSBluetoothPeripheralUsageDescription The app needs access to Bluetooth to communicate with your Flic buttons. ``` If you want to receive button events while the app is in the background, you must also enable the `Uses Bluetooth LE accessories` background mode in the **Signing & Capabilities** section of your Xcode project and call `initialize(...)` with the `iosBackground` option set to `true`. ## Configuration No configuration required for this plugin. ## Usage ### Initialize the plugin Call `initialize()` as soon as possible after the app has launched to minimize the delay of any pending button events: ``` import { Flic } from '@capawesome/capacitor-flic'; const initialize = async () => { await Flic.initialize({ iosBackground: true }); }; ``` ### Pair a new button To pair a new button, start a scan and press and hold the button for at least 6 seconds: ``` import { Flic, ScanStatus } from '@capawesome/capacitor-flic'; const pairButton = async () => { await Flic.addListener('scanStatusChanged', (event) => { if (event.status === ScanStatus.Discovered) { console.log('Button discovered. Keep holding it...'); } }); const { button } = await Flic.startScan(); return button; }; ``` ### List paired buttons ``` import { Flic } from '@capawesome/capacitor-flic'; const getButtons = async () => { const { buttons } = await Flic.getButtons(); return buttons; }; ``` ### Connect a button After an app restart, each paired button must be connected again: ``` import { Flic } from '@capawesome/capacitor-flic'; const connectButtons = async () => { const { buttons } = await Flic.getButtons(); for (const button of buttons) { await Flic.connectButtonById({ id: button.id }); } }; ``` ### Listen for button events ``` import { Flic } from '@capawesome/capacitor-flic'; const addListeners = async () => { await Flic.addListener('buttonSingleClick', (event) => { console.log('Button was clicked', event); }); await Flic.addListener('buttonDoubleClick', (event) => { console.log('Button was double clicked', event); }); await Flic.addListener('buttonHold', (event) => { console.log('Button was held down', event); }); }; ``` ### Forget a button ``` import { Flic } from '@capawesome/capacitor-flic'; const forgetButton = async (id: string) => { await Flic.forgetButtonById({ id }); }; ``` ### Check and request permissions ``` import { Flic } from '@capawesome/capacitor-flic'; const checkPermissions = async () => { const permissionStatus = await Flic.checkPermissions(); return permissionStatus; }; const requestPermissions = async () => { const permissionStatus = await Flic.requestPermissions(); return permissionStatus; }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`connectButtonById(...)`](#connectbuttonbyid) - [`disconnectButtonById(...)`](#disconnectbuttonbyid) - [`forgetButtonById(...)`](#forgetbuttonbyid) - [`getButtons()`](#getbuttons) - [`initialize(...)`](#initialize) - [`requestPermissions()`](#requestpermissions) - [`startScan()`](#startscan) - [`stopScan()`](#stopscan) - [`addListener('buttonConnected', ...)`](#addlistenerbuttonconnected-) - [`addListener('buttonConnectionFailed', ...)`](#addlistenerbuttonconnectionfailed-) - [`addListener('buttonDisconnected', ...)`](#addlistenerbuttondisconnected-) - [`addListener('buttonDoubleClick', ...)`](#addlistenerbuttondoubleclick-) - [`addListener('buttonDown', ...)`](#addlistenerbuttondown-) - [`addListener('buttonHold', ...)`](#addlistenerbuttonhold-) - [`addListener('buttonReady', ...)`](#addlistenerbuttonready-) - [`addListener('buttonSingleClick', ...)`](#addlistenerbuttonsingleclick-) - [`addListener('buttonUnpaired', ...)`](#addlistenerbuttonunpaired-) - [`addListener('buttonUp', ...)`](#addlistenerbuttonup-) - [`addListener('scanStatusChanged', ...)`](#addlistenerscanstatuschanged-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### checkPermissions() ``` checkPermissions() => Promise ``` Check the status of the permissions that are required to use Flic buttons. On iOS, the Bluetooth permission is requested when `initialize(...)` is called. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### connectButtonById(...) ``` connectButtonById(options: ConnectButtonByIdOptions) => Promise ``` Connect a button. The returned promise resolves immediately. The connection is established as soon as the button is available and does not time out. Listen to the `buttonConnected` and `buttonReady` events to know when the button is ready to be used. Only available on Android and iOS. | Param | Type | | ------------- | -------------------------- | | **`options`** | `ConnectButtonByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### disconnectButtonById(...) ``` disconnectButtonById(options: DisconnectButtonByIdOptions) => Promise ``` Disconnect a button or cancel a pending connection. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `DisconnectButtonByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### forgetButtonById(...) ``` forgetButtonById(options: ForgetButtonByIdOptions) => Promise ``` Forget a button. This removes the pairing with the button. To use the button again, it must be paired again using `startScan()`. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `ForgetButtonByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### getButtons() ``` getButtons() => Promise ``` Get all buttons that are currently paired with the app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options?: InitializeOptions | undefined) => Promise ``` Initialize the plugin. This method must be called before any other method. It is recommended to call this method as soon as possible after the app has launched to minimize the delay of any pending button events. On iOS, this method triggers the Bluetooth permission prompt if the permission has not yet been granted. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request the permissions that are required to use Flic buttons. On iOS, this method only returns the current permission status since the Bluetooth permission is requested when `initialize(...)` is called. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startScan() ``` startScan() => Promise ``` Start scanning for new buttons to pair. To pair a button, press and hold it for at least 6 seconds while scanning. The returned promise resolves with the paired button once the pairing has completed. Listen to the `scanStatusChanged` event to keep the user informed about the scan progress. Only one scan can be running at a time. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### stopScan() ``` stopScan() => Promise ``` Stop an ongoing scan. This rejects the pending `startScan()` call. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonConnected', ...) ``` addListener(eventName: 'buttonConnected', listenerFunc: (event: ButtonConnectedEvent) => void) => Promise ``` Called when a button establishes a Bluetooth connection. The button is not ready to be used until the `buttonReady` event is emitted. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'buttonConnected'` | | **`listenerFunc`** | `(event: ButtonConnectedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonConnectionFailed', ...) ``` addListener(eventName: 'buttonConnectionFailed', listenerFunc: (event: ButtonConnectionFailedEvent) => void) => Promise ``` Called when a connection attempt to a button fails. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------------------- | | **`eventName`** | `'buttonConnectionFailed'` | | **`listenerFunc`** | `(event: ButtonConnectionFailedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonDisconnected', ...) ``` addListener(eventName: 'buttonDisconnected', listenerFunc: (event: ButtonDisconnectedEvent) => void) => Promise ``` Called when the Bluetooth connection with a button is lost. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'buttonDisconnected'` | | **`listenerFunc`** | `(event: ButtonDisconnectedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonDoubleClick', ...) ``` addListener(eventName: 'buttonDoubleClick', listenerFunc: (event: ButtonEvent) => void) => Promise ``` Called when a button is double clicked. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'buttonDoubleClick'` | | **`listenerFunc`** | `(event: ButtonEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonDown', ...) ``` addListener(eventName: 'buttonDown', listenerFunc: (event: ButtonEvent) => void) => Promise ``` Called when a button is pressed down. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'buttonDown'` | | **`listenerFunc`** | `(event: ButtonEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonHold', ...) ``` addListener(eventName: 'buttonHold', listenerFunc: (event: ButtonEvent) => void) => Promise ``` Called when a button is held down. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'buttonHold'` | | **`listenerFunc`** | `(event: ButtonEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonReady', ...) ``` addListener(eventName: 'buttonReady', listenerFunc: (event: ButtonReadyEvent) => void) => Promise ``` Called when a button has been cryptographically verified after a connection and is ready to be used. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'buttonReady'` | | **`listenerFunc`** | `(event: ButtonReadyEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonSingleClick', ...) ``` addListener(eventName: 'buttonSingleClick', listenerFunc: (event: ButtonEvent) => void) => Promise ``` Called when a button is clicked once. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'buttonSingleClick'` | | **`listenerFunc`** | `(event: ButtonEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonUnpaired', ...) ``` addListener(eventName: 'buttonUnpaired', listenerFunc: (event: ButtonUnpairedEvent) => void) => Promise ``` Called when the pairing with a button is no longer valid, for example because the button has been factory reset. In this case, the button must be forgotten using `forgetButtonById(...)` and then paired again. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------- | | **`eventName`** | `'buttonUnpaired'` | | **`listenerFunc`** | `(event: ButtonUnpairedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('buttonUp', ...) ``` addListener(eventName: 'buttonUp', listenerFunc: (event: ButtonEvent) => void) => Promise ``` Called when a button is released. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------ | | **`eventName`** | `'buttonUp'` | | **`listenerFunc`** | `(event: ButtonEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('scanStatusChanged', ...) ``` addListener(eventName: 'scanStatusChanged', listenerFunc: (event: ScanStatusChangedEvent) => void) => Promise ``` Called when the status of an ongoing scan changes. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'scanStatusChanged'` | | **`listenerFunc`** | `(event: ScanStatusChangedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### PermissionStatus | Prop | Type | Description | Since | | ---------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bluetooth`** | `PermissionState` | The permission state of the Bluetooth permission. Only available on iOS. | 0.1.0 | | **`bluetoothConnect`** | `PermissionState` | The permission state of the `BLUETOOTH_CONNECT` permission. Only required on Android 12 and later. Only available on Android. | 0.1.0 | | **`bluetoothScan`** | `PermissionState` | The permission state of the `BLUETOOTH_SCAN` permission. Only required on Android 12 and later. Only available on Android. | 0.1.0 | | **`location`** | `PermissionState` | The permission state of the `ACCESS_FINE_LOCATION` permission. Only required on Android 11 and earlier. Only available on Android. | 0.1.0 | #### ConnectButtonByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier of the button. | 0.1.0 | #### DisconnectButtonByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier of the button. | 0.1.0 | #### ForgetButtonByIdOptions | Prop | Type | Description | Since | | -------- | -------- | ----------------------------- | ----- | | **`id`** | `string` | The identifier of the button. | 0.1.0 | #### GetButtonsResult | Prop | Type | Description | Since | | ------------- | ---------- | --------------------------------------------------- | ----- | | **`buttons`** | `Button[]` | The buttons that are currently paired with the app. | 0.1.0 | #### Button | Prop | Type | Description | Since | | --------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`batteryVoltage`** | `number` | The last known battery voltage of the button in volts. If no battery sample has been taken yet, this property is not available. It is recommended to show a "change the battery soon" hint in your app once the voltage goes below `2.65`. | 0.1.0 | | **`connectionState`** | `ButtonConnectionState` | The connection state of the button. | 0.1.0 | | **`firmwareVersion`** | `number` | The revision of the firmware currently running on the button. | 0.1.0 | | **`id`** | `string` | The identifier of the button on this device. On Android, this is the Bluetooth address of the button. On iOS, this is an identifier that is guaranteed to be the same for each button paired to a particular device. | 0.1.0 | | **`isReady`** | `boolean` | Whether or not the button has been cryptographically verified after a connection and is ready to be used. | 0.1.0 | | **`isUnpaired`** | `boolean` | Whether or not the pairing with the button is no longer valid, for example because the button has been factory reset. In this case, the button must be forgotten using `forgetButtonById(...)` and then paired again. | 0.1.0 | | **`name`** | `string` | The human readable name of the button that the user may change in the official Flic app. | 0.1.0 | | **`pressCount`** | `number` | The number of times the button has been clicked since it last booted. | 0.1.0 | | **`serialNumber`** | `string` | The serial number of the button that is printed on the backside of the button inside the battery hatch. | 0.1.0 | | **`uuid`** | `string` | The unique identifier of the button that is the same across devices and apps. | 0.1.0 | #### InitializeOptions | Prop | Type | Description | Default | Since | | ------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`iosBackground`** | `boolean` | Whether or not you intend to use the buttons while the app is in the background. If set to `true`, the `Uses Bluetooth LE accessories` background mode must be enabled in the app's capabilities. Only available on iOS. | `false` | 0.1.0 | #### StartScanResult | Prop | Type | Description | Since | | ------------ | -------- | --------------------------- | ----- | | **`button`** | `Button` | The button that was paired. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### ButtonConnectedEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | #### ButtonConnectionFailedEvent | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | | **`message`** | `string` | The message that describes why the connection attempt failed. | 0.1.0 | #### ButtonDisconnectedEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | #### ButtonEvent | Prop | Type | Description | Since | | --------------- | --------- | ---------------------------------------------------------------------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | | **`timestamp`** | `number` | The timestamp of the event in milliseconds since the Unix epoch. | 0.1.0 | | **`wasQueued`** | `boolean` | Whether or not the event was queued because it occurred before the button was connected. | 0.1.0 | #### ButtonReadyEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | #### ButtonUnpairedEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------- | ----- | | **`buttonId`** | `string` | The identifier of the button. | 0.1.0 | #### ScanStatusChangedEvent | Prop | Type | Description | Since | | ------------ | ------------ | ----------------------- | ----- | | **`status`** | `ScanStatus` | The status of the scan. | 0.1.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### ButtonConnectionState | Members | Value | Description | Since | | ------------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------- | ----- | | **`Connected`** | `'CONNECTED'` | The button is connected. | 0.1.0 | | **`Connecting`** | `'CONNECTING'` | The button is disconnected but a pending connection is set. The button will connect as soon as it becomes available. | 0.1.0 | | **`Disconnected`** | `'DISCONNECTED'` | The button is disconnected and no pending connection is set. | 0.1.0 | | **`Disconnecting`** | `'DISCONNECTING'` | The button is connected but is attempting to disconnect. Only available on iOS. | 0.1.0 | #### ScanStatus | Members | Value | Description | Since | | ---------------------------- | ------------------------------ | -------------------------------------------------------------------------------------- | ----- | | **`AskToAcceptPairRequest`** | `'ASK_TO_ACCEPT_PAIR_REQUEST'` | The user must accept the system pairing dialog to continue. Only available on Android. | 0.1.0 | | **`Connected`** | `'CONNECTED'` | A button was found and a connection is being established. | 0.1.0 | | **`Discovered`** | `'DISCOVERED'` | A button was discovered. | 0.1.0 | | **`Verified`** | `'VERIFIED'` | The button has been cryptographically verified. Only available on iOS. | 0.1.0 | ## FAQ ### Which Flic buttons are supported? The plugin supports Flic 2 buttons. For the Flic Duo, only the events of the big button are currently delivered. ### Does the plugin receive button events while the app is in the background? On Android, events are delivered as long as the app process is alive. Consider using the [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/) plugin to keep the app process alive. On iOS, enable the `Uses Bluetooth LE accessories` background mode and call `initialize(...)` with the `iosBackground` option set to `true` (see [Installation](#installation)). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Android Foreground Service](https://capawesome.io/docs/sdks/capacitor/android-foreground-service/): Keep the app process alive to receive button events in the background on Android. - [Bluetooth Low Energy](https://capawesome.io/docs/sdks/capacitor/bluetooth-low-energy/): Communicate with other Bluetooth Low Energy devices. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/flic/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/flic/LICENSE). The bundled [flic2lib](https://github.com/50ButtonsEach/flic2lib-ios) binary is distributed under its own license from Shortcut Labs AB (see [flic2lib-LICENCE.txt](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/flic/ios/flic2lib-LICENCE.txt)). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Shortcut Labs AB or any of its affiliates or subsidiaries. "Flic" is a trademark of Shortcut Labs AB. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Formbricks Plugin Unofficial Capacitor plugin for [Formbricks](https://formbricks.com/).[1](#fn:1) ## Use Cases The Formbricks plugin is typically used to collect user feedback directly in your app, for example: - **In-app surveys**: Track actions like a button press to trigger Formbricks surveys at the right moment. - **Personalized feedback**: Identify users with a user ID to connect survey responses to specific users. - **Targeted surveys**: Set user attributes such as plan or tier to show surveys only to specific user segments. - **Localized surveys**: Set the survey language to show surveys in the user's language. ## 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-formbricks` 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-formbricks @formbricks/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: - `$formbricksAndroidVersion` version of `com.formbricks:android` (default: `1.2.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. #### Data Binding The Formbricks Android SDK requires Data Binding to be enabled in your app's `build.gradle` file: ``` android { buildFeatures { dataBinding true } } ``` ### iOS #### Minimum Deployment Target If you are using **Swift Package Manager**, make sure that your iOS deployment target is set to at least `16.6` in your Xcode project settings (usually in `ios/App/App.xcodeproj`): ``` -IPHONEOS_DEPLOYMENT_TARGET = 15.0 +IPHONEOS_DEPLOYMENT_TARGET = 16.6 ``` If you are using **CocoaPods**, make sure that your iOS deployment target is set to at least `16.6` in your `Podfile`: ``` platform :ios, '16.6' ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to set up the SDK, identify the user, set user attributes, set the survey language, track an action, and log out the user. ### Set up the SDK Set up the Formbricks SDK with the URL of your Formbricks instance and the environment ID of your Formbricks project. This method must be called before any other method: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const setup = async () => { await Formbricks.setup({ appUrl: 'https://app.formbricks.com', environmentId: 'YOUR_ENVIRONMENT_ID', }); }; ``` ### Identify the user Set the user ID of the current user to connect survey responses to that user: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const setUserId = async () => { await Formbricks.setUserId({ userId: 'user-123', }); }; ``` ### Set user attributes Set a single attribute with `setAttribute(...)` or multiple attributes at once with `setAttributes(...)`, for example to target surveys to specific user segments: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const setAttribute = async () => { await Formbricks.setAttribute({ key: 'plan', value: 'pro', }); }; const setAttributes = async () => { await Formbricks.setAttributes({ attributes: { plan: 'pro', tier: 'gold', }, }); }; ``` ### Set the survey language Set the language in which surveys are displayed: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const setLanguage = async () => { await Formbricks.setLanguage({ language: 'de', }); }; ``` ### Track an action Track an action that may trigger a survey: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const track = async () => { await Formbricks.track({ action: 'button_pressed', }); }; ``` ### Log out the user Log out the current user and clear all attributes: ``` import { Formbricks } from '@capawesome/capacitor-formbricks'; const logout = async () => { await Formbricks.logout(); }; ``` ## API - [`logout()`](#logout) - [`setAttribute(...)`](#setattribute) - [`setAttributes(...)`](#setattributes) - [`setLanguage(...)`](#setlanguage) - [`setUserId(...)`](#setuserid) - [`setup(...)`](#setup) - [`track(...)`](#track) - [Interfaces](#interfaces) ### logout() ``` logout() => Promise ``` Log out the current user and clear all attributes. **Since:** 0.1.0 ______________________________________________________________________ ### setAttribute(...) ``` setAttribute(options: SetAttributeOptions) => Promise ``` Set a single user attribute. | Param | Type | | ------------- | --------------------- | | **`options`** | `SetAttributeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setAttributes(...) ``` setAttributes(options: SetAttributesOptions) => Promise ``` Set multiple user attributes at once. | Param | Type | | ------------- | ---------------------- | | **`options`** | `SetAttributesOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setLanguage(...) ``` setLanguage(options: SetLanguageOptions) => Promise ``` Set the survey language. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetLanguageOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUserId(...) ``` setUserId(options: SetUserIdOptions) => Promise ``` Set the user ID of the current user. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetUserIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setup(...) ``` setup(options: SetupOptions) => Promise ``` Setup the Formbricks SDK with the provided options. **Attention**: This method must be called before any other method. | Param | Type | | ------------- | -------------- | | **`options`** | `SetupOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### track(...) ``` track(options: TrackOptions) => Promise ``` Track an action that may trigger a survey. | Param | Type | | ------------- | -------------- | | **`options`** | `TrackOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### SetAttributeOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------- | ----- | | **`key`** | `string` | The name of the attribute. | 0.1.0 | | **`value`** | `string` | The value of the attribute. | 0.1.0 | #### SetAttributesOptions | Prop | Type | Description | Since | | ---------------- | ---------------------------- | ---------------------- | ----- | | **`attributes`** | `{ [key: string]: string; }` | The attributes to set. | 0.1.0 | #### SetLanguageOptions | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------- | ----- | | **`language`** | `string` | The language code of the survey language (e.g. `de`). | 0.1.0 | #### SetUserIdOptions | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------- | ----- | | **`userId`** | `string` | The user ID of the current user. | 0.1.0 | #### SetupOptions | Prop | Type | Description | Since | | ------------------- | -------- | ---------------------------------------------- | ----- | | **`appUrl`** | `string` | The URL of your Formbricks instance. | 0.1.0 | | **`environmentId`** | `string` | The environment ID of your Formbricks project. | 0.1.0 | #### TrackOptions | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------- | ----- | | **`action`** | `string` | The name of the action to track. | 0.1.0 | ## FAQ ### Can I use this plugin with a self-hosted Formbricks instance? Yes, the `appUrl` option of the `setup(...)` method takes the URL of your Formbricks instance. Simply pass the URL of your self-hosted instance instead of `https://app.formbricks.com`. ### Why is no survey shown after calling `track`? Make sure that `setup(...)` has been called first, since it must be called before any other method. Also note that `track(...)` only tracks an action that *may* trigger a survey. Whether a survey is actually shown depends on how the survey is configured in your Formbricks project. ### Why does my iOS build fail after installing the plugin? The Formbricks iOS SDK requires an iOS deployment target of at least `16.6`. Make sure that your deployment target is set accordingly in your Xcode project settings or `Podfile`, as described in the [Installation](#installation) section. ### Why does my Android build fail after installing the plugin? The Formbricks Android SDK requires Data Binding to be enabled in your app's `build.gradle` file. See the [Installation](#installation) section for the required configuration. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Review](https://capawesome.io/docs/sdks/capacitor/app-review/): Let users submit app store reviews and ratings. - [Grafana Faro](https://capawesome.io/docs/sdks/capacitor/grafana-faro/): Unofficial Capacitor plugin for Grafana Faro. - [Intercom](https://capawesome.io/docs/sdks/capacitor/intercom/): Unofficial Capacitor plugin for the Intercom live chat and customer support platform. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/): Unofficial Capacitor plugin for PostHog. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/formbricks/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/formbricks/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Formbricks GmbH or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Geocoder Plugin Capacitor plugin for handling geocoding and reverse geocoding. ## Features The Capacitor Geocoder plugin is one of the most complete geocoding solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [Compass](https://capawesome.io/docs/sdks/capacitor/compass/) and [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/) 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! ## Use Cases The Geocoder plugin is typically used whenever an app needs to translate between addresses and coordinates, for example: - **Address search**: Convert a user-entered address into geographic coordinates to display the location on a map. - **Location labeling**: Show a human-readable address for given coordinates, for example the user's current position. - **Address forms**: Prefill address fields such as postal code, country, or administrative area based on coordinates. - **Localized results**: Return addresses in the user's preferred language using the `locale` option. ## 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 The following examples show how to convert an address into coordinates and how to convert coordinates back into an address. ### Convert an address into coordinates Use the `geocode(...)` method to translate a human-readable address into geographic coordinates, for example to display the location on a map: ``` 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); }; ``` ### Convert coordinates into an address Use the `geodecode(...)` method to translate geographic coordinates into one or more human-readable addresses. The number of returned addresses can be limited with the `limit` option: ``` import { Geocoder } from '@capawesome-team/capacitor-geocoder'; 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 | ## FAQ ### What is the difference between geocoding and reverse geocoding? Geocoding translates a human-readable address into geographic coordinates, which is what the `geocode` method does. Reverse geocoding is the opposite: the `geodecode` method translates a latitude and longitude into one or more human-readable addresses. ### Which geocoding services does the plugin use? On Android and iOS, the plugin uses the platform-native geocoding services for reliable and accurate results. On the Web, you can choose between Google Maps and OpenStreetMap using the `webProvider` option, with OpenStreetMap being the default. ### Do I need an API key to use this plugin? No API key is required on Android and iOS since the plugin uses the platform-native geocoding services. On the Web, you can provide an API key for the geocoding service using the `webApiKey` option. When using the default OpenStreetMap provider, you should also set the `webUserAgent` option to identify your application, as required by the [Nominatim usage policy](https://operations.osmfoundation.org/policies/nominatim/). ### How can I limit the number of addresses returned by a reverse geocoding request? Use the `limit` option of the `geodecode` method. By default, a maximum of 5 addresses is returned for the given coordinates. ### Can I get results in a specific language? Yes, both methods accept a `locale` option that takes a BCP 47 language tag. If no locale is provided, the device's locale is used. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/): Read the device compass heading. - [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/): Launch navigation apps with turn-by-turn directions. ## 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/). ## 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). # Capacitor Geofences Plugin Capacitor plugin for monitoring OS-managed geofences (region monitoring) on Android and iOS. Detects enter, exit and dwell transitions even while the app is in the background or terminated. ## Features The Capacitor Geofences plugin lets your app react when a device enters or leaves a geographic region, using the battery-efficient region monitoring built into the operating system. Here are some of the key features: - 🌍 **OS-Managed Regions**: Uses `GeofencingClient` on Android and `CLLocationManager` region monitoring on iOS, so transitions are detected by the system with minimal battery impact. - 🔔 **Transition Events**: Get notified about enter, exit and (on Android) dwell transitions. - 💀 **Killed-App Delivery**: Transitions that occur while the app is terminated are stored in a durable on-device queue and can trigger a native local notification. - 📥 **Durable Queue**: Read the queue from JavaScript with keyset pagination and acknowledge what you processed, so nothing is lost between two app starts. - ☁️ **HTTP Upload**: Upload transitions to your own server with automatic retries and at-least-once delivery — even while the app is in the background or terminated. - 🔁 **Auto Re-Registration**: On Android, geofences are automatically re-registered after a device reboot or an app update. - 🔒 **Public APIs Only**: Built exclusively on public platform APIs, so it is safe for App Review and resilient to OS updates. - 🤝 **Compatibility**: Works hand in hand with the [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/) plugin for continuous location tracking. - 📦 **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! ## Use Cases The Geofences plugin is typically used whenever an app needs to react when a device enters or leaves a specific area, for example: - **Location-based reminders**: Notify users when they arrive at or leave a place, for example a store, an office, or their home. - **Attendance and check-ins**: Check users in or out automatically when they enter or leave a site, using dwell transitions on Android to confirm that they actually stayed. - **Proximity marketing**: Display a native local notification with an offer when a customer walks near one of your branches, even while the app is terminated. - **Field service and logistics**: Record arrivals at and departures from customer sites or depots without draining the battery through continuous tracking. - **Safety zones**: Alert caregivers or fleet managers when a person or vehicle leaves a defined safe area. - **Context-aware apps**: Adapt the app to the user's surroundings, for example by switching to a venue-specific view once the user is on site. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.3.x | >=8.x.x | Active support | | 0.2.x | >=8.x.x | Deprecated | | 0.1.x | >=8.x.x | Deprecated | ## Guides - [Announcing the Capacitor Geofences Plugin](https://capawesome.io/blog/announcing-the-capacitor-geofences-plugin/): Monitor OS-managed regions on Android and iOS and get enter, exit, and dwell events, even after the app is killed. - [Why Are Your Geofences Not Triggering on iOS?](https://capawesome.io/blog/why-are-your-geofences-not-triggering-on-ios/): The iOS threshold rule, the already-inside case, permission levels, radius and reboot behavior, and how the queue preserves transitions. ## 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-geofences` 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-geofences npx cap sync ``` ### Android #### Permissions The plugin already declares the `ACCESS_FINE_LOCATION`, `POST_NOTIFICATIONS` and `RECEIVE_BOOT_COMPLETED` permissions in its manifest. Geofencing additionally requires the **background location** permission. For [Google Play policy](https://support.google.com/googleplay/android-developer/answer/9799150) reasons, this permission is **not** declared by the plugin and must be added to your app's `AndroidManifest.xml` before or after the `application` tag: ``` ``` Starting with Android 10 (API level 29), the background location permission cannot be requested together with the foreground location permission. You must first request the foreground location permission and only afterwards request the background location permission (see [Check and request permissions](#check-and-request-permissions)). #### 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 variables in your app's `variables.gradle` file to change the default versions of the dependencies: - `$androidxWorkVersion` version of `androidx.work:work-runtime` (default: `2.11.2`) - `$playServicesLocationVersion` version of `com.google.android.gms:play-services-location` (default: `21.4.0`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS #### Privacy Descriptions Add the `NSLocationWhenInUseUsageDescription` and `NSLocationAlwaysAndWhenInUseUsageDescription` keys to the `ios/App/App/Info.plist` file, which tells the user why your app needs access to the location: ``` NSLocationWhenInUseUsageDescription The app needs access to your location to monitor geofences. NSLocationAlwaysAndWhenInUseUsageDescription The app needs access to your location to monitor geofences while it is in the background. ``` If the keys are missing, `addGeofences(...)` and `requestPermissions(...)` reject with a clear error message. ## Configuration No configuration required for this plugin. ## Usage The following examples are grouped into permissions, geofences, transitions and upload. ### Getting started Request the required permissions, register a geofence and drain the transition queue whenever your app becomes active. The `geofenceTransition` event is a live feed that only fires while your app is running — the **queue is the durable record** of every transition, including the ones that were detected while the app was terminated. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const start = async () => { // 1. Request the foreground and afterwards the background location permission. let status = await Geofences.requestPermissions({ permissions: ['location'] }); if (status.location !== 'granted') { return; } status = await Geofences.requestPermissions({ permissions: ['backgroundLocation'], }); if (status.backgroundLocation !== 'granted') { return; } // 2. Add the geofence to monitor. await Geofences.addGeofences({ geofences: [{ latitude: 37.33182, longitude: -122.03118, radius: 200 }], }); // 3. Drain the queue now and whenever the app becomes active again. await drainQueue(); document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible') { drainQueue(); } }); }; const drainQueue = async () => { let hasMore = true; while (hasMore) { const result = await Geofences.getQueuedTransitions({ limit: 1000 }); if (!result.transitions.length) { break; } await persist(result.transitions); await Geofences.deleteQueuedTransitions({ upToId: result.transitions[result.transitions.length - 1].id, }); hasMore = result.hasMore; } }; ``` ### Permissions #### Check and request permissions Geofencing requires the **Always** location authorization on iOS and the **background location** permission on Android. Because of the platform restrictions described in the [Installation](#installation) section, the permissions must be requested in two steps: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const checkPermissions = async () => { return Geofences.checkPermissions(); }; const requestPermissions = async () => { // Step 1: Request the foreground location permission. let status = await Geofences.requestPermissions({ permissions: ['location'], }); // Step 2: Request the background location permission. if (status.location === 'granted') { status = await Geofences.requestPermissions({ permissions: ['backgroundLocation'], }); } // Optionally: Request the notifications permission. await Geofences.requestPermissions({ permissions: ['notifications'] }); return status; }; ``` #### Open the app settings If a permission was permanently denied, send the user to the native app settings: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const openSettings = async () => { await Geofences.openSettings(); }; ``` ### Geofences #### Add geofences Add one or more circular regions to be monitored by the operating system. Optionally, provide a `notification` that is displayed natively when a transition is detected, which is especially useful while the app is terminated. On Android, an enter transition is triggered immediately if the device is already inside a geofence that was just added, while iOS only reports a transition once the device crosses the boundary. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const addGeofences = async () => { const { ids } = await Geofences.addGeofences({ geofences: [ { latitude: 37.33182, longitude: -122.03118, radius: 200, notification: { title: 'Welcome', text: 'You have entered the area.', }, }, ], }); return ids; }; ``` #### Retrieve geofences Retrieve all geofences that are currently being monitored. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const getGeofences = async () => { const { geofences } = await Geofences.getGeofences(); return geofences; }; ``` #### Remove geofences Remove specific geofences by their identifier or remove all of them at once. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const removeGeofences = async (ids: string[]) => { await Geofences.removeGeofences({ ids }); }; const removeAllGeofences = async () => { await Geofences.removeAllGeofences(); }; ``` ### Transitions #### Listen for geofence transitions Get notified when the device enters, exits, or (on Android) dwells inside a geofence. The event is a **live feed**: it is only delivered while your app is running and a listener is registered. Transitions that were detected while the app was in the background or terminated are **not** replayed — read them from the [queue](#read-the-queue) instead. Only available on Android and iOS: ``` import { Geofences, TransitionType } from '@capawesome-team/capacitor-geofences'; const addListener = async () => { await Geofences.addListener('geofenceTransition', (event) => { if (event.transition.type === TransitionType.Enter) { console.log(`Entered the geofence ${event.transition.geofenceId}.`); } }); }; ``` #### Read the queue Every transition is appended to a durable on-device queue, whether or not your app is running. Read the queue in pages, process the transitions and delete the ones you processed. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const drainQueue = async () => { let hasMore = true; while (hasMore) { const result = await Geofences.getQueuedTransitions({ limit: 1000 }); if (!result.transitions.length) { break; } await persist(result.transitions); await Geofences.deleteQueuedTransitions({ upToId: result.transitions[result.transitions.length - 1].id, }); hasMore = result.hasMore; } }; ``` See [Queue](#queue) for the capacity, the identifiers and the delivery guarantees. #### Configure the queue The queue is disabled until you configure it and then holds `1000` transitions by default. Call `resetConfig()` to disable it again. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const configureQueue = async () => { await Geofences.setConfig({ maxSize: 5000 }); }; ``` #### Inspect and clear the queue Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const getQueueStatus = async () => { const { pendingCount, droppedCount, lastUploadedAt } = await Geofences.getQueueStatus(); console.log( `${pendingCount} transitions pending, ${droppedCount} dropped, last upload: ${lastUploadedAt}`, ); }; const clearQueue = async () => { await Geofences.clearQueue(); }; ``` #### Remove all listeners Remove all listeners for this plugin when they are no longer needed: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const removeAllListeners = async () => { await Geofences.removeAllListeners(); }; ``` ### Upload #### Configure the upload Configure the plugin to upload every queued transition to your own server, even while the app is in the background or terminated. The configuration is persisted natively, so it only needs to be set once (e.g. after sign-in). Failed upload attempts are reported via the `uploadFailed` event. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const configureUpload = async () => { await Geofences.addListener('uploadFailed', (event) => { console.error('Upload failed: ', event.statusCode, event.message); }); await Geofences.setConfig({ url: 'https://api.example.com/transitions', headers: { Authorization: 'Bearer eyJhbGciOi...', }, extras: { userId: 'abc', }, }); }; const disableUpload = async () => { await Geofences.resetConfig(); }; ``` **Attention**: `setConfig(...)` replaces the stored configuration entirely, so always pass every property you want to keep. See [HTTP Upload](#http-upload) for the server contract and the response handling. #### Trigger the upload Upload the queued transitions right away instead of waiting for the next retry. Only available on Android and iOS: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const triggerUpload = async () => { await Geofences.triggerUpload(); }; ``` ## API - [`addGeofences(...)`](#addgeofences) - [`checkPermissions()`](#checkpermissions) - [`clearQueue()`](#clearqueue) - [`deleteQueuedTransitions(...)`](#deletequeuedtransitions) - [`getConfig()`](#getconfig) - [`getGeofences()`](#getgeofences) - [`getQueuedTransitions(...)`](#getqueuedtransitions) - [`getQueueStatus()`](#getqueuestatus) - [`openSettings()`](#opensettings) - [`removeAllGeofences()`](#removeallgeofences) - [`removeGeofences(...)`](#removegeofences) - [`requestPermissions(...)`](#requestpermissions) - [`resetConfig()`](#resetconfig) - [`setConfig(...)`](#setconfig) - [`triggerUpload()`](#triggerupload) - [`addListener('geofenceTransition', ...)`](#addlistenergeofencetransition-) - [`addListener('uploadFailed', ...)`](#addlisteneruploadfailed-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### addGeofences(...) ``` addGeofences(options: AddGeofencesOptions) => Promise ``` Add one or more geofences to be monitored. On **Android**, an enter transition is triggered immediately if the device is already inside a geofence that was just added. On **iOS**, no transition is triggered until the device crosses the boundary of the geofence. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `AddGeofencesOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check permissions for the plugin. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### clearQueue() ``` clearQueue() => Promise ``` Delete all transitions from the queue. Use `deleteQueuedTransitions(...)` to only delete the transitions that have already been read. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### deleteQueuedTransitions(...) ``` deleteQueuedTransitions(options: DeleteQueuedTransitionsOptions) => Promise ``` Delete all transitions from the queue up to and including the given identifier. Call this method after the transitions returned by `getQueuedTransitions(...)` have been processed successfully. Only available on Android and iOS. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `DeleteQueuedTransitionsOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### getConfig() ``` getConfig() => Promise ``` Get the configuration that was last set via `setConfig(...)`. Only the properties that were provided are returned, so the result can be spread into `setConfig(...)` to change a single property: ``` const config = await Geofences.getConfig(); await Geofences.setConfig({ ...config, maxSize: 5000 }); ``` An empty object is returned if `setConfig(...)` has not been called yet. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### getGeofences() ``` getGeofences() => Promise ``` Get all geofences that are currently being monitored. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getQueuedTransitions(...) ``` getQueuedTransitions(options?: GetQueuedTransitionsOptions | undefined) => Promise ``` Get the transitions that are currently stored in the queue, oldest first. The transitions remain in the queue until they are deleted via `deleteQueuedTransitions(...)` or `clearQueue()`. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `GetQueuedTransitionsOptions` | **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### getQueueStatus() ``` getQueueStatus() => Promise ``` Get the current status of the queue. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Opens the native app settings page to allow the user to grant the app the required permissions. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### removeAllGeofences() ``` removeAllGeofences() => Promise ``` Remove all geofences that are currently being monitored. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### removeGeofences(...) ``` removeGeofences(options: RemoveGeofencesOptions) => Promise ``` Remove one or more geofences by their identifier. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `RemoveGeofencesOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options?: RequestPermissionsOptions | undefined) => Promise ``` Request permissions for the plugin. The `backgroundLocation` permission must be requested in a **second**, separate call after the `location` permission has been granted: - On **Android 11+**, the user is taken to the location settings of the app where the `Allow all the time` option must be selected. - On **iOS**, the operating system presents the upgrade prompt that asks the user to change the permission from `While Using the App` to `Always`. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### resetConfig() ``` resetConfig() => Promise ``` Discard the configuration so that no transitions are stored or uploaded. Transitions that are already queued are kept, use `clearQueue()` to discard them. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### setConfig(...) ``` setConfig(options: SetConfigOptions) => Promise ``` Set the configuration of the transition queue and of the upload. The configuration is persisted natively so that it keeps working when the operating system wakes your app without a web view. Transitions are stored in the queue if `maxSize` or `url` is provided, and they are uploaded if `url` is provided. Use `resetConfig()` to stop storing and uploading transitions. **This method replaces the whole configuration**, so every property you omit falls back to its default. `setConfig({ maxSize: 5000 })` therefore also stops the upload, because `url` is no longer set. Keep your configuration in one place and pass it whole: ``` await Geofences.setConfig({ maxSize: 5000, url }); ``` Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetConfigOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### triggerUpload() ``` triggerUpload() => Promise ``` Immediately attempt to upload all queued transitions. Any pending retry backoff is cancelled and a new upload attempt is started right away. The promise resolves as soon as the attempt has been scheduled, not when the transitions have been delivered. The promise rejects if no `url` has been set via `setConfig(...)`. Only available on Android and iOS. **Since:** 0.2.0 ______________________________________________________________________ ### addListener('geofenceTransition', ...) ``` addListener(eventName: 'geofenceTransition', listenerFunc: (event: GeofenceTransitionEvent) => void) => Promise ``` Called when a geofence transition (enter, exit or dwell) is detected. This event is a live feed that is only delivered while a listener is registered. Transitions that occurred while the app was in the background or terminated are **not** replayed. Read them from the queue via `getQueuedTransitions(...)` instead. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'geofenceTransition'` | | **`listenerFunc`** | `(event: GeofenceTransitionEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('uploadFailed', ...) ``` addListener(eventName: 'uploadFailed', listenerFunc: (event: UploadFailedEvent) => void) => Promise ``` Called when an upload attempt of queued transitions fails. The affected transitions remain in the queue and are retried automatically unless the server rejected them permanently. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'uploadFailed'` | | **`listenerFunc`** | `(event: UploadFailedEvent) => void` | **Returns:** `Promise` **Since:** 0.2.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### AddGeofencesResult | Prop | Type | Description | Since | | --------- | ---------- | ---------------------------------------------------------------------------------------------------- | ----- | | **`ids`** | `string[]` | The identifiers of the added geofences. The order matches the order of the geofences in the request. | 0.0.1 | #### AddGeofencesOptions | Prop | Type | Description | Since | | --------------- | ------------ | --------------------- | ----- | | **`geofences`** | `Geofence[]` | The geofences to add. | 0.0.1 | #### Geofence | Prop | Type | Description | Default | Since | | --------------------------- | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`androidLoiteringDelay`** | `number` | The time in milliseconds the device must dwell inside the geofence before a dwell transition event is triggered. Only available on Android. | | 0.0.1 | | **`androidNotifyOnDwell`** | `boolean` | Whether a transition event should be triggered when the device dwells inside the geofence. Only available on Android. | `false` | 0.0.1 | | **`id`** | `string` | A unique identifier for the geofence. If not provided, a random identifier (UUID) is generated and returned in the result of the `addGeofences(...)` method. | | 0.0.1 | | **`latitude`** | `number` | The latitude of the center of the geofence in degrees. | | 0.0.1 | | **`longitude`** | `number` | The longitude of the center of the geofence in degrees. | | 0.0.1 | | **`radius`** | `number` | The radius of the geofence in meters. Apple recommends a radius of at least 200 meters, as smaller radii may not trigger transitions reliably. | | 0.0.1 | | **`notifyOnEnter`** | `boolean` | Whether a transition event should be triggered when the device enters the geofence. | `true` | 0.0.1 | | **`notifyOnExit`** | `boolean` | Whether a transition event should be triggered when the device exits the geofence. | `true` | 0.0.1 | | **`notification`** | `GeofenceNotification` | A local notification to display natively when a transition for this geofence is detected. This is especially useful to notify the user while the app is terminated. | | 0.0.1 | #### GeofenceNotification | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------- | ----- | | **`title`** | `string` | The title of the notification. | 0.0.1 | | **`text`** | `string` | The body text of the notification. | 0.0.1 | #### PermissionStatus | Prop | Type | Description | Since | | ------------------------ | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`location`** | `PermissionState` | The permission state for using the location while the app is in use. | 0.0.1 | | **`backgroundLocation`** | `PermissionState` | The permission state for using the location while the app is in the background. This permission is required to monitor geofences while the app is in the background or terminated. | 0.0.1 | | **`notifications`** | `PermissionState` | The permission state for displaying local notifications on a transition. | 0.0.1 | #### DeleteQueuedTransitionsOptions | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------------------------------------------------------------------------------- | ----- | | **`upToId`** | `number` | The identifier of the newest transition to delete. All transitions with this identifier or a lower one are deleted. | 0.2.0 | #### SetConfigOptions | Prop | Type | Description | Default | Since | | ------------- | ---------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------------------------------------------------------------ | | **`extras`** | \`{ \[key: string\]: string | number | boolean; }\` | Static metadata that is attached to every upload request as the `extras` property of the request body. | | **`headers`** | `{ [key: string]: string; }` | Static HTTP headers that are sent with every upload request, for example for authorization. | | 0.2.0 | | **`maxSize`** | `number` | The maximum number of transitions that are stored in the queue. When the queue is full, the oldest transitions are dropped first. A stored transition occupies about `140` bytes, so the default of `1000` needs about `140` KB. Must be positive. | `1000` | 0.2.0 | | **`url`** | `string` | The URL the transitions are uploaded to via HTTP `POST`. Providing this property enables the upload. Omit it to keep the transitions on the device only. | | 0.2.0 | #### GetGeofencesResult | Prop | Type | Description | Since | | --------------- | ------------ | ------------------------------------------------- | ----- | | **`geofences`** | `Geofence[]` | The geofences that are currently being monitored. | 0.0.1 | #### GetQueuedTransitionsResult | Prop | Type | Description | Since | | ----------------- | -------------------- | -------------------------------------------------------------------- | ----- | | **`hasMore`** | `boolean` | Whether more transitions are stored in the queue than were returned. | 0.2.0 | | **`transitions`** | `QueuedTransition[]` | The queued transitions, oldest first. | 0.2.0 | #### QueuedTransition | Prop | Type | Description | Since | | -------- | -------- | ---------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `number` | The identifier of the transition in the queue. The identifier increases monotonically and is never reused. | 0.2.0 | #### GetQueuedTransitionsOptions | Prop | Type | Description | Default | Since | | ------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`afterId`** | `number` | Only return transitions with an identifier greater than this value. If not provided, the transitions are returned from the start of the queue. | | 0.2.0 | | **`limit`** | `number` | The maximum number of transitions to return. Must be greater than `0`. | `100` | 0.2.0 | #### GetQueueStatusResult | Prop | Type | Description | Since | | -------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`droppedCount`** | `number` | The number of transitions that were dropped without being read since the queue was last cleared, for example because the queue was full. This counter is persisted across app restarts. | 0.2.0 | | **`lastUploadedAt`** | \`number | null\` | The time at which the last batch of transitions was uploaded successfully in milliseconds since the Unix epoch or `null` if no batch has been uploaded successfully yet. This value is persisted across app restarts. | | **`pendingCount`** | `number` | The number of transitions that are currently stored in the queue. | 0.2.0 | #### RemoveGeofencesOptions | Prop | Type | Description | Since | | --------- | ---------- | ------------------------------------------- | ----- | | **`ids`** | `string[]` | The identifiers of the geofences to remove. | 0.0.1 | #### RequestPermissionsOptions | Prop | Type | Description | Default | Since | | ----------------- | ------------------ | --------------------------- | ------------------------------- | ----- | | **`permissions`** | `PermissionType[]` | The permissions to request. | `['location', 'notifications']` | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### GeofenceTransitionEvent | Prop | Type | Description | Since | | ---------------- | -------------------- | ------------------------ | ----- | | **`transition`** | `GeofenceTransition` | The detected transition. | 0.2.0 | #### GeofenceTransition | Prop | Type | Description | Since | | ---------------- | ---------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`geofenceId`** | `string` | The identifier of the geofence that triggered the transition. | 0.2.0 | | **`latitude`** | \`number | null\` | The latitude of the location that triggered the transition in degrees. On **iOS**, this is always `null` because the triggering location is not provided. | | **`longitude`** | \`number | null\` | The longitude of the location that triggered the transition in degrees. On **iOS**, this is always `null` because the triggering location is not provided. | | **`timestamp`** | `number` | The time the transition was detected in milliseconds since epoch. | 0.2.0 | | **`type`** | `TransitionType` | The type of the transition. | 0.2.0 | #### UploadFailedEvent | Prop | Type | Description | Since | | ---------------- | -------- | ----------------------------------------------------------------- | ----- | | **`message`** | `string` | The error message. | 0.0.1 | | **`statusCode`** | `number` | The HTTP status code of the response, if a response was received. | 0.0.1 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### GetConfigResult `SetConfigOptions` #### PermissionType `'location' | 'backgroundLocation' | 'notifications'` ### Enums #### TransitionType | Members | Value | Description | Since | | ----------- | --------- | ---------------------------------------------------------------------- | ----- | | **`Dwell`** | `'DWELL'` | The device has dwelled inside the geofence. Only available on Android. | 0.0.1 | | **`Enter`** | `'ENTER'` | The device has entered the geofence. | 0.0.1 | | **`Exit`** | `'EXIT'` | The device has exited the geofence. | 0.0.1 | ## Queue Once the queue is enabled, every geofence transition is appended to it, whether or not your app is running. The queue is then the **single source of truth**: the `geofenceTransition` event is an ephemeral live feed that only fires while your app is running, so an app that only listens to the event misses every transition that was detected while it was terminated. Read the queue with `getQueuedTransitions(...)` and acknowledge the transitions you processed with `deleteQueuedTransitions(...)`. Reading and deleting are two separate steps on purpose: a crash between reading and persisting must not lose a transition. ### Identifiers Every queued transition carries a numeric `id` that increases monotonically and is never reused, even after the queue was cleared or the app was restarted. This makes it usable both for keyset pagination via `afterId` and for the inclusive prefix delete via `upToId`. ### Behavior - **Persistence**: The queue is stored in the sandboxed app storage and survives app restarts, force-quits and device reboots. - **Opt-in**: Nothing is stored until you call `setConfig(...)` with `maxSize` or `url`, and transitions detected before that are not stored. Call `resetConfig()` to disable it again; transitions that are already queued are kept until `clearQueue()`. - **Capacity**: The queue holds at most `1000` transitions by default, which can be changed via `setConfig({ maxSize })`. If the queue is full, the **oldest** transitions are dropped first. - **Observability**: The `droppedCount` property of `getQueueStatus()` counts the transitions that were dropped since the queue was last cleared. The counter is persisted across app restarts. - **Duplicates**: A transition that is delivered via the `geofenceTransition` event while your app is running is also stored in the queue. Treat the queue as the source of truth to avoid processing it twice. ## HTTP Upload The plugin can upload every queued transition to your own server without involving JavaScript. Transitions are uploaded to the configured URL and only deleted from the queue after the server has acknowledged them. Since the whole pipeline runs natively, it keeps working while the web view is suspended — and, unlike a watch session of the [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/) plugin, even while the app is terminated. **Attention**: The upload drains the same queue that `getQueuedTransitions(...)` reads. Transitions are stored once and deleted once, so a transition your app deletes with `deleteQueuedTransitions(...)` is never uploaded, and a transition the server acknowledged is no longer readable. Use either the upload or your own drain loop, not both. ### Options Call `setConfig({ url })` to enable the upload pipeline. The configuration is persisted natively so that transitions can be uploaded when the operating system wakes your app without a web view, and it applies to every future transition until `resetConfig()` is called. Call it on every app start anyway: the stored copy is only a cache of your last call, and it is discarded whenever the plugin's configuration format changes in a future version. Queued transitions are never discarded with it: ``` import { Geofences } from '@capawesome-team/capacitor-geofences'; const configureUpload = async () => { await Geofences.setConfig({ url: 'https://api.example.com/transitions', headers: { Authorization: 'Bearer eyJhbGciOi...', }, extras: { userId: 'abc', }, }); }; ``` **Attention**: The configuration you pass replaces the stored one entirely. Every property you omit falls back to its default, so read the stored configuration with `getConfig()` first if you only want to change a single property. To stop the upload but keep storing transitions, call `setConfig(...)` again without `url`: ``` const { url, ...config } = await Geofences.getConfig(); await Geofences.setConfig(config); ``` ### Server Contract Transitions are uploaded with an HTTP `POST` request and the `Content-Type: application/json; charset=utf-8` header. Your own `headers` are applied afterwards and may override it. The request body looks as follows: ``` { "transitions": [ { "id": 42, "geofenceId": "2ca23ff9-b95d-4962-b64f-3e1efe6f2e7d", "type": "ENTER", "timestamp": 1723291200000, "latitude": 52.52, "longitude": 13.405 } ], "extras": { "userId": "abc" } } ``` Every entry of the `transitions` array is a [`QueuedTransition`](#queuedtransition) object whose `id` property is the identifier of the transition in the queue and whose `geofenceId` property is the identifier of the geofence. On iOS, `latitude` and `longitude` are always `null`. The `extras` property is omitted entirely if the `extras` option was not provided. **Idempotency**: The `id` is unique per app installation, not globally. Transitions are delivered **at least once**, so the same transition may be uploaded more than once, for example if the acknowledgment of the server is lost on the way back. Deduplicate the transitions on the server by the `id` together with a device or user identifier from `extras` to make the upload idempotent. ### Response Handling The response body is always ignored. Only the status code decides what happens to the uploaded transitions: | Status Code | Behavior | | -------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | `2xx` | The transitions are acknowledged and deleted from the queue. | | `401`, `408`, `429`, `5xx`, network error, timeout | The transitions stay in the queue and are retried with an exponential backoff. A `uploadFailed` event is emitted. | | Any other status code | The transitions are **dropped permanently without being uploaded again** and a `uploadFailed` event is emitted. | **Attention**: Transitions that the server rejects with any status code other than `2xx`, `401`, `408`, `429` or `5xx` (for example `400 Bad Request` or `422 Unprocessable Entity`) are deleted from the queue and **lost**. This is intentional: a permanently rejected upload must never block the queue forever. `401` is treated as retryable, because it usually means an expired token rather than an unacceptable upload — refresh the credentials with `setConfig(...)` and the queued transitions are uploaded afterwards. Make sure your endpoint answers with a retryable status code (e.g. `503`) if it is temporarily unable to accept transitions, and listen to the `uploadFailed` event to detect such drops. Dropped transitions are also counted in the `droppedCount` property of `getQueueStatus()`. The request timeout is 30 seconds. On Android, retries are scheduled via WorkManager with an exponential backoff starting at 10 seconds, so they survive a process death and even a device reboot. On iOS, retries start with a delay of 5 seconds and double after every attempt up to a maximum of 10 minutes while the app is alive; pending transitions are also uploaded on the next app launch and whenever a new transition is detected. The backoff is reset whenever `setConfig({ url })` or `triggerUpload()` is called. ### Deliberately Not Supported The following features are intentionally not part of the upload pipeline: - **Network constraints**: Uploads cannot be restricted to Wi-Fi or unmetered networks. - **Response processing**: The response body of the server is always ignored, so the server cannot send commands back to the device. - **Encryption at rest**: The queue is not encrypted. It is stored in the sandboxed app storage of the operating system. ## FAQ ### When should I use this plugin instead of the Background Geolocation plugin? Both plugins solve different problems and complement each other. The [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/) plugin continuously tracks the device's location and streams position updates to your app, which is what you need when you want the actual location trail. This plugin monitors circular regions and only notifies your app when the device crosses a region boundary, which is much cheaper on battery and can even deliver transitions while your app is terminated. Use Background Geolocation when you need the location trail, and Geofences when you only care about entering or leaving specific areas. ### How is this plugin different from other similar plugins? It uses the operating system's own region monitoring — `GeofencingClient` on Android and Core Location on iOS — so enter, exit and dwell transitions are detected with minimal battery impact, even while your app is in the background or terminated. Transitions that occur while the app is killed are stored in a durable queue your app drains on the next launch and can trigger a native local notification, and on Android geofences are automatically re-registered after a reboot or app update. It's all exposed through one fully typed, actively maintained API with dedicated support; if you need the full location trail rather than boundary crossings, continuous tracking fits better, but for reacting to specific areas efficiently, this plugin is purpose-built. ### Is geofencing available on the web? No. All methods are only available on Android and iOS. On the web, they reject with an unimplemented error. ### How many geofences can I register? Android allows up to 100 geofences per app, iOS up to 20 regions (a hard limit of the operating system). If you exceed the limit, `addGeofences(...)` rejects with the `GEOFENCE_LIMIT_EXCEEDED` error code. If your app needs more regions, register only the geofences closest to the user and update them as the user moves. ### What radius should I use for a geofence? Apple recommends a radius of at least 200 meters, as smaller radii may not trigger transitions reliably. See the [Apple documentation](https://developer.apple.com/documentation/corelocation/monitoring-the-user-s-proximity-to-geographic-regions) for details. On Android, a radius of at least 100 meters is recommended. There is no fixed maximum radius on Android, while iOS clamps the radius to `maximumRegionMonitoringDistance`. For the other reasons a geofence can stay silent on iOS, from the threshold rule to the permission level, see [Why Are Your Geofences Not Triggering on iOS?](https://capawesome.io/blog/why-are-your-geofences-not-triggering-on-ios/). ### Why does `addGeofences(...)` reject with a permission error? Geofencing requires the **Always** location authorization on iOS and the **background location** permission on Android. If only the "while in use" (foreground) permission is granted, `addGeofences(...)` rejects with the `PERMISSION_DENIED` error code. See [Check and request permissions](#check-and-request-permissions) for the required two-step request flow. ### What happens to transitions that occur while my app is in the background or terminated? If the queue is enabled, they are appended to it. It holds `1000` transitions by default and drops the **oldest** ones when it is full. Read them with `getQueuedTransitions(...)` when your app becomes active again — the `geofenceTransition` event only fires while your app is running and does not replay them. If a geofence defines a `notification`, it is displayed natively regardless of the app state. If your server needs to know about transitions right away instead of on the next launch, configure [HTTP Upload](#http-upload), which uploads them natively at the moment they are detected. On Android, geofences are automatically re-registered after a device reboot or an app update, while on iOS the monitored regions are persisted by the operating system. ### Why don't I receive dwell transitions on iOS? Dwell transitions are only supported on Android. On iOS, the `androidNotifyOnDwell` and `androidLoiteringDelay` options are ignored, and only enter and exit transitions are reported. ### Why are `latitude` and `longitude` `null` on iOS? Core Location does not provide the triggering location for a region transition. If you need the geofence's coordinates, look them up by its `geofenceId` (you already know them because you added the geofence). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/): Continuously track the device's location in the background. - [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/): Convert between coordinates and human-readable addresses. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geofences/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geofences/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/geofences/LICENSE). # Capacitor Google Play Services Plugin Unofficial Capacitor plugin to check the availability of Google Play Services on Android and prompt the user to install or update it.[1](#fn:1) ## Features - ✅ **Availability Check**: Check whether Google Play Services is installed, enabled and up to date. - 🔍 **Status**: Find out why Google Play Services is not available, for example on Huawei or AOSP devices. - 📲 **Resolution Dialog**: Prompt the user to install, enable or update Google Play Services with the system dialog. - 🔢 **Version**: Read the version code of the installed Google Play Services APK. - 🪶 **Lightweight**: Only depends on the `play-services-base` library, no other Google SDKs. - 🔒 **Play Store safe**: Uses only official Google APIs. - 🔁 **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! ## Use Cases The Google Play Services plugin is typically used whenever an app depends on Google Play Services and wants to degrade gracefully on devices without it, for example: - **Graceful degradation**: Hide or disable features that depend on Google Play Services, such as maps or push notifications, on devices without it. - **Alternative services**: Fall back to alternative providers, such as Huawei Mobile Services, when Google Play Services is missing. - **Update prompts**: Ask the user to update Google Play Services before using a feature that requires a recent version. - **Diagnostics**: Include the Google Play Services status and version in bug reports and analytics. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-google-play-services` 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-play-services 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: - `$androidPlayServicesBaseVersion` version of `com.google.android.gms:play-services-base` (default: `18.10.1`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS This plugin is not available on iOS. ### Web This plugin is not available on the web. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check the availability of Google Play Services, detect devices without it, prompt the user to make it available and read the installed version. ### Check the availability Check whether Google Play Services is installed, enabled and up to date before using a feature that depends on it: ``` import { GooglePlayServices } from '@capawesome/capacitor-google-play-services'; const isAvailable = async () => { const { available } = await GooglePlayServices.isAvailable(); return available; }; ``` ### Detect devices without Google Play Services Some devices, such as Huawei devices with Huawei Mobile Services or devices with a custom Android build, ship without Google Play Services. Use the `getStatus()` method to find out why Google Play Services is not available and hide the features that depend on it: ``` import { GooglePlayServices, Status } from '@capawesome/capacitor-google-play-services'; const getStatus = async () => { const { status } = await GooglePlayServices.getStatus(); if (status === Status.ServiceMissing) { console.log('This device has no Google Play Services.'); } else if (status !== Status.Success) { console.log('Google Play Services is installed but not usable: ', status); } }; ``` ### Make Google Play Services available If Google Play Services is disabled, outdated or currently updating, prompt the user to fix it with the system dialog. The promise resolves once Google Play Services is available and is rejected with the `CANCELED` error code if the user dismisses the dialog: ``` import { ErrorCode, GooglePlayServices } from '@capawesome/capacitor-google-play-services'; const makeAvailable = async () => { try { await GooglePlayServices.makeAvailable(); console.log('Google Play Services is now available.'); } catch (error) { if (error.code === ErrorCode.Canceled) { console.log('The user dismissed the dialog.'); } } }; ``` ### Get the version Read the version code of the installed Google Play Services APK, for example to include it in bug reports: ``` import { GooglePlayServices } from '@capawesome/capacitor-google-play-services'; const getVersion = async () => { const { version } = await GooglePlayServices.getVersion(); console.log('Google Play Services version: ', version); }; ``` ## API - [`getStatus()`](#getstatus) - [`getVersion()`](#getversion) - [`isAvailable()`](#isavailable) - [`makeAvailable()`](#makeavailable) - [Interfaces](#interfaces) - [Enums](#enums) ### getStatus() ``` getStatus() => Promise ``` Get the status of Google Play Services on this device. Use this method to find out why Google Play Services is not available, for example to distinguish devices without Google Play Services from devices with an outdated version. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getVersion() ``` getVersion() => Promise ``` Get the version code of the Google Play Services APK installed on this device. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether Google Play Services is available on this device. This resolves to `true` if Google Play Services is installed, enabled and up to date. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### makeAvailable() ``` makeAvailable() => Promise ``` Make Google Play Services available on this device. If Google Play Services is missing, disabled, outdated or currently updating, this shows the system dialog that prompts the user to install, enable or update it. The promise resolves once Google Play Services is available. If the user dismisses the dialog, the promise is rejected with the `CANCELED` error code. If Google Play Services can not be made available, for example on devices without the Google Play Store, the promise is rejected with the error message from Google Play Services. Only available on Android. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetStatusResult | Prop | Type | Description | Since | | ------------ | -------- | -------------------------------------------------- | ----- | | **`status`** | `Status` | The status of Google Play Services on this device. | 0.1.0 | #### GetVersionResult | Prop | Type | Description | Since | | ------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`version`** | `number` | The version code of the Google Play Services APK installed on this device. This is `0` if Google Play Services is not installed. | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ---------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not Google Play Services is available on this device. | 0.1.0 | ### Enums #### Status | Members | Value | Description | Since | | ---------------------------------- | ----------------------------------- | --------------------------------------------------------------- | ----- | | **`ServiceDisabled`** | `'SERVICE_DISABLED'` | Google Play Services is disabled on this device. | 0.1.0 | | **`ServiceInvalid`** | `'SERVICE_INVALID'` | The installed version of Google Play Services is not authentic. | 0.1.0 | | **`ServiceMissing`** | `'SERVICE_MISSING'` | Google Play Services is not installed on this device. | 0.1.0 | | **`ServiceUpdating`** | `'SERVICE_UPDATING'` | Google Play Services is currently being updated on this device. | 0.1.0 | | **`ServiceVersionUpdateRequired`** | `'SERVICE_VERSION_UPDATE_REQUIRED'` | The installed version of Google Play Services is out of date. | 0.1.0 | | **`Success`** | `'SUCCESS'` | Google Play Services is available. | 0.1.0 | ## FAQ ### How is this plugin different from other similar plugins? It focuses on one thing: telling your app whether Google Play Services can be used on the current device and, if not, why. It wraps the official `GoogleApiAvailability` API in a small, fully typed plugin that only depends on the `play-services-base` library, so you can degrade gracefully on devices without Google Play Services without pulling in Firebase, Maps or any other Google SDK. Actively maintained against the latest Capacitor version, it pairs naturally with the App Integrity, App Update and Google Sign-In plugins. ### Does this plugin work on iOS or Web? No, this plugin only provides an Android implementation. Google Play Services does not exist on other platforms, so all methods are rejected with the `UNIMPLEMENTED` error code on iOS and Web. Check the platform with `Capacitor.getPlatform()` before calling the plugin in a cross-platform app. ### Does this plugin crash on devices without Google Play Services? No. The availability check is part of the `play-services-base` library that is bundled with your app. It only asks the package manager whether Google Play Services is installed and signed by Google, so it works on any Android device, including devices without Google Play Services. ### How do I detect devices without Google Play Services, such as Huawei devices? Call the `getStatus()` method. It resolves to `SERVICE_MISSING` on devices where Google Play Services is not installed at all, which is the case for Huawei devices with Huawei Mobile Services, Amazon Fire devices and custom Android builds. See [Detect devices without Google Play Services](#detect-devices-without-google-play-services). ### Why does `makeAvailable()` not work on my device? The system dialog can only enable or update Google Play Services if it is already installed, or install it via the Google Play Store. On devices without the Google Play Store, such as Huawei devices with Huawei Mobile Services, there is no way to install Google Play Services and the promise is rejected. Check the status with the `getStatus()` method first and only call `makeAvailable()` if the status is `SERVICE_DISABLED`, `SERVICE_VERSION_UPDATE_REQUIRED` or `SERVICE_UPDATING`. ### Can I check the availability of a specific Google API, such as Maps or Location? No, the plugin only checks the availability of Google Play Services as a whole. Individual Google APIs are covered by dedicated plugins. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/): Verify app and device integrity using the Play Integrity API and App Attest. - [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/): Check for app updates and start in-app updates. - [Google Sign-In](https://capawesome.io/docs/sdks/capacitor/google-sign-in/): Sign in with Google on Android, iOS and Web. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/google-play-services/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/google-play-services/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Google LLC or any of its affiliates or subsidiaries. "Google Play" is a trademark of Google LLC. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Google Sign-In Plugin Unofficial Capacitor plugin to sign-in with Google.[1](#fn:1) ## Features The Capacitor Google Sign-In plugin is one of the most complete Google authentication solutions for Capacitor apps. 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 [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/) and [OAuth](https://capawesome.io/docs/sdks/capacitor/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! ## Use Cases The Google Sign-In plugin is typically used wherever users should sign in with their existing Google account, for example: - **Social login**: Let users sign in to your app with their Google account instead of creating a new password. - **Backend authentication**: Send the ID token (JWT) to your backend to verify the user's identity. - **Google API access**: Request OAuth scopes to receive an access token for accessing Google APIs on behalf of the user. - **Server-side API access**: Exchange the server auth code on your backend for access and refresh tokens. - **Profile pre-filling**: Use the user's email, display name, and profile picture to pre-fill their profile in your app. ## 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 ``` On all platforms, your brand must be [verified](https://support.google.com/cloud/answer/13463073) for your app name to be shown on the Sign in with Google consent screen. ### Android Create an **Android** OAuth client in the [Google Cloud Console](https://console.cloud.google.com/apis/credentials) in the same project as your web client: - **Package name**: the application ID of your app (e.g. `com.example.app`), as defined in `android/app/build.gradle`. - **SHA-1 certificate fingerprint**: the fingerprint of the certificate that signs the app. **Attention**: The Android client ID is never passed to the plugin, because `initialize(...)` always receives the **web** client ID. The Android client must still exist and match the app, otherwise the sign-in flow fails. Which SHA-1 fingerprint to use depends on how the app is signed: - **Local debug builds**: the fingerprint of the debug keystore. Print it with `keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey -storepass android -keypass android`. - **Builds distributed via Google Play**: the fingerprint of the **app signing key**, not the upload key. Find it in the Google Play Console under `Test and release` › `Setup` › `App signing`. Add an OAuth client for every fingerprint you want to support. A missing fingerprint is the most common cause of a failing sign-in flow. #### 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 The following examples show how to initialize the plugin, sign in and sign out a user, and complete the sign-in flow on the Web. ### Initialize the plugin Call `initialize(...)` once before all other methods. The `clientId` must be a **web client ID** from the Google Cloud Console on all platforms, even on Android and iOS. Optionally provide `scopes` to also request authorization, which enables the access token and server auth code in the sign-in result: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const initialize = async () => { await GoogleSignIn.initialize({ clientId: '123456789-abc.apps.googleusercontent.com', scopes: ['https://www.googleapis.com/auth/userinfo.profile'], }); }; ``` ### Sign in a user Start the Google Sign-In flow and retrieve the ID token (JWT) and the user's profile. Note that on Web, this redirects to the Google OAuth authorization page and the promise never resolves: ``` import { ErrorCode, GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; const signIn = async () => { try { 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); } catch (error) { if (error.code === ErrorCode.SignInCanceled) { console.log('The user canceled the sign-in flow.'); } else if (error.code === ErrorCode.NoCredentialAvailable) { console.log('No Google account is available on this device.'); } else if (error.code === ErrorCode.ProviderConfigurationError) { console.log('Google Play services is not available or not up to date.'); } else { console.log('Another error occurred:', error); } } }; ``` ### Complete the sign-in flow on the Web On Web, the app is redirected back to the `redirectUrl` after the user signs in. Call `handleRedirectCallback()` there to exchange the authorization code for tokens and complete the sign-in flow. Only available on Web: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; import { Capacitor } from '@capacitor/core'; 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); }; ``` ### Sign out a user Sign out the current user: ``` import { GoogleSignIn } from '@capawesome/capacitor-google-sign-in'; 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. ### Why do I need a web client ID on Android and iOS? The `clientId` option must be a web client ID from the Google Cloud Console on all platforms. On Android, it is passed as the server client ID to the Credential Manager API and the AuthorizationClient API. On iOS, it is used as the server client ID for the Google Sign-In SDK. On iOS, you additionally configure your iOS client ID via the `GIDClientID` key in the `Info.plist` file, see the [Installation](#installation) section. ### Why does the `signIn` promise never resolve on the Web? On Web, the plugin uses a redirect-based OAuth flow. The `signIn(...)` method redirects to the Google OAuth authorization page, so the promise never resolves. After the user signs in, the app is redirected back to the `redirectUrl` and you must call `handleRedirectCallback()` to complete the sign-in flow, see the [usage example](#complete-the-sign-in-flow-on-the-web) above. ### How do I get an access token for Google APIs? Configure the `scopes` option in the `initialize(...)` method. The plugin then requests authorization in addition to authentication, which enables the `accessToken` and `serverAuthCode` properties in the sign-in result. If you need access and refresh tokens on your backend, exchange the `serverAuthCode` there, never client-side, as described in the [Security](#security) section. ### Why does the sign-in flow fail on Android right after picking an account? If the account picker opens but the flow fails as soon as an account is picked, the app is most likely missing a matching Android OAuth client. Look for one of the following entries in the logcat output: ``` Auth.Api.Credentials: colz: [8] Unknown error [status=UNREGISTERED_ON_API_CONSOLE]. Auth.Api.Credentials: colz: [16] Account reauth failed. ``` Google Play services reports this as a cancellation, so the plugin rejects the call with the `SIGN_IN_CANCELED` error code and `Account reauth failed` as the error message. Make sure that an Android OAuth client exists for the package name and the SHA-1 fingerprint of the certificate that signs the app, as described in the [Installation](#android) section. Builds distributed via Google Play must use the fingerprint of the app signing key, not the upload key. ## Related Plugins - [Apple Sign-In](https://capawesome.io/docs/sdks/capacitor/apple-sign-in/): Sign in users with their Apple account. - [Facebook Sign-In](https://capawesome.io/docs/sdks/capacitor/facebook-sign-in/): Sign in users with their Facebook account. - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/): Communicate with any OAuth 2.0 and OpenID Connect provider. - [Passkeys](https://capawesome.io/docs/sdks/capacitor/passkeys/): Create and authenticate with passkeys based on the WebAuthn standard. ## 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/). ## 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") # Capacitor Grafana Faro Plugin Unofficial Capacitor plugin for [Grafana Faro](https://grafana.com/oss/faro/).[1](#fn:1) > ⚠️ **Experimental:** This plugin is in early development. APIs may change between minor versions. Feedback and bug reports are very welcome — please [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues). ## Features The Capacitor Grafana Faro plugin is one of the most complete frontend observability solutions for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS and Web. - 📝 **Logs**: Capture log messages with severity levels and structured context. - 📡 **Events**: Track custom events with attributes and a platform domain. - 💥 **Errors**: Capture errors with optional stack frames (compatible with [`stacktrace.js`](https://www.stacktracejs.com/)). - 📊 **Measurements**: Record numeric metrics for performance and business KPIs. - 👤 **User, Session and View Metadata**: Attach user, session and view information to every signal. - 🛡️ **Native Crash Reporting**: Capture native crashes via [PLCrashReporter](https://github.com/microsoft/plcrashreporter) on iOS and [`ApplicationExitInfo`](https://developer.android.com/reference/android/app/ApplicationExitInfo) on Android. - 🐌 **ANR Detection**: Detect Application Not Responding events on the Android main thread. - 🌐 **Web Auto-Instrumentation**: Automatically capture uncaught errors, console output, Core Web Vitals, route changes and network performance on Web. - ⏯️ **Pause and Resume**: Pause and resume telemetry on demand. - 🎯 **Session Sampling**: Sample sessions with a configurable rate to control data volume. - 🤝 **Compatibility**: Works alongside the [Firebase Analytics](https://capawesome.io/docs/sdks/capacitor/firebase/analytics/) and [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/) plugins. ## Use Cases The Grafana Faro plugin is typically used to monitor Capacitor apps in production, for example: - **Crash and error monitoring**: Capture JavaScript errors as well as native crashes on Android and iOS and analyze them in Grafana. - **Performance monitoring**: Record Core Web Vitals and network performance on Web, or push custom measurements such as startup or sign-in durations. - **Usage analytics**: Track custom events with attributes to understand how users move through your app. - **Centralized logging**: Send log messages with severity levels and structured context to your Faro collector. - **Stability monitoring on Android**: Detect Application Not Responding (ANR) events on the main thread. ## 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-grafana-faro` 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-grafana-faro @grafana/faro-web-sdk npx cap sync ``` ### iOS #### Minimum Deployment Target This plugin depends on [PLCrashReporter](https://github.com/microsoft/plcrashreporter) for native crash reporting. Make sure that your iOS deployment target is set to at least `15.0` in your Xcode project settings (usually in `ios/App/App.xcodeproj`): ``` -IPHONEOS_DEPLOYMENT_TARGET = 14.0 +IPHONEOS_DEPLOYMENT_TARGET = 15.0 ``` If you are using **CocoaPods**, make sure that your iOS deployment target is set to at least `15.0` in your `Podfile`: ``` platform :ios, '15.0' ``` #### Conflicts with other crash reporters PLCrashReporter installs global crash handlers. Running this plugin alongside another crash reporter that also installs handlers (e.g. Firebase Crashlytics, Sentry) can lead to one or both reporters losing crash reports. Enable `instrumentations.nativeCrashReporting` only when no other crash reporter is active. ## Configuration Build-time configuration for the Grafana Faro plugin. When `url` and `appName` are both provided, the native plugin auto-initializes during app startup. This allows native crash and ANR handlers to be installed before any JavaScript code runs. Calling `GrafanaFaro.initialize(...)` from JavaScript afterwards is not required and will fail with an `already initialized` error. Only applies to Android and iOS. | Prop | Type | Description | Since | | ---------------------- | ------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------- | ----- | | **`apiKey`** | `string` | The API key sent as the `x-api-key` header. | 0.1.0 | | **`appEnvironment`** | `string` | The environment of the application (e.g. `production`, `staging`). | 0.1.0 | | **`appName`** | `string` | The name of the application. | 0.1.0 | | **`appNamespace`** | `string` | The namespace of the application. | 0.1.0 | | **`appVersion`** | `string` | The version of the application. | 0.1.0 | | **`instrumentations`** | `{ anrTracking?: boolean; nativeCrashReporting?: boolean; }` | Toggles for built-in automatic instrumentations. Only the toggles that apply at build time on Android and iOS are honored. | 0.1.0 | | **`url`** | `string` | The Faro collector endpoint URL. | 0.1.0 | ### Examples In `capacitor.config.json`: ``` { "plugins": { "GrafanaFaro": { "apiKey": undefined, "appEnvironment": undefined, "appName": undefined, "appNamespace": undefined, "appVersion": undefined, "instrumentations": undefined, "url": 'https://faro-collector-prod.grafana.net/collect/' } } } ``` In `capacitor.config.ts`: ``` /// import { CapacitorConfig } from '@capacitor/cli'; const config: CapacitorConfig = { plugins: { GrafanaFaro: { apiKey: undefined, appEnvironment: undefined, appName: undefined, appNamespace: undefined, appVersion: undefined, instrumentations: undefined, url: 'https://faro-collector-prod.grafana.net/collect/', }, }, }; export default config; ``` ## Usage The following examples show how to initialize the Faro SDK, send log messages, track custom events, capture errors, record measurements, and attach user and view metadata. ### Initialize the Faro SDK Call `initialize(...)` before any other method to connect the plugin to your Faro collector endpoint. Alternatively, provide the `url` and `appName` options in the [Configuration](#configuration) so that the native plugin auto-initializes during app startup: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const initialize = async () => { await GrafanaFaro.initialize({ app: { environment: 'production', name: 'my-app', version: '1.0.0', }, instrumentations: { anrTracking: true, console: true, errors: true, nativeCrashReporting: true, performance: true, view: true, webVitals: true, }, url: 'https://faro-collector-prod-us-central-0.grafana.net/collect/REPLACE_ME', }); }; ``` ### Send log messages Capture log messages with a severity level and an optional structured context: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const pushLog = async () => { await GrafanaFaro.pushLog({ level: 'info', message: 'User pressed sign-in button', }); }; ``` ### Track custom events Track user interactions or business events with custom attributes: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const pushEvent = async () => { await GrafanaFaro.pushEvent({ attributes: { provider: 'google' }, name: 'sign_in_started', }); }; ``` ### Capture errors Push errors with an optional pre-parsed stack trace. Use a library like `stacktrace-js` to parse the stack into structured frames: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const pushError = async (error: Error) => { await GrafanaFaro.pushError({ type: error.name, value: error.message, }); }; const pushErrorWithStacktrace = async (error: Error) => { // Use `stacktrace-js` (or any equivalent) to parse the stack into // structured frames. const StackTrace = await import('stacktrace-js'); const stackFrames = await StackTrace.fromError(error); await GrafanaFaro.pushError({ type: error.name, value: error.message, stackFrames: stackFrames.map(frame => ({ columnNumber: frame.columnNumber, fileName: frame.fileName, functionName: frame.functionName, lineNumber: frame.lineNumber, })), }); }; ``` ### Record measurements Record numeric metrics, for example performance timings or business KPIs: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const pushMeasurement = async () => { await GrafanaFaro.pushMeasurement({ type: 'sign_in_duration', values: { duration_ms: 320 }, }); }; ``` ### Attach user and view metadata Attach user and view information to every signal: ``` import { GrafanaFaro } from '@capawesome/capacitor-grafana-faro'; const setUser = async () => { await GrafanaFaro.setUser({ email: 'jane@example.com', id: 'user-123', username: 'jane', }); }; const setView = async () => { await GrafanaFaro.setView({ name: 'SignInView' }); }; ``` ## API - [`getSession()`](#getsession) - [`getView()`](#getview) - [`initialize(...)`](#initialize) - [`pause()`](#pause) - [`pushError(...)`](#pusherror) - [`pushEvent(...)`](#pushevent) - [`pushLog(...)`](#pushlog) - [`pushMeasurement(...)`](#pushmeasurement) - [`resetSession()`](#resetsession) - [`resetUser()`](#resetuser) - [`setSession(...)`](#setsession) - [`setUser(...)`](#setuser) - [`setView(...)`](#setview) - [`unpause()`](#unpause) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getSession() ``` getSession() => Promise ``` Get the current session. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getView() ``` getView() => Promise ``` Get the current view. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the Faro SDK. **Attention**: This method must be called before any other method. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### pause() ``` pause() => Promise ``` Pause sending telemetry. New signals are still buffered locally. **Since:** 0.1.0 ______________________________________________________________________ ### pushError(...) ``` pushError(options: PushErrorOptions) => Promise ``` Push an error/exception. | Param | Type | | ------------- | ------------------ | | **`options`** | `PushErrorOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### pushEvent(...) ``` pushEvent(options: PushEventOptions) => Promise ``` Push a custom event. | Param | Type | | ------------- | ------------------ | | **`options`** | `PushEventOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### pushLog(...) ``` pushLog(options: PushLogOptions) => Promise ``` Push a log message. | Param | Type | | ------------- | ---------------- | | **`options`** | `PushLogOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### pushMeasurement(...) ``` pushMeasurement(options: PushMeasurementOptions) => Promise ``` Push a measurement (numeric metric). | Param | Type | | ------------- | ------------------------ | | **`options`** | `PushMeasurementOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### resetSession() ``` resetSession() => Promise ``` Clear the current session and start a new one. **Since:** 0.1.0 ______________________________________________________________________ ### resetUser() ``` resetUser() => Promise ``` Clear the current user. **Since:** 0.1.0 ______________________________________________________________________ ### setSession(...) ``` setSession(options: SetSessionOptions) => Promise ``` Set the current session. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetSessionOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUser(...) ``` setUser(options: SetUserOptions) => Promise ``` Set the current user. | Param | Type | | ------------- | -------------- | | **`options`** | `UserMetadata` | **Since:** 0.1.0 ______________________________________________________________________ ### setView(...) ``` setView(options: SetViewOptions) => Promise ``` Set the current view (e.g. screen / route name). | Param | Type | | ------------- | -------------- | | **`options`** | `ViewMetadata` | **Since:** 0.1.0 ______________________________________________________________________ ### unpause() ``` unpause() => Promise ``` Resume sending telemetry. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetSessionResult | Prop | Type | Description | Since | | ---------------- | ---------------------------- | ------------------------------- | ----- | | **`attributes`** | `{ [key: string]: string; }` | The current session attributes. | 0.1.0 | | **`id`** | `string` | The current session ID. | 0.1.0 | #### GetViewResult | Prop | Type | Description | Since | | ---------- | -------- | ---------------------- | ----- | | **`name`** | `string` | The current view name. | 0.1.0 | #### InitializeOptions | Prop | Type | Description | Default | Since | | ------------------------- | ---------------------------- | ------------------------------------------------------------------- | ------- | ----- | | **`apiKey`** | `string` | API key sent as the `x-api-key` header to the collector. | | 0.1.0 | | **`app`** | `AppMetadata` | Application metadata attached to every signal. | | 0.1.0 | | **`ignoreErrors`** | `string[]` | Error message patterns to ignore. | | 0.1.0 | | **`ignoreUrls`** | `string[]` | URL patterns to ignore when instrumenting network calls. | | 0.1.0 | | **`instrumentations`** | `InstrumentationsOptions` | Toggles for built-in automatic instrumentations. | | 0.1.0 | | **`paused`** | `boolean` | Start in a paused state. Telemetry must be resumed via `unpause()`. | `false` | 0.1.0 | | **`sessionAttributes`** | `{ [key: string]: string; }` | Initial session attributes. | | 0.1.0 | | **`sessionSamplingRate`** | `number` | Session sampling rate in `[0, 1]`. `1` = all sessions, `0` = none. | `1` | 0.1.0 | | **`url`** | `string` | The Faro collector endpoint URL. | | 0.1.0 | | **`user`** | `UserMetadata` | Initial user metadata. | | 0.1.0 | | **`view`** | `ViewMetadata` | Initial view metadata. | | 0.1.0 | #### AppMetadata | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------------------------ | ----- | | **`environment`** | `string` | The environment of the application (e.g. `production`, `staging`). | 0.1.0 | | **`name`** | `string` | The name of the application. | 0.1.0 | | **`namespace`** | `string` | The namespace of the application. | 0.1.0 | | **`version`** | `string` | The version of the application. | 0.1.0 | #### InstrumentationsOptions Toggles for built-in automatic instrumentations. Note: some toggles only apply on specific platforms. The plugin silently ignores toggles that do not apply on the current platform. | Prop | Type | Description | Default | Since | | -------------------------- | --------- | ------------------------------------------------------------------------------------------ | ------- | ----- | | **`anrTracking`** | `boolean` | Detect Application Not Responding events on the main thread. Only available for Android. | `false` | 0.1.0 | | **`console`** | `boolean` | Capture `console.warn` and `console.error` calls. Only available for Web. | `true` | 0.1.0 | | **`errors`** | `boolean` | Capture uncaught errors and unhandled promise rejections. Only available for Web. | `true` | 0.1.0 | | **`nativeCrashReporting`** | `boolean` | Capture native crashes (reported on the next session). Only available for Android and iOS. | `false` | 0.1.0 | | **`performance`** | `boolean` | Capture `fetch` and `XHR` performance entries. Only available for Web. | `true` | 0.1.0 | | **`view`** | `boolean` | Capture History API navigation as view changes. Only available for Web. | `true` | 0.1.0 | | **`webVitals`** | `boolean` | Capture Core Web Vitals (LCP, FID, CLS, INP, TTFB). Only available for Web. | `true` | 0.1.0 | #### UserMetadata | Prop | Type | Description | Since | | ---------------- | ---------------------------- | --------------------------------------------- | ----- | | **`attributes`** | `{ [key: string]: string; }` | Additional key-value attributes for the user. | 0.1.0 | | **`email`** | `string` | The user's email address. | 0.1.0 | | **`fullName`** | `string` | The user's full name. | 0.1.0 | | **`id`** | `string` | The user's ID. | 0.1.0 | | **`username`** | `string` | The user's username. | 0.1.0 | #### ViewMetadata | Prop | Type | Description | Since | | ---------- | -------- | --------------------- | ----- | | **`name`** | `string` | The name of the view. | 0.1.0 | #### PushErrorOptions | Prop | Type | Description | Default | Since | | ----------------- | ---------------------------- | ----------------------------------------------------- | ------- | ----- | | **`context`** | `{ [key: string]: string; }` | Additional context attached to the error. | | 0.1.0 | | **`fatal`** | `boolean` | Mark the error as fatal (e.g. caused a crash). | `false` | 0.1.0 | | **`stackFrames`** | `StackFrame[]` | Pre-parsed stack frames generated by `stacktrace.js`. | | 0.1.0 | | **`type`** | `string` | The error type (e.g. `'TypeError'`). | | 0.1.0 | | **`value`** | `string` | The error message. | | 0.1.0 | #### StackFrame Subset of the stacktrace generated by `stacktrace.js`. | Prop | Type | Since | | ------------------ | -------- | ----- | | **`columnNumber`** | `number` | 0.1.0 | | **`fileName`** | `string` | 0.1.0 | | **`functionName`** | `string` | 0.1.0 | | **`lineNumber`** | `number` | 0.1.0 | #### PushEventOptions | Prop | Type | Description | Since | | ---------------- | ---------------------------- | ------------------------------------------------------------------------------------------------ | ----- | | **`attributes`** | `{ [key: string]: string; }` | Key-value attributes attached to the event. | 0.1.0 | | **`domain`** | `string` | Logical grouping for the event. Defaults to the platform domain (`'web'`, `'android'`, `'ios'`). | 0.1.0 | | **`name`** | `string` | The name of the event. | 0.1.0 | #### PushLogOptions | Prop | Type | Description | Default | Since | | ------------- | ---------------------------- | --------------------------------------- | -------- | ----- | | **`context`** | `{ [key: string]: string; }` | Additional context attached to the log. | | 0.1.0 | | **`level`** | `LogLevel` | The log level. | `'info'` | 0.1.0 | | **`message`** | `string` | The log message. | | 0.1.0 | #### PushMeasurementOptions | Prop | Type | Description | Since | | ------------- | ---------------------------- | ----------------------------------------------- | ----- | | **`context`** | `{ [key: string]: string; }` | Additional context attached to the measurement. | 0.1.0 | | **`type`** | `string` | The measurement type (e.g. `'custom_latency'`). | 0.1.0 | | **`values`** | `{ [key: string]: number; }` | Key-value numeric measurements. | 0.1.0 | #### SetSessionOptions | Prop | Type | Description | Since | | ---------------- | ---------------------------- | --------------------------------------------------------- | ----- | | **`attributes`** | `{ [key: string]: string; }` | The session attributes. | 0.1.0 | | **`id`** | `string` | Optional session ID. A UUID v4 is generated when omitted. | 0.1.0 | ### Type Aliases #### LogLevel `'debug' | 'error' | 'info' | 'log' | 'trace' | 'warn'` #### SetUserOptions `UserMetadata` #### SetViewOptions `ViewMetadata` ## FAQ ### Is this an official Grafana Labs plugin? No, this is an unofficial plugin maintained by Capawesome. The project is not affiliated with, endorsed by, sponsored by, or approved by Grafana Labs or any of their affiliates or subsidiaries. It integrates [Grafana Faro](https://grafana.com/oss/faro/) into Capacitor apps. ### Can I use this plugin together with Firebase Crashlytics or Sentry? Only with care. The plugin uses PLCrashReporter for native crash reporting on iOS, which installs global crash handlers. Running it alongside another crash reporter that also installs handlers (for example Firebase Crashlytics or Sentry) can lead to one or both reporters losing crash reports. Enable the `instrumentations.nativeCrashReporting` toggle only when no other crash reporter is active. ### Why does the plugin fail with an `already initialized` error? If you provide the `url` and `appName` options in the plugin [configuration](#configuration), the native plugin auto-initializes during app startup so that native crash and ANR handlers are installed before any JavaScript code runs. In that case, calling `GrafanaFaro.initialize(...)` from JavaScript is not required and will fail with an `already initialized` error. ### How does native crash reporting work? Native crashes are captured via [PLCrashReporter](https://github.com/microsoft/plcrashreporter) on iOS and [`ApplicationExitInfo`](https://developer.android.com/reference/android/app/ApplicationExitInfo) on Android and are reported on the next session. The `instrumentations.nativeCrashReporting` toggle is disabled by default, and on iOS your deployment target must be set to at least `15.0` (see [Installation](#installation)). ### How can I reduce the amount of telemetry that is sent? You can set the `sessionSamplingRate` option to a value between `0` and `1` to only report a subset of sessions. You can also pause and resume telemetry on demand using the `pause()` and `unpause()` methods, or start in a paused state with the `paused` option. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Firebase Analytics](https://capawesome.io/docs/sdks/capacitor/firebase/analytics/): Unofficial Capacitor plugin for Firebase Analytics. - [Firebase Crashlytics](https://capawesome.io/docs/sdks/capacitor/firebase/crashlytics/): Unofficial Capacitor plugin for Firebase Crashlytics crash reporting. - [Firebase Performance Monitoring](https://capawesome.io/docs/sdks/capacitor/firebase/performance-monitoring/): Unofficial Capacitor plugin for Firebase Performance Monitoring. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/): Unofficial Capacitor plugin for the PostHog product analytics platform. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/grafana-faro/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/grafana-faro/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Grafana Labs or any of their affiliates or subsidiaries. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Gyroscope Plugin Capacitor plugin to read the device's gyroscope sensor. ## Features The Capacitor Gyroscope plugin is one of the most complete motion sensing solutions for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - ⚡ **Real-time measurements**: Continuous gyroscope data with event listeners. - 📊 **Rotation rate**: Accurate x, y, and z-axis rotation rate in rad/s. - 🔒 **Permission handling**: Built-in permission management for sensor access. - 🤝 **Compatibility**: Works alongside the [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/), [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/) and [Pedometer](https://capawesome.io/docs/sdks/capacitor/pedometer/) 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! ## Use Cases The Gyroscope plugin is typically used whenever an app needs to react to the rotation of the device, for example: - **Motion-controlled games**: Use the real-time rotation rate to steer a character or vehicle by tilting the device. - **Immersive experiences**: Rotate 360° views or augmented reality scenes based on the device's rotation. - **Gesture detection**: Detect rotation gestures such as twisting the device to trigger actions in your app. - **Motion analysis**: Record the rotation rate around the x, y, and z axes to analyze movement patterns. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How rotation data powers tilt-to-steer game mechanics and AR features together with the Accelerometer. ## 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-gyroscope` 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-gyroscope 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 device's motion data: ``` NSMotionUsageDescription The app needs to access the motion activity. ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check if the gyroscope is available, get a single measurement, receive continuous measurement updates, check and request permissions, and remove all listeners. ### Check if the gyroscope is available Check whether the device has a gyroscope sensor before using the other methods: ``` import { Gyroscope } from '@capawesome/capacitor-gyroscope'; const isAvailable = async () => { const result = await Gyroscope.isAvailable(); return result.available; }; ``` ### Get a single measurement Get the most recent measurement from the gyroscope sensor. The rotation rate around the x, y, and z axes is reported in radians per second (rad/s): ``` import { Gyroscope } from '@capawesome/capacitor-gyroscope'; const getMeasurement = async () => { const measurement = await Gyroscope.getMeasurement(); console.log('X: ', measurement.x); console.log('Y: ', measurement.y); console.log('Z: ', measurement.z); }; ``` ### Receive continuous measurement updates Add a listener for the `measurement` event and start the measurement updates to receive the rotation rate in real time. The `measurement` event is only available on Android and iOS: ``` import { Gyroscope } from '@capawesome/capacitor-gyroscope'; const startMeasurementUpdates = async () => { await Gyroscope.addListener('measurement', measurement => { console.log('X: ', measurement.x); console.log('Y: ', measurement.y); console.log('Z: ', measurement.z); }); await Gyroscope.startMeasurementUpdates(); }; const stopMeasurementUpdates = async () => { await Gyroscope.stopMeasurementUpdates(); }; ``` ### Check and request permissions Check and request the permission to access the gyroscope sensor: ``` import { Gyroscope } from '@capawesome/capacitor-gyroscope'; const checkPermissions = async () => { const result = await Gyroscope.checkPermissions(); return result; }; const requestPermissions = async () => { const result = await Gyroscope.requestPermissions(); return result; }; ``` ### Remove all listeners Remove all listeners when you no longer need them: ``` import { Gyroscope } from '@capawesome/capacitor-gyroscope'; const removeAllListeners = async () => { await Gyroscope.removeAllListeners(); }; ``` ## API - [`checkPermissions()`](#checkpermissions) - [`getMeasurement()`](#getmeasurement) - [`isAvailable()`](#isavailable) - [`requestPermissions()`](#requestpermissions) - [`startMeasurementUpdates()`](#startmeasurementupdates) - [`stopMeasurementUpdates()`](#stopmeasurementupdates) - [`addListener('measurement', ...)`](#addlistenermeasurement-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### checkPermissions() ``` checkPermissions() => Promise ``` Check if the app has permission to access the gyroscope sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getMeasurement() ``` getMeasurement() => Promise ``` Get the latest measurement. This method returns the most recent measurement from the gyroscope sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the gyroscope sensor is available on the device. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request permission to access the gyroscope sensor. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startMeasurementUpdates() ``` startMeasurementUpdates() => Promise ``` Start emitting `measurement` events. **Since:** 0.1.0 ______________________________________________________________________ ### stopMeasurementUpdates() ``` stopMeasurementUpdates() => Promise ``` Stop emitting `measurement` events. **Since:** 0.1.0 ______________________________________________________________________ ### addListener('measurement', ...) ``` addListener(eventName: 'measurement', listenerFunc: (event: MeasurementEvent) => void) => Promise ``` Called when a new measurement is available. 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 #### PermissionStatus | Prop | Type | Description | Since | | --------------- | -------------------------- | ---------------------------------------------- | ----- | | **`gyroscope`** | `GyroscopePermissionState` | The permission status of the gyroscope sensor. | 0.1.0 | #### Measurement | Prop | Type | Description | Since | | ------- | -------- | ------------------------------------------------------------------ | ----- | | **`x`** | `number` | The rotation rate around the x-axis in radians per second (rad/s). | 0.1.0 | | **`y`** | `number` | The rotation rate around the y-axis in radians per second (rad/s). | 0.1.0 | | **`z`** | `number` | The rotation rate around the z-axis in radians per second (rad/s). | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether the gyroscope sensor is available on the device. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GyroscopePermissionState `PermissionState | 'limited'` #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` #### GetMeasurementResult `Measurement` #### MeasurementEvent `Measurement` ## FAQ ### How is this plugin different from other similar plugins? It delivers accurate x, y, and z-axis rotation rate in rad/s with both one-off readings via `getMeasurement()` and continuous real-time updates via the `measurement` event, plus built-in permission handling for sensor access on Android and iOS. The API is fully typed and actively maintained against the latest Capacitor and OS versions, and it pairs cleanly with our Accelerometer, Barometer, and Pedometer plugins so a single, consistent motion-sensing story is covered. If you only need an occasional reading, a simpler setup is perfectly fine; if you need continuous, low-level rotation data, this plugin is built for exactly that. ### Which platforms does the plugin support? The plugin supports Android and iOS. The continuous `measurement` event is only emitted on Android and iOS, so there is no gyroscope support in the browser. ### What units are the measurements reported in? Each measurement contains the rotation rate around the x, y, and z axes in radians per second (rad/s). ### What is the difference between `getMeasurement` and the `measurement` event? The `getMeasurement()` method returns the most recent measurement from the gyroscope sensor once. If you need continuous real-time updates instead, add a listener for the `measurement` event and call `startMeasurementUpdates()`. Call `stopMeasurementUpdates()` when you no longer need updates. ### Do I need any permissions to read the gyroscope? The plugin provides the `checkPermissions()` and `requestPermissions()` methods to manage access to the gyroscope sensor. On iOS, you also have to add the `NSMotionUsageDescription` key to your `Info.plist` file, which tells the user why your app needs access to the device's motion data (see [Installation](#installation)). ### How do I know if the device has a gyroscope sensor? Call the `isAvailable()` method. It returns whether the gyroscope sensor is available on the device, so you can hide or disable motion features on devices without a gyroscope. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/): Capture the acceleration force along the x, y, and z axes. - [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/): Obtain the static air pressure measured in hectopascals (hPa). - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/): Read the device compass heading. - [Pedometer](https://capawesome.io/docs/sdks/capacitor/pedometer/): Retrieve motion data such as the number of steps and the distance traveled. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/gyroscope/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/gyroscope/LICENSE). # Capacitor Haptics Plugin Capacitor plugin to provide haptic feedback such as impacts, notifications, vibrations and custom haptic patterns. ## Features - 💥 **Impact**: Trigger impact haptic feedback with five different styles, including the modern `Rigid` and `Soft` styles. - 🔔 **Notification**: Trigger notification haptic feedback for success, warning and error events. - 🎯 **Selection**: Trigger selection haptic feedback for picker-style interactions. - 🎼 **Custom patterns**: Play custom haptic patterns with per-event intensity and sharpness (Core Haptics on iOS, vibration effects on Android). - 🤖 **Android haptics**: Perform semantic Android haptic feedback effects that respect the user's haptic feedback settings. - 📳 **Vibrate**: Vibrate the device with a custom duration. - 🔎 **Availability check**: Check if haptic feedback is available on the device. - 🌐 **Web support**: Best-effort support for the Web using the Vibration API. - 🤝 **Compatibility**: Works alongside the [Shake](https://capawesome.io/docs/sdks/capacitor/shake/), [Silent Mode](https://capawesome.io/docs/sdks/capacitor/silent-mode/) and [Volume](https://capawesome.io/docs/sdks/capacitor/volume/) 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! ## Use Cases The Haptics plugin is typically used to make an app feel more responsive and tactile, for example: - **Button and gesture feedback**: Trigger impact feedback when user interface elements collide or a drag operation snaps into place. - **Success and error feedback**: Use notification haptics to communicate that a task has succeeded, failed, or produced a warning. - **Pickers and selection controls**: Give subtle selection feedback while the user scrolls through a picker. - **Games and rich interactions**: Play custom haptic patterns with per-event intensity and sharpness for immersive experiences. - **Alerts**: Vibrate the device with a custom duration to get the user's attention. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-haptics` 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-haptics npx cap sync ``` ### Android This plugin already declares the `android.permission.VIBRATE` permission in its manifest, so no additional configuration is required. ### iOS No additional configuration is required for this plugin. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check if haptic feedback is available, trigger impact and notification feedback, give selection feedback, play a custom haptic pattern, perform an Android haptic effect, and vibrate the device. ### Check if haptic feedback is available Check whether the device supports haptic feedback before using the other methods: ``` import { Haptics } from '@capawesome/capacitor-haptics'; const isAvailable = async () => { const { available } = await Haptics.isAvailable(); return available; }; ``` ### Trigger impact feedback Simulate a physical impact, for example when user interface elements collide or a drag operation snaps into place. Five different styles are available, including the modern `Rigid` and `Soft` styles: ``` import { Haptics, ImpactStyle } from '@capawesome/capacitor-haptics'; const impact = async () => { await Haptics.impact({ style: ImpactStyle.Medium }); }; ``` ### Trigger notification feedback Communicate that a task or action has succeeded, failed, or produced a warning: ``` import { Haptics, NotificationType } from '@capawesome/capacitor-haptics'; const notification = async () => { await Haptics.notification({ type: NotificationType.Success }); }; ``` ### Give selection feedback Give subtle feedback during picker-style interactions. Call `selectionStart()` when the interaction begins, `selectionChanged()` whenever the selection changes, and `selectionEnd()` when the interaction finishes: ``` import { Haptics } from '@capawesome/capacitor-haptics'; const selection = async () => { await Haptics.selectionStart(); await Haptics.selectionChanged(); await Haptics.selectionEnd(); }; ``` ### Play a custom haptic pattern Play a custom pattern composed of individual haptic events with per-event intensity and sharpness. On iOS, the pattern is played using Core Haptics; on Android, it is approximated using vibration effects: ``` import { Haptics } from '@capawesome/capacitor-haptics'; const playPattern = async () => { await Haptics.playPattern({ events: [ { time: 0, intensity: 1, sharpness: 0.8 }, { time: 0.2, intensity: 0.6, sharpness: 0.4 }, { time: 0.4, intensity: 0.8, duration: 0.5 }, ], }); }; ``` ### Perform an Android haptic effect Perform a semantic Android haptic feedback effect that respects the user's haptic feedback settings. Only available on Android: ``` import { AndroidHapticType, Haptics } from '@capawesome/capacitor-haptics'; const performAndroidHaptic = async () => { await Haptics.performAndroidHaptic({ type: AndroidHapticType.Confirm }); }; ``` ### Vibrate the device Vibrate the device. The `duration` option is only available on Android and Web: ``` import { Haptics } from '@capawesome/capacitor-haptics'; const vibrate = async () => { await Haptics.vibrate({ duration: 500 }); }; ``` ## API - [`impact(...)`](#impact) - [`isAvailable()`](#isavailable) - [`notification(...)`](#notification) - [`performAndroidHaptic(...)`](#performandroidhaptic) - [`playPattern(...)`](#playpattern) - [`selectionChanged()`](#selectionchanged) - [`selectionEnd()`](#selectionend) - [`selectionStart()`](#selectionstart) - [`vibrate(...)`](#vibrate) - [Interfaces](#interfaces) - [Enums](#enums) ### impact(...) ``` impact(options?: ImpactOptions | undefined) => Promise ``` Trigger an impact haptic feedback. Use this to simulate a physical impact, for example when user interface elements collide or a drag operation snaps into place. On Android, the impact style is mapped to a best-effort vibration effect. | Param | Type | | ------------- | --------------- | | **`options`** | `ImpactOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if haptic feedback is available on the device. On Android, this checks whether the device has a vibrator. On iOS, this checks whether the device supports haptic event playback. On the web, this checks whether the Vibration API is supported. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### notification(...) ``` notification(options?: NotificationOptions | undefined) => Promise ``` Trigger a notification haptic feedback. Use this to communicate that a task or action has succeeded, failed, or produced a warning. On Android, the notification type is mapped to a best-effort vibration effect. | Param | Type | | ------------- | --------------------- | | **`options`** | `NotificationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### performAndroidHaptic(...) ``` performAndroidHaptic(options: PerformAndroidHapticOptions) => Promise ``` Perform a predefined Android haptic feedback effect. In contrast to the other methods, this method does not use the vibrator but the semantic haptic feedback constants of the Android view system. This respects the user's haptic feedback settings. Only available on Android. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `PerformAndroidHapticOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### playPattern(...) ``` playPattern(options: PlayPatternOptions) => Promise ``` Play a custom haptic pattern composed of individual haptic events. On iOS, the pattern is played using Core Haptics with full support for intensity and sharpness. This requires a device with haptic event playback support (for example an iPhone with a Taptic Engine). Otherwise, the call rejects as unavailable. On Android, the pattern is played using vibration effects. Intensity is only respected on devices with amplitude control. On the web, the pattern is approximated using the Vibration API. | Param | Type | | ------------- | -------------------- | | **`options`** | `PlayPatternOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### selectionChanged() ``` selectionChanged() => Promise ``` Trigger a selection changed haptic feedback. Call this method after `selectionStart()` whenever the selection changes. On the web, this method does nothing. **Since:** 0.1.0 ______________________________________________________________________ ### selectionEnd() ``` selectionEnd() => Promise ``` End a selection session. Call this method when the user finishes a selection interaction. On the web, this method does nothing. **Since:** 0.1.0 ______________________________________________________________________ ### selectionStart() ``` selectionStart() => Promise ``` Start a selection session. Call this method when the user starts a selection interaction, for example when a picker starts scrolling. It prepares the haptic hardware to reduce the latency of subsequent `selectionChanged()` calls. On the web, this method does nothing. **Since:** 0.1.0 ______________________________________________________________________ ### vibrate(...) ``` vibrate(options?: VibrateOptions | undefined) => Promise ``` Vibrate the device. | Param | Type | | ------------- | ---------------- | | **`options`** | `VibrateOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ImpactOptions | Prop | Type | Description | Default | Since | | ----------- | ------------- | ------------------------ | -------------------- | ----- | | **`style`** | `ImpactStyle` | The style of the impact. | `ImpactStyle.Medium` | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ---------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether or not haptic feedback is available on the device. | 0.1.0 | #### NotificationOptions | Prop | Type | Description | Default | Since | | ---------- | ------------------ | ----------------------------- | -------------------------- | ----- | | **`type`** | `NotificationType` | The type of the notification. | `NotificationType.Success` | 0.1.0 | #### PerformAndroidHapticOptions | Prop | Type | Description | Since | | ---------- | ------------------- | ---------------------------------------------- | ----- | | **`type`** | `AndroidHapticType` | The Android haptic feedback effect to perform. | 0.1.0 | #### PlayPatternOptions | Prop | Type | Description | Since | | ------------ | --------------- | ------------------------------------------- | ----- | | **`events`** | `HapticEvent[]` | The haptic events that make up the pattern. | 0.1.0 | #### HapticEvent | Prop | Type | Description | Default | Since | | --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`duration`** | `number` | The duration of the event in seconds. If omitted, the event is a transient tap. | | 0.1.0 | | **`intensity`** | `number` | The intensity of the event as a value between `0` and `1`. On Android, the intensity is only respected on devices with amplitude control. | | 0.1.0 | | **`sharpness`** | `number` | The sharpness of the event as a value between `0` and `1`. A lower value results in a rounder, softer feedback while a higher value results in a crisper, more precise feedback. Only available on iOS. | `0.5` | 0.1.0 | | **`time`** | `number` | The relative time at which the event occurs, in seconds. | | 0.1.0 | #### VibrateOptions | Prop | Type | Description | Default | Since | | -------------- | -------- | --------------------------------------------------------------------------------- | ------- | ----- | | **`duration`** | `number` | The duration of the vibration in milliseconds. Only available on Android and Web. | `300` | 0.1.0 | ### Enums #### ImpactStyle | Members | Value | Description | Since | | ------------ | ---------- | --------------------------------------------------------------- | ----- | | **`Heavy`** | `'HEAVY'` | A collision between large, heavy user interface elements. | 0.1.0 | | **`Light`** | `'LIGHT'` | A collision between small, light user interface elements. | 0.1.0 | | **`Medium`** | `'MEDIUM'` | A collision between moderately sized user interface elements. | 0.1.0 | | **`Rigid`** | `'RIGID'` | A collision between hard or inflexible user interface elements. | 0.1.0 | | **`Soft`** | `'SOFT'` | A collision between soft or flexible user interface elements. | 0.1.0 | #### NotificationType | Members | Value | Description | Since | | ------------- | ----------- | -------------------------------------------- | ----- | | **`Error`** | `'ERROR'` | A task or action has failed. | 0.1.0 | | **`Success`** | `'SUCCESS'` | A task or action has completed successfully. | 0.1.0 | | **`Warning`** | `'WARNING'` | A task or action has produced a warning. | 0.1.0 | #### AndroidHapticType | Members | Value | Description | Since | | ------------------ | ----------------- | ------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ClockTick`** | `'CLOCK_TICK'` | The user has pressed either an hour or minute tick of a clock. | 0.1.0 | | **`Confirm`** | `'CONFIRM'` | The confirmation of a user's action. On Android 10 and older, `ContextClick` is performed instead. | 0.1.0 | | **`ContextClick`** | `'CONTEXT_CLICK'` | The user has performed a context click on an object. | 0.1.0 | | **`KeyboardTap`** | `'KEYBOARD_TAP'` | The user has pressed a virtual or software keyboard key. | 0.1.0 | | **`LongPress`** | `'LONG_PRESS'` | The user has performed a long press on an object. | 0.1.0 | | **`Reject`** | `'REJECT'` | The rejection or failure of a user's action. On Android 10 and older, `LongPress` is performed instead. | 0.1.0 | | **`ToggleOff`** | `'TOGGLE_OFF'` | The user has toggled a switch or button into the off position. On Android 13 and older, `ClockTick` is performed instead. | 0.1.0 | | **`ToggleOn`** | `'TOGGLE_ON'` | The user has toggled a switch or button into the on position. On Android 13 and older, `ClockTick` is performed instead. | 0.1.0 | | **`VirtualKey`** | `'VIRTUAL_KEY'` | The user has pressed on a virtual on-screen key. | 0.1.0 | ## Migrating from `@capacitor/haptics` This plugin is a drop-in replacement for the official `@capacitor/haptics` plugin with a few differences: | `@capacitor/haptics` | Haptics | | ------------------------------------------------ | ------------------------------------------------------------ | | `impact()` (defaults to `ImpactStyle.Heavy`) | `impact()` (defaults to `ImpactStyle.Medium`) | | `impact({ style })` (`Heavy`, `Medium`, `Light`) | `impact({ style })` (plus the new `Rigid` and `Soft` styles) | | `notification({ type })` | `notification({ type })` | | `vibrate({ duration })` | `vibrate({ duration })` | | `selectionStart()` | `selectionStart()` | | `selectionChanged()` | `selectionChanged()` | | `selectionEnd()` | `selectionEnd()` | | — | `isAvailable()` | | — | `performAndroidHaptic({ type })` | | — | `playPattern({ events })` | **Attention**: The default impact style changed from `ImpactStyle.Heavy` to `ImpactStyle.Medium`. If you rely on the old default, pass `ImpactStyle.Heavy` explicitly. ## Platform behavior The haptic capabilities of the platforms differ. This plugin maps every method to the best available platform API: | Method | Android | iOS | Web | | --------------------------- | ------------------------------------------- | ------------------------------------------ | ------------- | | `impact(...)` | Predefined or tuned vibration effects | `UIImpactFeedbackGenerator` (all 5 styles) | Vibration API | | `notification(...)` | Tuned vibration waveforms | `UINotificationFeedbackGenerator` | Vibration API | | `selection*()` | Tick vibration effect | `UISelectionFeedbackGenerator` | No-op | | `vibrate(...)` | Vibrator with custom duration | System vibration (fixed duration) | Vibration API | | `performAndroidHaptic(...)` | Haptic feedback constants (no vibrator) | Not available | Not available | | `playPattern(...)` | Vibration effects (composition or waveform) | Core Haptics (intensity and sharpness) | Vibration API | Custom haptic patterns are strongest on iOS, where Core Haptics supports per-event intensity and sharpness. On Android, patterns are approximated using vibration effect primitives (Android 11+ with supported hardware) or amplitude-controlled waveforms. On the web, patterns are approximated using the Vibration API, which only supports on/off timings. ## FAQ ### How is this plugin different from other similar plugins? It covers the full range of haptic feedback through one fully typed API: impacts with five styles including the modern Rigid and Soft, notification and selection feedback, custom patterns with per-event intensity and sharpness via Core Haptics on iOS, semantic Android effects that respect the user's system settings, and an availability check — consistently across Android, iOS, and best-effort Web. It is actively maintained against the latest Capacitor and OS versions. If you only need a simple vibration, a minimal setup is fine; if you want rich, platform-aware haptics, this plugin is designed for exactly that. ### How is this plugin different from the official Capacitor Haptics plugin? This plugin is a drop-in replacement for the official `@capacitor/haptics` plugin with additional features: an `isAvailable()` method, semantic Android haptic effects via `performAndroidHaptic(...)`, custom haptic patterns via `playPattern(...)`, and the modern `Rigid` and `Soft` impact styles. Note that the default impact style is `ImpactStyle.Medium` instead of `ImpactStyle.Heavy`, see [Migrating from `@capacitor/haptics`](#migrating-from-capacitorhaptics). ### Do I need to configure any permissions? No, the plugin already declares the `android.permission.VIBRATE` permission in its own manifest, so no additional configuration is required on Android. On iOS and Web, no configuration is required either. ### Why don't I feel any haptic feedback on my device? First, use the `isAvailable()` method to check whether haptic feedback is available: on Android it checks whether the device has a vibrator, on iOS whether the device supports haptic event playback, and on the web whether the Vibration API is supported. Also keep in mind that on Android, the intensity of haptic events is only respected on devices with amplitude control, and that `performAndroidHaptic(...)` respects the user's haptic feedback settings. ### Are custom haptic patterns supported on all platforms? Custom patterns are strongest on iOS, where Core Haptics supports per-event intensity and sharpness; this requires a device with haptic event playback support (for example an iPhone with a Taptic Engine), otherwise the call rejects as unavailable. On Android, patterns are approximated using vibration effect primitives or amplitude-controlled waveforms. On the web, patterns are approximated using the Vibration API, which only supports on/off timings. ### What is the difference between `vibrate` and `playPattern`? The `vibrate(...)` method triggers a single vibration with a custom duration, whereby the duration is only respected on Android and Web (on iOS, a system vibration with a fixed duration is played). The `playPattern(...)` method plays a sequence of individual haptic events, each with its own time, intensity, sharpness, and duration. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Shake](https://capawesome.io/docs/sdks/capacitor/shake/): Detect shake gestures, for example to respond with haptic feedback. - [Silent Mode](https://capawesome.io/docs/sdks/capacitor/silent-mode/): Detect whether the device is in silent mode. - [Volume](https://capawesome.io/docs/sdks/capacitor/volume/): Control the volume and observe hardware volume button presses. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/haptics/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/haptics/LICENSE). # Capacitor Health Plugin Capacitor plugin to read, write and aggregate health data via Apple HealthKit and Android Health Connect with a single, strictly typed API. ## Features The Capacitor Health plugin connects your app to Apple Health (HealthKit) on iOS and Health Connect on Android. Here are some of the key features: - 🏥 **Cross-Platform**: One API for Apple HealthKit and Android Health Connect. - 📖 **~20 Data Types**: Steps, distance, calories, heart rate, sleep, blood pressure, blood glucose, workouts and more. - 📊 **Aggregation-First**: Query sums, averages, minimums and maximums grouped by hour, day, week or month — with multi-source deduplication handled by the platform. - ✍️ **Writing**: Log the record types apps commonly write, such as weight, hydration, blood pressure and workouts. - 🔒 **Honest Permission Model**: Faithfully models what each platform actually reveals about permissions instead of pretending (see the FAQ). - 🧭 **Availability Handling**: Explicitly models the Health Connect installation states and provides an install helper. - 📋 **Policy Documentation**: Detailed guidance for the Google Play Health apps declaration and Apple's App Review Guideline 5.1.3. - 🛡️ **Typed Errors**: Invalid queries reject with clear error codes instead of resolving with silently empty results. - 🤝 **Compatibility**: Works hand in hand with the [Pedometer](https://capawesome.io/docs/sdks/capacitor/pedometer/), [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/) and [Local Notifications](https://capacitorjs.com/docs/apis/local-notifications) 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! ## Use Cases The Health plugin is typically used whenever an app works with health and fitness data, for example: - **Fitness dashboards**: Show daily steps, distance and calories with hourly or daily breakdowns. - **Workout tracking**: Read workouts from other apps or log your own workout sessions. - **Health logging**: Let users log weight, hydration, blood pressure or blood glucose. - **Sleep insights**: Analyze sleep sessions and sleep stages. - **Coaching and habit apps**: Reward users based on their real activity data. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [Announcing the Capacitor Health Plugin](https://capawesome.io/blog/announcing-the-capacitor-health-plugin/): A tour of the plugin's aggregation-first API, permission model, and app review guidance. - [Migrating from Google Fit to Health Connect in Capacitor](https://capawesome.io/blog/google-fit-to-health-connect-migration-in-capacitor/): Data type mapping and migration steps before the Google Fit APIs shut down at the end of 2026. ## 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-health` 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-health npx cap sync ``` ### Android The plugin uses [Health Connect](https://health.google/health-connect-android/), Google's platform for health data on Android. Health data is available on devices running Android 9 (API level 28) or higher. On Android 9 to 13, the user may need to install the Health Connect app first (see `isAvailable()` and `installHealthConnect()`). On Android 14 and higher, Health Connect is part of the operating system. #### Minimum SDK Version The Health Connect SDK requires a minimum SDK version of `26`. Make sure that the `minSdkVersion` in your `android/variables.gradle` file is set to at least `26`: ``` ext { minSdkVersion = 26 } ``` On devices below Android 9, `isAvailable()` resolves with the reason `not-supported` and all other methods reject as unavailable. #### Permissions Health Connect permissions are declared **in your app**, not by the plugin. This is intentional: Google Play reviews every declared health permission, so your app must only declare the permissions for the data types it actually uses. Add the permissions for the data types you use to your `AndroidManifest.xml` file before or after the `application` tag: ``` ``` The following table lists the permission for each data type supported by this plugin: | Data Type | Read Permission | Write Permission (only if you write) | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ | | `ACTIVE_CALORIES` | `android.permission.health.READ_ACTIVE_CALORIES_BURNED` | - | | `BLOOD_GLUCOSE` | `android.permission.health.READ_BLOOD_GLUCOSE` | `android.permission.health.WRITE_BLOOD_GLUCOSE` | | `BLOOD_PRESSURE` | `android.permission.health.READ_BLOOD_PRESSURE` | `android.permission.health.WRITE_BLOOD_PRESSURE` | | `BODY_FAT` | `android.permission.health.READ_BODY_FAT` | - | | `BODY_TEMPERATURE` | `android.permission.health.READ_BODY_TEMPERATURE` | - | | `DISTANCE` | `android.permission.health.READ_DISTANCE` | - | | `FLOORS_CLIMBED` | `android.permission.health.READ_FLOORS_CLIMBED` | - | | `HEART_RATE` | `android.permission.health.READ_HEART_RATE` | - | | `HEART_RATE_VARIABILITY` | `android.permission.health.READ_HEART_RATE_VARIABILITY` | - | | `HEIGHT` | `android.permission.health.READ_HEIGHT` | `android.permission.health.WRITE_HEIGHT` | | `HYDRATION` | `android.permission.health.READ_HYDRATION` | `android.permission.health.WRITE_HYDRATION` | | `OXYGEN_SATURATION` | `android.permission.health.READ_OXYGEN_SATURATION` | - | | `RESPIRATORY_RATE` | `android.permission.health.READ_RESPIRATORY_RATE` | - | | `RESTING_HEART_RATE` | `android.permission.health.READ_RESTING_HEART_RATE` | - | | `SLEEP` | `android.permission.health.READ_SLEEP` | - | | `STEPS` | `android.permission.health.READ_STEPS` | `android.permission.health.WRITE_STEPS` | | `TOTAL_CALORIES` | `android.permission.health.READ_TOTAL_CALORIES_BURNED` | - | | `VO2_MAX` | `android.permission.health.READ_VO2_MAX` | - | | `WEIGHT` | `android.permission.health.READ_WEIGHT` | `android.permission.health.WRITE_WEIGHT` | | `WORKOUT` | `android.permission.health.READ_EXERCISE` (optionally `android.permission.health.READ_ACTIVE_CALORIES_BURNED` and `android.permission.health.READ_DISTANCE`) | `android.permission.health.WRITE_EXERCISE`, `android.permission.health.WRITE_DISTANCE`, `android.permission.health.WRITE_ACTIVE_CALORIES_BURNED` | **Note**: Writing a workout requires three permissions because the plugin writes the optional workout totals (`totalDistance`, `totalCalories`) as separate distance and active calories records, which is how Health Connect models workout totals. For the same reason, `readWorkouts(...)` only returns the workout totals if the read permissions for active calories and distance are granted. Without them, the totals are `null`. #### Privacy Policy Health Connect **requires** every app to explain how it uses health data. The plugin already ships the required activity that handles the privacy policy intents of Health Connect. You only need to configure the URL of your privacy policy by adding the following `meta-data` element inside the `application` tag of your `AndroidManifest.xml` file: ``` ``` When the user taps the privacy policy link in the Health Connect permission dialog or in the Health Connect settings, this URL is opened in the browser. #### Google Play Health Apps Declaration Google Play requires **every** app that integrates with Health Connect to be approved for access. Without this approval, your app **will be rejected** during Google Play review. Follow these steps before you submit your app: 1. Open the [Google Play Console](https://play.google.com/console/) and select your app. 1. Go to **Monitor and improve** → **Policy and programs** → **App content**. 1. Complete the **Health apps** declaration form: declare that your app integrates with Health Connect, select the requested permission types and describe your use case. 1. Make sure your app provides a **privacy policy** that explains how health data is used (see above). 1. Wait for the approval before rolling out your release. Health Connect data may only be used for the permitted health use cases described in the [Health Connect policy](https://support.google.com/googleplay/android-developer/answer/12991134). Using health data for advertising or undisclosed data-mining purposes is prohibited and will get your app rejected or removed. #### Variables If needed, you can define the following project variables in your app’s `variables.gradle` file to change the default versions of the dependencies: - `$healthConnectVersion` version of `androidx.health.connect:connect-client` (default: `1.1.0`) - `$kotlinVersion` version of `org.jetbrains.kotlin:kotlin-gradle-plugin` (default: `2.1.20`) - `$kotlinxCoroutinesVersion` version of `org.jetbrains.kotlinx:kotlinx-coroutines-android` (default: `1.10.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. The Health Connect SDK is a lightweight client library. The actual health data store is provided by the Health Connect app (Android 9 to 13) or the operating system (Android 14+) and is **not** bundled with your app. #### 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 The plugin uses [HealthKit](https://developer.apple.com/documentation/healthkit), Apple's framework for health data on iOS. Health data is not available on iPadOS versions before 17 and in visionOS; use `isAvailable()` to check at runtime. #### Capability Add the **HealthKit** capability to your app in Xcode: select your app target, open the **Signing & Capabilities** tab and add the **HealthKit** capability. This adds the `com.apple.developer.healthkit` entitlement to your app. #### Privacy Descriptions Add the following keys to the `Info.plist` file of your app to explain why your app needs access to health data: ``` NSHealthShareUsageDescription The app needs access to your health data to show your activity and progress. NSHealthUpdateUsageDescription The app writes the health data you log back to Apple Health. ``` `NSHealthShareUsageDescription` is required for reading and `NSHealthUpdateUsageDescription` is required for writing. **Attention**: If a required key is missing, requesting authorization would crash your app. The plugin therefore pre-checks the `Info.plist` file and rejects `requestPermissions(...)` with a clear error message instead. #### App Review Guideline 5.1.3 Apple reviews health apps against [App Review Guideline 5.1.3 (Health and Health Research)](https://developer.apple.com/app-store/review/guidelines/#health-and-health-research). The most important rules: - Your app must provide a **genuine health or fitness feature** that justifies the HealthKit integration. Requesting health permissions without a visible feature is a common rejection reason. - Health data may **not** be used for advertising, marketing or other use-based data mining. Serving ads based on health data will get your app rejected. - Health data may **not** be shared with third parties without explicit user consent, and never for advertising purposes. - Apps that write data to HealthKit must ensure the data is **accurate** and clearly attributed. - Provide a **privacy policy** that explains how health data is collected and used. ## Configuration No configuration required for this plugin. ## Platform Behavior The two platforms model health data differently. The plugin exposes these differences honestly instead of hiding them: | Behavior | Android (Health Connect) | iOS (HealthKit) | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | Availability | Three states: available, Health Connect not installed (installable via `installHealthConnect()`) or unsupported device. | Available on devices with health data support (`HKHealthStore.isHealthDataAvailable()`). | | Read permissions | Reported as `granted` or `prompt` (`denied` after a rejected request). `checkPermissions(...)` never reports `denied` since Health Connect does not reveal it. | Reported as `prompt` before the first request and `unknown` afterwards. HealthKit deliberately hides read permission status (see FAQ). Never `granted`. | | Write permissions | Reported as `granted` or `prompt` (`denied` after a rejected request). | Reported as `granted`, `denied` or `prompt`. | | Historical reads | Limited to 30 days before the permission was first granted. | No time limit. | | `SLEEP` records | Sleep sessions containing all sleep stages. | Individual sleep analysis samples, each with exactly one stage (HealthKit has no session concept). | | `TOTAL_CALORIES` | Dedicated total calories record type. | Only available via `aggregate(...)`, computed as active + basal energy burned. | | `HEART_RATE_VARIABILITY` | RMSSD metric. | SDNN metric. **The values are not comparable across platforms!** | | Workout totals | Aggregated from the records the workout app wrote for the workout time range. | Read from the workout statistics (iOS 16+; `null` on earlier versions). | | `sourceName` | Not provided by Health Connect (always `null`). Use `sourceBundleId`. | The display name of the source (e.g. "Apple Watch"). | ## Usage ``` import { DataType, Health } from '@capawesome-team/capacitor-health'; const checkAvailability = async () => { const { available, reason } = await Health.isAvailable(); if (!available && reason === 'health-connect-not-installed') { // Prompt the user to install Health Connect (Android 9 to 13) await Health.installHealthConnect(); } return available; }; const requestPermissions = async () => { const { permissions } = await Health.requestPermissions({ read: [DataType.Steps, DataType.HeartRate, DataType.Sleep], write: [DataType.Weight], }); return permissions; }; const readSteps = async () => { const { records } = await Health.readRecords({ dataType: DataType.Steps, startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), endDate: new Date().toISOString(), }); return records; }; const aggregateDailySteps = async () => { const { buckets } = await Health.aggregate({ dataType: DataType.Steps, startDate: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString(), endDate: new Date().toISOString(), bucket: 'day', operations: ['sum'], }); return buckets; }; const readWorkouts = async () => { const { workouts } = await Health.readWorkouts({ startDate: new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString(), endDate: new Date().toISOString(), limit: 10, }); return workouts; }; const writeWeight = async () => { await Health.writeRecord({ dataType: DataType.Weight, startDate: new Date().toISOString(), value: 71.5, }); }; const openSettings = async () => { await Health.openSettings(); }; ``` ## API - [`aggregate(...)`](#aggregate) - [`checkPermissions(...)`](#checkpermissions) - [`installHealthConnect()`](#installhealthconnect) - [`isAvailable()`](#isavailable) - [`openSettings()`](#opensettings) - [`readRecords(...)`](#readrecords) - [`readWorkouts(...)`](#readworkouts) - [`requestPermissions(...)`](#requestpermissions) - [`writeRecord(...)`](#writerecord) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### aggregate(...) ``` aggregate(options: AggregateOptions) => Promise ``` Aggregate health data over a time range, optionally grouped into buckets. The aggregation is performed by the platform's health store, which deduplicates overlapping data from multiple sources (e.g. a phone and a smartwatch) automatically. Only the following combinations of data type and operation are supported: | Data Type | `sum` | `average`, `maximum`, `minimum` | | -------------------- | ----- | ------------------------------- | | `ACTIVE_CALORIES` | ✅ | ❌ | | `DISTANCE` | ✅ | ❌ | | `FLOORS_CLIMBED` | ✅ | ❌ | | `HEART_RATE` | ❌ | ✅ | | `HEIGHT` | ❌ | ✅ | | `HYDRATION` | ✅ | ❌ | | `RESTING_HEART_RATE` | ❌ | ✅ | | `STEPS` | ✅ | ❌ | | `TOTAL_CALORIES` | ✅ | ❌ | | `WEIGHT` | ❌ | ✅ | Any other combination rejects with the `INVALID_AGGREGATION` error code. It never resolves with silently empty results. On **iOS**, `TOTAL_CALORIES` is computed as the sum of active and basal energy burned since HealthKit has no total energy type. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `AggregateOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### checkPermissions(...) ``` checkPermissions(options: CheckPermissionsOptions) => Promise ``` Check the permission status for the given data types. On **Android**, Health Connect only reports whether a permission is currently granted. A permission that has never been requested and a permission that has been denied are both reported as `prompt`. On **iOS**, HealthKit deliberately hides the read permission status to prevent apps from inferring sensitive information (a denied read permission is indistinguishable from the absence of data). Read permissions are therefore reported as `prompt` before the first request and as `unknown` afterwards. They are **never** reported as `granted`. Design your app around the presence of data, not around the read permission status. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `CheckPermissionsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### installHealthConnect() ``` installHealthConnect() => Promise ``` Open the Play Store to install or update the Health Connect app. On Android 9 to 13, Health Connect must be installed as a separate app. On Android 14 and later, Health Connect is part of the operating system but may still require an update. Call this method if `isAvailable()` returns the reason `health-connect-not-installed` or `health-connect-update-required`. Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check whether health data is available on the device. On **Android**, this checks the Health Connect SDK status and reports one of three states: available, not installed (but supported) or unsupported. If Health Connect is not installed, you can prompt the user to install it with `installHealthConnect()`. On **iOS**, this checks `HKHealthStore.isHealthDataAvailable()`, which returns `false` on devices without health data support (e.g. some iPads). Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### openSettings() ``` openSettings() => Promise ``` Open the platform's health settings. On **Android**, this opens the Health Connect settings where the user can manage app permissions and data. On **iOS**, this opens the Apple Health app. Apple does not provide a way to open the privacy settings of your app directly. The user can manage permissions in the Health app under `Profile → Privacy → Apps`. Only available on Android and iOS. **Since:** 0.0.1 ______________________________________________________________________ ### readRecords(...) ``` readRecords(options: ReadRecordsOptions) => Promise ``` Read individual health records of a single data type. On **Android**, reads are limited to the last 30 days before the permission was first granted. On **iOS**, the `TOTAL_CALORIES` data type is not supported by this method since HealthKit has no total energy type. Use `aggregate(...)` instead. To read workouts, use `readWorkouts(...)`. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `ReadRecordsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### readWorkouts(...) ``` readWorkouts(options: ReadWorkoutsOptions) => Promise ``` Read workouts. Requires the read permission for the `WORKOUT` data type. On **Android**, the `totalCalories` and `totalDistance` values additionally require the read permissions for the `ACTIVE_CALORIES` and `DISTANCE` data types. Without them, both values are `null`. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ReadWorkoutsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### requestPermissions(...) ``` requestPermissions(options: RequestPermissionsOptions) => Promise ``` Request permissions for the given data types. On **Android**, the Health Connect permission screen is shown. If the user denies the request twice, Health Connect permanently ignores further requests and the permissions can only be granted via `openSettings()`. On **iOS**, the HealthKit authorization sheet is shown once per data type. After the request, read permissions are reported as `unknown` since HealthKit hides the read permission status (see `checkPermissions(...)`). On **iOS**, this method rejects with a clear error message if the required usage descriptions are missing in the `Info.plist` file (see the installation instructions). Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RequestPermissionsOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### writeRecord(...) ``` writeRecord(options: WriteRecordOptions) => Promise ``` Write a single health record. Requires the write permission for the given data type. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `WriteRecordOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### AggregateResult | Prop | Type | Description | Since | | ------------- | ------------------------- | ---------------------------------------------- | ----- | | **`buckets`** | `AggregateResultBucket[]` | The aggregated buckets in chronological order. | 0.0.1 | #### AggregateResultBucket | Prop | Type | Description | Since | | --------------- | ------------------------ | --------------------------------------------------- | ----- | | **`endDate`** | `string` | The end of the bucket as ISO 8601 string. | 0.0.1 | | **`startDate`** | `string` | The start of the bucket as ISO 8601 string. | 0.0.1 | | **`values`** | `AggregateResultValue[]` | The aggregated values for each requested operation. | 0.0.1 | #### AggregateResultValue | Prop | Type | Description | Since | | --------------- | ---------------------- | --------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | | **`operation`** | `AggregationOperation` | The operation that produced this value. | 0.0.1 | | **`value`** | \`number | null\` | The aggregated value in the fixed unit of the data type (see [`DataType`](#datatype)). If no data is available in the bucket, the value is `null`. | #### AggregateOptions | Prop | Type | Description | Since | | ---------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`bucket`** | `AggregationBucket` | The bucket size to group the results by. The buckets are aligned to `startDate`. The `day`, `week` and `month` buckets are calendar-aware (e.g. months have different lengths) and are based on the device's time zone. If `none`, a single bucket spanning the entire time range is returned. | 0.0.1 | | **`dataType`** | `DataType` | The data type to aggregate. | 0.0.1 | | **`endDate`** | `string` | The end of the time range (exclusive) as ISO 8601 string. | 0.0.1 | | **`operations`** | `AggregationOperation[]` | The operations to perform on the data in each bucket. See `aggregate(...)` for the supported combinations of data type and operation. | 0.0.1 | | **`startDate`** | `string` | The start of the time range (inclusive) as ISO 8601 string. | 0.0.1 | #### PermissionStatus | Prop | Type | Description | Since | | ----------------- | -------------------------- | --------------------------------------------------- | ----- | | **`permissions`** | `HealthPermissionStatus[]` | The permission status for each requested data type. | 0.0.1 | #### HealthPermissionStatus | Prop | Type | Description | Since | | -------------- | ----------------------- | ------------------------------------------------------------------------------------------ | ----- | | **`dataType`** | `DataType` | The data type this status belongs to. | 0.0.1 | | **`read`** | `HealthPermissionState` | The read permission status. Only set if the data type was included in the `read` option. | 0.0.1 | | **`write`** | `HealthPermissionState` | The write permission status. Only set if the data type was included in the `write` option. | 0.0.1 | #### CheckPermissionsOptions | Prop | Type | Description | Since | | ----------- | -------------------- | -------------------------------------------------------- | ----- | | **`read`** | `DataType[]` | The data types to check the read permission status for. | 0.0.1 | | **`write`** | `WritableDataType[]` | The data types to check the write permission status for. | 0.0.1 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | ------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`available`** | `boolean` | Whether or not health data is available on the device. | 0.0.1 | | **`reason`** | \`IsAvailableReason | null\` | The reason why health data is not available on the device. If health data is available, the value is `null`. On **Android**, the reason `health-connect-not-installed` means that the device is supported but the Health Connect app is not installed. Call `installHealthConnect()` to prompt the user to install it. The reason `health-connect-update-required` means that the installed Health Connect app is too old. | #### ReadRecordsResult | Prop | Type | Description | Since | | ------------- | ---------------- | --------------------------- | ----- | | **`records`** | `HealthRecord[]` | The records that were read. | 0.0.1 | #### HealthRecord A single health record. | Prop | Type | Description | Since | | -------------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`dataType`** | `DataType` | The data type of the record. | 0.0.1 | | **`diastolic`** | `number` | The diastolic blood pressure in millimeters of mercury. Only set for the `BLOOD_PRESSURE` data type. | 0.0.1 | | **`endDate`** | `string` | The end of the record as ISO 8601 string. For instantaneous records (e.g. weight), this is the same as `startDate`. | 0.0.1 | | **`id`** | \`string | null\` | The unique identifier of the record. If the platform does not provide an identifier, the value is `null`. | | **`sourceBundleId`** | \`string | null\` | The bundle identifier (iOS) or package name (Android) of the app that wrote the record. | | **`sourceName`** | \`string | null\` | The display name of the source that wrote the record. On **Android**, Health Connect does not provide a display name, so the value is always `null`. | | **`stages`** | `SleepStageSample[]` | The sleep stages of the record. Only set for the `SLEEP` data type. On **Android**, a record is a sleep session that contains all its stages. On **iOS**, HealthKit has no session concept. Each record is a single sleep analysis sample with exactly one stage. | 0.0.1 | | **`startDate`** | `string` | The start of the record as ISO 8601 string. | 0.0.1 | | **`systolic`** | `number` | The systolic blood pressure in millimeters of mercury. Only set for the `BLOOD_PRESSURE` data type. | 0.0.1 | | **`unit`** | `string` | The fixed unit of the value (see [`DataType`](#datatype)). | 0.0.1 | | **`value`** | \`number | null\` | The value of the record in the fixed unit of the data type (see `DataType`). For the `BLOOD_PRESSURE` data type, the value is `null` (see `systolic` and `diastolic`). For the `SLEEP` data type, the value is the duration in minutes. Non-finite values reported by third-party data sources are also normalized to `null`. | #### SleepStageSample A single sleep stage within a sleep record. | Prop | Type | Description | Since | | --------------- | ------------ | ------------------------------------------------ | ----- | | **`endDate`** | `string` | The end of the sleep stage as ISO 8601 string. | 0.0.1 | | **`stage`** | `SleepStage` | The sleep stage. | 0.0.1 | | **`startDate`** | `string` | The start of the sleep stage as ISO 8601 string. | 0.0.1 | #### ReadRecordsOptions | Prop | Type | Description | Default | Since | | ----------------- | ---------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`ascending`** | `boolean` | Whether to return the records in ascending order by start date. | `true` | 0.0.1 | | **`dataOrigins`** | `string[]` | Filter the records by the apps that wrote them, identified by bundle identifier (iOS) or package name (Android). An empty array is treated like an omitted filter. | | 0.0.1 | | **`dataType`** | `DataType` | The data type to read. The `WORKOUT` data type is not supported by this method. Use `readWorkouts(...)` instead. | | 0.0.1 | | **`endDate`** | `string` | The end of the time range (exclusive) as ISO 8601 string. | | 0.0.1 | | **`limit`** | `number` | The maximum number of records to return. If not set, all records in the time range are returned. | | 0.0.1 | | **`startDate`** | `string` | The start of the time range (inclusive) as ISO 8601 string. | | 0.0.1 | #### ReadWorkoutsResult | Prop | Type | Description | Since | | -------------- | ----------- | ---------------------------- | ----- | | **`workouts`** | `Workout[]` | The workouts that were read. | 0.0.1 | #### Workout A single workout. | Prop | Type | Description | Since | | -------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **`duration`** | `number` | The duration of the workout in seconds. On **Android**, this is the wall-clock time between `startDate` and `endDate`. On **iOS**, paused intervals are excluded. | 0.0.1 | | **`endDate`** | `string` | The end of the workout as ISO 8601 string. | 0.0.1 | | **`id`** | \`string | null\` | The unique identifier of the workout. If the platform does not provide an identifier, the value is `null`. | | **`rawWorkoutType`** | \`string | null\` | The platform-specific raw workout type. On **Android**, this is the numeric Health Connect exercise type. On **iOS**, this is the numeric `HKWorkoutActivityType` raw value. Use this value to distinguish workout types that are mapped to `OTHER`. | | **`sourceBundleId`** | \`string | null\` | The bundle identifier (iOS) or package name (Android) of the app that wrote the workout. | | **`sourceName`** | \`string | null\` | The display name of the source that wrote the workout. On **Android**, Health Connect does not provide a display name, so the value is always `null`. | | **`startDate`** | `string` | The start of the workout as ISO 8601 string. | 0.0.1 | | **`totalCalories`** | \`number | null\` | The total energy burned during the workout in kilocalories. If the platform does not provide a value, the value is `null`. On **iOS**, the value is only available on iOS 16 and later. | | **`totalDistance`** | \`number | null\` | The total distance covered during the workout in meters. If the platform does not provide a value, the value is `null`. On **iOS**, the value is only available on iOS 16 and later. | | **`workoutType`** | `WorkoutType` | The type of the workout. If the platform-specific workout type has no equivalent in [`WorkoutType`](#workouttype), the value is `OTHER` and the platform-specific value is available in `rawWorkoutType`. | 0.0.1 | #### ReadWorkoutsOptions | Prop | Type | Description | Since | | ----------------- | ------------- | -------------------------------------------------------------------------------------------------- | ----- | | **`endDate`** | `string` | The end of the time range (exclusive) as ISO 8601 string. | 0.0.1 | | **`limit`** | `number` | The maximum number of workouts to return. If not set, all workouts in the time range are returned. | 0.0.1 | | **`startDate`** | `string` | The start of the time range (inclusive) as ISO 8601 string. | 0.0.1 | | **`workoutType`** | `WorkoutType` | Filter the workouts by workout type. | 0.0.1 | #### RequestPermissionsOptions | Prop | Type | Description | Since | | ----------- | -------------------- | --------------------------------------------------- | ----- | | **`read`** | `DataType[]` | The data types to request the read permission for. | 0.0.1 | | **`write`** | `WritableDataType[]` | The data types to request the write permission for. | 0.0.1 | #### WriteRecordOptions | Prop | Type | Description | Since | | --------------- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`dataType`** | `WritableDataType` | The data type to write. | 0.0.1 | | **`diastolic`** | `number` | The diastolic blood pressure in millimeters of mercury. Required for the `BLOOD_PRESSURE` data type. | 0.0.1 | | **`endDate`** | `string` | The end of the record as ISO 8601 string. Required for the `HYDRATION`, `STEPS` and `WORKOUT` data types. Ignored for instantaneous data types (e.g. `WEIGHT`). | 0.0.1 | | **`startDate`** | `string` | The start of the record as ISO 8601 string. | 0.0.1 | | **`systolic`** | `number` | The systolic blood pressure in millimeters of mercury. Required for the `BLOOD_PRESSURE` data type. | 0.0.1 | | **`value`** | `number` | The value of the record in the fixed unit of the data type (see `DataType`). Required for the `BLOOD_GLUCOSE`, `HEIGHT`, `HYDRATION`, `STEPS` and `WEIGHT` data types. Must be a finite number. | 0.0.1 | | **`workout`** | `WriteRecordWorkout` | The workout to write. Required for the `WORKOUT` data type. | 0.0.1 | #### WriteRecordWorkout | Prop | Type | Description | Since | | ------------------- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`totalCalories`** | `number` | The total energy burned during the workout in kilocalories. On **Android**, this additionally writes an active calories record for the workout time range, which requires the write permission for the `ACTIVE_CALORIES` data type. | 0.0.1 | | **`totalDistance`** | `number` | The total distance covered during the workout in meters. On **Android**, this additionally writes a distance record for the workout time range, which requires the write permission for the `DISTANCE` data type. | 0.0.1 | | **`workoutType`** | `WorkoutType` | The type of the workout. If the platform has no equivalent for the given workout type, it is written as the platform's generic workout type. | 0.0.1 | ### Type Aliases #### AggregationOperation The operation to perform when aggregating data. `'average' | 'maximum' | 'minimum' | 'sum'` #### AggregationBucket The bucket size to group aggregation results by. `'day' | 'hour' | 'month' | 'none' | 'week'` #### HealthPermissionState The status of a single health permission. The value `unknown` is used on iOS for read permissions after the first request since HealthKit deliberately hides whether a read permission was granted or denied. `'denied' | 'granted' | 'prompt' | 'unknown'` #### WritableDataType The data types that can be written with `writeRecord(...)`. `DataType.BloodGlucose | DataType.BloodPressure | DataType.Height | DataType.Hydration | DataType.Steps | DataType.Weight | DataType.Workout` #### IsAvailableReason The reason why health data is not available on the device. `'health-connect-not-installed' | 'health-connect-update-required' | 'not-supported'` ### Enums #### DataType | Members | Value | Description | Since | | -------------------------- | -------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`ActiveCalories`** | `'ACTIVE_CALORIES'` | Active energy burned in kilocalories (`kcal`). | 0.0.1 | | **`BloodGlucose`** | `'BLOOD_GLUCOSE'` | Blood glucose level in millimoles per liter (`mmol/L`). | 0.0.1 | | **`BloodPressure`** | `'BLOOD_PRESSURE'` | Blood pressure in millimeters of mercury (`mmHg`). Records of this data type use the `systolic` and `diastolic` properties instead of `value`. | 0.0.1 | | **`BodyFat`** | `'BODY_FAT'` | Body fat percentage (`percent`, 0 to 100). | 0.0.1 | | **`BodyTemperature`** | `'BODY_TEMPERATURE'` | Body temperature in degrees Celsius (`celsius`). | 0.0.1 | | **`Distance`** | `'DISTANCE'` | Distance covered in meters (`m`). On **iOS**, this maps to walking and running distance. | 0.0.1 | | **`FloorsClimbed`** | `'FLOORS_CLIMBED'` | Floors climbed (`count`). | 0.0.1 | | **`HeartRate`** | `'HEART_RATE'` | Heart rate in beats per minute (`bpm`). | 0.0.1 | | **`HeartRateVariability`** | `'HEART_RATE_VARIABILITY'` | Heart rate variability in milliseconds (`ms`). **Attention**: The platforms measure different metrics! Android (Health Connect) provides RMSSD values while iOS (HealthKit) provides SDNN values. The values are exposed as-is and are **not** comparable across platforms. | 0.0.1 | | **`Height`** | `'HEIGHT'` | Height in meters (`m`). | 0.0.1 | | **`Hydration`** | `'HYDRATION'` | Water intake in liters (`L`). | 0.0.1 | | **`OxygenSaturation`** | `'OXYGEN_SATURATION'` | Blood oxygen saturation percentage (`percent`, 0 to 100). | 0.0.1 | | **`RespiratoryRate`** | `'RESPIRATORY_RATE'` | Respiratory rate in breaths per minute (`breaths/min`). | 0.0.1 | | **`RestingHeartRate`** | `'RESTING_HEART_RATE'` | Resting heart rate in beats per minute (`bpm`). | 0.0.1 | | **`Sleep`** | `'SLEEP'` | Sleep sessions with sleep stages. The record value is the duration in minutes (`min`). | 0.0.1 | | **`Steps`** | `'STEPS'` | Steps taken (`count`). | 0.0.1 | | **`TotalCalories`** | `'TOTAL_CALORIES'` | Total energy burned in kilocalories (`kcal`). **Attention**: The platforms compose this value differently! Android (Health Connect) has a dedicated total calories record type while on iOS (HealthKit) the value is computed as the sum of active and basal energy burned. On iOS, this data type is only supported by `aggregate(...)`. | 0.0.1 | | **`Vo2Max`** | `'VO2_MAX'` | Maximal oxygen consumption in milliliters per kilogram of body weight per minute (`mL/kg/min`). | 0.0.1 | | **`Weight`** | `'WEIGHT'` | Body weight in kilograms (`kg`). | 0.0.1 | | **`Workout`** | `'WORKOUT'` | Workouts (exercise sessions). This data type is used for permission requests, `readWorkouts(...)` and `writeRecord(...)`. It is not supported by `readRecords(...)`. | 0.0.1 | #### SleepStage | Members | Value | Description | Since | | ------------- | ----------- | ------------------------------------------------ | ----- | | **`Awake`** | `'AWAKE'` | The user is awake. | 0.0.1 | | **`Deep`** | `'DEEP'` | The user is in deep sleep. | 0.0.1 | | **`InBed`** | `'IN_BED'` | The user is in bed but not necessarily sleeping. | 0.0.1 | | **`Light`** | `'LIGHT'` | The user is in light sleep. | 0.0.1 | | **`Rem`** | `'REM'` | The user is in REM sleep. | 0.0.1 | | **`Unknown`** | `'UNKNOWN'` | The sleep stage is unknown. | 0.0.1 | #### WorkoutType | Members | Value | Description | Since | | ----------------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`AmericanFootball`** | `'AMERICAN_FOOTBALL'` | | 0.0.1 | | **`AustralianFootball`** | `'AUSTRALIAN_FOOTBALL'` | | 0.0.1 | | **`Badminton`** | `'BADMINTON'` | | 0.0.1 | | **`Baseball`** | `'BASEBALL'` | | 0.0.1 | | **`Basketball`** | `'BASKETBALL'` | | 0.0.1 | | **`Biking`** | `'BIKING'` | | 0.0.1 | | **`Boxing`** | `'BOXING'` | | 0.0.1 | | **`Calisthenics`** | `'CALISTHENICS'` | On **iOS**, this is written as functional strength training and read as `STRENGTH_TRAINING`. | 0.0.1 | | **`Cricket`** | `'CRICKET'` | | 0.0.1 | | **`Dancing`** | `'DANCING'` | | 0.0.1 | | **`Elliptical`** | `'ELLIPTICAL'` | | 0.0.1 | | **`Fencing`** | `'FENCING'` | | 0.0.1 | | **`Golf`** | `'GOLF'` | | 0.0.1 | | **`Gymnastics`** | `'GYMNASTICS'` | | 0.0.1 | | **`Handball`** | `'HANDBALL'` | | 0.0.1 | | **`HighIntensityIntervalTraining`** | `'HIGH_INTENSITY_INTERVAL_TRAINING'` | | 0.0.1 | | **`Hiking`** | `'HIKING'` | | 0.0.1 | | **`IceHockey`** | `'ICE_HOCKEY'` | | 0.0.1 | | **`IceSkating`** | `'ICE_SKATING'` | | 0.0.1 | | **`MartialArts`** | `'MARTIAL_ARTS'` | | 0.0.1 | | **`Other`** | `'OTHER'` | | 0.0.1 | | **`Paddling`** | `'PADDLING'` | | 0.0.1 | | **`Pilates`** | `'PILATES'` | | 0.0.1 | | **`Racquetball`** | `'RACQUETBALL'` | | 0.0.1 | | **`RockClimbing`** | `'ROCK_CLIMBING'` | | 0.0.1 | | **`Rowing`** | `'ROWING'` | | 0.0.1 | | **`RowingMachine`** | `'ROWING_MACHINE'` | On **iOS**, this is written and read as `ROWING`. | 0.0.1 | | **`Rugby`** | `'RUGBY'` | | 0.0.1 | | **`Running`** | `'RUNNING'` | | 0.0.1 | | **`RunningTreadmill`** | `'RUNNING_TREADMILL'` | On **iOS**, this is written and read as `RUNNING`. | 0.0.1 | | **`Sailing`** | `'SAILING'` | | 0.0.1 | | **`ScubaDiving`** | `'SCUBA_DIVING'` | | 0.0.1 | | **`Skiing`** | `'SKIING'` | | 0.0.1 | | **`Snowboarding`** | `'SNOWBOARDING'` | | 0.0.1 | | **`Soccer`** | `'SOCCER'` | | 0.0.1 | | **`Softball`** | `'SOFTBALL'` | | 0.0.1 | | **`Squash`** | `'SQUASH'` | | 0.0.1 | | **`StairClimbing`** | `'STAIR_CLIMBING'` | | 0.0.1 | | **`StrengthTraining`** | `'STRENGTH_TRAINING'` | | 0.0.1 | | **`Stretching`** | `'STRETCHING'` | | 0.0.1 | | **`Surfing`** | `'SURFING'` | | 0.0.1 | | **`Swimming`** | `'SWIMMING'` | Generic swimming (iOS only). On **Android**, this is written as the generic workout type. Use `SWIMMING_POOL` or `SWIMMING_OPEN_WATER` for cross-platform swimming workouts. | 0.0.1 | | **`SwimmingOpenWater`** | `'SWIMMING_OPEN_WATER'` | On **iOS**, this is written and read as `SWIMMING`. | 0.0.1 | | **`SwimmingPool`** | `'SWIMMING_POOL'` | On **iOS**, this is written and read as `SWIMMING`. | 0.0.1 | | **`TableTennis`** | `'TABLE_TENNIS'` | | 0.0.1 | | **`Tennis`** | `'TENNIS'` | | 0.0.1 | | **`Volleyball`** | `'VOLLEYBALL'` | | 0.0.1 | | **`Walking`** | `'WALKING'` | | 0.0.1 | | **`WaterPolo`** | `'WATER_POLO'` | | 0.0.1 | | **`Wheelchair`** | `'WHEELCHAIR'` | | 0.0.1 | | **`Yoga`** | `'YOGA'` | | 0.0.1 | ## FAQ ### How is this plugin different from other similar plugins? It is built for correctness and honesty with health data: it reports iOS permission states accurately, surfaces the real availability states on Android, and returns strongly-typed, aggregation-first results. It also ships the setup and policy documentation you need to pass App Review and the required Google Play health-data declaration — the steps that most often decide whether a health app ships — and it's backed by dedicated support. ### Why are read permissions reported as `unknown` on iOS? HealthKit deliberately hides whether a read permission was granted or denied to prevent apps from inferring sensitive information (an app that knows it was denied access to, say, blood glucose data could conclude the user is likely diabetic). A denied read permission behaves exactly like the absence of data. Some plugins pretend read permissions are `granted` after a request — this plugin does not, because it would simply be wrong. Design your app around the **presence of data**, not around the read permission status: request the permissions, query the data and show an appropriate empty state (e.g. "No data available. Check the Health app if you expected data here.") when nothing is returned. ### Why does my app get rejected by Google Play? Every app that integrates with Health Connect must complete the **Health apps declaration** in the Google Play Console and be approved for the requested permission types (see the installation instructions above). Also make sure your app only declares the health permissions it actually uses and provides a privacy policy. ### Why can't I read data older than 30 days on Android? Health Connect only allows apps to read data from up to 30 days before the moment the permission was first granted. If the permission is revoked and granted again, the 30-day window is calculated from the new grant. Support for the `READ_HEALTH_DATA_HISTORY` permission is planned as a fast-follow feature. ### Is health data available on the web? No. There is no web API for health data. All methods reject with an unimplemented error on the web. ### Why do steps from my phone and smartwatch not add up? They do — that's the point of `aggregate(...)`. The platform deduplicates overlapping data from multiple sources automatically. If you sum up the individual records from `readRecords(...)` yourself, you will count overlapping data twice. Use `aggregate(...)` for totals. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Pedometer](https://capawesome.io/docs/sdks/capacitor/pedometer/): Live step counting from the device's motion sensors. - [Background Geolocation](https://capawesome.io/docs/sdks/capacitor/background-geolocation/): Track workout routes in the background. - [Local Notifications](https://capacitorjs.com/docs/apis/local-notifications): Remind users to log their health data. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/health/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/health/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/health/LICENSE). # Capacitor Home Indicator Plugin Capacitor plugin to hide and show the iOS [home indicator](https://developer.apple.com/documentation/uikit/uiviewcontroller/prefershomeindicatorautohidden). ## Features - 🫥 **Hide & show**: Hide and show the home indicator at runtime. - 🔍 **State**: Read the current plugin-managed state. - 🧩 **Zero-config**: No changes to your native project required. - 🤝 **Compatibility**: Need to control the Android navigation bar? Use the [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/) plugin. - 📦 **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! ## Use Cases The Home Indicator plugin is typically used to reduce visual distraction in fullscreen experiences on iOS, for example: - **Video players**: Hide the home indicator while a video is playing so it fades out when the screen is idle. - **Games**: Keep the focus on the gameplay by letting the home indicator auto-hide. - **Reading modes**: Provide a distraction-free reading experience for articles, books, or documents. - **Kiosk-style apps**: Minimize on-screen system UI for apps running in a presentation or kiosk context. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-home-indicator` 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-home-indicator npx cap sync ``` This plugin is only available on **iOS**. On Android and Web, all methods reject as unimplemented. To control the navigation bar on Android, use the [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/) plugin instead. ### iOS No additional setup is required. The plugin injects the home indicator override into the Capacitor bridge view controller automatically, so you do not need to subclass `CAPBridgeViewController` or edit your `AppDelegate`. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to hide and show the home indicator and check its current state. ### Hide and show the home indicator Hide and show the home indicator at runtime. Note that calling `hide()` sets the home indicator to auto-hide: it only fades out while the screen is idle and reappears as soon as the user touches the screen. Only available on iOS: ``` import { HomeIndicator } from '@capawesome/capacitor-home-indicator'; const hide = async () => { await HomeIndicator.hide(); }; const show = async () => { await HomeIndicator.show(); }; ``` ### Check the current state Check whether the home indicator is currently set to be hidden. This reflects the plugin-managed state, not the actual visibility on screen. Only available on iOS: ``` import { HomeIndicator } from '@capawesome/capacitor-home-indicator'; const isHidden = async () => { const { hidden } = await HomeIndicator.isHidden(); return hidden; }; ``` ## API - [`hide()`](#hide) - [`isHidden()`](#ishidden) - [`show()`](#show) - [Interfaces](#interfaces) ### hide() ``` hide() => Promise ``` Hide the home indicator. The home indicator auto-hides while the screen is idle and reappears when the user touches the screen. The system does not allow permanently removing the home indicator. Only available on iOS. **Since:** 0.1.0 ______________________________________________________________________ ### isHidden() ``` isHidden() => Promise ``` Check whether the home indicator is currently set to be hidden. This reflects the plugin-managed state, not the actual visibility of the home indicator on screen (which the system controls). The state is kept in memory only and resets when the app is restarted. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### show() ``` show() => Promise ``` Show the home indicator. Only available on iOS. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### IsHiddenResult | Prop | Type | Description | Since | | ------------ | --------- | --------------------------------------------------------- | ----- | | **`hidden`** | `boolean` | Whether the home indicator is currently set to be hidden. | 0.1.0 | ## Home Indicator Behavior It is important to understand how the iOS home indicator behaves, as the operating system does not allow it to be permanently removed: - **Auto-hide only**: Calling `hide()` sets the home indicator to auto-hide. The indicator only fades out while the screen is idle. - **Reappears on touch**: As soon as the user touches the screen, the home indicator reappears. It fades out again once the screen becomes idle. This is a system behavior enforced by iOS and cannot be changed. - **In-memory state**: The state managed by this plugin is kept in memory only. It resets to shown when the app is restarted, so call `hide()` again after your app relaunches if needed. ## FAQ ### How is this plugin different from other similar plugins? It focuses on doing one thing well: hiding and showing the iOS home indicator at runtime with zero native setup, injecting the override into the Capacitor bridge view controller automatically so you never subclass `CAPBridgeViewController` or edit your `AppDelegate`. The API is fully typed, ships for both CocoaPods and Swift Package Manager, and is kept current with the latest Capacitor and iOS releases. If all you need is to toggle the home indicator, this is a focused, drop-in solution. ### Why is the home indicator still visible after calling the hide method? Calling `hide()` only sets the home indicator to auto-hide. The indicator fades out while the screen is idle and reappears as soon as the user touches the screen. This is a system behavior enforced by iOS and cannot be changed, since the operating system does not allow the home indicator to be permanently removed. ### Does this plugin work on Android or the Web? No, the plugin is only available on iOS. On Android and Web, all methods reject as unimplemented. If you want to control the navigation bar on Android, use the [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/) plugin instead. ### Do I need to modify my native iOS project? No, the plugin injects the home indicator override into the Capacitor bridge view controller automatically. You do not need to subclass `CAPBridgeViewController` or edit your `AppDelegate`. ### Is the hidden state persisted across app restarts? No, the state managed by the plugin is kept in memory only and resets to shown when the app is restarted. If your app should always hide the home indicator, call `hide()` again after your app relaunches. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Android Edge-to-Edge Support](https://capawesome.io/docs/sdks/capacitor/android-edge-to-edge-support/): Support edge-to-edge display on Android with features like setting the status bar and navigation bar color. - [Navigation Bar](https://capawesome.io/docs/sdks/capacitor/navigation-bar/): Set the background color and button style of the Android navigation bar. - [Screen Orientation](https://capawesome.io/docs/sdks/capacitor/screen-orientation/): Lock and unlock the screen orientation. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/home-indicator/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/home-indicator/LICENSE). # Capacitor In-App Browser Plugin Capacitor plugin to open URLs in the external browser, the system browser or an embedded web view. ## Features The Capacitor In-App Browser plugin is one of the most complete in-app browsing solutions for Capacitor apps. Here are some of the key features: - 🌐 **Three browser modes**: Open URLs in the external browser, the system browser (Custom Tabs on Android, `SFSafariViewController` on iOS) or an embedded web view. - 🧭 **Navigation events**: Get notified when the browser is closed, a page has been loaded or a navigation has been completed. - 💉 **JavaScript execution**: Execute any JavaScript code in the embedded web view. - 💬 **Messaging**: Exchange messages between your app and the web page in both directions. - 🎨 **Toolbar theming**: Customize the toolbar color, title, close button and navigation buttons. - 🍪 **Session control**: Clear the cache and cookies, or use an isolated data store on iOS. - 🎥 **Media permissions**: Camera and microphone permission requests from web pages are forwarded to the app. - 🤝 **Compatibility**: Works alongside the [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/), [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/) and [System WebView](https://capawesome.io/docs/sdks/capacitor/system-webview/) 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! ## Use Cases The In-App Browser plugin is typically used whenever an app needs to display web content without losing the user, for example: - **External links**: Open links, terms of service, or documentation in the system browser without leaving the app context. - **Login and checkout flows**: Open a web-based flow in the embedded web view and watch for a redirect using the `browserUrlChanged` event. - **Hybrid web content**: Embed a web page with a themed native toolbar and exchange messages between the app and the page. - **Background loading**: Load a URL in a hidden web view with the `visible` option and present it once the page has loaded. - **Session control**: Clear the cache and cookies of the web view, or use an isolated data store on iOS. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | iOS | | --- | | | ## 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-in-app-browser` 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-in-app-browser npx cap sync ``` ### Android #### Variables This plugin will use the following project variables (defined in your app’s `variables.gradle` file): - `$androidxBrowserVersion` version of `androidx.browser:browser` (default: `1.9.0`) #### Permissions If web pages opened in the embedded web view should be able to access the camera or microphone, the following elements must be added to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` The permissions must also be granted before a web page requests access to the camera or microphone. ### iOS #### Privacy Descriptions If web pages opened in the embedded web view should be able to access the camera or microphone, the `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` keys must be added to the `Info.plist` file of your app: ``` NSCameraUsageDescription The camera is used by websites opened in the in-app browser. NSMicrophoneUsageDescription The microphone is used by websites opened in the in-app browser. ``` ## Configuration No configuration required for this plugin. ## Usage The following examples show how to open URLs in the external, system, and in-app browsers, control the embedded web view, exchange messages with the loaded page, clear browsing data, and listen for browser events. ### Open a URL in the external browser Open a URL in the default browser app of the device. Since the browser is opened in a separate app, no events are emitted in this mode: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const openInExternalBrowser = async () => { await InAppBrowser.openInExternalBrowser({ url: 'https://capawesome.io', }); }; ``` ### Open a URL in the system browser Open a URL in the system browser (Custom Tabs on Android, `SFSafariViewController` on iOS) and customize the toolbar with platform-specific options. Only available on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const openInSystemBrowser = async () => { await InAppBrowser.openInSystemBrowser({ url: 'https://capawesome.io', android: { showTitle: true, toolbarColor: '#008080', }, ios: { dismissButtonStyle: 'close', toolbarColor: '#008080', }, }); }; ``` ### Open a URL in an embedded web view Open a URL in an embedded web view with a native toolbar whose color, title, and buttons can be customized. Only available on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const openInWebView = async () => { await InAppBrowser.openInWebView({ url: 'https://capawesome.io', toolbar: { backgroundColor: '#008080', color: '#FFFFFF', showNavigationButtons: true, }, }); }; ``` ### Close the browser Close the currently open browser. This closes browsers opened with `openInWebView(...)` or `openInSystemBrowser(...)`. Only available on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const close = async () => { await InAppBrowser.close(); }; ``` ### Execute JavaScript in the web view Execute any JavaScript code in the currently open web view. This method is only available for browsers opened with `openInWebView(...)` on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const executeScript = async () => { const { result } = await InAppBrowser.executeScript({ script: 'document.title', }); return result; }; ``` ### Post a message to the web page Post a message to the currently open web view. The web page receives the message by listening for the `capacitorInAppBrowserMessage` window event, see [Messaging](#messaging). This method is only available for browsers opened with `openInWebView(...)` on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const postMessage = async () => { await InAppBrowser.postMessage({ data: { name: 'Capawesome' }, }); }; ``` ### Clear the cache and cookies Clear the cache or the cookies of the web view, either for all URLs or only for a specific URL. Only available on Android and iOS: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const clearCache = async () => { await InAppBrowser.clearCache(); }; const clearAllCookies = async () => { await InAppBrowser.clearCookies(); }; const clearCookiesForUrl = async () => { await InAppBrowser.clearCookies({ url: 'https://capawesome.io' }); }; ``` ### Listen for browser events Get notified when the browser is closed, a message is received, a navigation has been completed, a page has been loaded, or the URL has changed: ``` import { InAppBrowser } from '@capawesome/capacitor-in-app-browser'; const addListeners = async () => { await InAppBrowser.addListener('browserClosed', () => { console.log('Browser closed'); }); await InAppBrowser.addListener('browserMessageReceived', event => { console.log('Message received', event.data); }); await InAppBrowser.addListener('browserNavigationCompleted', event => { console.log('Navigation completed', event.url); }); await InAppBrowser.addListener('browserPageLoaded', () => { console.log('Browser page loaded'); }); await InAppBrowser.addListener('browserUrlChanged', event => { console.log('URL changed', event.url); }); }; ``` ## API - [`clearCache()`](#clearcache) - [`clearCookies(...)`](#clearcookies) - [`close()`](#close) - [`executeScript(...)`](#executescript) - [`getCookies(...)`](#getcookies) - [`openInExternalBrowser(...)`](#openinexternalbrowser) - [`openInSystemBrowser(...)`](#openinsystembrowser) - [`openInWebView(...)`](#openinwebview) - [`postMessage(...)`](#postmessage) - [`show()`](#show) - [`addListener('browserClosed', ...)`](#addlistenerbrowserclosed-) - [`addListener('browserMessageReceived', ...)`](#addlistenerbrowsermessagereceived-) - [`addListener('browserNavigationCompleted', ...)`](#addlistenerbrowsernavigationcompleted-) - [`addListener('browserPageLoaded', ...)`](#addlistenerbrowserpageloaded-) - [`addListener('browserUrlChanged', ...)`](#addlistenerbrowserurlchanged-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### clearCache() ``` clearCache() => Promise ``` Clear the cache of the web view. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### clearCookies(...) ``` clearCookies(options?: ClearCookiesOptions | undefined) => Promise ``` Clear the cookies of the web view. Provide the `url` option to clear only the cookies of a specific URL. Otherwise, all cookies are cleared. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `ClearCookiesOptions` | **Since:** 0.2.0 ______________________________________________________________________ ### close() ``` close() => Promise ``` Close the currently open browser. This closes browsers opened with `openInWebView(...)` or `openInSystemBrowser(...)`. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### executeScript(...) ``` executeScript(options: ExecuteScriptOptions) => Promise ``` Execute a JavaScript script in the currently open web view. This method is only available for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `ExecuteScriptOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getCookies(...) ``` getCookies(options: GetCookiesOptions) => Promise ``` Get the cookies for a specific URL. On iOS, only cookies from the shared data store are returned. Cookies from a web view opened with `dataStore: 'isolated'` are not included. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `GetCookiesOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### openInExternalBrowser(...) ``` openInExternalBrowser(options: OpenInExternalBrowserOptions) => Promise ``` Open a URL in the default browser app of the device. | Param | Type | | ------------- | ------------------------------ | | **`options`** | `OpenInExternalBrowserOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### openInSystemBrowser(...) ``` openInSystemBrowser(options: OpenInSystemBrowserOptions) => Promise ``` Open a URL in the system browser (Custom Tabs on Android, `SFSafariViewController` on iOS). Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `OpenInSystemBrowserOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### openInWebView(...) ``` openInWebView(options: OpenInWebViewOptions) => Promise ``` Open a URL in an embedded web view with a native toolbar. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------- | | **`options`** | `OpenInWebViewOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### postMessage(...) ``` postMessage(options: PostMessageOptions) => Promise ``` Post a message to the currently open web view. The web page receives the message by listening for the `capacitorInAppBrowserMessage` window event. The message data is available in the `detail` property of the event. This method is only available for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `PostMessageOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### show() ``` show() => Promise ``` Show the web view if it was opened with `visible: false`. This method is only available for browsers opened with `openInWebView(...)`. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### addListener('browserClosed', ...) ``` addListener(eventName: 'browserClosed', listenerFunc: () => void) => Promise ``` Called when the browser is closed. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------- | | **`eventName`** | `'browserClosed'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('browserMessageReceived', ...) ``` addListener(eventName: 'browserMessageReceived', listenerFunc: (event: BrowserMessageReceivedEvent) => void) => Promise ``` Called when the web page posts a message to the app using the injected `window.CapacitorInAppBrowser.postMessage(...)` function. This event is only emitted for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------------------- | | **`eventName`** | `'browserMessageReceived'` | | **`listenerFunc`** | `(event: BrowserMessageReceivedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('browserNavigationCompleted', ...) ``` addListener(eventName: 'browserNavigationCompleted', listenerFunc: (event: BrowserNavigationCompletedEvent) => void) => Promise ``` Called when a page navigation has been completed in the web view. This event is only emitted for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------------ | -------------------------------------------------- | | **`eventName`** | `'browserNavigationCompleted'` | | **`listenerFunc`** | `(event: BrowserNavigationCompletedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('browserPageLoaded', ...) ``` addListener(eventName: 'browserPageLoaded', listenerFunc: () => void) => Promise ``` Called when the initial page of the browser has finished loading. On Android, this event is only emitted for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------- | | **`eventName`** | `'browserPageLoaded'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('browserUrlChanged', ...) ``` addListener(eventName: 'browserUrlChanged', listenerFunc: (event: BrowserUrlChangedEvent) => void) => Promise ``` Called when the current URL of the web view changes, e.g. when the user navigates to a new page, a server redirect occurs, or a single-page application updates the browser history. This event is also emitted for the initial URL and fires earlier than `browserNavigationCompleted`. This event is only emitted for browsers opened with `openInWebView(...)`. Only available on Android and iOS. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'browserUrlChanged'` | | **`listenerFunc`** | `(event: BrowserUrlChangedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### ClearCookiesOptions | Prop | Type | Description | Since | | --------- | -------- | --------------------------------------------------------------------------- | ----- | | **`url`** | `string` | The URL to clear the cookies for. If not provided, all cookies are cleared. | 0.2.0 | #### ExecuteScriptResult | Prop | Type | Description | Since | | ------------ | -------- | ----------- | -------------------------------------------------------------------------------------------------------------------------------------------- | | **`result`** | \`string | null\` | The result of the script execution serialized as a JSON string. If the script does not return a JSON-serializable value, `null` is returned. | #### ExecuteScriptOptions | Prop | Type | Description | Since | | ------------ | -------- | ----------------------------------------------- | ----- | | **`script`** | `string` | The JavaScript code to execute in the web view. | 0.1.0 | #### GetCookiesResult | Prop | Type | Description | Since | | ------------- | ---------------------------- | ----------------------------------------------------------- | ----- | | **`cookies`** | `{ [key: string]: string; }` | The cookies for the URL as a map of cookie names to values. | 0.1.0 | #### GetCookiesOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------- | ----- | | **`url`** | `string` | The URL to get the cookies for. | 0.1.0 | #### OpenInExternalBrowserOptions | Prop | Type | Description | Since | | --------- | -------- | ---------------------------------------- | ----- | | **`url`** | `string` | The URL to open in the external browser. | 0.1.0 | #### OpenInSystemBrowserOptions | Prop | Type | Description | Since | | ------------- | ----------------------------------- | -------------------------------------------------------------------- | ----- | | **`android`** | `OpenInSystemBrowserAndroidOptions` | Options that are only applied on Android. Only available on Android. | 0.1.0 | | **`ios`** | `OpenInSystemBrowserIosOptions` | Options that are only applied on iOS. Only available on iOS. | 0.1.0 | | **`url`** | `string` | The URL to open in the system browser. | 0.1.0 | #### OpenInSystemBrowserAndroidOptions | Prop | Type | Description | Default | Since | | ------------------------- | --------- | ---------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`hideToolbarOnScroll`** | `boolean` | Whether or not the toolbar should be hidden when the user scrolls down and shown when the user scrolls up. | `false` | 0.1.0 | | **`showTitle`** | `boolean` | Whether or not the title of the web page should be shown in the toolbar. | `false` | 0.1.0 | | **`toolbarColor`** | `string` | The background color of the toolbar as a hex color code. | | 0.1.0 | #### OpenInSystemBrowserIosOptions | Prop | Type | Description | Default | Since | | ------------------------ | -------------------- | ------------------------------------------------------------------------------------------------------- | -------- | ----- | | **`barCollapsing`** | `boolean` | Whether or not the toolbar should collapse when the user scrolls down. | `true` | 0.1.0 | | **`dismissButtonStyle`** | `DismissButtonStyle` | The style of the dismiss button in the toolbar. | `'done'` | 0.1.0 | | **`readerMode`** | `boolean` | Whether or not the reader mode should be entered if it is available for the web page. | `false` | 0.1.0 | | **`toolbarColor`** | `string` | The background color of the toolbar as a hex color code. This option has no effect on iOS 26 and later. | | 0.1.0 | #### OpenInWebViewOptions | Prop | Type | Description | Default | Since | | ------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ----- | | **`android`** | `OpenInWebViewAndroidOptions` | Options that are only applied on Android. Only available on Android. | | 0.1.0 | | **`dataStore`** | `WebViewDataStore` | The data store to use for the web view. On Android, this option is ignored. The web view always uses the app-global (`shared`) data store. Only available on iOS. | `'shared'` | 0.1.0 | | **`headers`** | `{ [key: string]: string; }` | Additional HTTP headers to send with the initial request. | | 0.1.0 | | **`ios`** | `OpenInWebViewIosOptions` | Options that are only applied on iOS. Only available on iOS. | | 0.1.0 | | **`mediaPlaybackRequiresUserAction`** | `boolean` | Whether or not media playback requires user action. | `false` | 0.1.0 | | **`toolbar`** | `WebViewToolbarOptions` | Options for the toolbar of the web view. | | 0.1.0 | | **`url`** | `string` | The URL to open in the web view. | | 0.1.0 | | **`userAgent`** | `string` | The custom user agent to use for the web view. | | 0.1.0 | | **`visible`** | `boolean` | Whether or not the web view is presented when opened. If `false`, the web view loads the URL in the background and stays hidden until `show()` is called. The `browserPageLoaded` event is still emitted and `close()` can be called while the web view is hidden. | `true` | 0.1.0 | #### OpenInWebViewAndroidOptions | Prop | Type | Description | Default | Since | | -------------------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`allowZoom`** | `boolean` | Whether or not the user can zoom the web page. | `false` | 0.1.0 | | **`hardwareBackButton`** | `boolean` | Whether or not the hardware back button navigates back in the web view's history before closing the web view. If `false`, the hardware back button closes the web view immediately. | `true` | 0.1.0 | | **`pauseMediaWhenHidden`** | `boolean` | Whether or not media playback is paused when the app is hidden. | `true` | 0.1.0 | #### OpenInWebViewIosOptions | Prop | Type | Description | Default | Since | | ----------------------------------------- | --------- | --------------------------------------------------------------------------------------------- | ------- | ----- | | **`allowsBackForwardNavigationGestures`** | `boolean` | Whether or not horizontal swipe gestures navigate back and forward in the web view's history. | `false` | 0.1.0 | | **`overscroll`** | `boolean` | Whether or not the web view bounces when scrolled past the edge of the content. | `true` | 0.1.0 | #### WebViewToolbarOptions | Prop | Type | Description | Default | Since | | --------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------------------- | --------- | ----- | | **`backgroundColor`** | `string` | The background color of the toolbar as a hex color code. On iOS 26 and later, this replaces the Liquid Glass toolbar with an opaque toolbar. | | 0.1.0 | | **`closeButtonText`** | `string` | The text of the close button in the toolbar. | `'Close'` | 0.1.0 | | **`color`** | `string` | The text color of the toolbar as a hex color code. | | 0.1.0 | | **`showNavigationButtons`** | `boolean` | Whether or not the back and forward navigation buttons should be shown in the toolbar. | `false` | 0.1.0 | | **`showUrl`** | `boolean` | Whether or not the current URL should be displayed in the toolbar instead of the title. | `false` | 0.1.0 | | **`title`** | `string` | The fixed title to display in the toolbar. If not set, the title of the current web page is displayed. | | 0.1.0 | | **`visible`** | `boolean` | Whether or not the toolbar should be visible. | `true` | 0.1.0 | #### PostMessageOptions | Prop | Type | Description | Since | | ---------- | ----------------------------- | ----------------------------------------------------------------------------- | ----- | | **`data`** | `{ [key: string]: unknown; }` | The message data to post to the web view. Must be a JSON-serializable object. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### BrowserMessageReceivedEvent | Prop | Type | Description | Since | | ---------- | --------- | ---------------------------------------- | ----- | | **`data`** | `unknown` | The message data posted by the web page. | 0.1.0 | #### BrowserNavigationCompletedEvent | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------ | ----- | | **`url`** | `string` | The URL of the page that was navigated to. | 0.1.0 | #### BrowserUrlChangedEvent | Prop | Type | Description | Since | | --------- | -------- | ---------------------------- | ----- | | **`url`** | `string` | The new URL of the web view. | 0.1.0 | ### Type Aliases #### DismissButtonStyle The style of the dismiss button in the toolbar of the system browser. - `cancel`: A button with the text "Cancel". - `close`: A button with the text "Close". - `done`: A button with the text "Done". `'cancel' | 'close' | 'done'` #### WebViewDataStore The data store to use for the web view. - `isolated`: The web view uses a non-persistent data store. Cookies and web storage are discarded when the web view is closed. - `shared`: The web view uses the app-global data store which is shared with other web views. `'isolated' | 'shared'` ## Messaging The embedded web view injects a small message bridge into every web page. This allows you to exchange messages between your app and the web page in both directions. ### From the web page to the app The web page can post a message to the app using the injected `window.CapacitorInAppBrowser.postMessage(...)` function: ``` window.CapacitorInAppBrowser.postMessage({ name: 'Capawesome' }); ``` The app receives the message via the `browserMessageReceived` event. ### From the app to the web page The app can post a message to the web page using the `postMessage(...)` method. The web page receives the message by listening for the `capacitorInAppBrowserMessage` window event: ``` window.addEventListener('capacitorInAppBrowserMessage', event => { console.log('Message received', event.detail); }); ``` ## Platform Behavior The three browser modes behave differently on each platform. Keep the following differences in mind: - **System browser**: Tracking the visited URLs is not possible by design. If you need the `browserNavigationCompleted` or `browserUrlChanged` event, use the `openInWebView(...)` method instead. On Android, the `browserPageLoaded` event is not emitted for the system browser and the `browserClosed` event is emitted when the user returns to the app. - **Embedded web view**: The web view always uses the app-global (`shared`) data store on Android. The `dataStore` option is only supported on iOS. On iOS, hiding the toolbar removes the close button, so the browser can then only be closed using the `close(...)` method. - **External browser**: The browser is opened in a separate app. For this reason, no events are emitted and the `close(...)` method has no effect. ## FAQ ### How is this plugin different from other similar plugins? It covers three browsing modes in one fully typed API — the external browser, the system browser (Custom Tabs on Android, `SFSafariViewController` on iOS), and an embedded web view with a themed native toolbar, JavaScript execution, two-way messaging, navigation events, and session control. Camera and microphone requests from web pages are forwarded to the app, and you can even load a URL hidden in the background and present it once it's ready. If you only need to open a link, the external mode is a simple one-liner; if you need to embed, theme, and communicate with web content, this plugin is designed for exactly that. ### What is the difference between the external browser, the system browser and the embedded web view? The `openInExternalBrowser(...)` method opens the URL in the default browser app of the device, so no events are emitted and the `close()` method has no effect. The `openInSystemBrowser(...)` method presents the system browser (Custom Tabs on Android, `SFSafariViewController` on iOS) inside your app with a customizable toolbar. The `openInWebView(...)` method opens an embedded web view with a native toolbar and offers the most control, including JavaScript execution, messaging, and navigation events. See [Platform Behavior](#platform-behavior) for the differences between the modes. ### How can I track which URLs the user visits? Tracking the visited URLs is only possible in the embedded web view. Open the URL with `openInWebView(...)` and listen for the `browserUrlChanged` or `browserNavigationCompleted` event. In the system browser, tracking the visited URLs is not possible by design, and in the external browser no events are emitted at all. ### How can I exchange data between my app and the opened web page? The embedded web view injects a small message bridge into every web page. The web page can post messages to the app using the injected `window.CapacitorInAppBrowser.postMessage(...)` function, which the app receives via the `browserMessageReceived` event. The app can post messages to the web page using the `postMessage(...)` method, which the web page receives via the `capacitorInAppBrowserMessage` window event. See [Messaging](#messaging) for more details. ### Can web pages access the camera or microphone? Yes, camera and microphone permission requests from web pages opened in the embedded web view are forwarded to the app. On Android, the corresponding permissions must be declared in your `AndroidManifest.xml` and granted before a web page requests access. On iOS, the `NSCameraUsageDescription` and `NSMicrophoneUsageDescription` keys must be added to your `Info.plist` file. See [Installation](#installation) for details. ### Can I load a URL in the background before showing it? Yes, open the URL with `openInWebView(...)` and set the `visible` option to `false`. The web view then loads the URL in the background and stays hidden until you call the `show()` method. The `browserPageLoaded` event is still emitted and `close()` can be called while the web view is hidden. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/): Check if an app can be opened and open it. - [OAuth](https://capawesome.io/docs/sdks/capacitor/oauth/): Communicate with OAuth 2.0 and OpenID Connect providers. - [System WebView](https://capawesome.io/docs/sdks/capacitor/system-webview/): Detect an outdated Android System WebView and guide users to update 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/in-app-browser/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/in-app-browser/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/in-app-browser/LICENSE). # Capacitor Install Referrer Plugin Capacitor plugin for reading install attribution data from the [Play Install Referrer](https://developer.android.com/google/play/installreferrer) and [Apple Ad Services](https://developer.apple.com/documentation/adservices). ## Features - 🤖 **Play Install Referrer**: Read the Play Store install referrer on Android. - 🍏 **Apple Ad Services**: Read the Apple Ad Services attribution token on iOS. - 🤝 **Compatibility**: Works alongside the [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/), [Firebase Analytics](https://capawesome.io/docs/sdks/capacitor/firebase/analytics/) and [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/) 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! ## Use Cases The Install Referrer plugin is typically used to attribute app installs to marketing campaigns, for example: - **Campaign attribution**: Attribute Android installs to a campaign by reading the referrer URL of the installed package. - **Apple Search Ads measurement**: Measure iOS campaign performance by exchanging the Apple Ad Services attribution token server-side. - **Deferred deep linking**: Pass parameters through the Play Store referrer URL to route users to the right content after the first launch. - **Google Play Instant detection**: Detect whether the app was installed via a Google Play Instant experience within the past 7 days. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-install-referrer` 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-install-referrer npx cap sync ``` The `getInstallReferrer(...)` method is only available on **Android**, the `getAttributionToken(...)` method is only available on **iOS**. On all other platforms, the respective method rejects as unimplemented. ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$installReferrerVersion` version of `com.android.installreferrer:installreferrer` (default: `2.2`) ## Configuration No configuration required for this plugin. ## Usage The following examples show how to read the Play Store install referrer on Android and the Apple Ad Services attribution token on iOS. ### Read the Play Store install referrer Get the Play Store install referrer information, including the referrer URL and the click and install timestamps. Fetch it once shortly after the first launch and persist it, since Google only guarantees its availability for a limited window after the install. Only available on Android: ``` import { InstallReferrer } from '@capawesome/capacitor-install-referrer'; const getInstallReferrer = async () => { const result = await InstallReferrer.getInstallReferrer(); return result; }; ``` ### Read the Apple Ad Services attribution token Get the Apple Ad Services attribution token. The token is opaque and must be exchanged server-side with Apple's attribution API (see [Attribution Token Exchange](#attribution-token-exchange-ios)). Only available on iOS: ``` import { InstallReferrer } from '@capawesome/capacitor-install-referrer'; const getAttributionToken = async () => { const { token } = await InstallReferrer.getAttributionToken(); return token; }; ``` ## API - [`getAttributionToken()`](#getattributiontoken) - [`getInstallReferrer()`](#getinstallreferrer) - [Interfaces](#interfaces) ### getAttributionToken() ``` getAttributionToken() => Promise ``` Get the Apple Ad Services attribution token. The token is opaque and only the client half of the attribution flow. It must be exchanged server-side with Apple's Ad Services attribution API (`https://api-adservices.apple.com/api/v1/`) and expires after 24 hours. Only available on iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getInstallReferrer() ``` getInstallReferrer() => Promise ``` Get the Play Store install referrer information. The referrer should be fetched once shortly after the first launch and persisted by your app. Google only guarantees availability for a limited window after the install. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetAttributionTokenResult | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------------- | ----- | | **`token`** | `string` | The Apple Ad Services attribution token. | 0.1.0 | #### GetInstallReferrerResult | Prop | Type | Description | Since | | ---------------------------------- | --------- | ------------------------------------------------------------------------------------------ | ----- | | **`googlePlayInstantParam`** | `boolean` | Whether the app was installed via a Google Play Instant experience within the past 7 days. | 0.1.0 | | **`installBeginTimestampMillis`** | `number` | The client-side timestamp (in milliseconds) when the app installation began. | 0.1.0 | | **`referrerClickTimestampMillis`** | `number` | The client-side timestamp (in milliseconds) when the referrer click happened. | 0.1.0 | | **`referrerUrl`** | `string` | The referrer URL of the installed package. | 0.1.0 | ## Install Referrer Window (Android) Google only guarantees that the Play Store install referrer is available for a limited window after the app is installed. You should fetch the referrer once shortly after the first launch and persist it in your app, so that it remains available later. ## Attribution Token Exchange (iOS) The token returned by `getAttributionToken(...)` is opaque and only the client half of the attribution flow. To resolve the attribution data, you must exchange the token server-side against Apple's Ad Services attribution API: ``` POST https://api-adservices.apple.com/api/v1/ Content-Type: text/plain ``` The token expires after 24 hours, so it should be exchanged promptly. See the [Apple documentation]() for more information. ## FAQ ### How is this plugin different from other similar plugins? It brings the Android Play Install Referrer and the iOS Apple Ad Services attribution token together under one fully typed API, so a single dependency covers install attribution across both platforms. It surfaces the complete referrer details — referrer URL, click and install timestamps, and Google Play Instant detection — and documents the Android availability window and the server-side Apple token exchange clearly. It's actively maintained against the latest Capacitor and OS versions, so campaign attribution and deferred deep linking stay reliable as the platforms evolve. ### Which platforms does this plugin support? The `getInstallReferrer(...)` method is only available on Android and the `getAttributionToken(...)` method is only available on iOS. On all other platforms, the respective method rejects as unimplemented. ### Why is the install referrer no longer available on Android? Google only guarantees that the Play Store install referrer is available for a limited window after the app is installed. You should therefore fetch the referrer once shortly after the first launch and persist it in your app, so that it remains available later. ### How do I resolve the attribution data from the Apple attribution token? The token returned by `getAttributionToken(...)` is opaque and only the client half of the attribution flow. You must exchange it server-side against Apple's Ad Services attribution API, as described in [Attribution Token Exchange](#attribution-token-exchange-ios). Note that the token expires after 24 hours, so it should be exchanged promptly. ### Do I need any permissions or special configuration? No, the plugin does not require any runtime permissions or configuration. On Android, it uses the `com.android.installreferrer:installreferrer` library, whose version can be changed via the `$installReferrerVersion` project variable if needed (see [Installation](#installation)). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Tracking Transparency](https://capawesome.io/docs/sdks/capacitor/app-tracking-transparency/): Request the user's permission to track them via the App Tracking Transparency framework on iOS. - [Firebase Analytics](https://capawesome.io/docs/sdks/capacitor/firebase/analytics/): Measure user engagement with Firebase Analytics. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/): Capture analytics events with the PostHog platform. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/install-referrer/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/install-referrer/LICENSE). # Capacitor Intercom Plugin Unofficial Capacitor plugin for [Intercom](https://www.intercom.com/).[1](#fn:1) ## Features The Capacitor Intercom plugin is a modern integration of the Intercom SDKs for Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 💬 **Messenger**: Present the Intercom Messenger and its Home, Messages, Help Center, and Tickets spaces. - 👤 **Identity**: Log in identified or unidentified users with the modern login APIs, wired to the native result callbacks. - 🔐 **Identity Verification**: Verify the user's identity with a user hash (HMAC) or a JSON Web Token (JWT). - 🧩 **User Attributes**: Update the user's name, email, phone, custom attributes, and companies. - 📚 **Content**: Present articles, carousels, surveys, conversations, and help center collections. - 🔢 **Unread Count**: Read the unread conversation count and listen for changes. - 🔔 **Push Notifications**: Compose cleanly with Firebase Cloud Messaging (Android) and APNs (iOS). - 🌐 **Typed Web SDK**: Uses the official `@intercom/messenger-js-sdk` package on the web. - 🤝 **Compatibility**: Looking for a different chat SDK? Check out the [Crisp](https://capawesome.io/docs/sdks/capacitor/crisp/) plugin. - 📦 **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! ## Use Cases The Intercom plugin is typically used wherever you want to offer live chat and customer support inside your app, for example: - **Customer support**: Let users chat with your support team directly from within the app. - **User identification**: Attach the signed-in user's identity and attributes to every conversation. - **Self-service**: Point users to articles, surveys, and help center collections without leaving the app. - **Engagement**: Track events and show the unread conversation count in your own UI. - **Re-engagement**: Notify users about new replies via push notifications. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-intercom` 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-intercom @intercom/messenger-js-sdk npx cap sync ``` This plugin requires an [Intercom](https://www.intercom.com/) account. You can find your **App ID** and the platform-specific **API keys** in the Intercom dashboard under **Settings → Installation**. ### Android #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$intercomSdkVersion` version of `io.intercom.android:intercom-sdk-base` (default: `18.4.0`) This plugin depends on `intercom-sdk-base` (not the full `intercom-sdk` artifact) on purpose. The full artifact ships its own `FirebaseMessagingService` and automatically integrates Firebase Cloud Messaging, which conflicts with apps that manage push notifications themselves (e.g. via [`@capacitor-firebase/messaging`](https://github.com/capawesome-team/capacitor-firebase)). The base artifact leaves push handling to you (see below). #### Push Notifications Push notifications on Android are delivered through [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging). Because this plugin uses the base SDK, you forward the push token and incoming messages to Intercom from your JavaScript code. This is what makes the plugin coexist cleanly with [`@capacitor-firebase/messaging`](https://github.com/capawesome-team/capacitor-firebase): ``` import { FirebaseMessaging } from '@capacitor-firebase/messaging'; import { Intercom } from '@capawesome/capacitor-intercom'; // Forward the FCM token to Intercom. FirebaseMessaging.addListener('tokenReceived', async ({ token }) => { await Intercom.sendPushTokenToIntercom({ token }); }); // Forward incoming messages to Intercom. FirebaseMessaging.addListener('notificationReceived', async ({ notification }) => { const data = notification.data ?? {}; const { intercom } = await Intercom.isIntercomPushNotification({ data }); if (intercom) { await Intercom.handlePushNotification({ data }); } }); ``` ### iOS The Intercom iOS SDK can be integrated via Swift Package Manager (recommended) or CocoaPods. #### Info.plist To manage push notifications yourself (and coexist with other push plugins), disable Intercom's automatic push integration by adding the following key to your `ios/App/App/Info.plist` file: ``` IntercomAutoIntegratePushNotifications ``` #### Push Notifications Push notifications on iOS are delivered through [Apple Push Notification service (APNs)](https://developer.apple.com/documentation/usernotifications): 1. Enable the **Push Notifications** capability for your app target in Xcode. 1. Register for remote notifications (e.g. via [`@capacitor-firebase/messaging`](https://github.com/capawesome-team/capacitor-firebase) or `@capacitor/push-notifications`). 1. Forward the APNs device token to Intercom as a hexadecimal string: ``` import { PushNotifications } from '@capacitor/push-notifications'; import { Intercom } from '@capawesome/capacitor-intercom'; PushNotifications.addListener('registration', async ({ value }) => { // `value` is the hexadecimal APNs device token on iOS. await Intercom.sendPushTokenToIntercom({ token: value }); }); ``` You can then check and handle incoming notifications the same way as on Android using `isIntercomPushNotification(...)` and `handlePushNotification(...)`. ### Web The web implementation loads the Intercom Messenger from Intercom's CDN at runtime. This requires an active network connection. If your app enforces a [Content Security Policy (CSP)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP), make sure to allow the Intercom domains (e.g. `https://widget.intercom.io`, `https://js.intercomcdn.com`, and `wss://*.intercom.io`). > [!NOTE] On the web, the Intercom launcher is visible by default after `initialize(...)`, whereas on Android and iOS it is hidden until you present the Messenger. Use `setLauncherVisible({ visible: false })` on the web if you want to match the mobile behavior. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to use the plugin. ### Initialize the plugin Call `initialize(...)` once before all other methods: ``` import { Intercom } from '@capawesome/capacitor-intercom'; const initialize = async () => { await Intercom.initialize({ appId: 'YOUR_APP_ID', androidApiKey: 'YOUR_ANDROID_API_KEY', iosApiKey: 'YOUR_IOS_API_KEY', }); }; ``` ### Log in a user Log in an identified user, optionally with identity verification: ``` import { Intercom } from '@capawesome/capacitor-intercom'; const loginUser = async () => { await Intercom.setUserHash({ userHash: 'YOUR_HMAC_HASH' }); await Intercom.loginUser({ userId: 'jane-doe', email: 'jane.doe@example.com', }); }; ``` ### Update the user Update the attributes of the current user: ``` import { Intercom } from '@capawesome/capacitor-intercom'; const updateUser = async () => { await Intercom.updateUser({ name: 'Jane Doe', customAttributes: { plan: 'pro' }, companies: [{ id: 'capawesome', name: 'Capawesome', plan: 'enterprise' }], }); }; ``` ### Present the Messenger Present the Intercom Messenger or a specific piece of content: ``` import { Intercom } from '@capawesome/capacitor-intercom'; const present = async () => { await Intercom.present({ space: 'home' }); }; const presentArticle = async () => { await Intercom.presentContent({ type: 'article', id: '123456' }); }; ``` ### Listen for events Listen for the unread conversation count: ``` import { Intercom } from '@capawesome/capacitor-intercom'; const addListeners = async () => { await Intercom.addListener('unreadConversationCountChange', ({ count }) => { console.log('Unread conversations:', count); }); }; ``` ## API - [`getUnreadConversationCount()`](#getunreadconversationcount) - [`handlePushNotification(...)`](#handlepushnotification) - [`hide()`](#hide) - [`initialize(...)`](#initialize) - [`isIntercomPushNotification(...)`](#isintercompushnotification) - [`logEvent(...)`](#logevent) - [`loginUnidentifiedUser()`](#loginunidentifieduser) - [`loginUser(...)`](#loginuser) - [`logout()`](#logout) - [`present(...)`](#present) - [`presentContent(...)`](#presentcontent) - [`presentMessageComposer(...)`](#presentmessagecomposer) - [`sendPushTokenToIntercom(...)`](#sendpushtokentointercom) - [`setBottomPadding(...)`](#setbottompadding) - [`setInAppMessagesVisible(...)`](#setinappmessagesvisible) - [`setLauncherVisible(...)`](#setlaunchervisible) - [`setUserHash(...)`](#setuserhash) - [`setUserJwt(...)`](#setuserjwt) - [`updateUser(...)`](#updateuser) - [`addListener('messengerHidden', ...)`](#addlistenermessengerhidden-) - [`addListener('messengerShown', ...)`](#addlistenermessengershown-) - [`addListener('unreadConversationCountChange', ...)`](#addlistenerunreadconversationcountchange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getUnreadConversationCount() ``` getUnreadConversationCount() => Promise ``` Get the number of unread conversations for the current user. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### handlePushNotification(...) ``` handlePushNotification(options: HandlePushNotificationOptions) => Promise ``` Handle an incoming push notification that belongs to Intercom. Use `isIntercomPushNotification(...)` to check whether the notification belongs to Intercom before calling this method. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `HandlePushNotificationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### hide() ``` hide() => Promise ``` Hide any currently presented Intercom UI (e.g. the Messenger). **Since:** 0.1.0 ______________________________________________________________________ ### initialize(...) ``` initialize(options: InitializeOptions) => Promise ``` Initialize the Intercom SDK with your app ID and API key. This method must be called before any other method. | Param | Type | | ------------- | ------------------- | | **`options`** | `InitializeOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### isIntercomPushNotification(...) ``` isIntercomPushNotification(options: IsIntercomPushNotificationOptions) => Promise ``` Check whether an incoming push notification belongs to Intercom. Only available on Android and iOS. | Param | Type | | ------------- | ----------------------------------- | | **`options`** | `IsIntercomPushNotificationOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### logEvent(...) ``` logEvent(options: LogEventOptions) => Promise ``` Log an event with an optional set of metadata. | Param | Type | | ------------- | ----------------- | | **`options`** | `LogEventOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### loginUnidentifiedUser() ``` loginUnidentifiedUser() => Promise ``` Log in an unidentified (anonymous) user. **Since:** 0.1.0 ______________________________________________________________________ ### loginUser(...) ``` loginUser(options: LoginUserOptions) => Promise ``` Log in an identified user with a user ID and/or an email address. At least one of `userId` or `email` must be provided. | Param | Type | | ------------- | ------------------ | | **`options`** | `LoginUserOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### logout() ``` logout() => Promise ``` Log out the current user and clear the local Intercom data. **Since:** 0.1.0 ______________________________________________________________________ ### present(...) ``` present(options?: PresentOptions | undefined) => Promise ``` Present the Intercom Messenger with the given space. | Param | Type | | ------------- | ---------------- | | **`options`** | `PresentOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### presentContent(...) ``` presentContent(options: PresentContentOptions) => Promise ``` Present a specific piece of Intercom content (e.g. an article, carousel, survey, or conversation). | Param | Type | | ------------- | ----------------------- | | **`options`** | `PresentContentOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### presentMessageComposer(...) ``` presentMessageComposer(options?: PresentMessageComposerOptions | undefined) => Promise ``` Present the Intercom message composer, optionally pre-filled with an initial message. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `PresentMessageComposerOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### sendPushTokenToIntercom(...) ``` sendPushTokenToIntercom(options: SendPushTokenToIntercomOptions) => Promise ``` Forward a push notification token to Intercom. On Android, pass the Firebase Cloud Messaging (FCM) token. On iOS, pass the hexadecimal APNs device token. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SendPushTokenToIntercomOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setBottomPadding(...) ``` setBottomPadding(options: SetBottomPaddingOptions) => Promise ``` Set the bottom padding of the Intercom UI (in-app messages and launcher). Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `SetBottomPaddingOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setInAppMessagesVisible(...) ``` setInAppMessagesVisible(options: SetInAppMessagesVisibleOptions) => Promise ``` Set whether in-app messages are visible. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `SetInAppMessagesVisibleOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setLauncherVisible(...) ``` setLauncherVisible(options: SetLauncherVisibleOptions) => Promise ``` Set whether the Intercom launcher is visible. | Param | Type | | ------------- | --------------------------- | | **`options`** | `SetLauncherVisibleOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUserHash(...) ``` setUserHash(options: SetUserHashOptions) => Promise ``` Set the user hash (HMAC) for identity verification. This must be called before logging in the user. | Param | Type | | ------------- | -------------------- | | **`options`** | `SetUserHashOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setUserJwt(...) ``` setUserJwt(options: SetUserJwtOptions) => Promise ``` Set the JSON Web Token (JWT) for identity verification. This must be called before logging in the user. | Param | Type | | ------------- | ------------------- | | **`options`** | `SetUserJwtOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updateUser(...) ``` updateUser(options: UpdateUserOptions) => Promise ``` Update the attributes of the current user. | Param | Type | | ------------- | ------------------- | | **`options`** | `UpdateUserOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addListener('messengerHidden', ...) ``` addListener(eventName: 'messengerHidden', listenerFunc: () => void) => Promise ``` Called when the Intercom Messenger is hidden. Only available on iOS and Web. | Param | Type | | ------------------ | ------------------- | | **`eventName`** | `'messengerHidden'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('messengerShown', ...) ``` addListener(eventName: 'messengerShown', listenerFunc: () => void) => Promise ``` Called when the Intercom Messenger is shown. Only available on iOS and Web. | Param | Type | | ------------------ | ------------------ | | **`eventName`** | `'messengerShown'` | | **`listenerFunc`** | `() => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('unreadConversationCountChange', ...) ``` addListener(eventName: 'unreadConversationCountChange', listenerFunc: (event: UnreadConversationCountChangeEvent) => void) => Promise ``` Called when the number of unread conversations changes. | Param | Type | | ------------------ | ----------------------------------------------------- | | **`eventName`** | `'unreadConversationCountChange'` | | **`listenerFunc`** | `(event: UnreadConversationCountChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetUnreadConversationCountResult | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------- | ----- | | **`count`** | `number` | The number of unread conversations. | 0.1.0 | #### HandlePushNotificationOptions | Prop | Type | Description | Since | | ---------- | ------------------------- | ---------------------------------- | ----- | | **`data`** | `Record` | The data of the push notification. | 0.1.0 | #### InitializeOptions | Prop | Type | Description | Since | | ------------------- | -------- | -------------------------------------------------------------------------------- | ----- | | **`androidApiKey`** | `string` | The Android API key of your Intercom app. Required to use the plugin on Android. | 0.1.0 | | **`appId`** | `string` | The app ID of your Intercom app. | 0.1.0 | | **`iosApiKey`** | `string` | The iOS API key of your Intercom app. Required to use the plugin on iOS. | 0.1.0 | #### IsIntercomPushNotificationResult | Prop | Type | Description | Since | | -------------- | --------- | -------------------------------------------------- | ----- | | **`intercom`** | `boolean` | Whether the push notification belongs to Intercom. | 0.1.0 | #### IsIntercomPushNotificationOptions | Prop | Type | Description | Since | | ---------- | ------------------------- | ---------------------------------- | ----- | | **`data`** | `Record` | The data of the push notification. | 0.1.0 | #### LogEventOptions | Prop | Type | Description | Since | | ---------- | ------------------------ | ---------------------- | ---------- | | **`data`** | \`Record\\` | | **`name`** | `string` | The name of the event. | 0.1.0 | #### LoginUserOptions | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------- | ----- | | **`email`** | `string` | The email address of the user. | 0.1.0 | | **`userId`** | `string` | The unique identifier of the user. | 0.1.0 | #### PresentOptions | Prop | Type | Description | Default | Since | | ----------- | --------------- | --------------------- | -------- | ----- | | **`space`** | `IntercomSpace` | The space to present. | `'home'` | 0.1.0 | #### PresentContentOptions | Prop | Type | Description | Since | | ---------- | --------------------- | ----------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The identifier of the content to present. Required for the `article`, `carousel`, `survey`, and `conversation` content types. | 0.1.0 | | **`ids`** | `string[]` | The identifiers of the content to present. Required for the `help-center-collections` content type. | 0.1.0 | | **`type`** | `IntercomContentType` | The type of content to present. | 0.1.0 | #### PresentMessageComposerOptions | Prop | Type | Description | Since | | -------------------- | -------- | ------------------------------------------ | ----- | | **`initialMessage`** | `string` | The message to pre-fill the composer with. | 0.1.0 | #### SendPushTokenToIntercomOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`token`** | `string` | The push notification token. On Android, this is the Firebase Cloud Messaging (FCM) token. On iOS, this is the hexadecimal APNs device token. | 0.1.0 | #### SetBottomPaddingOptions | Prop | Type | Description | Since | | ------------- | -------- | -------------------------- | ----- | | **`padding`** | `number` | The bottom padding to set. | 0.1.0 | #### SetInAppMessagesVisibleOptions | Prop | Type | Description | Since | | ------------- | --------- | ------------------------------------------ | ----- | | **`visible`** | `boolean` | Whether in-app messages should be visible. | 0.1.0 | #### SetLauncherVisibleOptions | Prop | Type | Description | Since | | ------------- | --------- | --------------------------------------- | ----- | | **`visible`** | `boolean` | Whether the launcher should be visible. | 0.1.0 | #### SetUserHashOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`userHash`** | `string` | The user hash (HMAC) for identity verification. The hash must be generated on your backend using your Intercom secret key. Never expose the secret key in your app. | 0.1.0 | #### SetUserJwtOptions | Prop | Type | Description | Since | | --------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`jwt`** | `string` | The JSON Web Token (JWT) for identity verification. The token must be generated on your backend using your Intercom secret key. Never expose the secret key in your app. | 0.1.0 | #### UpdateUserOptions | Prop | Type | Description | Since | | ---------------------------- | ------------------------ | ----------------------------------------------------------- | ---------- | | **`companies`** | `UpdateUserCompany[]` | The companies the user belongs to. | 0.1.0 | | **`customAttributes`** | \`Record\\` | | **`email`** | `string` | The email address of the user. | 0.1.0 | | **`languageOverride`** | `string` | The preferred language of the user as an ISO 639-1 code. | 0.1.0 | | **`name`** | `string` | The name of the user. | 0.1.0 | | **`phone`** | `string` | The phone number of the user. | 0.1.0 | | **`signedUpAt`** | `number` | The date the user signed up as a Unix timestamp in seconds. | 0.1.0 | | **`unsubscribedFromEmails`** | `boolean` | Whether the user is unsubscribed from emails. | 0.1.0 | | **`userId`** | `string` | The unique identifier of the user. | 0.1.0 | #### UpdateUserCompany | Prop | Type | Description | Since | | ---------------------- | ------------------------ | ---------------------------------------------------------------- | ---------- | | **`createdAt`** | `number` | The date the company was created as a Unix timestamp in seconds. | 0.1.0 | | **`customAttributes`** | \`Record\\` | | **`id`** | `string` | The unique identifier of the company. | 0.1.0 | | **`monthlySpend`** | `number` | The monthly spend of the company. | 0.1.0 | | **`name`** | `string` | The name of the company. | 0.1.0 | | **`plan`** | `string` | The plan of the company. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### UnreadConversationCountChangeEvent | Prop | Type | Description | Since | | ----------- | -------- | ----------------------------------- | ----- | | **`count`** | `number` | The number of unread conversations. | 0.1.0 | ### Type Aliases #### IntercomSpace A space of the Intercom Messenger. `'home' | 'messages' | 'help-center' | 'tickets'` #### IntercomContentType A type of Intercom content that can be presented. `'article' | 'carousel' | 'conversation' | 'help-center-collections' | 'survey'` ## Migration If you are migrating from [`@capacitor-community/intercom`](https://github.com/capacitor-community/intercom) or another Intercom plugin, note that this plugin uses the **modern** Intercom API names throughout. The deprecated `register*` login methods are intentionally not exposed. The following table maps common legacy method names to their modern equivalents: | Legacy method | This plugin | | --------------------------- | ----------------------------------- | | `registerIdentifiedUser` | `loginUser` | | `registerUnidentifiedUser` | `loginUnidentifiedUser` | | `logout` | `logout` | | `displayMessenger` | `present` | | `displayMessageComposer` | `presentMessageComposer` | | `displayArticle` | `presentContent` | | `displayCarousel` | `presentContent` | | `displayHelpCenter` | `present({ space: 'help-center' })` | | `hideMessenger` | `hide` | | `unreadConversationCount` | `getUnreadConversationCount` | | `setLauncherVisibility` | `setLauncherVisible` | | `setInAppMessageVisibility` | `setInAppMessagesVisible` | | `sendPushTokenToIntercom` | `sendPushTokenToIntercom` | ## Platform Support Not every Intercom SDK feature is available on all platforms. The following table lists the notable per-platform differences of the plugin's API: | Method / Event | Android | iOS | Web | | ------------------------------------------------- | ------- | --- | --- | | `handlePushNotification(...)` | ✅ | ✅ | ❌ | | `isIntercomPushNotification(...)` | ✅ | ✅ | ❌ | | `presentContent(...)` (`carousel`) | ✅ | ✅ | ❌ | | `presentContent(...)` (`help-center-collections`) | ✅ | ✅ | ❌ | | `sendPushTokenToIntercom(...)` | ✅ | ✅ | ❌ | | `setBottomPadding(...)` | ✅ | ✅ | ❌ | | `messengerShown` / `messengerHidden` events | ❌ | ✅ | ✅ | Additional notes: - On the web, `getUnreadConversationCount(...)` returns the last value received from the change event, since the web SDK exposes the count only through a callback. - The `messengerShown` and `messengerHidden` events are not available on Android because the Intercom Android SDK does not expose a window visibility hook. - `setBottomPadding(...)` uses pixels on Android and points on iOS, matching the respective native SDK. ## FAQ ### Do I need an Intercom account to use this plugin? Yes. This plugin wraps the official Intercom SDKs, which require an active [Intercom](https://www.intercom.com/) account, an app ID, and platform-specific API keys. ### How is this plugin different from other similar plugins? This plugin uses Intercom's modern login APIs (`loginUnidentifiedUser(...)` and `loginUser(...)`). On Android it depends on the base SDK, so it coexists cleanly with your own Firebase Cloud Messaging or APNs setup and lets you forward push notifications to Intercom yourself. It also ships a fully typed web implementation, cross-platform push notification helpers, and is backed by dedicated support. ### Why does this plugin use `intercom-sdk-base` instead of `intercom-sdk` on Android? The full `intercom-sdk` artifact automatically integrates Firebase Cloud Messaging by registering its own `FirebaseMessagingService`. This conflicts with apps that manage push notifications themselves (e.g. via `@capacitor-firebase/messaging`). The base artifact avoids this conflict and lets you forward push notifications to Intercom yourself. ### How does identity verification work? Generate a user hash (HMAC) or a JSON Web Token (JWT) on your backend using your Intercom secret key, then pass it via `setUserHash(...)` or `setUserJwt(...)` **before** logging in the user. Never expose your secret key in your app. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Crisp](https://capawesome.io/docs/sdks/capacitor/crisp/): Unofficial Capacitor plugin for the Crisp live chat and customer support platform. - [Formbricks](https://capawesome.io/docs/sdks/capacitor/formbricks/): Unofficial Capacitor plugin for Formbricks to run in-app surveys. - [PostHog](https://capawesome.io/docs/sdks/capacitor/posthog/): Unofficial Capacitor plugin for the PostHog product analytics platform. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/intercom/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/intercom/LICENSE). ## Third-Party Notices The Intercom Android and iOS SDKs are licensed under the Apache License 2.0 and are distributed via Maven Central, CocoaPods, and Swift Package Manager. The web SDK is licensed under the MIT license. This plugin only declares these SDKs as dependencies and does not bundle or modify them. Using the plugin requires an active Intercom account. The MIT license of this plugin covers the wrapper code only, not the Intercom SDKs. ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Intercom Inc. or any of its affiliates or subsidiaries. "Intercom" is a trademark of Intercom Inc. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Intune Plugin Unofficial Capacitor plugin for [Microsoft Intune](https://www.microsoft.com/en-us/security/business/microsoft-intune).[1](#fn:1) ## Features The Capacitor Intune plugin integrates the Microsoft Intune App SDK for Mobile Application Management (MAM) into Capacitor apps. Here are some of the key features: - 🖥️ **Cross-platform**: Supports Android and iOS. - 🛡️ **App Protection Policies**: Automatic enforcement of PIN, copy/paste and screenshot restrictions after the native integration. - 🔐 **File Protection**: Encrypt, inspect and decrypt files through the Intune App SDK to honor the "Encrypt org data" policy on iOS, where the SDK does not encrypt files on its own. - 🔑 **MSAL**: Acquire tokens interactively or silently via the Microsoft Authentication Library. - 🧾 **Enrollment**: Register and enroll accounts in Mobile Application Management (MAM) — without device enrollment. - 🚦 **App Protection Conditional Access**: Remediate compliance when Microsoft Entra ID requires an app protection policy before issuing tokens. - 📋 **Typed Policy Introspection**: Read the applied app protection policy as typed booleans to adapt your UI. - ⚙️ **App Configuration**: Read the application configuration deployed via the MAM channel, including conflict information. - 🧹 **Selective Wipe Events**: Get notified when the Intune service requests a wipe so you can purge the web layer storage (e.g. IndexedDB, Local Storage) that the SDK cannot wipe itself. - 🩺 **Diagnostics**: Show the Intune diagnostic console from JavaScript. - 📌 **Current SDK Pins**: Built against current Microsoft Intune App SDK and MSAL versions — Microsoft blocks apps that ship outdated SDKs. - 🤝 **Compatibility**: Works alongside the [Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/) plugin, which covers the MDM channel (see [Choosing between the MAM and MDM channel](#choosing-between-the-mam-and-mdm-channel)). - 📦 **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! ## Use Cases The Intune plugin is typically used in line-of-business apps that are distributed to employees of organizations that manage corporate data with Microsoft Intune, for example: - **App protection without device enrollment**: Protect corporate data in your app on personal (BYOD) devices via Mobile Application Management (MAM). - **Conditional access**: Combine with Microsoft Entra conditional access policies that require an Intune-protected app (see [Handle App Protection Conditional Access](#handle-app-protection-conditional-access)). - **Policy-aware UI**: Read the applied app protection policy and hide or disable features (e.g. local export) that the policy does not allow. - **Encrypt organization data**: Protect recordings, downloads and exports on iOS when the app protection policy requires file encryption. - **Per-tenant configuration**: Read the application configuration that the organization's IT administrator has deployed for the signed-in account. - **Selective wipe**: Clean up the web layer storage of your app when the organization wipes its corporate data. - **Ionic enterprise migration**: Migrate from the discontinued Ionic enterprise Intune integration to a maintained, free plugin. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-intune` 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-intune npx cap sync ``` This plugin requires a [Microsoft Intune](https://www.microsoft.com/en-us/security/business/microsoft-intune) tenant with Intune licenses and an app registration in [Microsoft Entra ID](https://entra.microsoft.com/). Create the app registration in the Microsoft Entra admin center, note its **Application (client) ID** and configure the platform-specific redirect URIs (see below). The `acquireToken(...)` scopes you request must be exposed or granted on this app registration. > [!IMPORTANT] The Intune App SDKs are developed and licensed by Microsoft (see [Third-Party Notices](#third-party-notices)). This plugin declares them as dependencies and downloads them from Microsoft's official repositories at build or install time. It does not bundle or modify them. ### Android The Microsoft Intune App SDK for Android is downloaded automatically from [Microsoft's official GitHub repository](https://github.com/microsoftconnect/ms-intune-app-sdk-android) during the Gradle build. However, the Intune App SDK requires several changes to your app project that no plugin can make for you. Follow the steps below or use [Trapeze](#automated-setup-with-trapeze) to automate them. #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$intuneMamSdkVersion` version of the [Microsoft Intune App SDK for Android](https://github.com/microsoftconnect/ms-intune-app-sdk-android) (default: `12.4.0`) - `$msalVersion` version of `com.microsoft.identity.client:msal` (default: `8.4.0`) #### MAM Build Plugin The Intune App SDK relies on a Gradle build plugin that rewrites the Android base classes of your app **and all Capacitor plugins** to their MAM equivalents. Only the app module can apply this plugin — it cannot be applied by a library. Add the following to the `buildscript` block of your `android/build.gradle` file: ``` buildscript { repositories { google() mavenCentral() + ivy { + url 'https://raw.githubusercontent.com/microsoftconnect/ms-intune-app-sdk-android' + patternLayout { artifact '[revision]/GradlePlugin/[artifact].[ext]' } + metadataSources { artifact() } + content { includeGroup 'com.microsoft.intune.mam.build' } + } } dependencies { classpath 'com.android.tools.build:gradle:8.13.0' + classpath 'org.javassist:javassist:3.29.2-GA' + classpath 'com.microsoft.intune.mam.build:com.microsoft.intune.mam.build:12.4.0@jar' } } ``` Then apply the plugin in your `android/app/build.gradle` file: ``` apply plugin: 'com.android.application' +apply plugin: 'com.microsoft.intune.mam' ``` #### Application Class The Intune App SDK requires an `Application` class that registers the MAM components before any other code runs. Set the ready-made class provided by this plugin in the `application` tag of your `android/app/src/main/AndroidManifest.xml` file: ``` ``` If your app already uses a custom `Application` class, call the initializer yourself instead: ``` import io.capawesome.capacitorjs.plugins.intune.Intune; public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); Intune.initialize(this); } } ``` It's also recommended to disable predictive back gestures in the `application` tag since the Intune App SDK does not support them yet: ``` ``` #### MSAL Configuration Create the file `android/app/src/main/res/raw/auth_config.json` with your MSAL configuration: ``` { "client_id": "YOUR_CLIENT_ID", "authorization_user_agent": "DEFAULT", "redirect_uri": "msauth://YOUR_PACKAGE_NAME/YOUR_BASE64_URL_ENCODED_PACKAGE_SIGNATURE", "account_mode": "MULTIPLE", "broker_redirect_uri_registered": true, "client_capabilities": "protapp", "authorities": [ { "type": "AAD", "audience": { "type": "AzureADMultipleOrgs" } } ] } ``` The `client_capabilities` entry declares that your app supports [App Protection Conditional Access](#handle-app-protection-conditional-access). On iOS, the plugin declares it automatically. You can generate the base64-encoded signature hash of your signing key with: ``` keytool -exportcert -alias YOUR_KEY_ALIAS -keystore YOUR_KEYSTORE | openssl sha1 -binary | openssl base64 ``` Make sure the same redirect URI (`msauth://YOUR_PACKAGE_NAME/YOUR_BASE64_URL_ENCODED_PACKAGE_SIGNATURE`) is registered as an **Android** platform redirect URI on your app registration in the Microsoft Entra admin center. Next, add the following activity to the `application` tag of your `android/app/src/main/AndroidManifest.xml` file so that MSAL can receive the redirect from the Microsoft sign-in flow: ``` ``` #### Gradle Properties Add the following to your `android/gradle.properties` file. Without it, release builds may report `MAM Enabled: No` in the diagnostic console: ``` android.enableResourceOptimizations=false ``` #### Company Portal App protection policies are only applied when the [Company Portal](https://play.google.com/store/apps/details?id=com.microsoft.windowsintune.companyportal) app is installed on the device. The user does **not** need to sign in to the Company Portal. Without it, your app behaves as unmanaged. ### iOS The Microsoft Intune App SDK for iOS requires **iOS 17+** as deployment target and a current Xcode version. This deviates from the usual iOS 15 minimum of the Capawesome plugin collection. Make sure the deployment target of your app is set to iOS 17.0 or later (in `ios/App/App.xcodeproj` and, if you use Swift Package Manager, in `ios/App/CapApp-SPM/Package.swift`). The SDK is consumed from [Microsoft's official GitHub repository](https://github.com/msintuneappsdk/ms-intune-app-sdk-ios): via Swift Package Manager it is resolved like any other package; via CocoaPods it is downloaded automatically during `pod install` (Microsoft does not publish a CocoaPods pod). #### Info.plist Add the `IntuneMAMSettings` dictionary to your `ios/App/App/Info.plist` file (the keys keep their legacy `ADAL` names for historical reasons): ``` IntuneMAMSettings ADALAuthority https://login.microsoftonline.com/YOUR_TENANT_ID ADALClientId YOUR_CLIENT_ID ADALRedirectUri msauth.YOUR_BUNDLE_ID://auth ``` This plugin also uses these values to configure MSAL, so no separate MSAL configuration is needed. Next, register the MSAL redirect URL scheme and the query schemes in the same file: ``` CFBundleURLTypes CFBundleURLName MSAL CFBundleURLSchemes msauth.YOUR_BUNDLE_ID LSApplicationQueriesSchemes msauthv2 msauthv3 http-intunemam https-intunemam ``` Make sure the redirect URI (`msauth.YOUR_BUNDLE_ID://auth`) is registered as an **iOS/macOS** platform redirect URI on your app registration in the Microsoft Entra admin center. The plugin handles the MSAL redirect automatically — no `AppDelegate` changes are required. Finally, add a Face ID usage description if your app does not have one yet, since app protection policies may require biometric unlock: ``` NSFaceIDUsageDescription This app uses Face ID to secure corporate data. ``` #### Keychain Sharing Enable the **Keychain Sharing** capability for your app target in Xcode and add the following keychain groups (in this order): 1. `com.example.app` (your bundle ID, usually already present) 1. `com.microsoft.intune.mam` 1. `com.microsoft.adalcache` #### IntuneMAMConfigurator Microsoft ships the `IntuneMAMConfigurator` tool with the [SDK repository](https://github.com/msintuneappsdk/ms-intune-app-sdk-ios). It applies the minimum required `Info.plist` changes for Intune management (including the `-intunemam` query scheme variants for every scheme your app queries) and is idempotent: ``` IntuneMAMConfigurator -i ios/App/App/Info.plist -e ios/App/App/App.entitlements ``` Running it is recommended before you ship, especially if your app passes additional URL schemes to `canOpenURL`. ### Automated Setup with Trapeze Most of the host app changes above can be automated with [Trapeze](https://trapeze.dev/), which is also used by [Capawesome Cloud](https://capawesome.io/cloud/). Save the following configuration as `trapeze.yaml` and run `npx @trapezedev/configure run trapeze.yaml` with the `CLIENT_ID`, `TENANT_ID`, `PACKAGE_NAME`, `BUNDLE_ID` and `SIGNATURE_HASH` variables set: ``` vars: CLIENT_ID: TENANT_ID: PACKAGE_NAME: BUNDLE_ID: SIGNATURE_HASH: platforms: android: gradle: - file: build.gradle target: buildscript: dependencies: insert: | classpath 'org.javassist:javassist:3.29.2-GA' classpath 'com.microsoft.intune.mam.build:com.microsoft.intune.mam.build:12.4.0@jar' - file: build.gradle target: buildscript: repositories: insert: | ivy { url 'https://raw.githubusercontent.com/microsoftconnect/ms-intune-app-sdk-android' patternLayout { artifact '[revision]/GradlePlugin/[artifact].[ext]' } metadataSources { artifact() } content { includeGroup 'com.microsoft.intune.mam.build' } } - file: app/build.gradle target: insert: | apply plugin: 'com.microsoft.intune.mam' manifest: - file: AndroidManifest.xml target: manifest/application attrs: android:name: io.capawesome.capacitorjs.plugins.intune.IntuneApplication android:enableOnBackInvokedCallback: 'false' - file: AndroidManifest.xml target: manifest/application inject: | res: - path: raw file: auth_config.json text: | { "client_id": "$CLIENT_ID", "authorization_user_agent": "DEFAULT", "redirect_uri": "msauth://$PACKAGE_NAME/$SIGNATURE_HASH", "account_mode": "MULTIPLE", "broker_redirect_uri_registered": true, "client_capabilities": "protapp", "authorities": [ { "type": "AAD", "audience": { "type": "AzureADMultipleOrgs" } } ] } ios: targets: App: buildSettings: IPHONEOS_DEPLOYMENT_TARGET: '17.0' entitlements: - keychain-access-groups: [ '$BUNDLE_ID', 'com.microsoft.intune.mam', 'com.microsoft.adalcache', ] plist: - entries: - IntuneMAMSettings: ADALAuthority: https://login.microsoftonline.com/$TENANT_ID ADALClientId: $CLIENT_ID ADALRedirectUri: msauth.$BUNDLE_ID://auth - CFBundleURLTypes: - CFBundleURLName: MSAL CFBundleURLSchemes: - msauth.$BUNDLE_ID - LSApplicationQueriesSchemes: - msauthv2 - msauthv3 - http-intunemam - https-intunemam - NSFaceIDUsageDescription: This app uses Face ID to secure corporate data. ``` > [!NOTE] Trapeze cannot edit `gradle.properties`, so the [Gradle Properties](#gradle-properties) step must still be done manually. Review the result after running Trapeze: some list-valued Info.plist entries may be merged rather than replaced, and the Gradle insertions are not idempotent — run them only once. ### Web This plugin does not provide a web implementation. All methods reject with an `unimplemented` error on the web platform. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to use the plugin. ### Sign in and enroll an account Acquire a token via MSAL and enroll the returned account in Mobile Application Management (MAM). The enrollment itself is asynchronous — listen for the `enrollmentChange` event to get the result: ``` import { Intune } from '@capawesome/capacitor-intune'; const signInAndEnroll = async () => { await Intune.addListener('enrollmentChange', event => { console.log('Enrollment status:', event.status); }); const { accountId } = await Intune.acquireToken({ scopes: ['https://graph.microsoft.com/.default'], }); await Intune.registerAndEnrollAccount({ accountId }); }; ``` ### Handle App Protection Conditional Access If your organization requires an app protection policy via Conditional Access, Microsoft Entra ID only issues tokens once Intune manages the app. In this case, the token acquisition is rejected with the `PROTECTION_POLICY_REQUIRED` error code. Remediate the compliance and retry the token acquisition: ``` import { ErrorCode, Intune } from '@capawesome/capacitor-intune'; const acquireToken = async (scopes: string[]) => { try { return await Intune.acquireToken({ scopes }); } catch (error) { if (error.code !== ErrorCode.ProtectionPolicyRequired) { throw error; } const { status } = await Intune.remediateCompliance(error.data); if (status !== 'compliant') { throw error; } return Intune.acquireTokenSilent({ accountId: error.data.accountId, scopes }); } }; ``` ### Read the app protection policy Adapt your UI to the applied app protection policy: ``` import { Intune } from '@capawesome/capacitor-intune'; const applyPolicy = async () => { const { account } = await Intune.getEnrolledAccount(); if (!account) { return; } const policy = await Intune.getPolicy({ accountId: account.accountId }); if (!policy.saveToPersonalStorageAllowed) { // Hide your export/download buttons. } }; ``` ### Protect files On iOS, the Intune App SDK does not encrypt files on its own. Protect every file that contains organization data, and decrypt it before handing it to other plugins or native components: ``` import { Intune } from '@capawesome/capacitor-intune'; const protectFile = async (path: string) => { const { account } = await Intune.getEnrolledAccount(); if (!account) { return; } await Intune.protectFile({ path, accountId: account.accountId }); }; const decryptFile = async (path: string, destination: string) => { const { encrypted } = await Intune.isFileEncrypted({ path }); if (encrypted) { await Intune.decryptFile({ path, destination }); } }; ``` ### Read the app configuration Read the configuration values that the organization's IT administrator has deployed: ``` import { Intune } from '@capawesome/capacitor-intune'; const readAppConfig = async () => { const { account } = await Intune.getEnrolledAccount(); if (!account) { return; } const { values } = await Intune.getAppConfig({ accountId: account.accountId }); console.log('Server URL:', values['com.example.serverUrl']); }; ``` ### Handle selective wipe The Intune App SDK wipes the data it manages, but it does **not** wipe the web layer storage of your Capacitor app. Register the `wipeRequested` listener as early as possible and purge your web storage when it fires: ``` import { Intune } from '@capawesome/capacitor-intune'; const registerWipeListener = async () => { await Intune.addListener('wipeRequested', async () => { localStorage.clear(); sessionStorage.clear(); const databases = await indexedDB.databases(); for (const database of databases) { if (database.name) { indexedDB.deleteDatabase(database.name); } } }); }; ``` ## API - [`acquireToken(...)`](#acquiretoken) - [`acquireTokenSilent(...)`](#acquiretokensilent) - [`decryptFile(...)`](#decryptfile) - [`getAppConfig(...)`](#getappconfig) - [`getEnrolledAccount()`](#getenrolledaccount) - [`getPolicy(...)`](#getpolicy) - [`getSdkVersion()`](#getsdkversion) - [`isFileEncrypted(...)`](#isfileencrypted) - [`loginAndEnrollAccount()`](#loginandenrollaccount) - [`protectFile(...)`](#protectfile) - [`registerAndEnrollAccount(...)`](#registerandenrollaccount) - [`remediateCompliance(...)`](#remediatecompliance) - [`showDiagnosticConsole()`](#showdiagnosticconsole) - [`unenrollAccount(...)`](#unenrollaccount) - [`addListener('appConfigChange', ...)`](#addlistenerappconfigchange-) - [`addListener('enrollmentChange', ...)`](#addlistenerenrollmentchange-) - [`addListener('policyChange', ...)`](#addlistenerpolicychange-) - [`addListener('wipeRequested', ...)`](#addlistenerwiperequested-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### acquireToken(...) ``` acquireToken(options: AcquireTokenOptions) => Promise ``` Acquire an access token interactively via the Microsoft Authentication Library (MSAL). This presents the Microsoft sign-in UI if necessary. Use the returned `accountId` to enroll the account via `registerAndEnrollAccount(...)`. If the tenant requires an app protection policy, the call is rejected with the `PROTECTION_POLICY_REQUIRED` error code. Use `remediateCompliance(...)` in this case. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `AcquireTokenOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### acquireTokenSilent(...) ``` acquireTokenSilent(options: AcquireTokenSilentOptions) => Promise ``` Acquire an access token silently via the Microsoft Authentication Library (MSAL) for an already signed-in account. If the tenant requires an app protection policy, the call is rejected with the `PROTECTION_POLICY_REQUIRED` error code. Use `remediateCompliance(...)` in this case. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------- | | **`options`** | `AcquireTokenSilentOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### decryptFile(...) ``` decryptFile(options: DecryptFileOptions) => Promise ``` Decrypt a file that was encrypted by the Intune App SDK. On iOS, encrypted files can only be read through the Intune App SDK. Call this method before handing a file to other consumers such as the Filesystem plugin, a media player or an uploader. On Android, the app reads encrypted files transparently, so this method is rarely needed. The file is tagged with the unmanaged identity, which removes the encryption and takes the file out of the scope of a selective wipe. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `DecryptFileOptions` | **Since:** 0.1.1 ______________________________________________________________________ ### getAppConfig(...) ``` getAppConfig(options: GetAppConfigOptions) => Promise ``` Get the application configuration values that the organization's IT administrator has deployed for the given account via the MAM channel. For configuration deployed via the MDM channel (device enrollment), use the Managed Configurations plugin instead. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `GetAppConfigOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getEnrolledAccount() ``` getEnrolledAccount() => Promise ``` Get the account that is currently enrolled in Mobile Application Management (MAM). Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getPolicy(...) ``` getPolicy(options: GetPolicyOptions) => Promise ``` Get the app protection policy that is currently applied for the given account. Only available on Android and iOS. | Param | Type | | ------------- | ------------------ | | **`options`** | `GetPolicyOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getSdkVersion() ``` getSdkVersion() => Promise ``` Get the versions of the Intune App SDK and the Microsoft Authentication Library (MSAL) that the plugin was built with. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isFileEncrypted(...) ``` isFileEncrypted(options: IsFileEncryptedOptions) => Promise ``` Check whether a file is encrypted by the Intune App SDK. On Android, this reflects whether the file is tagged with a managed identity whose app protection policy uses file encryption, since the Intune App SDK for Android does not expose the encryption state of a single file. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `IsFileEncryptedOptions` | **Returns:** `Promise` **Since:** 0.1.1 ______________________________________________________________________ ### loginAndEnrollAccount() ``` loginAndEnrollAccount() => Promise ``` Sign in and enroll an account using the login UI provided by the Intune App SDK. On Android, use `acquireToken(...)` followed by `registerAndEnrollAccount(...)` instead. Only available on iOS. **Since:** 0.1.0 ______________________________________________________________________ ### protectFile(...) ``` protectFile(options: ProtectFileOptions) => Promise ``` Protect a file or directory for the given account. On iOS, the Intune App SDK does not encrypt files on its own. Call this method for every file that contains organization data if the app protection policy requires file encryption (see `GetPolicyResult.fileEncryptionRequired`). The file is encrypted in place if the policy requires it. For a directory, all files it currently contains are protected; files added later must be protected separately. Encrypted files can only be read through the Intune App SDK, so use `decryptFile(...)` before reading them with other plugins. On Android, the Intune App SDK encrypts files automatically and the app reads them transparently. This method tags the file or directory with the account so that it is in the scope of a selective wipe. Files added to a protected directory later inherit the protection. Only available on Android and iOS. | Param | Type | | ------------- | -------------------- | | **`options`** | `ProtectFileOptions` | **Since:** 0.1.1 ______________________________________________________________________ ### registerAndEnrollAccount(...) ``` registerAndEnrollAccount(options: RegisterAndEnrollAccountOptions) => Promise ``` Register an account for Mobile Application Management (MAM) and enroll it in the Intune service. Call this after a successful `acquireToken(...)` call. The enrollment itself is asynchronous; listen for the `enrollmentChange` event to get the enrollment result. Only available on Android and iOS. | Param | Type | | ------------- | --------------------------------- | | **`options`** | `RegisterAndEnrollAccountOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### remediateCompliance(...) ``` remediateCompliance(options: RemediateComplianceOptions) => Promise ``` Bring the app into compliance with the app protection policy of an account so that Microsoft Entra ID grants tokens for it. Call this when `acquireToken(...)` or `acquireTokenSilent(...)` is rejected with the `PROTECTION_POLICY_REQUIRED` error code, passing the `data` of the error as options. The Intune App SDK registers and enrolls the account as needed. If the returned status is `compliant`, retry the token acquisition. On iOS, the Intune App SDK may restart the app during the remediation if the account was not enrolled before. In that case, the promise is never settled and the app should retry the sign-in after the restart. Only available on Android and iOS. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `RemediateComplianceOptions` | **Returns:** `Promise` **Since:** 0.1.2 ______________________________________________________________________ ### showDiagnosticConsole() ``` showDiagnosticConsole() => Promise ``` Show the diagnostic console of the Intune App SDK. The console allows the user to inspect the SDK state and collect logs for support requests. Only available on Android and iOS. **Since:** 0.1.0 ______________________________________________________________________ ### unenrollAccount(...) ``` unenrollAccount(options: UnenrollAccountOptions) => Promise ``` Unenroll an account from Mobile Application Management (MAM) and unregister it from the Intune service. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------ | | **`options`** | `UnenrollAccountOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addListener('appConfigChange', ...) ``` addListener(eventName: 'appConfigChange', listenerFunc: (event: AppConfigChangeEvent) => void) => Promise ``` Called when the application configuration changes. Use `getAppConfig(...)` to read the new configuration values. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'appConfigChange'` | | **`listenerFunc`** | `(event: AppConfigChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('enrollmentChange', ...) ``` addListener(eventName: 'enrollmentChange', listenerFunc: (event: EnrollmentChangeEvent) => void) => Promise ``` Called when the enrollment state of an account changes, for example when an enrollment attempt succeeds or fails. Only available on Android and iOS. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'enrollmentChange'` | | **`listenerFunc`** | `(event: EnrollmentChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('policyChange', ...) ``` addListener(eventName: 'policyChange', listenerFunc: (event: PolicyChangeEvent) => void) => Promise ``` Called when the app protection policy changes. Use `getPolicy(...)` to read the new policy values. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------ | | **`eventName`** | `'policyChange'` | | **`listenerFunc`** | `(event: PolicyChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('wipeRequested', ...) ``` addListener(eventName: 'wipeRequested', listenerFunc: (event: WipeRequestedEvent) => void) => Promise ``` Called when the Intune service requests a selective wipe of the account's data. The Intune App SDK wipes the data it manages, but it does **not** wipe the web layer storage of your Capacitor app (e.g. IndexedDB, Local Storage). Use this event to clean up any data your web code has persisted. The event is delivered even if the wipe was requested while your app was not running (see the documentation for details). Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'wipeRequested'` | | **`listenerFunc`** | `(event: WipeRequestedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### AcquireTokenResult | Prop | Type | Description | Since | | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------- | | **`accessToken`** | `string` | The acquired access token. | 0.1.0 | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the signed-in account. Use this identifier for all other methods of this plugin. | 0.1.0 | | **`idToken`** | \`string | null\` | The raw ID token of the signed-in account, if available. | | **`tenantId`** | \`string | null\` | The Microsoft Entra tenant ID of the signed-in account, if available. | | **`username`** | \`string | null\` | The username (usually the UPN) of the signed-in account, if available. | #### AcquireTokenOptions | Prop | Type | Description | Default | Since | | ----------------- | ---------- | ------------------------------------------------------------------------------------------------------ | ------- | ----- | | **`forcePrompt`** | `boolean` | Whether or not to force the account selection prompt to be shown, even if a user is already signed in. | `false` | 0.1.0 | | **`loginHint`** | `string` | The username to pre-fill in the sign-in UI. | | 0.1.0 | | **`scopes`** | `string[]` | The scopes to request the access token for. | | 0.1.0 | #### AcquireTokenSilentOptions | Prop | Type | Description | Default | Since | | ------------------ | ---------- | ---------------------------------------------------------------------------- | ------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to acquire the token for. | | 0.1.0 | | **`forceRefresh`** | `boolean` | Whether or not to ignore any cached token and force a token refresh. | `false` | 0.1.0 | | **`scopes`** | `string[]` | The scopes to request the access token for. | | 0.1.0 | #### DecryptFileOptions | Prop | Type | Description | Since | | ----------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`destination`** | `string` | The absolute path or `file://` URI to write the decrypted copy to. If not provided, the file is decrypted in place. An existing file at the destination is overwritten. | 0.1.1 | | **`path`** | `string` | The absolute path or `file://` URI of the encrypted file. | 0.1.1 | #### GetAppConfigResult | Prop | Type | Description | Since | | --------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`conflicts`** | `AppConfigConflict[]` | The configuration keys for which multiple conflicting values have been deployed. | 0.1.0 | | **`values`** | `Record` | The merged application configuration values. For keys with conflicting values, the value that the Intune App SDK returns first is used. | 0.1.0 | #### AppConfigConflict | Prop | Type | Description | Since | | ------------ | ---------- | ------------------------------------------------------------- | ----- | | **`key`** | `string` | The configuration key for which conflicting values exist. | 0.1.0 | | **`values`** | `string[]` | All values that have been deployed for the configuration key. | 0.1.0 | #### GetAppConfigOptions | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------------------------------------------------------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to get the application configuration for. | 0.1.0 | #### GetEnrolledAccountResult | Prop | Type | Description | Since | | ------------- | ----------------- | ----------- | --------------------------------------------------------- | | **`account`** | \`EnrolledAccount | null\` | The enrolled account or `null` if no account is enrolled. | #### EnrolledAccount | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------------------------------ | --------------------------------------------------------------------- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the enrolled account. | 0.1.0 | | **`username`** | \`string | null\` | The username (usually the UPN) of the enrolled account, if available. | #### GetPolicyResult | Prop | Type | Description | Since | | ---------------------------------- | --------- | -------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`contactSyncAllowed`** | `boolean` | Whether or not the policy allows syncing contacts to the device. | 0.1.0 | | **`fileEncryptionRequired`** | `boolean` | Whether or not the policy requires files to be encrypted. On Android, this reflects whether file encryption is currently in use. | 0.1.0 | | **`managedBrowserRequired`** | `boolean` | Whether or not the policy requires links to be opened in a managed browser (e.g. Microsoft Edge). | 0.1.0 | | **`pinRequired`** | `boolean` | Whether or not the policy requires a PIN to access the app. | 0.1.0 | | **`saveToPersonalStorageAllowed`** | `boolean` | Whether or not the policy allows saving files to personal (local) storage. | 0.1.0 | | **`screenCaptureAllowed`** | `boolean` | Whether or not the policy allows taking screenshots. | 0.1.0 | #### GetPolicyOptions | Prop | Type | Description | Since | | --------------- | -------- | ---------------------------------------------------------------------------------------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to get the app protection policy for. | 0.1.0 | #### GetSdkVersionResult | Prop | Type | Description | Since | | ---------------------- | -------- | ---------------------------------- | ------------------------------------------------------------------------- | | **`intuneSdkVersion`** | `string` | The version of the Intune App SDK. | 0.1.0 | | **`msalVersion`** | \`string | null\` | The version of the Microsoft Authentication Library (MSAL), if available. | #### IsFileEncryptedResult | Prop | Type | Description | Since | | --------------- | --------- | ----------------------------------------------------------- | ----- | | **`encrypted`** | `boolean` | Whether or not the file is encrypted by the Intune App SDK. | 0.1.1 | #### IsFileEncryptedOptions | Prop | Type | Description | Since | | ---------- | -------- | -------------------------------------------------------- | ----- | | **`path`** | `string` | The absolute path or `file://` URI of the file to check. | 0.1.1 | #### ProtectFileOptions | Prop | Type | Description | Since | | --------------- | -------- | ----------------------------------------------------------------------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account that owns the file. | 0.1.1 | | **`path`** | `string` | The absolute path or `file://` URI of the file or directory to protect. | 0.1.1 | #### RegisterAndEnrollAccountOptions | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------------------------------------------------------------------------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to register and enroll, as returned by `acquireToken(...)`. | 0.1.0 | #### RemediateComplianceResult | Prop | Type | Description | Since | | ------------------ | ------------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | **`errorMessage`** | \`string | null\` | A localized error message that can be displayed to the user if the account is not compliant, if available. | | **`errorTitle`** | \`string | null\` | A localized error title that can be displayed to the user if the account is not compliant, if available. | | **`status`** | `ComplianceStatus` | The compliance status of the account after the remediation. | 0.1.2 | #### RemediateComplianceOptions | Prop | Type | Description | Default | Since | | --------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to remediate. | | 0.1.2 | | **`authority`** | `string` | The authority URL of the account. Only available on Android. Required on Android. | | 0.1.2 | | **`silent`** | `boolean` | Whether or not to remediate without showing any UI of the Intune App SDK. On iOS, the status `interactionRequired` is returned if the remediation cannot be completed without user interaction. | `false` | 0.1.2 | | **`tenantId`** | `string` | The Microsoft Entra tenant ID of the account. Only available on Android. Required on Android. | | 0.1.2 | | **`username`** | `string` | The username (usually the UPN) of the account. Only available on Android. Required on Android. | | 0.1.2 | #### UnenrollAccountOptions | Prop | Type | Description | Default | Since | | --------------- | --------- | ----------------------------------------------------------------------------------------------------- | ------- | ----- | | **`accountId`** | `string` | The Microsoft Entra object ID (OID) of the account to unenroll. | | 0.1.0 | | **`wipe`** | `boolean` | Whether or not the account's data should be wiped as part of the unenrollment. Only available on iOS. | `false` | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### AppConfigChangeEvent | Prop | Type | Description | Since | | --------------- | -------- | ----------- | -------------------------------------------------------------------------- | | **`accountId`** | \`string | null\` | The Microsoft Entra object ID (OID) of the affected account, if available. | #### EnrollmentChangeEvent | Prop | Type | Description | Since | | --------------- | ------------------ | ----------------------------------------- | -------------------------------------------------------------------------- | | **`accountId`** | \`string | null\` | The Microsoft Entra object ID (OID) of the affected account, if available. | | **`status`** | `EnrollmentStatus` | The new enrollment status of the account. | 0.1.0 | #### PolicyChangeEvent | Prop | Type | Description | Since | | --------------- | -------- | ----------- | -------------------------------------------------------------------------- | | **`accountId`** | \`string | null\` | The Microsoft Entra object ID (OID) of the affected account, if available. | #### WipeRequestedEvent | Prop | Type | Description | Since | | --------------- | -------- | ----------- | -------------------------------------------------------------------------- | | **`accountId`** | \`string | null\` | The Microsoft Entra object ID (OID) of the affected account, if available. | ### Type Aliases #### ComplianceStatus The compliance status of an account. - `canceled`: The user canceled the remediation. Only available on iOS. - `clientError`: The remediation failed due to a client issue, such as a missing or invalid token. Only available on Android. - `companyPortalRequired`: The Company Portal app must be installed. If it is already installed, the app must be restarted. Only available on Android. - `compliant`: The account is compliant. Retry the token acquisition. - `interactionRequired`: The remediation requires user interaction. Call `remediateCompliance(...)` again with `silent` set to `false`. Only available on iOS. - `networkFailure`: The Intune service could not be reached. Retry when the network connection is restored. - `notCompliant`: The account is not compliant. - `pending`: The Intune service did not respond in time. Retry later. Only available on Android. - `serviceFailure`: The compliance data could not be retrieved from the Intune service. Retry later. - `unknown`: The status is unknown. Only available on Android. `'canceled' | 'clientError' | 'companyPortalRequired' | 'compliant' | 'interactionRequired' | 'networkFailure' | 'notCompliant' | 'pending' | 'serviceFailure' | 'unknown'` #### EnrollmentStatus The enrollment status of an account. `'enrolled' | 'failed' | 'pending' | 'unenrolled'` ## Platform Support Not every feature is available on all platforms. The following table lists the notable per-platform differences of the plugin's API: | Method / Option | Android | iOS | Web | | -------------------------------------- | ------- | --- | --- | | `loginAndEnrollAccount()` | ❌ | ✅ | ❌ | | `unenrollAccount(...)` (`wipe` option) | ❌ | ✅ | ❌ | Additional notes: - On Android, use `acquireToken(...)` followed by `registerAndEnrollAccount(...)` instead of `loginAndEnrollAccount()`. The Intune App SDK for Android does not provide its own login UI. - On Android, `remediateCompliance(...)` requires the `authority`, `tenantId` and `username` options. All of them are included in the `data` of the `PROTECTION_POLICY_REQUIRED` error. - On Android, `fileEncryptionRequired` reflects whether file encryption is currently **in use** by the Intune App SDK, which is the closest equivalent the SDK exposes. - The `wipeRequested` event is persisted and replayed on the next app launch if no listener was registered when the wipe arrived. In rare cases the event may be delivered more than once, so make sure your wipe handler is idempotent. - On iOS, the Intune App SDK does not encrypt files on its own. Call `protectFile(...)` for every file that contains organization data. On Android, file encryption is automatic and `protectFile(...)` only tags the file with the account so that it is in the scope of a selective wipe. - On iOS, encrypted files can only be read through the Intune App SDK. Reading them with the Filesystem plugin or handing them to other native plugins (e.g. media players or uploaders) yields the encrypted content. Call `decryptFile(...)` first. On Android, the app reads encrypted files transparently, so `decryptFile(...)` is rarely needed. - On Android, `isFileEncrypted(...)` reflects whether the file is tagged with a managed identity whose policy uses file encryption, since the Intune App SDK does not expose the encryption state of a single file. - Policy **enforcement** (PIN, copy/paste and screenshot restrictions, etc.) is performed automatically by the Intune App SDK once the native integration is in place. The JavaScript API exists for the parts that enforcement cannot do: enrolling accounts, reading configuration, adapting your UI to the policy, protecting files on iOS, and cleaning up web storage on selective wipe. ## Choosing between the MAM and MDM channel Organizations can deliver app configuration through two different channels, and administrators frequently mix them up. This plugin covers the **MAM channel**; the [Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/) plugin covers the **MDM channel**: | | MAM channel (this plugin) | MDM channel ([Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/)) | | -------------------------- | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------- | | Device enrollment required | No | Yes | | Delivered via | Intune App SDK (Intune service) | `RestrictionsManager` / `com.apple.configuration.managed` | | Targeted at | The signed-in account (identity) | The device | | EMM vendor | Microsoft Intune only | Any EMM/MDM vendor | | Typical scenario | BYOD / app protection without device management | Corporate-owned, fully managed devices | If your organization deploys the app configuration policy with **"Managed apps"** as the delivery channel in the Intune admin center, use this plugin. If it is deployed with **"Managed devices"**, use the Managed Configurations plugin. Apps that must support both scenarios should read both channels. ## Testing Exercising Mobile Application Management (MAM) requires infrastructure that cannot be simulated locally: - A **Microsoft Intune tenant** with **Intune licenses** and a **licensed test user**. - An **app protection policy** and (optionally) an **app configuration policy** targeted at the test user and your app. - On Android, the **Company Portal** app installed on the test device. Without a tenant, the plugin compiles and loads, but enrollment fails with `AccountNotLicensed`-style errors and no policy is applied. Building this plugin (e.g. via `npm run verify`) only proves that the code compiles — it does not exercise any MAM functionality. ## FAQ ### How is this plugin different from other similar plugins? It integrates the Microsoft Intune App SDK for Mobile Application Management on Android and iOS through a fully typed API, enforcing app protection policies and exposing them as typed booleans so you can adapt your UI, acquiring tokens via MSAL, and enrolling accounts without device enrollment. It emits selective-wipe events so you can purge the web-layer storage (such as IndexedDB and Local Storage) that the SDK cannot reach itself, and it is built against current Intune and MSAL versions, which matters because Microsoft blocks apps that ship outdated SDKs. It is a free, actively maintained plugin that stays current with the latest Capacitor version. ### Do I need a Microsoft Intune tenant to use this plugin? Yes. This plugin wraps the Microsoft Intune App SDK, which requires a Microsoft Intune tenant, Intune licenses, and an app registration in Microsoft Entra ID (see [Testing](#testing)). ### Why does the plugin require iOS 17? The current Microsoft Intune App SDK for iOS requires iOS 17 or later. Microsoft actively blocks apps built with outdated Intune App SDK versions, so this plugin always tracks Microsoft's current SDK line instead of pinning an older one. ### Does this plugin enforce the app protection policies? Mostly, yes. Once the native integration (MAM build plugin on Android, SDK integration on iOS) is in place, the Intune App SDK enforces PIN, data transfer and screenshot restrictions itself. File encryption is automatic on Android, but on iOS the app must protect its files via `protectFile(...)`. The JavaScript API of this plugin is for the parts enforcement cannot do: enrollment, introspection, file protection on iOS, and cleaning up web storage on selective wipe. ### What is the difference between this plugin and the Managed Configurations plugin? This plugin reads the MAM channel (Intune App SDK, no device enrollment required). The [Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/) plugin reads the MDM channel (device enrollment required, works with any EMM vendor). See [Choosing between the MAM and MDM channel](#choosing-between-the-mam-and-mdm-channel). ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/): Verify app and device integrity using the Play Integrity API and App Attest. - [Managed Configurations](https://capawesome.io/docs/sdks/capacitor/managed-configurations/): Access managed configuration settings deployed by an MDM channel, the counterpart to Intune's MAM channel. - [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/): Detect rooted and jailbroken devices. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/intune/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/intune/LICENSE). ## Third-Party Notices The Microsoft Intune App SDKs for Android and iOS are proprietary software, licensed by Microsoft under the [Microsoft License Terms Intune App SDK](https://github.com/msintuneappsdk/ms-intune-app-sdk-ios/blob/master/Microsoft%20License%20Terms%20Intune%20App%20SDK%20for%20iOS.pdf). This plugin does **not** bundle or modify the Intune App SDKs — it declares them as dependencies and downloads them from Microsoft's official repositories at build or install time. By building an app with this plugin, you accept Microsoft's license terms for the Intune App SDK. The Microsoft Authentication Library (MSAL) is licensed under the MIT license. Using the plugin requires a Microsoft Intune tenant with appropriate licenses. The MIT license of this plugin covers the wrapper code only, not the Microsoft SDKs. Note that Microsoft's license terms also place obligations on **your app** as the distributor of the Intune App SDK binaries — among other things regarding your app's own functionality and copyright notice, license compatibility, and indemnification. Review the license terms linked above before shipping your app. ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by Microsoft Corporation or any of its affiliates or subsidiaries. "Microsoft" and "Microsoft Intune" are trademarks of the Microsoft group of companies. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Keep Awake Plugin Capacitor plugin to keep the screen awake. ## Features - 🌙 **Keep awake**: Prevent the screen from dimming or turning off. - 💤 **Allow sleep**: Restore the default screen sleep behavior at any time. - 🔍 **State**: Read whether the screen is currently kept awake. - 🌐 **Cross-platform**: Supports Android, iOS and Web (via the Screen Wake Lock API). - 🤝 **Compatibility**: Works alongside the [Battery](https://capawesome.io/docs/sdks/capacitor/battery/), [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/) and [Screen Orientation](https://capawesome.io/docs/sdks/capacitor/screen-orientation/) 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! ## Use Cases The Keep Awake plugin is typically used whenever the screen must stay visible without user interaction, for example: - **Media playback**: Keep the screen on while users watch videos or follow along with content. - **Navigation and tracking**: Prevent the display from turning off during turn-by-turn navigation or workout tracking. - **Hands-free reading**: Keep recipes, sheet music, or step-by-step instructions visible while the user's hands are busy. - **Dashboards and kiosks**: Display live data or presentations without the screen dimming or sleeping. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-keep-awake` plugin in my project. ``` If you prefer **Manual Setup**, install the plugin by running the following commands: ``` npm install @capawesome/capacitor-keep-awake npx cap sync ``` This plugin is available on **Android**, **iOS** and **Web**. No additional permissions or configuration are required. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to keep the screen awake, allow it to sleep again, check whether it is currently kept awake, and check whether the feature is available. ### Keep the screen awake Prevent the screen from dimming or turning off. The screen is kept awake until `allowSleep(...)` is called or the app is restarted. This only affects your app and is not a system-wide setting: ``` import { KeepAwake } from '@capawesome/capacitor-keep-awake'; const keepAwake = async () => { await KeepAwake.keepAwake(); }; ``` ### Allow the screen to sleep again Restore the default screen sleep behavior at any time: ``` import { KeepAwake } from '@capawesome/capacitor-keep-awake'; const allowSleep = async () => { await KeepAwake.allowSleep(); }; ``` ### Check if the screen is kept awake Read whether the screen is currently kept awake: ``` import { KeepAwake } from '@capawesome/capacitor-keep-awake'; const isKeptAwake = async () => { const { keptAwake } = await KeepAwake.isKeptAwake(); return keptAwake; }; ``` ### Check if the feature is available On the Web, keeping the screen awake depends on whether the browser supports the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API). On Android and iOS, this always returns `true`: ``` import { KeepAwake } from '@capawesome/capacitor-keep-awake'; const isAvailable = async () => { const { available } = await KeepAwake.isAvailable(); return available; }; ``` ## API - [`allowSleep()`](#allowsleep) - [`isAvailable()`](#isavailable) - [`isKeptAwake()`](#iskeptawake) - [`keepAwake()`](#keepawake) - [Interfaces](#interfaces) ### allowSleep() ``` allowSleep() => Promise ``` Allow the screen to dim or turn off again. This reverses the effect of `keepAwake(...)`. Only available on Android, iOS and Web. **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if keeping the screen awake is available on this device. On Web, this depends on whether the browser supports the [Screen Wake Lock API](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API). On Android and iOS, this always returns `true`. Only available on Android, iOS and Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isKeptAwake() ``` isKeptAwake() => Promise ``` Check if the screen is currently kept awake. Only available on Android, iOS and Web. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### keepAwake() ``` keepAwake() => Promise ``` Keep the screen awake by preventing it from dimming or turning off. The screen is kept awake until `allowSleep(...)` is called or the app is restarted. This setting only affects your app and is not system-wide. Only available on Android, iOS and Web. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------------------------- | ----- | | **`available`** | `boolean` | Whether keeping the screen awake is available on this device or not. | 0.1.0 | #### IsKeptAwakeResult | Prop | Type | Description | Since | | --------------- | --------- | -------------------------------------------------- | ----- | | **`keptAwake`** | `boolean` | Whether the screen is currently kept awake or not. | 0.1.0 | ## Behavior Keep the following in mind when using this plugin: - **App scope**: Keeping the screen awake only affects your app and is not a system-wide setting. - **State reset**: The screen is only kept awake until `allowSleep(...)` is called or the app is restarted. - **Web auto-release**: On the Web, the underlying [Screen Wake Lock](https://developer.mozilla.org/en-US/docs/Web/API/Screen_Wake_Lock_API) is automatically released by the browser whenever the tab becomes hidden (for example, when the user switches tabs or minimizes the window). The plugin automatically re-acquires the wake lock once the tab becomes visible again, as long as `allowSleep(...)` has not been called in the meantime. ## FAQ ### How is this plugin different from other similar plugins? It keeps the screen awake on Android, iOS, and the Web from one fully typed API, and lets you check both whether the feature is available and whether the screen is currently kept awake. On the Web it uses the Screen Wake Lock API and automatically re-acquires the lock when a hidden tab becomes visible again, so the behavior stays predictable across platforms. It needs no permissions or configuration and is actively maintained against the latest Capacitor version. ### Which platforms does this plugin support? The plugin supports Android, iOS and Web. On the Web, it relies on the Screen Wake Lock API, so availability depends on the browser. You can check this at runtime with the `isAvailable()` method, which always returns `true` on Android and iOS. ### Does keeping the screen awake affect other apps? No, keeping the screen awake only affects your app and is not a system-wide setting. As soon as the user leaves your app, the default screen sleep behavior of the device applies again. ### How long is the screen kept awake? The screen is kept awake until `allowSleep(...)` is called or the app is restarted. The setting is not persisted across app restarts. ### Why does the screen sleep again on the Web after switching tabs? On the Web, the browser automatically releases the underlying wake lock whenever the tab becomes hidden, for example when the user switches tabs or minimizes the window. The plugin automatically re-acquires the wake lock once the tab becomes visible again, as long as `allowSleep(...)` has not been called in the meantime. See the [Behavior](#behavior) section for details. ### Do I need any permissions or configuration? No, the plugin does not require any permissions or configuration on any platform. Simply install it and call `keepAwake()`. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Battery](https://capawesome.io/docs/sdks/capacitor/battery/): Access battery information of the device. - [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/): Read and control the screen brightness. - [Screen Orientation](https://capawesome.io/docs/sdks/capacitor/screen-orientation/): Lock and unlock the screen orientation. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/keep-awake/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/keep-awake/LICENSE). # Capacitor libSQL Plugin Capacitor plugin for [libSQL](https://docs.turso.tech/libsql) databases.[1](#fn:1) ## Use Cases The libSQL plugin is typically used whenever an app needs a SQL database, for example: - **Offline-first apps**: Store data in a local database file on the device and synchronize it with a remote server using the `sync(...)` method. - **Remote databases**: Connect directly to a remote libSQL database, such as one hosted on Turso, using a URL and authentication token. - **Structured local storage**: Create tables and insert, update, delete, and query data with SQL statements and bound values. - **Atomic operations**: Group multiple statements into a transaction that can be committed or rolled back as a whole. ## 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 The following examples show how to connect to local and remote databases, query data, execute insert, update, and delete statements, run statements inside a transaction, and synchronize with a remote server. ### Connect to a local database Connect to a local database file on the device. If no file exists at the specified path, a new file is created. If neither a path nor a URL is provided, the plugin creates a new in-memory database. This method must be called before any other methods that interact with the database: ``` 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); }; ``` ### Connect to a remote database Connect to a remote libSQL database using its URL and an authentication token: ``` import { Libsql } from '@capawesome/capacitor-libsql'; 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); }; ``` ### Query data Execute a `SELECT` statement and retrieve the result set: ``` import { Libsql } from '@capawesome/capacitor-libsql'; const query = async () => { const result = await Libsql.query({ connectionId: 'my-connection-id', statement: 'SELECT * FROM my_table', }); console.log('Query result:', result.rows); }; ``` ### Insert, update, and delete data Execute any SQL statement, including `INSERT`, `UPDATE`, `DELETE`, and `CREATE TABLE`, optionally with bound values: ``` import { Libsql } from '@capawesome/capacitor-libsql'; 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'); }; ``` ### Run multiple statements in a transaction Begin a transaction, execute statements as part of it, and either commit or roll back all changes. Transactions are only available on Android: ``` import { Libsql } from '@capawesome/capacitor-libsql'; 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); } }; ``` ### Synchronize with a remote server Synchronize the database with the remote server: ``` import { Libsql } from '@capawesome/capacitor-libsql'; 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` ## FAQ ### Can I use this plugin with Turso? Yes, you can connect to a remote libSQL database hosted on [Turso](https://docs.turso.tech/libsql) by passing the database URL and an authentication token to the `connect(...)` method, as shown in the [usage example](#connect-to-a-remote-database) above. ### Do I need a remote database to use this plugin? No, the plugin also works with purely local databases. If you pass a `path` to the `connect(...)` method, the plugin uses a database file on the device and creates it if it does not exist. If you provide neither a path nor a URL, the plugin creates a new in-memory database. ### Are transactions supported? Yes, you can use the `beginTransaction(...)`, `commitTransaction(...)` and `rollbackTransaction(...)` methods to group multiple statements into a transaction. Note that transactions are only available on Android. ### What is the difference between the `query` and `execute` methods? The `query(...)` method is used to execute `SELECT` statements and retrieve the result set. The `execute(...)` method is used for any other SQL statement, including `INSERT`, `UPDATE`, `DELETE`, and `CREATE TABLE`, and does not return a result set. Both methods support binding values to the statement. ### How do I keep a local database in sync with a remote server? Call the `sync(...)` method with the ID of the connection you want to synchronize. This synchronizes the database with the remote server and is available on Android and iOS. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/): Securely store key/value pairs such as passwords or tokens. - [SQLite](https://capawesome.io/docs/sdks/capacitor/sqlite/): Access SQLite databases with support for encryption, transactions, and schema migrations. ## 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/). ______________________________________________________________________ 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") # Capacitor Light Sensor Plugin Capacitor plugin to read the device's ambient light sensor. ## Features The Capacitor Light Sensor plugin provides a complete solution for ambient light measurements in Capacitor apps. Here are some of the key features: - 🌗 **Illuminance**: Read the ambient light level in lux. - ⚡ **Real-time measurements**: Continuous light data with event listeners. - 🖥️ **Native**: Supports Android. - 🤝 **Compatibility**: Works alongside the [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/) plugin. - 📦 **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! ## Use Cases The Light Sensor plugin is typically used to react to the lighting conditions around the device, for example: - **Adaptive theming**: Switch between a light and dark theme based on the ambient light level. - **Brightness control**: Combine the illuminance readings with the [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/) plugin to adjust the screen brightness. - **Environment monitoring**: Continuously measure the light level in a room using real-time measurement events. - **Reading comfort**: Detect dark environments and suggest a more comfortable display mode to the user. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [The Complete Guide to Capacitor Device Sensors](https://capawesome.io/blog/capacitor-device-sensors-guide/): How ambient light readings pair with the Proximity Sensor for adaptive UX. ## 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-light-sensor` 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-light-sensor 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 iOS does not provide a public API to read the ambient light sensor, so the plugin is not implemented on this platform. The closest available signal is the screen brightness, which you can read using the [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/) plugin. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check whether an ambient light sensor is available, read the current light level, and receive or stop continuous measurement updates. ### Check if the light sensor is available Check whether the device has an ambient light sensor before using the other methods. Only available on Android: ``` import { LightSensor } from '@capawesome/capacitor-light-sensor'; const isAvailable = async () => { const result = await LightSensor.isAvailable(); return result.available; }; ``` ### Read the current light level Get the most recent measurement from the ambient light sensor in lux. Only available on Android: ``` import { LightSensor } from '@capawesome/capacitor-light-sensor'; const getMeasurement = async () => { const measurement = await LightSensor.getMeasurement(); console.log('Illuminance: ', measurement.illuminance); }; ``` ### Receive continuous measurements Add a listener for the `measurement` event and start the measurement updates to receive real-time light data. Only available on Android: ``` import { LightSensor } from '@capawesome/capacitor-light-sensor'; const startMeasurementUpdates = async () => { await LightSensor.addListener('measurement', measurement => { console.log('Illuminance: ', measurement.illuminance); }); await LightSensor.startMeasurementUpdates(); }; ``` ### Stop receiving measurements Stop the measurement updates and remove the listeners when you no longer need them: ``` import { LightSensor } from '@capawesome/capacitor-light-sensor'; const stopMeasurementUpdates = async () => { await LightSensor.stopMeasurementUpdates(); }; const removeAllListeners = async () => { await LightSensor.removeAllListeners(); }; ``` ## API - [`getMeasurement()`](#getmeasurement) - [`isAvailable()`](#isavailable) - [`startMeasurementUpdates()`](#startmeasurementupdates) - [`stopMeasurementUpdates()`](#stopmeasurementupdates) - [`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 ambient light sensor. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### isAvailable() ``` isAvailable() => Promise ``` Check if the ambient light sensor is available on the device. Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### startMeasurementUpdates() ``` startMeasurementUpdates() => Promise ``` Start emitting `measurement` events. Only available on Android. **Since:** 0.1.0 ______________________________________________________________________ ### stopMeasurementUpdates() ``` stopMeasurementUpdates() => Promise ``` Stop emitting `measurement` events. Only available on Android. **Since:** 0.1.0 ______________________________________________________________________ ### addListener('measurement', ...) ``` addListener(eventName: 'measurement', listenerFunc: (event: MeasurementEvent) => void) => Promise ``` Called when a new measurement is available. Only available on Android. | 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 | | ----------------- | -------- | ------------------------------------ | ----- | | **`illuminance`** | `number` | The ambient light level in lux (lx). | 0.1.0 | #### IsAvailableResult | Prop | Type | Description | Since | | --------------- | --------- | ------------------------------------------------------------ | ----- | | **`available`** | `boolean` | Whether the ambient light sensor is available on the device. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | ### Type Aliases #### GetMeasurementResult `Measurement` #### MeasurementEvent `Measurement` ## FAQ ### How is this plugin different from other similar plugins? It reads the Android ambient light sensor in lux through a fully typed API, offering both a one-time reading and continuous real-time `measurement` events, plus an availability check so you can gracefully handle devices without the sensor. It pairs naturally with the Screen Brightness plugin for adaptive theming and brightness control, and is kept current with the latest Capacitor version. If a rough light signal is enough, screen brightness may cover it; if you need true illuminance values in lux, this plugin is built for exactly that. ### Why is this plugin not available on iOS? iOS does not provide a public API to read the ambient light sensor, so the plugin is not implemented on this platform. The closest available signal is the screen brightness, which you can read using the [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/) plugin. ### What unit are the measurements in? The `illuminance` property of a measurement contains the ambient light level in lux (lx). ### How do I know if the device has a light sensor? Call the `isAvailable()` method, which returns whether the ambient light sensor is available on the device. It is recommended to check this before calling the other methods. ### What is the difference between `getMeasurement` and the `measurement` event? The `getMeasurement()` method returns the most recent measurement from the ambient light sensor as a one-time value. The `measurement` event, in combination with `startMeasurementUpdates()`, delivers continuous real-time measurements until you call `stopMeasurementUpdates()`. See the [usage examples](#usage) above. ### Do I need any permissions or configuration on Android? No permissions are required. However, if you are using Proguard, you need to add a keep rule for the plugin classes to your `proguard-rules.pro` file, as described in the [Installation](#installation) section. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Accelerometer](https://capawesome.io/docs/sdks/capacitor/accelerometer/): Capture the acceleration force along the x, y, and z axes. - [Barometer](https://capawesome.io/docs/sdks/capacitor/barometer/): Obtain the static air pressure in hectopascals (hPa). - [Proximity Sensor](https://capawesome.io/docs/sdks/capacitor/proximity-sensor/): Read the device's proximity sensor. - [Screen Brightness](https://capawesome.io/docs/sdks/capacitor/screen-brightness/): Read and control the screen brightness. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/light-sensor/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/light-sensor/LICENSE). # Capacitor Live Update Plugin 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 The Capacitor Live Update plugin is one of the most feature-rich Over-the-Air (OTA) update solutions for Capacitor apps. 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. - 🤝 **Compatibility**: Works alongside the [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/) and [Firebase Remote Config](https://capawesome.io/docs/sdks/capacitor/firebase/remote-config/) plugins. Missing a feature? Just [open an issue](https://github.com/capawesome-team/capacitor-plugins/issues) and we'll take a look! ## Use Cases The Live Update plugin is typically used to ship changes to users without going through the app stores, for example: - **Instant bug fixes**: Deliver critical fixes to your users in real-time without waiting for app store reviews. - **Fast feature delivery**: Ship new web-based features as soon as they are ready by publishing a new bundle. - **Staged rollouts**: Gradually roll out new bundles to a subset of devices to gather feedback before a full release. - **Testing and QA channels**: Distribute preview versions to testers via dedicated channels before promoting them to production. - **Safe releases**: Automatically roll back to the default bundle if a faulty update prevents the app from becoming ready. ## 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 | ## Demo A working example can be found here: [capawesome-team/capacitor-live-update-demo](https://github.com/capawesome-team/capacitor-live-update-demo) | Android | iOS | | ------- | --- | | | | ## Guides - [Getting Started with Capawesome Cloud Live Updates](https://capawesome.io/docs/cloud/live-updates/setup/) - [Capacitor Live Updates: The Complete OTA Guide](https://capawesome.io/blog/capacitor-live-updates-guide/) - [Best Live Update Tools for Capacitor (2026)](https://capawesome.io/blog/best-ota-update-tools-for-capacitor/) - [Capacitor Live Updates: Signing vs Encryption](https://capawesome.io/blog/capacitor-live-updates-end-to-end-encryption/) - [Capacitor Live Updates: Reliability & Uptime](https://capawesome.io/blog/capacitor-live-updates-reliability-and-uptime/) - [Capawesome vs. Capgo: Which Is More Reliable?](https://capawesome.io/blog/capawesome-cloud-vs-capgo-reliability/) - [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/) - [Live Updates for Nuxt Capacitor Apps](https://capawesome.io/blog/live-updates-for-nuxt-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-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/docs/cloud/live-updates/channels/#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` and `com.squareup.okhttp3:okhttp-brotli` (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/docs/cloud/live-updates/channels/#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 All methods are only available on Android and iOS. The following examples show how to sync with Capawesome Cloud, mark the app as ready, check for and download new bundles, switch between and manage bundles, work with channels, inspect the current and next bundle, identify devices, read the app version, and reset to the default bundle. ### Sync with Capawesome Cloud Automatically download and set the latest bundle for the app using [Capawesome Cloud](https://cloud.capawesome.io/). Call `reload()` or restart the app to apply the changes. The `channel` option overrides the channel for this single sync call without persisting it: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const sync = async () => { const result = await LiveUpdate.sync({ channel: 'production-5', }); return result.nextBundleId; }; ``` ### Notify the plugin that the app is ready Call `ready()` as soon as the app is ready to use so that the plugin knows no rollback is needed. If the `readyTimeout` configuration option is set and this method is not called in time, the plugin resets the app to the default bundle: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; 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.'); } }; ``` ### Check if a new bundle is available Fetch the latest bundle from Capawesome Cloud and compare it with the bundle that is currently in use: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const fetchLatestBundle = async () => { await LiveUpdate.fetchLatestBundle(); }; 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; } }; ``` ### Download a bundle from your own server Download a self-hosted bundle from any URL, without any Capawesome Cloud dependency: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const downloadBundle = async () => { await LiveUpdate.downloadBundle({ url: 'https://example.com/1.0.0.zip', bundleId: '1.0.0' }); }; ``` ### Switch to a downloaded bundle Set the next bundle to use for the app. Call `reload()` or restart the app to apply the changes: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setNextBundle = async () => { await LiveUpdate.setNextBundle({ bundleId: '7f0b9bf2-dff6-4be2-bcac-b068cc5ea756' }); }; const reload = async () => { await LiveUpdate.reload(); }; ``` ### Manage downloaded bundles Get the identifiers of all bundles that have been downloaded and delete bundles that are no longer needed: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const getBundles = async () => { const result = await LiveUpdate.getBundles(); return result.bundleIds; }; const deleteBundle = async () => { await LiveUpdate.deleteBundle({ bundleId: 'my-bundle' }); }; ``` ### Work with channels Set the channel to use for updates, read the currently resolved channel, and fetch the available channels from Capawesome Cloud. The channel set via `setChannel(...)` is persisted across app restarts. Note that `fetchChannels(...)` only works for apps with public channels enabled: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const setChannel = async () => { await LiveUpdate.setChannel({ channel: 'production-5' }); }; const getChannel = async () => { const result = await LiveUpdate.getChannel(); return result.channel; }; const fetchChannels = async () => { const result = await LiveUpdate.fetchChannels(); return result.channels; }; ``` ### Inspect the current and next bundle Get the identifier of the bundle that is currently used by the app and of the bundle that will be used after the next reload or app restart: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const getCurrentBundle = async () => { const result = await LiveUpdate.getCurrentBundle(); return result.bundleId; }; const getNextBundle = async () => { const result = await LiveUpdate.getNextBundle(); return result.bundleId; }; ``` ### Identify devices Read the unique device identifier and manage a custom identifier for the device: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const getDeviceId = async () => { const result = await LiveUpdate.getDeviceId(); return result.deviceId; }; const setCustomId = async () => { await LiveUpdate.setCustomId({ customId: 'my-custom-id' }); }; const getCustomId = async () => { const result = await LiveUpdate.getCustomId(); return result.customId; }; ``` ### Read the app version Get the native version code and version name of the app, for example to restrict live updates to compatible native versions: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const getVersionCode = async () => { const result = await LiveUpdate.getVersionCode(); return result.versionCode; }; const getVersionName = async () => { const result = await LiveUpdate.getVersionName(); return result.versionName; }; ``` ### Reset to the default bundle Reset the app to the bundle that shipped with the native app binary. Call `reload()` or restart the app to apply the changes: ``` import { LiveUpdate } from '@capawesome/capacitor-live-update'; const reset = async () => { await LiveUpdate.reset(); }; ``` ## 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. It must always be called before `sync(...)` or any other method that changes the bundle. Only available on Android and iOS. **Returns:** `Promise` **Since:** 5.0.0 ______________________________________________________________________ ### reload() ``` reload() => Promise ``` Reload the app to apply the new bundle. This method reloads the web view only, the native process is **not** restarted. Resources of the web layer that hold an exclusive lock (e.g. IndexedDB or WebSockets) are therefore not released automatically and may prevent the new bundle from starting. Release these resources before calling this method or apply the new bundle on the next app start instead. **Attention**: The in-memory state of the app (e.g. unsaved user input) is lost. 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. | | **`channel`** | `string` | The name of the channel that the bundle is actually from. This is the resolved channel after applying any forced channel assignment and may differ from the channel set by the Live Update SDK. | 8.3.0 | | **`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 preferred channel name from which the latest bundle should be fetched. This is the SDK preference and may be overridden by a forced channel assignment configured in the Capawesome Cloud Console. | 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 preferred channel name from which the latest bundle should be fetched. This is the SDK preference and may be overridden by a forced channel assignment configured in the Capawesome Cloud Console. | 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. ### Check which bundle is running If an update does not seem to be applied, log the identifier of the current bundle and the path that the web view is serving as early as possible at app start: ``` import { WebView } from '@capacitor/core'; import { LiveUpdate } from '@capawesome/capacitor-live-update'; const logCurrentBundle = async () => { const { bundleId } = await LiveUpdate.getCurrentBundle(); const { path } = await WebView.getServerBasePath(); console.log(`Bundle: ${bundleId ?? 'default'} (${path})`); }; ``` Because [`reload()`](#reload) restarts the web layer, this also logs both values after a reload. If they change after the reload, the new bundle was applied and any remaining problem is in the web layer of your app (see [Why does my app hang after calling `reload()`?](#why-does-my-app-hang-after-calling-reload)). ## Limitations Live updates are only supported for [binary-compatible changes](https://capawesome.io/docs/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/docs/cloud/live-updates/channels/#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. ### Do I have to use Capawesome Cloud? No, the plugin also supports self-hosted bundles. You can download a bundle from any URL using the [`downloadBundle(...)`](#downloadbundle) method and apply it with [`setNextBundle(...)`](#setnextbundle), without any Capawesome Cloud dependency. Capawesome Cloud is only required for the convenience features such as [`sync(...)`](#sync), channels, and rollouts. ### What changes can I ship with a live update? Live updates are only supported for [binary-compatible changes](https://capawesome.io/docs/cloud/live-updates/faq/#what-are-binary-compatible-changes) such as HTML, CSS, and JavaScript. If you change native code, for example by adding a new plugin, you need to resubmit your app to the app stores. You should therefore [restrict live updates to compatible native versions](https://capawesome.io/blog/how-to-restrict-capacitor-live-updates-to-native-versions/) of your app, as described in the [Limitations](#limitations) section. ### In which order should I call `ready()` and `sync()`? Always call [`ready()`](#ready) first, as early as possible after the app has started, and only then [`sync()`](#sync) or any other method that changes the bundle. `ready()` confirms that the current bundle works and stops the rollback timer. If you call `sync()` first, the `readyTimeout` keeps running while the next bundle is being downloaded, which can roll back a bundle that actually works. ### How does the rollback mechanism work? If the `readyTimeout` configuration option is set, the plugin waits for the app to call the [`ready()`](#ready) method after a new bundle has been applied. If the method is not called within the configured timeout, the plugin assumes the bundle is faulty and resets the app to the default bundle. It is strongly recommended to configure `readyTimeout` (e.g. `10000` ms). Additionally, with the `autoBlockRolledBackBundles` option enabled, bundles that caused a rollback are automatically blocked and skipped in future sync operations. ### Why does my app hang after calling `reload()`? If your app shows a blank or loading screen after calling [`reload()`](#reload) but works after a force quit, the new bundle was most likely applied correctly. You can verify this by logging the [current bundle](#check-which-bundle-is-running) at app start. `reload()` reloads the web view without restarting the native process. Resources of the web layer that hold an exclusive lock are therefore not released automatically and can block the new bundle from starting. A force quit starts a new process and releases these resources, which is why the update seems to be applied only after a restart. The most common cause is IndexedDB. The Firebase JS SDK, for example, holds an IndexedDB lease as long as Firestore offline persistence is enabled. Terminate the client before reloading the app: ``` import { getFirestore, terminate } from 'firebase/firestore'; await terminate(getFirestore()); await LiveUpdate.reload(); ``` Keep in mind that offline persistence may be active even if you try to exclude web views, because guards that detect the browser by user agent usually also match the `WKWebView` on iOS. On Android, the problem is less likely to occur because Chromium recovers from a stale lease. Other candidates are WebSockets, Web Locks and service workers. If you cannot release these resources, apply the new bundle on the next app start instead of reloading the app. ## Related Plugins - [App Update](https://capawesome.io/docs/sdks/capacitor/app-update/): Assist users with native app updates, which are required for changes that are not binary-compatible. - [Firebase Remote Config](https://capawesome.io/docs/sdks/capacitor/firebase/remote-config/): Change the behavior and appearance of your app remotely without publishing an update. ## 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/). ## 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). # Capacitor LLM Plugin Capacitor plugin for running on-device large language models (LLMs) on Android and iOS. Uses the system-provided models Apple Intelligence (Foundation Models) and Gemini Nano (AICore) with chat sessions, token streaming and cancellation. ## Features The Capacitor LLM plugin brings the platform's own on-device AI models to your Capacitor app. Here are some of the key features: - 🧠 **System Models**: Uses the models that ship with the operating system (Apple Intelligence on iOS, Gemini Nano on Android). No model files to bundle, no API keys, no cloud calls. - 🔒 **Private by Design**: All inference runs on the device. Prompts and responses never leave the user's phone. - 💬 **Chat Sessions**: Create chats with instructions (system prompt) that keep the conversation context across multiple generations. - 🌊 **Token Streaming**: Stream the response chunk by chunk via events for a responsive UI. - ✋ **Cancellation**: Cancel an in-flight generation at any time. - 🚦 **Typed Availability**: Check the model availability with typed status values and get notified about changes. - ⤵️ **Model Download**: Trigger the Gemini Nano model download on Android with progress events. - 🔓 **Public APIs Only**: Built exclusively on public platform APIs, so it is safe for App Review and resilient to OS updates. - 🤝 **Compatibility**: Works hand in hand with the [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/) and [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/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! ## Use Cases The LLM plugin is typically used whenever an app needs AI text generation without sending data to a server, for example: - **Summarization**: Summarize notes, articles or messages on the device. - **Smart Replies**: Suggest replies for chats and emails. - **Rewriting**: Rephrase, shorten or proofread user-written text. - **Offline Assistants**: Build chat assistants that work without an internet connection. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Demo | Android | iOS | | ------- | --- | | | | ## Guides - [Announcing the Capacitor LLM Plugin](https://capawesome.io/blog/announcing-the-capacitor-llm-plugin/): Run on-device language models with Apple Intelligence on iOS and Gemini Nano on Android, fully offline. - [How to Use Apple Intelligence in a Capacitor App](https://capawesome.io/blog/how-to-use-apple-intelligence-in-a-capacitor-app/): Gate on availability, generate and stream text, and manage the context window on iOS 26. ## 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-llm` 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-llm npx cap sync ``` ### Android The plugin uses the [ML Kit GenAI Prompt API](https://developers.google.com/ml-kit/genai/prompt/android) to run Gemini Nano via AICore. The SDK is declared as a regular Gradle dependency and fetched from Google's Maven repository when your app is built. The model itself is managed by the system (AICore) and is **not** bundled with your app. **Attention**: - Gemini Nano is only available on [Gemini Nano-capable devices](https://developers.google.com/ml-kit/genai#supported_devices) (for example the Google Pixel 9 series or Samsung Galaxy S25 series) with AICore. The list of supported devices is still short, so always check `getAvailability()` at runtime and design a fallback. - The ML Kit GenAI Prompt SDK is still in **beta**, so breaking changes in the underlying SDK are possible. - Apps using Gemini Nano are subject to Google's [Generative AI Prohibited Use Policy](https://policies.google.com/terms/generative-ai/use-policy). #### Minimum SDK Version The ML Kit GenAI Prompt SDK requires a minimum SDK version of `26`. Make sure that the `minSdkVersion` in your `android/variables.gradle` file is set to at least `26`: ``` ext { minSdkVersion = 26 } ``` #### 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 variables in your app’s `variables.gradle` file to change the default versions of the dependencies: - `$mlkitGenaiPromptVersion` version of `com.google.mlkit:genai-prompt` (default: `1.0.0-beta2`) - `$kotlinVersion` version of `org.jetbrains.kotlin:kotlin-gradle-plugin` (default: `2.1.20`) - `$kotlinxCoroutinesVersion` version of `org.jetbrains.kotlinx:kotlinx-coroutines-android` (default: `1.10.2`) This can be useful if you encounter dependency conflicts with other plugins in your project. ### iOS The plugin uses the [Foundation Models](https://developer.apple.com/documentation/foundationmodels) framework, which is part of the operating system. No additional dependencies or configuration are required. **Attention**: - The Foundation Models framework requires **iOS 26 or later** on an [Apple Intelligence-enabled device](https://www.apple.com/apple-intelligence/) (iPhone 15 Pro or later) with Apple Intelligence turned on. On older iOS versions, all methods except `getAvailability()` reject as unavailable. - Building the plugin requires **Xcode 26 or later**. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check the model availability, download the model, create and delete chats, generate text, stream the response, cancel a generation, and tune the generation parameters. ### Check the model availability The plugin uses the model that ships with the operating system, so its availability depends on the device and OS version. Always call `getAvailability()` before you generate text and handle every status. Listen for the `availabilityChange` event to be notified when the status changes: | Status | Meaning | What you can do | | --------------------- | --------------------------------------------------- | ----------------------------------------------------------------- | | `available` | The model is ready to use. | Start generating. | | `device-not-eligible` | The device hardware does not support the model. | Offer a fallback (for example a cloud-based or custom model). | | `downloadable` | The model can be downloaded. | Call `downloadModel()` to start the download. | | `downloading` | The model is currently being downloaded. | Wait and listen for `availabilityChange` events. | | `not-enabled` | The model is disabled on the device. | Ask the user to enable Apple Intelligence in the system settings. | | `not-ready` | The model is not ready yet. | Try again later. | | `unavailable` | No system model exists on this platform/OS version. | Offer a fallback or hide the feature. | On Android, only `available`, `downloadable`, `downloading` and `unavailable` are reported. On iOS, only `available`, `device-not-eligible`, `not-enabled`, `not-ready` and `unavailable` are reported: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const getAvailability = async () => { const { status } = await Llm.getAvailability(); return status; }; const addAvailabilityChangeListener = async () => { await Llm.addListener('availabilityChange', event => { console.log(`Availability changed: ${event.status}`); }); }; ``` ### Download the model If the availability status is `downloadable`, the model must be downloaded before it can be used. On Android, trigger the download explicitly and follow its progress via the `downloadProgress` event. On iOS, the download is managed by the system and cannot be triggered by the app. Only available on Android: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const downloadModel = async () => { const { status } = await Llm.getAvailability(); if (status !== 'downloadable') { return; } await Llm.addListener('downloadProgress', event => { console.log(`Download progress: ${event.progress * 100}%`); }); await Llm.downloadModel(); }; ``` ### Create and delete a chat A chat keeps the conversation context across multiple generations and can be given instructions (system prompt) that guide the model's responses. Chats live in memory until they are deleted with `deleteChat(...)`, so delete the chats you no longer need to free the associated native resources. Only available on Android and iOS: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const createChat = async () => { const { id } = await Llm.createChat({ instructions: 'You are a helpful assistant that answers briefly.', }); return id; }; const deleteChat = async (chatId: string) => { await Llm.deleteChat({ id: chatId }); }; ``` ### Generate text Generate a response for a prompt and wait for the complete text. Only one generation can be in flight per chat at a time. Only available on Android and iOS: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const generateText = async (chatId: string) => { const { text } = await Llm.generateText({ chatId, prompt: 'Why is the sky blue?', }); return text; }; ``` ### Stream the response Stream the response chunk by chunk via the `textChunk` event for a more responsive UI. The returned promise resolves with the complete response text. Only available on Android and iOS: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const streamText = async (chatId: string) => { await Llm.addListener('textChunk', event => { if (event.chatId === chatId) { console.log(event.text); // Append the chunks to your UI } }); const { text } = await Llm.streamText({ chatId, prompt: 'Tell me a short story about a magical dog.', }); return text; // The complete response text }; ``` ### Cancel a generation Cancel the in-flight generation of a chat. The pending promise rejects with the `GENERATION_CANCELED` error code. On iOS, the generation is canceled immediately. On Android, cancellation is best-effort, so a few more chunks may be emitted. Only available on Android and iOS: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const cancelGeneration = async (chatId: string) => { await Llm.cancelGeneration({ chatId }); }; ``` ### Tune the generation parameters `maxOutputTokens` and `temperature` can be set per chat with `createChat(...)` and overridden per request with `generateText(...)` or `streamText(...)`. Only available on Android and iOS: ``` import { Llm } from '@capawesome-team/capacitor-llm'; const createChatWithParameters = async () => { const { id } = await Llm.createChat({ maxOutputTokens: 256, temperature: 0.7, }); const { text } = await Llm.generateText({ chatId: id, prompt: 'Write a haiku about the sea.', temperature: 0.2, // Overrides the chat's default value }); return text; }; ``` The platforms enforce different limits on the generation parameters: | Parameter | Android (Gemini Nano) | iOS (Apple Intelligence) | | ----------------- | -------------------------------------- | -------------------------------------------- | | `maxOutputTokens` | Limited to a maximum of `4096` tokens. | No documented hard limit. | | `temperature` | Must be between `0.0` and `1.0`. | Values greater than `1.0` are allowed. | | Context size | Input must be under ~4,000 tokens. | Context window of ~4,096 tokens per session. | If the context limit of a chat is exceeded, the generation rejects with the `GENERATION_FAILED` error code. In that case, create a new chat. ## API - [`cancelGeneration(...)`](#cancelgeneration) - [`createChat(...)`](#createchat) - [`deleteChat(...)`](#deletechat) - [`downloadModel()`](#downloadmodel) - [`generateText(...)`](#generatetext) - [`getAvailability()`](#getavailability) - [`streamText(...)`](#streamtext) - [`addListener('availabilityChange', ...)`](#addlisteneravailabilitychange-) - [`addListener('downloadProgress', ...)`](#addlistenerdownloadprogress-) - [`addListener('textChunk', ...)`](#addlistenertextchunk-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### cancelGeneration(...) ``` cancelGeneration(options: CancelGenerationOptions) => Promise ``` Cancel the in-flight text generation of a chat. The pending `generateText(...)` or `streamText(...)` promise rejects with the `GENERATION_CANCELED` error code. On **Android**, cancellation is best-effort. The generation is interrupted as soon as possible but a few more chunks may be emitted. Only available on Android and iOS. | Param | Type | | ------------- | ------------------------- | | **`options`** | `CancelGenerationOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### createChat(...) ``` createChat(options?: CreateChatOptions | undefined) => Promise ``` Create a new chat session. A chat keeps the conversation context across multiple generations until it is deleted with `deleteChat(...)`. If a chat with the provided identifier already exists, the promise rejects with the `CHAT_ALREADY_EXISTS` error code. On **iOS**, each chat is backed by a native language model session that maintains the conversation context. On **Android**, the conversation history is kept in memory by the plugin and included in each prompt since the system API does not provide native multi-turn sessions. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `CreateChatOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### deleteChat(...) ``` deleteChat(options: DeleteChatOptions) => Promise ``` Delete a chat session and free the associated native resources. An in-flight generation of the chat is canceled. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `DeleteChatOptions` | **Since:** 0.0.1 ______________________________________________________________________ ### downloadModel() ``` downloadModel() => Promise ``` Download the on-device model. Call this method if `getAvailability()` returns the `downloadable` status. The returned promise resolves when the download is complete. The download progress is emitted via the `downloadProgress` event. Only available on Android. **Since:** 0.0.1 ______________________________________________________________________ ### generateText(...) ``` generateText(options: GenerateTextOptions) => Promise ``` Generate text for a prompt and resolve with the complete response. Only one generation can be in flight per chat at a time. Starting another one rejects with the `GENERATION_IN_PROGRESS` error code. Only available on Android and iOS. | Param | Type | | ------------- | --------------------- | | **`options`** | `GenerateTextOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### getAvailability() ``` getAvailability() => Promise ``` Get the availability status of the on-device model. This method never rejects. On platforms or OS versions without a system model it resolves with the `unavailable` status. **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### streamText(...) ``` streamText(options: StreamTextOptions) => Promise ``` Generate text for a prompt and stream the response. Incremental chunks are emitted via the `textChunk` event while the generation is running. The returned promise resolves with the complete response text when the generation is finished. Only one generation can be in flight per chat at a time. Starting another one rejects with the `GENERATION_IN_PROGRESS` error code. Only available on Android and iOS. | Param | Type | | ------------- | ------------------- | | **`options`** | `StreamTextOptions` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('availabilityChange', ...) ``` addListener(eventName: 'availabilityChange', listenerFunc: (event: AvailabilityChangeEvent) => void) => Promise ``` Called when the availability status of the on-device model changes. The plugin watches the availability status only while at least one listener for this event is attached. Only available on Android and iOS. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'availabilityChange'` | | **`listenerFunc`** | `(event: AvailabilityChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('downloadProgress', ...) ``` addListener(eventName: 'downloadProgress', listenerFunc: (event: DownloadProgressEvent) => void) => Promise ``` Called while the on-device model is being downloaded. Only available on Android. | Param | Type | | ------------------ | ---------------------------------------- | | **`eventName`** | `'downloadProgress'` | | **`listenerFunc`** | `(event: DownloadProgressEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### addListener('textChunk', ...) ``` addListener(eventName: 'textChunk', listenerFunc: (event: TextChunkEvent) => void) => Promise ``` Called when a new text chunk is generated during a streaming generation started with `streamText(...)`. Only available on Android and iOS. | Param | Type | | ------------------ | --------------------------------- | | **`eventName`** | `'textChunk'` | | **`listenerFunc`** | `(event: TextChunkEvent) => void` | **Returns:** `Promise` **Since:** 0.0.1 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.0.1 ______________________________________________________________________ ### Interfaces #### CancelGenerationOptions | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------------------------------------- | ----- | | **`chatId`** | `string` | The identifier of the chat whose in-flight generation should be canceled. | 0.0.1 | #### CreateChatResult | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------------ | ----- | | **`id`** | `string` | The unique identifier of the created chat. | 0.0.1 | #### CreateChatOptions | Prop | Type | Description | Since | | --------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`id`** | `string` | The unique identifier of the chat. If not provided, a random UUID is generated. | 0.0.1 | | **`instructions`** | `string` | The instructions (system prompt) that guide the model's responses in this chat. | 0.0.1 | | **`maxOutputTokens`** | `number` | The default maximum number of tokens the model may generate per response in this chat. Can be overridden per request. On **Android**, the value is limited to a maximum of `4096` tokens. | 0.0.1 | | **`temperature`** | `number` | The default sampling temperature for responses in this chat. Higher values produce more creative results, lower values produce more deterministic results. Can be overridden per request. On **Android**, the value must be between `0.0` and `1.0`. | 0.0.1 | #### DeleteChatOptions | Prop | Type | Description | Since | | -------- | -------- | ------------------------------------- | ----- | | **`id`** | `string` | The identifier of the chat to delete. | 0.0.1 | #### GenerateTextResult | Prop | Type | Description | Since | | ---------- | -------- | ------------------- | ----- | | **`text`** | `string` | The generated text. | 0.0.1 | #### GenerateTextOptions | Prop | Type | Description | Since | | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`chatId`** | `string` | The identifier of the chat to generate the text in. | 0.0.1 | | **`maxOutputTokens`** | `number` | The maximum number of tokens the model may generate for this request. Overrides the chat's default value. On **Android**, the value is limited to a maximum of `4096` tokens. | 0.0.1 | | **`prompt`** | `string` | The prompt to generate a response for. | 0.0.1 | | **`temperature`** | `number` | The sampling temperature for this request. Overrides the chat's default value. On **Android**, the value must be between `0.0` and `1.0`. | 0.0.1 | #### GetAvailabilityResult | Prop | Type | Description | Since | | ------------ | -------------------- | ----------------------------------------------- | ----- | | **`status`** | `AvailabilityStatus` | The availability status of the on-device model. | 0.0.1 | #### StreamTextResult | Prop | Type | Description | Since | | ---------- | -------- | ---------------------------- | ----- | | **`text`** | `string` | The complete generated text. | 0.0.1 | #### StreamTextOptions | Prop | Type | Description | Since | | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`chatId`** | `string` | The identifier of the chat to generate the text in. | 0.0.1 | | **`maxOutputTokens`** | `number` | The maximum number of tokens the model may generate for this request. Overrides the chat's default value. On **Android**, the value is limited to a maximum of `4096` tokens. | 0.0.1 | | **`prompt`** | `string` | The prompt to generate a response for. | 0.0.1 | | **`temperature`** | `number` | The sampling temperature for this request. Overrides the chat's default value. On **Android**, the value must be between `0.0` and `1.0`. | 0.0.1 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### AvailabilityChangeEvent | Prop | Type | Description | Since | | ------------ | -------------------- | --------------------------------------------------- | ----- | | **`status`** | `AvailabilityStatus` | The new availability status of the on-device model. | 0.0.1 | #### DownloadProgressEvent | Prop | Type | Description | Since | | -------------- | -------- | ----------------------------------------------------- | ----- | | **`progress`** | `number` | The download progress as a value between `0` and `1`. | 0.0.1 | #### TextChunkEvent | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------------------------------------------------------------------------------------- | ----- | | **`chatId`** | `string` | The identifier of the chat the chunk belongs to. | 0.0.1 | | **`text`** | `string` | The newly generated text chunk. Append the chunks in the order they are received to reconstruct the complete response. | 0.0.1 | ### Type Aliases #### AvailabilityStatus The availability status of the on-device model. - `available`: The model is downloaded and ready to use. - `device-not-eligible`: The device does not support the on-device model. - `downloadable`: The model can be downloaded. On Android, call `downloadModel()` to trigger the download. - `downloading`: The model is currently being downloaded. - `not-enabled`: The on-device model is disabled. On iOS, the user must enable Apple Intelligence in the system settings. - `not-ready`: The model is not ready yet, for example because the system is still preparing it. Try again later. - `unavailable`: The model is not available on this platform or OS version. `'available' | 'device-not-eligible' | 'downloadable' | 'downloading' | 'not-enabled' | 'not-ready' | 'unavailable'` ## FAQ ### How is this plugin different from other similar plugins? It runs entirely on the platform's own on-device models — Apple Intelligence on iOS and Gemini Nano on Android — so prompts and responses never leave the device and there are no model files to bundle, API keys or cloud calls. You get chat sessions that keep context, token streaming, cancellation and typed availability checks through one fully typed API that is actively maintained against the latest OS and Capacitor versions, and it's backed by dedicated support. If you just need occasional cloud completions, a plain HTTP call may be enough; if you want private, offline text generation with a complete on-device workflow, this plugin is built for exactly that. ### Is text generation available on the web? No. Browsers do not provide a system language model, so all methods except `getAvailability()` reject with an unimplemented error on the web. `getAvailability()` resolves with the `unavailable` status. ### Which devices support on-device text generation? On iOS, all Apple Intelligence-enabled devices (iPhone 15 Pro or later) running iOS 26 or later. On Android, only [Gemini Nano-capable devices](https://developers.google.com/ml-kit/genai#supported_devices) with AICore, for example the Google Pixel 9 series or Samsung Galaxy S25 series. Always check `getAvailability()` at runtime. ### Can I use my own model files? No. The plugin deliberately supports only the system-provided models. This keeps your app small and inference fast. Support for custom model runtimes may be added in the future. ### Why does a generation reject with `GENERATION_FAILED`? The most common reasons are an exceeded context window (create a new chat in that case), content that is blocked by the platform's safety guardrails, or an exceeded AICore quota on Android. The error message contains the platform-specific reason. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/): Store chat data securely on the device. - [Speech Recognition](https://capawesome.io/docs/sdks/capacitor/speech-recognition/): Turn the user's voice into prompts. - [Speech Synthesis](https://capawesome.io/docs/sdks/capacitor/speech-synthesis/): Read the generated responses aloud. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/llm/CHANGELOG.md). ## Breaking Changes See [BREAKING.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/llm/BREAKING.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/llm/LICENSE). # Capacitor Localization Plugin Capacitor plugin to read the user's localization preferences, such as preferred locales, time zone, and regional formatting settings. ## Features - 🌍 **Preferred locales**: Read the user's preferred locales, ordered by preference. - 💱 **Regional formatting**: Read currency, decimal, and grouping separators per locale. - ↔️ **Text direction**: Detect whether a locale is written left-to-right or right-to-left. - 📏 **Measurement system**: Detect the metric, US, or UK measurement system. - 🕐 **Settings**: Read the time zone, clock format, and first day of the week. - 🖥️ **Cross-platform**: Full support for Android, iOS, and Web. - 🤝 **Compatibility**: Works alongside the [App Language](https://capawesome.io/docs/sdks/capacitor/app-language/), [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/) and [Device Info](https://capawesome.io/docs/sdks/capacitor/device-info/) 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! ## Use Cases The Localization plugin is typically used to adapt an app to the user's language and regional preferences, for example: - **Localized formatting**: Format numbers and prices with the correct decimal separator, grouping separator, and currency symbol for the user's locale. - **Right-to-left layouts**: Switch the layout direction of your app based on the text direction of the user's preferred language. - **Unit display**: Show distances and weights in metric or imperial units depending on the user's measurement system. - **Date and time display**: Render times in the user's time zone and clock format, and start calendar views on the correct first day of the week. - **Language selection**: Preselect the best matching app language from the user's preferred locales. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-localization` 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-localization npx cap sync ``` No additional permissions or configuration are required on any platform. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to read the user's preferred locales and their regional formatting settings. ### Get the user's preferred locales Read the user's preferred locales, ordered by preference. The first entry is the most preferred locale. Each locale contains the language tag, region code, currency, number separators, text direction, and measurement system: ``` import { Localization } from '@capawesome/capacitor-localization'; const getLocales = async () => { const { locales } = await Localization.getLocales(); return locales; }; ``` ### Read the user's regional settings Read the user's regional formatting settings, such as the time zone, whether a 24-hour clock is preferred, and the first day of the week: ``` import { Localization } from '@capawesome/capacitor-localization'; const getSettings = async () => { const settings = await Localization.getSettings(); return settings; }; ``` ## API - [`getLocales()`](#getlocales) - [`getSettings()`](#getsettings) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### getLocales() ``` getLocales() => Promise ``` Get the user's preferred locales, ordered by preference. The first entry is the most preferred locale. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getSettings() ``` getSettings() => Promise ``` Get the user's regional formatting settings. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetLocalesResult | Prop | Type | Description | Since | | ------------- | ---------- | -------------------------------------------------------------------------------------------------- | ----- | | **`locales`** | `Locale[]` | The user's preferred locales, ordered by preference. The first entry is the most preferred locale. | 0.1.0 | #### Locale | Prop | Type | Description | Since | | ----------------------- | ------------------- | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | | **`currencyCode`** | \`string | null\` | The ISO 4217 currency code of the locale. Returns `null` if the currency cannot be determined. | | **`currencySymbol`** | \`string | null\` | The currency symbol of the locale. Returns `null` if the currency symbol cannot be determined. | | **`decimalSeparator`** | \`string | null\` | The character used to separate the integer part from the fractional part of a number. Returns `null` if the separator cannot be determined. | | **`groupingSeparator`** | \`string | null\` | The character used to group digits of the integer part of a number. Returns `null` if the separator cannot be determined. | | **`languageCode`** | `string` | The ISO 639 language code of the locale. | 0.1.0 | | **`languageTag`** | `string` | The BCP 47 language tag of the locale. | 0.1.0 | | **`measurementSystem`** | \`MeasurementSystem | null\` | The measurement system used by the locale. Returns `null` if the measurement system cannot be determined. | | **`regionCode`** | \`string | null\` | The ISO 3166 region code of the locale. Returns `null` if the region cannot be determined. | | **`textDirection`** | `TextDirection` | The writing direction of the locale's language. | 0.1.0 | #### GetSettingsResult | Prop | Type | Description | Since | | --------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`firstDayOfWeek`** | `number` | The first day of the week as configured by the user. The value follows the ISO 8601 convention where `1` is Monday and `7` is Sunday. | 0.1.0 | | **`timeZone`** | `string` | The identifier of the user's current time zone. | 0.1.0 | | **`uses24HourClock`** | `boolean` | Whether the user prefers a 24-hour clock over a 12-hour clock. | 0.1.0 | ### Type Aliases #### MeasurementSystem The measurement system used by a locale. - `metric`: The metric system (e.g. meters, kilograms). - `us`: The United States customary system (e.g. feet, pounds). - `uk`: The United Kingdom imperial system. `'metric' | 'us' | 'uk'` #### TextDirection The writing direction of a language. - `ltr`: Left-to-right (e.g. English, German). - `rtl`: Right-to-left (e.g. Arabic, Hebrew). `'ltr' | 'rtl'` ## Platform Support The plugin returns `null` for any field that a platform cannot determine. On the **Web** platform, `currencyCode`, `currencySymbol`, and `measurementSystem` are always `null`, because the browser does not expose this information. ## FAQ ### How is this plugin different from other similar plugins? It reads the user's complete localization picture in one fully typed API — ordered preferred locales, currency and number separators, text direction, measurement system, time zone, clock format, and first day of the week — across Android, iOS, and the Web. It's honest about platform limits, returning `null` for fields a platform can't determine. If you only need the current language, a minimal lookup is enough; if you want to format numbers, currencies, dates, and layouts to match each user's region, this plugin is designed for exactly that. ### Why are some locale fields `null` on the Web? The plugin returns `null` for any field that a platform cannot determine. On the Web, `currencyCode`, `currencySymbol`, and `measurementSystem` are always `null` because the browser does not expose this information. On Android and iOS, the plugin has full access to the system's localization settings. ### Do I need any permissions to use this plugin? No, reading the user's localization preferences does not require any permissions. No additional configuration is required on any platform either, so the plugin works right after [installation](#installation). ### How do I detect right-to-left languages? Every locale returned by `getLocales()` contains a `textDirection` property that is either `ltr` (left-to-right, e.g. English or German) or `rtl` (right-to-left, e.g. Arabic or Hebrew). You can use this value to switch the layout direction of your app. ### How is this plugin different from the App Language plugin? The Localization plugin reads the user's system-level preferences, such as preferred locales, time zone, and regional formatting settings. The [App Language](https://capawesome.io/docs/sdks/capacitor/app-language/) plugin, on the other hand, manages the app's own language override, independent of the device language. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Language](https://capawesome.io/docs/sdks/capacitor/app-language/): Manage the app's own language override, independent of the device language. - [Datetime Picker](https://capawesome.io/docs/sdks/capacitor/datetime-picker/): Native date and time selection with localization and theming support. - [Device Info](https://capawesome.io/docs/sdks/capacitor/device-info/): Read device information, such as the model, manufacturer, and operating system. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/localization/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/localization/LICENSE). # Capacitor Mail Composer Plugin Capacitor plugin to open the native email composer. ## Features - ✉️ **Compose**: Open the native email composer prefilled with recipients, subject and body. - 📎 **Attachments**: Attach one or more files from the file system or from base64 strings. - ✅ **Availability**: Check whether the device is able to compose and send emails. - 🌐 **Cross-platform**: Supports Android, iOS and the web (via `mailto:`). - 🤝 **Compatibility**: Works alongside the [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/), [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/) and [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/) 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! ## Use Cases The Mail Composer plugin is typically used whenever an app wants the user to send an email, for example: - **Support and feedback**: Open a prefilled email to your support address with a subject and body so users can reach out with one tap. - **Bug reports**: Attach log files or screenshots to a bug report email using the `attachments` option, either from the file system or directly from a base64 string. - **Sharing content**: Let users share content from your app via email with prefilled recipients, subject, and body. - **Invitations**: Prefill invitation emails with CC and BCC recipients that the user only needs to review and send. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-mail-composer` 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-mail-composer npx cap sync ``` ### Android #### Attachments To attach files, the plugin uses the [`FileProvider`](https://developer.android.com/reference/androidx/core/content/FileProvider) that Capacitor already registers for your app (authority `${applicationId}.fileprovider`). Make sure that the directories your attachments are stored in are covered by the `res/xml/file_paths.xml` resource of your app. Otherwise, the `composeMail(...)` method will reject with an error. Attachments that are provided as base64 strings are written to the cache directory of your app, which the default `res/xml/file_paths.xml` resource of a Capacitor app already covers with a `cache-path` entry. ### Web On the web, the plugin builds a `mailto:` URL and opens it. This has the following limitations: - Attachments are **not** supported and cause the `composeMail(...)` method to reject. - HTML bodies are **not** supported. The body is always sent as plain text. - The overall URL length is limited, so long bodies may be truncated. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to check whether the device can send emails and how to open the native email composer. ### Check whether the device can send emails Before opening the composer, check whether the device is able to compose and send emails. On iOS, this returns `true` only if a mail account is configured. On Android, it returns `true` if a mail app is installed. On the web, it always returns `true`: ``` import { MailComposer } from '@capawesome/capacitor-mail-composer'; const canComposeMail = async () => { const { canCompose } = await MailComposer.canComposeMail(); return canCompose; }; ``` ### Compose an email Open the native email composer prefilled with recipients, subject, and body. The user reviews the email and decides whether to send it. Note that on Android, the returned `status` is always `unknown`: ``` import { MailComposer } from '@capawesome/capacitor-mail-composer'; const composeMail = async () => { const { status } = await MailComposer.composeMail({ to: ['jane@example.com'], cc: ['john@example.com'], subject: 'Hello World', body: 'This is the body of the email.', }); return status; }; ``` ### Compose an email with attachments Attach a file from the file system using the `path` property, or attach a file that only exists in memory using the `data` and `name` properties. Attachments are not supported on the web: ``` import { MailComposer } from '@capawesome/capacitor-mail-composer'; const composeMailWithAttachments = async () => { await MailComposer.composeMail({ to: ['support@example.com'], subject: 'Bug report', attachments: [ { path: '/path/to/screenshot.png' }, { data: 'SGVsbG8gV29ybGQ=', name: 'log.txt' }, ], }); }; ``` ## API - [`canComposeMail()`](#cancomposemail) - [`composeMail(...)`](#composemail) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) ### canComposeMail() ``` canComposeMail() => Promise ``` Check whether the device is able to compose and send emails. On iOS, this returns `true` only if a mail account is configured. On Android, this returns `true` if a mail app is installed. On the web, this always returns `true` (best effort, not verifiable). **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### composeMail(...) ``` composeMail(options: ComposeMailOptions) => Promise ``` Open the native email composer prefilled with the provided data. The user reviews the email and decides whether to send it. The plugin never sends the email itself. | Param | Type | | ------------- | -------------------- | | **`options`** | `ComposeMailOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### CanComposeMailResult | Prop | Type | Description | Since | | ---------------- | --------- | ------------------------------------------------------ | ----- | | **`canCompose`** | `boolean` | Whether the device is able to compose and send emails. | 0.1.0 | #### ComposeMailResult | Prop | Type | Description | Since | | ------------ | ------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`status`** | `MailComposeStatus` | The status of the email compose operation. **Note**: On Android, the status is always `unknown` because the mail intent does not return a reliable result. | 0.1.0 | #### ComposeMailOptions | Prop | Type | Description | Default | Since | | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------- | ----- | | **`attachments`** | `MailAttachment[]` | The files to attach to the email. Attachments are not supported on the web. | | 0.1.0 | | **`bcc`** | `string[]` | The email addresses of the blind carbon copy (BCC) recipients. | | 0.1.0 | | **`body`** | `string` | The body of the email. | | 0.1.0 | | **`cc`** | `string[]` | The email addresses of the carbon copy (CC) recipients. | | 0.1.0 | | **`isHtml`** | `boolean` | Whether the body should be interpreted as HTML. **Note**: On Android, HTML is best-effort as many mail apps ignore it. On the web, HTML is not supported and the body is always sent as plain text. | `false` | 0.1.0 | | **`subject`** | `string` | The subject of the email. | | 0.1.0 | | **`to`** | `string[]` | The email addresses of the primary recipients. | | 0.1.0 | #### MailAttachment | Prop | Type | Description | Since | | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | | **`data`** | `string` | The content of the file to attach, encoded as a base64 string. Either `data` or `path` must be provided. If both are provided, `path` is used. **Attention**: The entire file is loaded into memory as a base64 string, which can lead to app crashes for large files. Use `path` instead whenever possible. | 0.2.0 | | **`name`** | `string` | The file name of the attachment, including the file extension. Must be provided if `data` is provided. Ignored if `path` is provided, because the file name is then derived from the path. | 0.2.0 | | **`path`** | `string` | The absolute file path or `file://` URI of the file to attach. Either `data` or `path` must be provided. | 0.2.0 | ### Type Aliases #### MailComposeStatus The status of an email compose operation. - `sent`: The email was sent. - `saved`: The email was saved as a draft. - `canceled`: The user canceled the operation. - `unknown`: The status is unknown. `'sent' | 'saved' | 'canceled' | 'unknown'` ## Platform Support Keep the following platform-specific behavior in mind: - **Status**: On Android, the `status` returned by `composeMail(...)` is always `unknown` because the mail intent does not return a reliable result. On iOS, the real status is returned via the delegate. On the web, the status is always `unknown`. - **HTML body**: On Android, the `isHtml` option is best-effort because many mail apps ignore HTML content. On the web, HTML is not supported at all. - **No mail account**: On iOS, `composeMail(...)` rejects as unavailable if no mail account is configured. Use `canComposeMail(...)` to check upfront. ## FAQ ### How is this plugin different from other similar plugins? It opens the native email composer prefilled with recipients, subject, body, and CC and BCC, and supports file attachments and HTML bodies through a fully typed API on Android, iOS, and the Web. A `canComposeMail()` check lets you confirm the device can actually send mail before you show the feature, and the user always stays in control of sending. If you only need a plain `mailto:` link, a simpler approach is fine; if you need attachments, rich bodies, and reliable capability detection, this plugin is built for exactly that. ### Does the plugin send the email itself? No, the plugin only opens the native email composer prefilled with the provided data. The user reviews the email and decides whether to send it. The email is always sent through the user's own mail app and account. ### Why is the status always `unknown` on Android? On Android, the mail intent does not return a reliable result, so the `status` returned by `composeMail` is always `unknown`. On iOS, the real status (`sent`, `saved`, or `canceled`) is returned. On the web, the status is always `unknown` as well. ### Why does `composeMail` reject on iOS? On iOS, `composeMail` rejects as unavailable if no mail account is configured on the device. Use `canComposeMail` upfront to check whether the device is able to compose and send emails, and only show your email feature if it returns `true`. ### Why does attaching a file fail on Android? On Android, the plugin shares attachments through the [`FileProvider`](https://developer.android.com/reference/androidx/core/content/FileProvider) that Capacitor already registers for your app. If the directory a file is stored in is not covered by the `res/xml/file_paths.xml` resource of your app, `composeMail` rejects with an error. See the [Installation](#installation) section for details. ### How do I attach a file that is not stored on the file system? Provide the content of the file as a base64 string using the `data` property of an attachment and set the `name` property to the file name including the file extension. This is useful for files that only exist in memory, for example a log file that you keep in IndexedDB. The plugin takes care of handing the content over to the mail app, so you do not need to write the file to the file system yourself. ### Why does my app crash when attaching large files? Attachments provided via the `data` property are loaded into memory as a base64 string, which can lead to out of memory (OOM) crashes for large files. Whenever the file is available on the file system, use the `path` property instead. ### Are attachments supported on the web? No, on the web the plugin builds a `mailto:` URL, which cannot carry attachments, so `composeMail` rejects if attachments are provided. Also keep in mind that HTML bodies are not supported on the web and long bodies may be truncated due to the URL length limit. ### Can I send HTML emails? Yes, set the `isHtml` option to `true` to have the body interpreted as HTML. On iOS, this is fully supported. On Android, it is best-effort because many mail apps ignore HTML content. On the web, HTML is not supported and the body is always sent as plain text. ## Related Plugins - [File Picker](https://capawesome.io/docs/sdks/capacitor/file-picker/): Let the user select files to attach to an email. - [Phone Dialer](https://capawesome.io/docs/sdks/capacitor/phone-dialer/): Open the native phone dialer prefilled with a phone number. - [SMS Composer](https://capawesome.io/docs/sdks/capacitor/sms-composer/): Open the native SMS composer prefilled with recipients and a message body. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/mail-composer/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/mail-composer/LICENSE). # Capacitor Managed Configurations Plugin Capacitor plugin to access managed configuration settings. ## Use Cases The Managed Configurations plugin is typically used in enterprise apps that are distributed through an EMM or MDM solution, for example: - **Preconfigured server settings**: Read a server URL or port that the organization's IT department has configured, instead of asking the user to enter it. - **Policy-driven features**: Use boolean configuration entries to enable or disable features per deployment, such as allowing downloads on cellular connections. - **Multi-tenant deployments**: Ship a single app to multiple organizations and adapt it at runtime based on the managed configuration values of each tenant. ## 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: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) ## 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. ## Usage The following example shows how to read managed configuration values as strings, numbers, and booleans. ### Read managed configuration values Fetch the value associated with a given key as a string, number, or boolean. The result contains `null` if no mapping exists for the given key. These methods are only available on Android and iOS: ``` 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/guide/deployment/distribute-managed-apps-dep575bfed86/web) with a MDM solution. ## FAQ ### What are managed configurations? Managed configurations are settings that an organization's EMM or MDM solution pushes to an app, for example a server URL or a feature toggle. This plugin lets your Capacitor app read these settings at runtime using the `getString`, `getNumber`, and `getBoolean` methods. ### Should I use this plugin or the Intune plugin? Organizations can deliver app configuration through two different channels, and administrators frequently mix them up. This plugin covers the **MDM channel**; the [Intune](https://capawesome.io/docs/sdks/capacitor/intune/) plugin covers the **MAM channel**: | | MDM channel (this plugin) | MAM channel ([Intune](https://capawesome.io/docs/sdks/capacitor/intune/)) | | -------------------------- | --------------------------------------------------------- | ------------------------------------------------------------------------- | | Device enrollment required | Yes | No | | Delivered via | `RestrictionsManager` / `com.apple.configuration.managed` | Intune App SDK (Intune service) | | Targeted at | The device | The signed-in account (identity) | | EMM vendor | Any EMM/MDM vendor | Microsoft Intune only | | Typical scenario | Corporate-owned, fully managed devices | BYOD / app protection without device management | If your organization deploys the app configuration policy with **"Managed devices"** as the delivery channel in the Intune admin center, use this plugin. If it is deployed with **"Managed apps"**, use the Intune plugin. Apps that must support both scenarios should read both channels. ### What happens if a configuration key does not exist? The `getString`, `getNumber`, and `getBoolean` methods return a result whose `value` is `null` if no mapping exists for the given key. Your app should handle this case, for example by falling back to a default value. ### Do I need to declare the managed configurations in my app? On Android, yes. You need to declare your app's managed configurations as described in [Define managed configurations](https://developer.android.com/work/managed-configurations#define-configuration) so that EMM solutions can discover and set them. See the [Installation](#installation) section for details. ### How can I test managed configurations during development? On Android, you can set up a device owner testing environment as described in [Set up device owner for testing](https://source.android.com/devices/tech/admin/testing-setup#set_up_the_device_owner_for_testing). On iOS, the app must be installed as a managed app through an MDM solution. See the [Test your implementation](#test-your-implementation) section for details. ### Is the Web platform supported? No, the `getString`, `getNumber`, and `getBoolean` methods are only available on Android and iOS. Managed configurations are a concept of mobile device management, which does not exist in the browser. ### Can I use this plugin with Ionic, React, Vue or Angular? Yes, the plugin is framework-agnostic. It works in any Capacitor app regardless of the web framework, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects. ## Related Plugins - [App Integrity](https://capawesome.io/docs/sdks/capacitor/app-integrity/): Verify app and device integrity using the Play Integrity API and App Attest. - [Intune](https://capawesome.io/docs/sdks/capacitor/intune/): Protect corporate data with Microsoft Intune Mobile Application Management (the MAM channel counterpart to this plugin). - [Root Detection](https://capawesome.io/docs/sdks/capacitor/root-detection/): Detect rooted and jailbroken devices. - [Secure Preferences](https://capawesome.io/docs/sdks/capacitor/secure-preferences/): Securely store key/value pairs such as passwords, tokens or other sensitive information. ## 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/). ## 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! # Capacitor MapLibre Plugin Unofficial Capacitor plugin to create native [MapLibre](https://maplibre.org/) maps on Android, iOS, and Web.[1](#fn:1) ## Features The Capacitor MapLibre plugin renders interactive maps with the native MapLibre SDKs on Android and iOS, and with MapLibre GL JS on the Web. Here are some of the key features: - 🗺️ **Native Rendering**: Renders a native map view on Android and iOS instead of a map inside the web view. - 🆓 **No API Key**: Built on the open-source MapLibre stack, so no vendor account or API key is required. - 🎨 **Map Styles**: Load any MapLibre style from a URL or a JSON string, and swap it at runtime. - 🎥 **Camera Controls**: Set center, zoom, bearing and pitch, or fit bounds — animated or instantly. - 📍 **Markers**: Add markers with custom icons, anchors, sizes, opacity and rotation, and update them with smooth animations. - ✋ **Draggable Markers**: Let users drag markers and react to drag events on Android and Web. - 〰️ **Polylines**: Draw and update routes and tracks with custom color, width and opacity. - 🧬 **GeoJSON**: Add GeoJSON sources from data or URL and render them with styled line, fill and circle layers. - 🧭 **User Location**: Display the location of the user and follow it with course or heading tracking modes. - 🎛️ **Gesture Controls**: Enable or disable panning, zooming, rotating and tilting at any time. - 🧩 **Multi-Instance**: Create and control multiple maps at the same time by ID. - 🪟 **DOM Overlays**: Place HTML elements such as floating action buttons or bottom sheets above the map — they just work. - 🔄 **Automatic Sync**: The native map follows the position and scroll offset of its element automatically. - 🖥️ **Cross-platform**: Supports Android, iOS, and Web. - 📦 **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! ## Use Cases The MapLibre plugin is typically used wherever you want to show geographical data in your app, for example: - **Delivery & fleet tracking**: Follow vehicles live with animated markers and rotation. - **Store & branch locators**: Display locations as markers and zoom to the results of a search. - **Outdoor & fitness apps**: Render recorded tracks as polylines and follow the location of the user. - **Field service**: Show jobs, areas and routes from GeoJSON data on a native map. - **Real estate & travel**: Browse listings on a map with custom styles that match your brand. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.x.x | >=8.x.x | Active support | ## Guides - [A Google Maps Alternative for Capacitor Apps](https://capawesome.io/blog/alternative-to-the-capacitor-google-maps-plugin/): Compares `@capacitor/google-maps` with this plugin — no billing account, no API key, and Web 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-maplibre` 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-maplibre maplibre-gl npx cap sync ``` ### Android On Android, the plugin uses the [MapLibre Native SDK for Android](https://github.com/maplibre/maplibre-native). #### Variables This plugin will use the following project variables (defined in your app's `variables.gradle` file): - `$mapLibreVersion` version of `org.maplibre.gl:android-sdk-vulkan-opengl` (default: `13.5.0`) - `$mapLibreAnnotationVersion` version of `org.maplibre.gl:android-plugin-annotation` (default: `4.0.0`) #### Permissions Location permissions are only required if you want to display the location of the user on the map. In that case, add the following elements to your `AndroidManifest.xml` before or after the `application` tag: ``` ``` ### iOS On iOS, the plugin uses the [MapLibre Native SDK for iOS](https://github.com/maplibre/maplibre-native). It can be integrated via Swift Package Manager or CocoaPods. If you want to display the location of the user on the map, add the `NSLocationWhenInUseUsageDescription` key to your `Info.plist` file: ``` NSLocationWhenInUseUsageDescription The app needs your location to display it on the map. ``` Without this key, `checkPermissions()`, `requestPermissions()` and `enableUserLocation(...)` reject with an error. ### Web The web implementation uses [MapLibre GL JS](https://maplibre.org/maplibre-gl-js/docs/), which is not bundled with the plugin. Its stylesheet is not bundled either, so you must import it once in your app: ``` import 'maplibre-gl/dist/maplibre-gl.css'; ``` Without the stylesheet, the map canvas and the markers are mispositioned. The `maplibre-gl` package is only required on the Web platform. On Android and iOS, the map is rendered by the native MapLibre SDKs. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to use the plugin. ### Getting started Add an **empty** element to your app that defines the position and size of the map: ```
``` ``` #map { /* The map must not have a background so that the native map stays visible. */ background: transparent; height: 400px; width: 100%; } ``` On Android and iOS, the map is rendered as a native view **behind** the web view. Your app must therefore fulfill the following contract: - The map element must be **empty**. Do not render any content into it. - The map element **and every ancestor** that covers the map region (including `body`) must have a transparent background (`background: transparent`). Otherwise the web view paints over the native map and the map is not visible. Elements that are not ancestors of the map element, such as floating action buttons or bottom sheets, can be layered above the map as usual. The plugin keeps the position and size of the native map in sync with the map element automatically, including while the page is scrolled or resized. ### Create a map ``` import { MapLibre } from '@capawesome/capacitor-maplibre'; const createMap = async () => { await MapLibre.createMap({ center: { latitude: 48.137154, longitude: 11.576124 }, elementId: 'map', mapId: 'my-map', styleUrl: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json', zoom: 12, }); }; ``` If no style is provided, the [MapLibre demo style](https://demotiles.maplibre.org/style.json) is used, which is a test style and not intended for production use (see [FAQ](#where-do-i-get-map-styles-and-do-i-need-an-api-key)). ### Move the camera ``` import { MapLibre } from '@capawesome/capacitor-maplibre'; const moveCamera = async () => { await MapLibre.setCamera({ animate: true, animationDuration: 1000, bearing: 30, center: { latitude: 52.520008, longitude: 13.404954 }, mapId: 'my-map', pitch: 45, zoom: 11, }); await MapLibre.fitBounds({ animate: true, bounds: { northeast: { latitude: 55.058347, longitude: 15.041896 }, southwest: { latitude: 47.270111, longitude: 5.866342 }, }, mapId: 'my-map', padding: { bottom: 32, left: 32, right: 32, top: 32 }, }); }; ``` ### Add markers ``` import { MapLibre, MarkerIconAnchor } from '@capawesome/capacitor-maplibre'; const addMarker = async () => { await MapLibre.addMarker({ mapId: 'my-map', marker: { coordinates: { latitude: 48.137154, longitude: 11.576124 }, iconAnchor: MarkerIconAnchor.Center, iconSize: { height: 32, width: 32 }, iconUrl: 'https://example.com/marker.png', id: 'my-marker', }, }); }; const moveMarker = async () => { await MapLibre.updateMarkerById({ animate: true, animationDuration: 1000, coordinates: { latitude: 48.370545, longitude: 10.89779 }, mapId: 'my-map', markerId: 'my-marker', rotation: 90, }); }; const removeMarker = async () => { await MapLibre.removeMarkerById({ mapId: 'my-map', markerId: 'my-marker' }); }; ``` ### Add polylines ``` import { MapLibre } from '@capawesome/capacitor-maplibre'; const addPolyline = async () => { await MapLibre.addPolyline({ mapId: 'my-map', polyline: { color: '#3887be', coordinates: [ { latitude: 48.137154, longitude: 11.576124 }, { latitude: 49.45203, longitude: 11.076665 }, { latitude: 52.520008, longitude: 13.404954 }, ], id: 'my-polyline', width: 5, }, }); }; ``` ### Add GeoJSON data ``` import { LayerType, MapLibre } from '@capawesome/capacitor-maplibre'; const addGeoJson = async () => { await MapLibre.addGeoJsonSource({ mapId: 'my-map', sourceId: 'my-source', url: 'https://example.com/routes.geojson', }); await MapLibre.addLayer({ layerId: 'my-layer', mapId: 'my-map', paint: { lineColor: '#3887be', lineWidth: 4 }, sourceId: 'my-source', type: LayerType.Line, }); }; ``` ### Display the location of the user ``` import { MapLibre, UserTrackingMode } from '@capawesome/capacitor-maplibre'; const enableUserLocation = async () => { let status = await MapLibre.checkPermissions(); if (status.location === 'prompt') { status = await MapLibre.requestPermissions(); } if (status.location !== 'granted') { return; } await MapLibre.enableUserLocation({ mapId: 'my-map', trackingMode: UserTrackingMode.Follow, }); }; ``` ### Listen for events ``` import { MapLibre } from '@capawesome/capacitor-maplibre'; const addListeners = async () => { await MapLibre.addListener('mapClick', event => { console.log('Map clicked:', event.coordinates); }); await MapLibre.addListener('markerClick', event => { console.log('Marker clicked:', event.markerId); }); await MapLibre.addListener('cameraIdle', event => { console.log('Camera idle:', event.camera); }); }; ``` ### Destroy a map ``` import { MapLibre } from '@capawesome/capacitor-maplibre'; const destroyMap = async () => { await MapLibre.destroyMap({ mapId: 'my-map' }); }; ``` ## API - [`addGeoJsonSource(...)`](#addgeojsonsource) - [`addLayer(...)`](#addlayer) - [`addMarker(...)`](#addmarker) - [`addMarkers(...)`](#addmarkers) - [`addPolyline(...)`](#addpolyline) - [`addPolylines(...)`](#addpolylines) - [`checkPermissions()`](#checkpermissions) - [`createMap(...)`](#createmap) - [`destroyMap(...)`](#destroymap) - [`disableUserLocation(...)`](#disableuserlocation) - [`elementFromPointResult(...)`](#elementfrompointresult) - [`enableUserLocation(...)`](#enableuserlocation) - [`fitBounds(...)`](#fitbounds) - [`getCamera(...)`](#getcamera) - [`removeAllMarkers(...)`](#removeallmarkers) - [`removeAllPolylines(...)`](#removeallpolylines) - [`removeGeoJsonSourceById(...)`](#removegeojsonsourcebyid) - [`removeLayerById(...)`](#removelayerbyid) - [`removeMarkerById(...)`](#removemarkerbyid) - [`removeMarkersByIds(...)`](#removemarkersbyids) - [`removePolylineById(...)`](#removepolylinebyid) - [`removePolylinesByIds(...)`](#removepolylinesbyids) - [`requestPermissions()`](#requestpermissions) - [`setCamera(...)`](#setcamera) - [`setFrame(...)`](#setframe) - [`setGesturesEnabled(...)`](#setgesturesenabled) - [`setStyle(...)`](#setstyle) - [`updateGeoJsonSourceById(...)`](#updategeojsonsourcebyid) - [`updateMarkerById(...)`](#updatemarkerbyid) - [`updatePolylineById(...)`](#updatepolylinebyid) - [`addListener('cameraIdle', ...)`](#addlistenercameraidle-) - [`addListener('cameraMoveStarted', ...)`](#addlistenercameramovestarted-) - [`addListener('elementFromPointRequest', ...)`](#addlistenerelementfrompointrequest-) - [`addListener('mapClick', ...)`](#addlistenermapclick-) - [`addListener('markerClick', ...)`](#addlistenermarkerclick-) - [`addListener('markerDrag', ...)`](#addlistenermarkerdrag-) - [`addListener('markerDragEnd', ...)`](#addlistenermarkerdragend-) - [`addListener('markerDragStart', ...)`](#addlistenermarkerdragstart-) - [`addListener('userLocationChange', ...)`](#addlisteneruserlocationchange-) - [`removeAllListeners()`](#removealllisteners) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### addGeoJsonSource(...) ``` addGeoJsonSource(options: AddGeoJsonSourceOptions) => Promise ``` Add a GeoJSON source to the map. Use `addLayer(...)` to render the data of the source. | Param | Type | | ------------- | ------------------------- | | **`options`** | `AddGeoJsonSourceOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addLayer(...) ``` addLayer(options: AddLayerOptions) => Promise ``` Add a layer to the map that renders the data of a GeoJSON source. | Param | Type | | ------------- | ----------------- | | **`options`** | `AddLayerOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addMarker(...) ``` addMarker(options: AddMarkerOptions) => Promise ``` Add a marker to the map. | Param | Type | | ------------- | ------------------ | | **`options`** | `AddMarkerOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addMarkers(...) ``` addMarkers(options: AddMarkersOptions) => Promise ``` Add multiple markers to the map. | Param | Type | | ------------- | ------------------- | | **`options`** | `AddMarkersOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addPolyline(...) ``` addPolyline(options: AddPolylineOptions) => Promise ``` Add a polyline to the map. | Param | Type | | ------------- | -------------------- | | **`options`** | `AddPolylineOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addPolylines(...) ``` addPolylines(options: AddPolylinesOptions) => Promise ``` Add multiple polylines to the map. | Param | Type | | ------------- | --------------------- | | **`options`** | `AddPolylinesOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### checkPermissions() ``` checkPermissions() => Promise ``` Check the location permission. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### createMap(...) ``` createMap(options: CreateMapOptions) => Promise ``` Create a new map. The map is rendered into the element with the given element ID. On Android and iOS, the map is rendered as a native view behind the web view, so the element and everything above the map must be transparent. The promise resolves as soon as the style of the map has finished loading. | Param | Type | | ------------- | ------------------ | | **`options`** | `CreateMapOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### destroyMap(...) ``` destroyMap(options: DestroyMapOptions) => Promise ``` Destroy a map and release all its resources. | Param | Type | | ------------- | ------------------- | | **`options`** | `DestroyMapOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### disableUserLocation(...) ``` disableUserLocation(options: DisableUserLocationOptions) => Promise ``` Stop displaying the location of the user on the map. | Param | Type | | ------------- | ---------------------------- | | **`options`** | `DisableUserLocationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### elementFromPointResult(...) ``` elementFromPointResult(options: ElementFromPointResultOptions) => Promise ``` Answer an `elementFromPointRequest` event. This method is called automatically by the plugin and must not be called manually. Only available on Android. | Param | Type | | ------------- | ------------------------------- | | **`options`** | `ElementFromPointResultOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### enableUserLocation(...) ``` enableUserLocation(options: EnableUserLocationOptions) => Promise ``` Display the location of the user on the map. Call this method again to change the tracking mode. | Param | Type | | ------------- | --------------------------- | | **`options`** | `EnableUserLocationOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### fitBounds(...) ``` fitBounds(options: FitBoundsOptions) => Promise ``` Move the camera so that the given bounds are visible. | Param | Type | | ------------- | ------------------ | | **`options`** | `FitBoundsOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### getCamera(...) ``` getCamera(options: GetCameraOptions) => Promise ``` Get the current camera of the map. | Param | Type | | ------------- | ------------------ | | **`options`** | `GetCameraOptions` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllMarkers(...) ``` removeAllMarkers(options: RemoveAllMarkersOptions) => Promise ``` Remove all markers from the map. | Param | Type | | ------------- | ------------------------- | | **`options`** | `RemoveAllMarkersOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removeAllPolylines(...) ``` removeAllPolylines(options: RemoveAllPolylinesOptions) => Promise ``` Remove all polylines from the map. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RemoveAllPolylinesOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removeGeoJsonSourceById(...) ``` removeGeoJsonSourceById(options: RemoveGeoJsonSourceByIdOptions) => Promise ``` Remove a GeoJSON source from the map. All layers that use the source must be removed first. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `RemoveGeoJsonSourceByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removeLayerById(...) ``` removeLayerById(options: RemoveLayerByIdOptions) => Promise ``` Remove a layer from the map. | Param | Type | | ------------- | ------------------------ | | **`options`** | `RemoveLayerByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removeMarkerById(...) ``` removeMarkerById(options: RemoveMarkerByIdOptions) => Promise ``` Remove a marker from the map. | Param | Type | | ------------- | ------------------------- | | **`options`** | `RemoveMarkerByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removeMarkersByIds(...) ``` removeMarkersByIds(options: RemoveMarkersByIdsOptions) => Promise ``` Remove multiple markers from the map. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RemoveMarkersByIdsOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removePolylineById(...) ``` removePolylineById(options: RemovePolylineByIdOptions) => Promise ``` Remove a polyline from the map. | Param | Type | | ------------- | --------------------------- | | **`options`** | `RemovePolylineByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### removePolylinesByIds(...) ``` removePolylinesByIds(options: RemovePolylinesByIdsOptions) => Promise ``` Remove multiple polylines from the map. | Param | Type | | ------------- | ----------------------------- | | **`options`** | `RemovePolylinesByIdsOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### requestPermissions() ``` requestPermissions() => Promise ``` Request the location permission. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### setCamera(...) ``` setCamera(options: SetCameraOptions) => Promise ``` Move the camera of the map. Only the provided properties are changed. | Param | Type | | ------------- | ------------------ | | **`options`** | `SetCameraOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setFrame(...) ``` setFrame(options: SetFrameOptions) => Promise ``` Update the position and size of the map viewport. This method is called automatically by the plugin and usually does not need to be called manually. Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `SetFrameOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setGesturesEnabled(...) ``` setGesturesEnabled(options: SetGesturesEnabledOptions) => Promise ``` Enable or disable the gestures of the map. Only the provided gestures are changed. | Param | Type | | ------------- | --------------------------- | | **`options`** | `SetGesturesEnabledOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### setStyle(...) ``` setStyle(options: SetStyleOptions) => Promise ``` Load a new style into the map. All markers, polylines, sources and layers must be added again after the new style has been loaded. | Param | Type | | ------------- | ----------------- | | **`options`** | `SetStyleOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updateGeoJsonSourceById(...) ``` updateGeoJsonSourceById(options: UpdateGeoJsonSourceByIdOptions) => Promise ``` Update the data of a GeoJSON source. | Param | Type | | ------------- | -------------------------------- | | **`options`** | `UpdateGeoJsonSourceByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updateMarkerById(...) ``` updateMarkerById(options: UpdateMarkerByIdOptions) => Promise ``` Update a marker on the map. Only the provided properties are changed. | Param | Type | | ------------- | ------------------------- | | **`options`** | `UpdateMarkerByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### updatePolylineById(...) ``` updatePolylineById(options: UpdatePolylineByIdOptions) => Promise ``` Update a polyline on the map. Only the provided properties are changed. | Param | Type | | ------------- | --------------------------- | | **`options`** | `UpdatePolylineByIdOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### addListener('cameraIdle', ...) ``` addListener(eventName: 'cameraIdle', listenerFunc: (event: CameraIdleEvent) => void) => Promise ``` Called when the camera of a map has stopped moving. | Param | Type | | ------------------ | ---------------------------------- | | **`eventName`** | `'cameraIdle'` | | **`listenerFunc`** | `(event: CameraIdleEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('cameraMoveStarted', ...) ``` addListener(eventName: 'cameraMoveStarted', listenerFunc: (event: CameraMoveStartedEvent) => void) => Promise ``` Called when the camera of a map has started moving. | Param | Type | | ------------------ | ----------------------------------------- | | **`eventName`** | `'cameraMoveStarted'` | | **`listenerFunc`** | `(event: CameraMoveStartedEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('elementFromPointRequest', ...) ``` addListener(eventName: 'elementFromPointRequest', listenerFunc: (event: ElementFromPointRequestEvent) => void) => Promise ``` Called when the plugin needs to know which map is located at a point of the screen. This event is handled automatically by the plugin and must not be handled manually. Only available on Android. | Param | Type | | ------------------ | ----------------------------------------------- | | **`eventName`** | `'elementFromPointRequest'` | | **`listenerFunc`** | `(event: ElementFromPointRequestEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('mapClick', ...) ``` addListener(eventName: 'mapClick', listenerFunc: (event: MapClickEvent) => void) => Promise ``` Called when the user taps on a map. | Param | Type | | ------------------ | -------------------------------- | | **`eventName`** | `'mapClick'` | | **`listenerFunc`** | `(event: MapClickEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('markerClick', ...) ``` addListener(eventName: 'markerClick', listenerFunc: (event: MarkerClickEvent) => void) => Promise ``` Called when the user taps on a marker. | Param | Type | | ------------------ | ----------------------------------- | | **`eventName`** | `'markerClick'` | | **`listenerFunc`** | `(event: MarkerClickEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('markerDrag', ...) ``` addListener(eventName: 'markerDrag', listenerFunc: (event: MarkerDragEvent) => void) => Promise ``` Called while the user drags a marker. Only available on Android and Web. | Param | Type | | ------------------ | ---------------------------------- | | **`eventName`** | `'markerDrag'` | | **`listenerFunc`** | `(event: MarkerDragEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('markerDragEnd', ...) ``` addListener(eventName: 'markerDragEnd', listenerFunc: (event: MarkerDragEndEvent) => void) => Promise ``` Called when the user has stopped dragging a marker. Only available on Android and Web. | Param | Type | | ------------------ | ------------------------------------- | | **`eventName`** | `'markerDragEnd'` | | **`listenerFunc`** | `(event: MarkerDragEndEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('markerDragStart', ...) ``` addListener(eventName: 'markerDragStart', listenerFunc: (event: MarkerDragStartEvent) => void) => Promise ``` Called when the user has started dragging a marker. Only available on Android and Web. | Param | Type | | ------------------ | --------------------------------------- | | **`eventName`** | `'markerDragStart'` | | **`listenerFunc`** | `(event: MarkerDragStartEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### addListener('userLocationChange', ...) ``` addListener(eventName: 'userLocationChange', listenerFunc: (event: UserLocationChangeEvent) => void) => Promise ``` Called when the location of the user changes. The event is only emitted while the location of the user is displayed on the map. | Param | Type | | ------------------ | ------------------------------------------ | | **`eventName`** | `'userLocationChange'` | | **`listenerFunc`** | `(event: UserLocationChangeEvent) => void` | **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### removeAllListeners() ``` removeAllListeners() => Promise ``` Remove all listeners for this plugin. **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### AddGeoJsonSourceOptions | Prop | Type | Description | Since | | -------------- | ------------------------- | -------------------------------------------------------------------------------------------- | ----- | | **`data`** | `Record` | The GeoJSON data of the source. Exactly one of `data` and `url` must be provided. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`sourceId`** | `string` | The unique identifier of the source. | 0.1.0 | | **`url`** | `string` | The URL of the GeoJSON data of the source. Exactly one of `data` and `url` must be provided. | 0.1.0 | #### AddLayerOptions | Prop | Type | Description | Since | | ------------------ | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`belowLayerId`** | `string` | The unique identifier of the layer below which the new layer is inserted. If not provided, the layer is added on top of all other layers. | 0.1.0 | | **`layerId`** | `string` | The unique identifier of the layer. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`maxZoom`** | `number` | The maximum zoom level at which the layer is visible. | 0.1.0 | | **`minZoom`** | `number` | The minimum zoom level at which the layer is visible. | 0.1.0 | | **`paint`** | `LayerPaint` | The paint properties of the layer. | 0.1.0 | | **`sourceId`** | `string` | The unique identifier of the source whose data is rendered by the layer. | 0.1.0 | | **`type`** | `LayerType` | The type of the layer. | 0.1.0 | #### LayerPaint The paint properties of a layer. Properties that do not apply to the type of the layer are ignored. | Prop | Type | Description | Since | | ----------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----- | | **`circleColor`** | `string` | The fill color of the circles as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. Only applies to layers of type `circle`. | 0.1.0 | | **`circleOpacity`** | `number` | The opacity of the circles as a value between `0` and `1`. Only applies to layers of type `circle`. | 0.1.0 | | **`circleRadius`** | `number` | The radius of the circles in CSS pixels. Only applies to layers of type `circle`. | 0.1.0 | | **`circleStrokeColor`** | `string` | The stroke color of the circles as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. Only applies to layers of type `circle`. | 0.1.0 | | **`circleStrokeWidth`** | `number` | The stroke width of the circles in CSS pixels. Only applies to layers of type `circle`. | 0.1.0 | | **`fillColor`** | `string` | The fill color of the areas as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. Only applies to layers of type `fill`. | 0.1.0 | | **`fillOpacity`** | `number` | The opacity of the areas as a value between `0` and `1`. Only applies to layers of type `fill`. | 0.1.0 | | **`fillOutlineColor`** | `string` | The outline color of the areas as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. Only applies to layers of type `fill`. | 0.1.0 | | **`lineColor`** | `string` | The color of the lines as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. Only applies to layers of type `line`. | 0.1.0 | | **`lineOpacity`** | `number` | The opacity of the lines as a value between `0` and `1`. Only applies to layers of type `line`. | 0.1.0 | | **`lineWidth`** | `number` | The width of the lines in CSS pixels. Only applies to layers of type `line`. | 0.1.0 | #### AddMarkerOptions | Prop | Type | Description | Since | | ------------ | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`marker`** | `Marker` | The marker to add. | 0.1.0 | #### Marker A marker on a map. | Prop | Type | Description | Default | Since | | ----------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------- | ------------------------- | ----- | | **`coordinates`** | `LatLng` | The geographical coordinate of the marker. | | 0.1.0 | | **`draggable`** | `boolean` | Whether the user can drag the marker. Only available on Android and Web. | `false` | 0.1.0 | | **`iconAnchor`** | `MarkerIconAnchor` | The part of the icon that is placed on the coordinate of the marker. | `MarkerIconAnchor.Bottom` | 0.1.0 | | **`iconSize`** | `MarkerIconSize` | The size the icon is scaled to. If not provided, the intrinsic size of the icon is used. | | 0.1.0 | | **`iconUrl`** | `string` | The URL of the icon of the marker. Must be a `https` URL or a data URI. If not provided, a default pin icon is used. | | 0.1.0 | | **`id`** | `string` | The unique identifier of the marker. | | 0.1.0 | | **`opacity`** | `number` | The opacity of the marker as a value between `0` and `1`. | `1` | 0.1.0 | | **`rotation`** | `number` | The rotation of the icon in degrees clockwise. | `0` | 0.1.0 | #### LatLng A geographical coordinate. | Prop | Type | Description | Since | | --------------- | -------- | ------------------------- | ----- | | **`latitude`** | `number` | The latitude in degrees. | 0.1.0 | | **`longitude`** | `number` | The longitude in degrees. | 0.1.0 | #### MarkerIconSize The size of the icon of a marker in CSS pixels. | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------- | ----- | | **`height`** | `number` | The height of the icon in CSS pixels. | 0.1.0 | | **`width`** | `number` | The width of the icon in CSS pixels. | 0.1.0 | #### AddMarkersOptions | Prop | Type | Description | Since | | ------------- | ---------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markers`** | `Marker[]` | The markers to add. | 0.1.0 | #### AddPolylineOptions | Prop | Type | Description | Since | | -------------- | ---------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`polyline`** | `Polyline` | The polyline to add. | 0.1.0 | #### Polyline A polyline on a map. | Prop | Type | Description | Default | Since | | ----------------- | ---------- | ----------------------------------------------------------------------------------------- | ----------- | ----- | | **`color`** | `string` | The color of the polyline as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. | `'#3887be'` | 0.1.0 | | **`coordinates`** | `LatLng[]` | The geographical coordinates of the polyline. | | 0.1.0 | | **`id`** | `string` | The unique identifier of the polyline. | | 0.1.0 | | **`opacity`** | `number` | The opacity of the polyline as a value between `0` and `1`. | `1` | 0.1.0 | | **`width`** | `number` | The width of the polyline in CSS pixels. | `4` | 0.1.0 | #### AddPolylinesOptions | Prop | Type | Description | Since | | --------------- | ------------ | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`polylines`** | `Polyline[]` | The polylines to add. | 0.1.0 | #### PermissionStatus | Prop | Type | Description | Since | | -------------- | ----------------- | ------------------------------------------------ | ----- | | **`location`** | `PermissionState` | The permission state of the location permission. | 0.1.0 | #### CreateMapOptions | Prop | Type | Description | Default | Since | | --------------- | ----------------- | ------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | ----- | | **`bearing`** | `number` | The direction the camera points in, in degrees clockwise from north. | `0` | 0.1.0 | | **`center`** | `LatLng` | The geographical coordinate at the center of the map. If not provided, the center of the style is used. | | 0.1.0 | | **`elementId`** | `string` | The ID of the DOM element the map is rendered into. The element must be empty and must not have a background. | | 0.1.0 | | **`gestures`** | `GestureSettings` | The gestures the user can perform on the map. | | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | | 0.1.0 | | **`maxZoom`** | `number` | The maximum zoom level of the map. If not provided, the maximum zoom level of the style is used. | | 0.1.0 | | **`minZoom`** | `number` | The minimum zoom level of the map. If not provided, the minimum zoom level of the style is used. | | 0.1.0 | | **`pitch`** | `number` | The tilt of the camera in degrees, measured from the plane of the map. | `0` | 0.1.0 | | **`styleJson`** | `string` | The style of the map as a JSON string. At most one of `styleJson` and `styleUrl` may be provided. | | 0.1.0 | | **`styleUrl`** | `string` | The URL of the style of the map. At most one of `styleJson` and `styleUrl` may be provided. | `'https://demotiles.maplibre.org/style.json'` | 0.1.0 | | **`zoom`** | `number` | The zoom level of the map. | `0` | 0.1.0 | #### GestureSettings The gestures the user can perform on a map. | Prop | Type | Description | Default | Since | | ------------ | --------- | --------------------------------------------------------- | ------- | ----- | | **`pan`** | `boolean` | Whether the user can move the camera by dragging the map. | `true` | 0.1.0 | | **`rotate`** | `boolean` | Whether the user can rotate the camera. | `true` | 0.1.0 | | **`tilt`** | `boolean` | Whether the user can tilt the camera. | `true` | 0.1.0 | | **`zoom`** | `boolean` | Whether the user can zoom the camera. | `true` | 0.1.0 | #### DestroyMapOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### DisableUserLocationOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### ElementFromPointResultOptions | Prop | Type | Description | Since | | --------------- | -------- | ------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | **`mapId`** | \`string | null\` | The unique identifier of the map at the requested point. Must be `null` if no map is located at the requested point. | | **`requestId`** | `string` | The unique identifier of the request. | 0.1.0 | #### EnableUserLocationOptions | Prop | Type | Description | Default | Since | | ------------------ | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | ----------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | | 0.1.0 | | **`trackingMode`** | `UserTrackingMode` | The mode used to track the location of the user with the camera. On Web, `FollowWithCourse` and `FollowWithHeading` behave like `Follow`. | `UserTrackingMode.None` | 0.1.0 | #### FitBoundsOptions | Prop | Type | Description | Default | Since | | ----------------------- | --------- | ---------------------------------------------------------- | ------- | ----- | | **`animate`** | `boolean` | Whether the camera movement is animated. | `false` | 0.1.0 | | **`animationDuration`** | `number` | The duration of the animation in milliseconds. | `300` | 0.1.0 | | **`bounds`** | `Bounds` | The bounds that must be visible after the camera movement. | | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | | 0.1.0 | | **`maxZoom`** | `number` | The maximum zoom level the camera may reach. | | 0.1.0 | | **`padding`** | `Padding` | The padding between the bounds and the edges of the map. | | 0.1.0 | #### Bounds A geographical area, defined by its southwest and northeast corner. | Prop | Type | Description | Since | | --------------- | -------- | --------------------------------- | ----- | | **`northeast`** | `LatLng` | The northeast corner of the area. | 0.1.0 | | **`southwest`** | `LatLng` | The southwest corner of the area. | 0.1.0 | #### Padding The padding between the content of a map and its edges in CSS pixels. | Prop | Type | Description | Default | Since | | ------------ | -------- | --------------------------------------------- | ------- | ----- | | **`bottom`** | `number` | The padding at the bottom edge in CSS pixels. | `0` | 0.1.0 | | **`left`** | `number` | The padding at the left edge in CSS pixels. | `0` | 0.1.0 | | **`right`** | `number` | The padding at the right edge in CSS pixels. | `0` | 0.1.0 | | **`top`** | `number` | The padding at the top edge in CSS pixels. | `0` | 0.1.0 | #### GetCameraResult | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------ | ----- | | **`camera`** | `Camera` | The current camera of the map. | 0.1.0 | #### Camera The camera of a map. | Prop | Type | Description | Since | | ------------- | -------- | ---------------------------------------------------------------------- | ----- | | **`bearing`** | `number` | The direction the camera points in, in degrees clockwise from north. | 0.1.0 | | **`center`** | `LatLng` | The geographical coordinate at the center of the map. | 0.1.0 | | **`pitch`** | `number` | The tilt of the camera in degrees, measured from the plane of the map. | 0.1.0 | | **`zoom`** | `number` | The zoom level of the map. | 0.1.0 | #### GetCameraOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### RemoveAllMarkersOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### RemoveAllPolylinesOptions | Prop | Type | Description | Since | | ----------- | -------- | --------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### RemoveGeoJsonSourceByIdOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------ | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`sourceId`** | `string` | The unique identifier of the source. | 0.1.0 | #### RemoveLayerByIdOptions | Prop | Type | Description | Since | | ------------- | -------- | ----------------------------------- | ----- | | **`layerId`** | `string` | The unique identifier of the layer. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### RemoveMarkerByIdOptions | Prop | Type | Description | Since | | -------------- | -------- | ------------------------------------ | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | 0.1.0 | #### RemoveMarkersByIdsOptions | Prop | Type | Description | Since | | --------------- | ---------- | -------------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerIds`** | `string[]` | The unique identifiers of the markers. | 0.1.0 | #### RemovePolylineByIdOptions | Prop | Type | Description | Since | | ---------------- | -------- | -------------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`polylineId`** | `string` | The unique identifier of the polyline. | 0.1.0 | #### RemovePolylinesByIdsOptions | Prop | Type | Description | Since | | ----------------- | ---------- | ---------------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`polylineIds`** | `string[]` | The unique identifiers of the polylines. | 0.1.0 | #### SetCameraOptions | Prop | Type | Description | Default | Since | | ----------------------- | --------- | ---------------------------------------------------------------------- | ------- | ----- | | **`animate`** | `boolean` | Whether the camera movement is animated. | `false` | 0.1.0 | | **`animationDuration`** | `number` | The duration of the animation in milliseconds. | `300` | 0.1.0 | | **`bearing`** | `number` | The direction the camera points in, in degrees clockwise from north. | | 0.1.0 | | **`center`** | `LatLng` | The geographical coordinate at the center of the map. | | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | | 0.1.0 | | **`padding`** | `Padding` | The padding between the content of the map and its edges. | | 0.1.0 | | **`pitch`** | `number` | The tilt of the camera in degrees, measured from the plane of the map. | | 0.1.0 | | **`zoom`** | `number` | The zoom level of the map. | | 0.1.0 | #### SetFrameOptions | Prop | Type | Description | Since | | ----------------- | ---------------- | ------------------------------------------------------ | ----- | | **`contentSize`** | `MapContentSize` | The size of the scrollable content of the map element. | 0.1.0 | | **`frame`** | `MapFrame` | The new position and size of the map viewport. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### MapContentSize The size of the scrollable content of a map element in CSS pixels. | Prop | Type | Description | Since | | ------------ | -------- | ---------------------------------------- | ----- | | **`height`** | `number` | The height of the content in CSS pixels. | 0.1.0 | | **`width`** | `number` | The width of the content in CSS pixels. | 0.1.0 | #### MapFrame The position and size of a map in CSS pixels, relative to the viewport. | Prop | Type | Description | Since | | ------------ | -------- | ------------------------------------------ | ----- | | **`height`** | `number` | The height of the map in CSS pixels. | 0.1.0 | | **`width`** | `number` | The width of the map in CSS pixels. | 0.1.0 | | **`x`** | `number` | The x-coordinate of the map in CSS pixels. | 0.1.0 | | **`y`** | `number` | The y-coordinate of the map in CSS pixels. | 0.1.0 | #### SetGesturesEnabledOptions | Prop | Type | Description | Since | | ------------ | --------- | ------------------------------------------------------------------------------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`pan`** | `boolean` | Whether the user can move the camera by dragging the map. If not provided, the current setting is kept. | 0.1.0 | | **`rotate`** | `boolean` | Whether the user can rotate the camera. If not provided, the current setting is kept. | 0.1.0 | | **`tilt`** | `boolean` | Whether the user can tilt the camera. If not provided, the current setting is kept. | 0.1.0 | | **`zoom`** | `boolean` | Whether the user can zoom the camera. If not provided, the current setting is kept. | 0.1.0 | #### SetStyleOptions | Prop | Type | Description | Since | | ----------- | -------- | ---------------------------------------------------------------------------------------- | ----- | | **`json`** | `string` | The style of the map as a JSON string. Exactly one of `json` and `url` must be provided. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`url`** | `string` | The URL of the style of the map. Exactly one of `json` and `url` must be provided. | 0.1.0 | #### UpdateGeoJsonSourceByIdOptions | Prop | Type | Description | Since | | -------------- | ------------------------- | ------------------------------------------------------------------------------------------------ | ----- | | **`data`** | `Record` | The new GeoJSON data of the source. Exactly one of `data` and `url` must be provided. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`sourceId`** | `string` | The unique identifier of the source. | 0.1.0 | | **`url`** | `string` | The URL of the new GeoJSON data of the source. Exactly one of `data` and `url` must be provided. | 0.1.0 | #### UpdateMarkerByIdOptions | Prop | Type | Description | Default | Since | | ----------------------- | ------------------ | --------------------------------------------------------------------------- | ------- | ----- | | **`animate`** | `boolean` | Whether the changes of the coordinates and the rotation are animated. | `false` | 0.1.0 | | **`animationDuration`** | `number` | The duration of the animation in milliseconds. | `300` | 0.1.0 | | **`coordinates`** | `LatLng` | The new geographical coordinate of the marker. | | 0.1.0 | | **`draggable`** | `boolean` | Whether the user can drag the marker. Only available on Android and Web. | | 0.1.0 | | **`iconAnchor`** | `MarkerIconAnchor` | The part of the icon that is placed on the coordinate of the marker. | | 0.1.0 | | **`iconSize`** | `MarkerIconSize` | The size the icon is scaled to. | | 0.1.0 | | **`iconUrl`** | `string` | The URL of the new icon of the marker. Must be a `https` URL or a data URI. | | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | | 0.1.0 | | **`opacity`** | `number` | The opacity of the marker as a value between `0` and `1`. | | 0.1.0 | | **`rotation`** | `number` | The rotation of the icon in degrees clockwise. | | 0.1.0 | #### UpdatePolylineByIdOptions | Prop | Type | Description | Since | | ----------------- | ---------- | ----------------------------------------------------------------------------------------- | ----- | | **`color`** | `string` | The color of the polyline as a hexadecimal string in the format `#RRGGBB` or `#RRGGBBAA`. | 0.1.0 | | **`coordinates`** | `LatLng[]` | The new geographical coordinates of the polyline. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`opacity`** | `number` | The opacity of the polyline as a value between `0` and `1`. | 0.1.0 | | **`polylineId`** | `string` | The unique identifier of the polyline. | 0.1.0 | | **`width`** | `number` | The width of the polyline in CSS pixels. | 0.1.0 | #### PluginListenerHandle | Prop | Type | | ------------ | --------------------- | | **`remove`** | `() => Promise` | #### CameraIdleEvent | Prop | Type | Description | Since | | ------------ | -------- | ----------------------------------------- | ----- | | **`camera`** | `Camera` | The camera of the map after the movement. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | #### CameraMoveStartedEvent | Prop | Type | Description | Since | | ------------ | ------------------ | ----------------------------------------- | ----- | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`reason`** | `CameraMoveReason` | The reason why the camera started moving. | 0.1.0 | #### ElementFromPointRequestEvent | Prop | Type | Description | Since | | --------------- | -------- | ---------------------------------------------------------------------- | ----- | | **`requestId`** | `string` | The unique identifier of the request. | 0.1.0 | | **`x`** | `number` | The x-coordinate of the point in CSS pixels, relative to the viewport. | 0.1.0 | | **`y`** | `number` | The y-coordinate of the point in CSS pixels, relative to the viewport. | 0.1.0 | #### MapClickEvent | Prop | Type | Description | Since | | ----------------- | ---------- | ----------------------------------------------- | ----- | | **`coordinates`** | `LatLng` | The geographical coordinate the user tapped on. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`point`** | `MapPoint` | The point the user tapped on. | 0.1.0 | #### MapPoint A point on a map in CSS pixels, relative to the map element. | Prop | Type | Description | Since | | ------- | -------- | -------------------------------------------- | ----- | | **`x`** | `number` | The x-coordinate of the point in CSS pixels. | 0.1.0 | | **`y`** | `number` | The y-coordinate of the point in CSS pixels. | 0.1.0 | #### MarkerClickEvent | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------ | ----- | | **`coordinates`** | `LatLng` | The geographical coordinate of the marker. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | 0.1.0 | #### MarkerDragEvent | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------- | ----- | | **`coordinates`** | `LatLng` | The current geographical coordinate of the marker. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | 0.1.0 | #### MarkerDragEndEvent | Prop | Type | Description | Since | | ----------------- | -------- | ------------------------------------------------------ | ----- | | **`coordinates`** | `LatLng` | The geographical coordinate the marker was dropped at. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | 0.1.0 | #### MarkerDragStartEvent | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------------- | ----- | | **`coordinates`** | `LatLng` | The geographical coordinate the marker was dragged from. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`markerId`** | `string` | The unique identifier of the marker. | 0.1.0 | #### UserLocationChangeEvent | Prop | Type | Description | Since | | ----------------- | -------- | -------------------------------------------------------------------- | ----- | | **`accuracy`** | `number` | The accuracy of the location in meters. | 0.1.0 | | **`coordinates`** | `LatLng` | The geographical coordinate of the user. | 0.1.0 | | **`heading`** | `number` | The direction the device points in, in degrees clockwise from north. | 0.1.0 | | **`mapId`** | `string` | The unique identifier of the map. | 0.1.0 | | **`speed`** | `number` | The speed of the device in meters per second. | 0.1.0 | ### Type Aliases #### PermissionState `'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'` ### Enums #### LayerType | Members | Value | Description | Since | | ------------ | ---------- | ---------------------------------------------- | ----- | | **`Circle`** | `'circle'` | A layer that renders points as circles. | 0.1.0 | | **`Fill`** | `'fill'` | A layer that renders polygons as filled areas. | 0.1.0 | | **`Line`** | `'line'` | A layer that renders line strings as lines. | 0.1.0 | #### MarkerIconAnchor | Members | Value | Description | Since | | ------------ | ---------- | ---------------------------- | ----- | | **`Bottom`** | `'bottom'` | The bottom edge of the icon. | 0.1.0 | | **`Center`** | `'center'` | The center of the icon. | 0.1.0 | | **`Left`** | `'left'` | The left edge of the icon. | 0.1.0 | | **`Right`** | `'right'` | The right edge of the icon. | 0.1.0 | | **`Top`** | `'top'` | The top edge of the icon. | 0.1.0 | #### UserTrackingMode | Members | Value | Description | Since | | ----------------------- | --------------------- | --------------------------------------------------------------------------------------------- | ----- | | **`Follow`** | `'follow'` | The camera follows the location of the user. | 0.1.0 | | **`FollowWithCourse`** | `'followWithCourse'` | The camera follows the location of the user and points in the direction the user moves in. | 0.1.0 | | **`FollowWithHeading`** | `'followWithHeading'` | The camera follows the location of the user and points in the direction the device points in. | 0.1.0 | | **`None`** | `'none'` | The camera does not follow the location of the user. | 0.1.0 | #### CameraMoveReason | Members | Value | Description | Since | | ------------- | ----------- | ---------------------------------------------- | ----- | | **`Api`** | `'api'` | The camera was moved by a method call. | 0.1.0 | | **`Gesture`** | `'gesture'` | The camera was moved by a gesture of the user. | 0.1.0 | ## FAQ ### Where do I get map styles and do I need an API key? No API key is required by the plugin itself. MapLibre is open source (BSD-licensed) and renders any style that follows the [MapLibre Style Spec](https://maplibre.org/maplibre-style-spec/). The default [demo style](https://demotiles.maplibre.org/style.json) is a test style and should not be used in production. Free providers include [CARTO basemaps](https://github.com/CartoDB/basemap-styles), [OpenFreeMap](https://openfreemap.org/) and [Versatiles](https://versatiles.org/). Commercial providers such as [MapTiler](https://www.maptiler.com/) offer styles that require their own API key. Always follow the attribution requirements and terms of service of the provider you choose. ### Why is my map not visible? On Android and iOS, the map is rendered behind the web view. If the map element or one of its ancestors has a background color, the web view paints over the map. Make sure the map element is empty and that it and all ancestors covering the map region use `background: transparent` (see [Getting started](#getting-started)). ### Can I display HTML content above the map? Yes. Since the native map is rendered behind the web view, any DOM element that is not an ancestor of the map element is displayed above the map. Floating action buttons, bottom sheets, overlays and dialogs work exactly as they do everywhere else in your app. ### Why are marker drag events not available on iOS? On iOS, markers are rendered as symbol layers, which do not support native dragging yet. The `draggable` property and the `markerDrag`, `markerDragStart` and `markerDragEnd` events are therefore only available on Android and Web. ### Does this plugin work offline? No. Styles, fonts, sprites and tiles are loaded from the network by the map. Offline tile management is not part of this version of the plugin. ## Related Plugins - [Compass](https://capawesome.io/docs/sdks/capacitor/compass/): Read the device compass heading. - [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/): Convert addresses into coordinates and vice versa. - [Maps Launcher](https://capawesome.io/docs/sdks/capacitor/maps-launcher/): Launch navigation apps with turn-by-turn directions. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/maplibre/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/maplibre/LICENSE). ______________________________________________________________________ 1. This project is not affiliated with, endorsed by, sponsored by, or approved by the MapLibre organization or any of its contributors. "MapLibre" is a trademark of the MapLibre organization. [↩](#fnref:1 "Jump back to footnote 1 in the text") # Capacitor Maps Launcher Plugin Capacitor plugin to launch navigation apps with turn-by-turn directions. ## Features - 🧭 **Navigate**: Launch a navigation app with turn-by-turn directions to a destination. - 🗺️ **Curated apps**: Support for Google Maps, Apple Maps (iOS) and Waze. - 📍 **Coordinates & addresses**: Navigate to coordinates or a plain address. - 🚗 **Travel modes**: Choose between driving, walking, bicycling and transit (best-effort per app). - ✅ **Availability**: Check which navigation apps are installed and can be launched. - 🤝 **Compatibility**: Works alongside the [Android Intent Launcher](https://capawesome.io/docs/sdks/capacitor/android-intent-launcher/), [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/) and [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/) 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! ## Use Cases The Maps Launcher plugin is typically used whenever an app wants to hand the user over to a navigation app, for example: - **Store locators**: Add a "Get directions" button that starts turn-by-turn navigation to a store or office. - **Delivery and field service**: Launch navigation to the next stop using its coordinates. - **Event apps**: Navigate attendees to a venue by its plain address, without needing coordinates. - **User choice**: Detect which navigation apps are installed with `getAvailableApps` and let users pick their favorite one. ## Compatibility | Plugin Version | Capacitor Version | Status | | -------------- | ----------------- | -------------- | | 0.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-maps-launcher` 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-maps-launcher npx cap sync ``` ### Android The plugin declares the required [package visibility](https://developer.android.com/training/package-visibility) `` for Google Maps and Waze in its own `AndroidManifest.xml`. No additional configuration is required. ### iOS #### Application Queries Schemes To detect and launch Google Maps and Waze, the following URL schemes must be added to the `LSApplicationQueriesSchemes` array in the `Info.plist` file of your app. Without them, `getAvailableApps(...)` reports those apps as unavailable and `navigate(...)` rejects with the `APP_NOT_AVAILABLE` error code. ``` LSApplicationQueriesSchemes comgooglemaps waze ``` Apple Maps is always available and does not require any configuration. ## Configuration No configuration required for this plugin. ## Usage The following examples show how to get the available and default navigation apps and how to navigate to coordinates or an address. ### Get the available navigation apps Check which navigation apps are installed and can be launched. On iOS, Apple Maps is always included, while Google Maps and Waze are only included if the corresponding URL schemes are declared in your `Info.plist` file (see [Installation](#installation)). Only available on Android and iOS: ``` import { MapsLauncher } from '@capawesome/capacitor-maps-launcher'; const getAvailableApps = async () => { const { apps } = await MapsLauncher.getAvailableApps(); return apps; }; ``` ### Get the default navigation app Find out which navigation app is configured as the default handler for navigation intents. Returns `null` if the default app is not part of the curated list of supported apps or if no default app is set. Only available on Android: ``` import { MapsLauncher } from '@capawesome/capacitor-maps-launcher'; const getDefaultApp = async () => { const { app } = await MapsLauncher.getDefaultApp(); return app; }; ``` ### Navigate to coordinates Launch a navigation app with turn-by-turn directions to a destination defined by its latitude and longitude. You can optionally specify the app to launch and the travel mode: ``` import { MapsLauncher, NavigationApp } from '@capawesome/capacitor-maps-launcher'; const navigate = async () => { await MapsLauncher.navigate({ destination: { latitude: 37.3349, longitude: -122.009, }, app: NavigationApp.GoogleMaps, travelMode: 'driving', }); }; ``` ### Navigate to an address Instead of coordinates, you can also pass a plain address as the destination. If no `app` is provided, the system default behavior is used (a chooser on Android, Apple Maps on iOS): ``` import { MapsLauncher } from '@capawesome/capacitor-maps-launcher'; const navigateToAddress = async () => { await MapsLauncher.navigate({ destination: { address: 'Apple Park, Cupertino, CA', }, }); }; ``` ## API - [`getAvailableApps()`](#getavailableapps) - [`getDefaultApp()`](#getdefaultapp) - [`navigate(...)`](#navigate) - [Interfaces](#interfaces) - [Type Aliases](#type-aliases) - [Enums](#enums) ### getAvailableApps() ``` getAvailableApps() => Promise ``` Get the navigation apps that are installed and can be launched. On iOS, Apple Maps is always included. Google Maps and Waze are only included if the corresponding `LSApplicationQueriesSchemes` entries are added to the `Info.plist` file of your app. Only available on Android and iOS. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### getDefaultApp() ``` getDefaultApp() => Promise ``` Get the navigation app that is configured as the default handler for navigation intents. Returns `null` if the default app is not part of the curated list of supported apps or if no default app is set (i.e. the system shows a chooser). Only available on Android. **Returns:** `Promise` **Since:** 0.1.0 ______________________________________________________________________ ### navigate(...) ``` navigate(options: NavigateOptions) => Promise ``` Launch a navigation app with turn-by-turn directions to a destination. If no `app` is provided, the system default behavior is used (a chooser on Android, Apple Maps on iOS). Only available on Android and iOS. | Param | Type | | ------------- | ----------------- | | **`options`** | `NavigateOptions` | **Since:** 0.1.0 ______________________________________________________________________ ### Interfaces #### GetAvailableAppsResult | Prop | Type | Description | Since | | ---------- | ----------------- | ----------------------------------------------------------- | ----- | | **`apps`** | `NavigationApp[]` | The navigation apps that are installed and can be launched. | 0.1.0 | #### GetDefaultAppResult | Prop | Type | Description | Since | | --------- | --------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`app`** | \`NavigationApp | null\` | The navigation app that is configured as the default handler. Returns `null` if the default app is not part of the curated list of supported apps or if no default app is set. | #### NavigateOptions | Prop | Type | Description | Default | Since | | ----------------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | ----- | | **`app`** | `NavigationApp` | The navigation app to launch. If not provided, the system default behavior is used (a chooser on Android, Apple Maps on iOS). | | 0.1.0 | | **`destination`** | `Destination` | The destination to navigate to. | | 0.1.0 | | **`start`** | `Destination` | The start location of the route. If not provided, the current location of the device is used. **Note**: The support depends on the selected app. Apple Maps supports it fully, Google Maps opens the directions preview instead of starting turn-by-turn navigation, and Waze ignores it. | | 0.1.0 | | **`travelMode`** | `TravelMode` | The travel mode to use for the directions. **Note**: The support depends on the selected app and is best-effort. Waze only supports driving and ignores this option. | `'driving'` | 0.1.0 | #### Destination A destination is either defined by its coordinates or by its address, but not both. | Prop | Type | Description | Since | | --------------- | -------- | -------------------------------------------------------------------------------------------------- | ----- | | **`address`** | `string` | The address of the destination. Must be provided without `latitude` and `longitude`. | 0.1.0 | | **`latitude`** | `number` | The latitude of the destination. Must be provided together with `longitude` and without `address`. | 0.1.0 | | **`longitude`** | `number` | The longitude of the destination. Must be provided together with `latitude` and without `address`. | 0.1.0 | ### Type Aliases #### TravelMode The travel mode to use for the directions. - `driving`: Driving directions. - `walking`: Walking directions. - `bicycling`: Bicycling directions. - `transit`: Public transit directions. `'driving' | 'walking' | 'bicycling' | 'transit'` ### Enums #### NavigationApp | Members | Value | Description | Since | | ---------------- | --------------- | ---------------------------------- | ----- | | **`AppleMaps`** | `'APPLE_MAPS'` | Apple Maps. Only available on iOS. | 0.1.0 | | **`GoogleMaps`** | `'GOOGLE_MAPS'` | Google Maps. | 0.1.0 | | **`Waze`** | `'WAZE'` | Waze. | 0.1.0 | ## Platform Support The behavior of the `navigate(...)` method depends on the selected app and platform. Keep the following in mind: ### Start location | App | `start` support | | ----------- | ------------------------------------------------------------------------------------------------- | | Apple Maps | Full support. | | Google Maps | Supported, but opens the directions preview instead of starting turn-by-turn navigation directly. | | Waze | Not supported. The option is ignored. | If `start` is not provided, the current location of the device is used. ### Travel mode | App | `driving` | `walking` | `bicycling` | `transit` | | ----------- | --------- | --------- | ----------- | --------- | | Apple Maps | ✅ | ✅ | ❌ | ✅ | | Google Maps | ✅ | ✅ | ✅ | ✅ | | Waze | ✅ | ❌ | ❌ | ❌ | Unsupported travel modes fall back to the default behavior of the respective app. Waze only supports driving and ignores the `travelMode` option. ## FAQ ### How is this plugin different from other similar plugins? It launches turn-by-turn navigation in Google Maps, Apple Maps, and Waze from a single fully typed API, accepting either coordinates or a plain address and supporting driving, walking, bicycling, and transit modes. It also detects which navigation apps are installed and, on Android, which one is the default handler, so you can let users pick their favorite. Per-app behavior is documented transparently, and the plugin is actively maintained against the latest Capacitor and OS versions across Android and iOS. ### Why are Google Maps and Waze reported as unavailable on iOS? To detect and launch Google Maps and Waze on iOS, the `comgooglemaps` and `waze` URL schemes must be added to the `LSApplicationQueriesSchemes` array in the `Info.plist` file of your app. Without them, `getAvailableApps` reports those apps as unavailable and `navigate` rejects with the `APP_NOT_AVAILABLE` error code. See the [Installation](#installation) section for details. ### What happens if I do not specify a navigation app? If no `app` is provided, the system default behavior is used: Android shows a chooser and iOS opens Apple Maps. On Android, you can use `getDefaultApp` to find out which supported navigation app is configured as the default handler. ### Which travel modes are supported? The plugin supports the `driving`, `walking`, `bicycling`, and `transit` travel modes, but the support depends on the selected app and is best-effort. Google Maps supports all four modes, Apple Maps does not support bicycling, and Waze only supports driving and ignores the option. Unsupported travel modes fall back to the default behavior of the respective app. ### Can I set a custom start location for the route? Yes, use the `start` option of the `navigate` method. However, the support depends on the selected app: Apple Maps supports it fully, Google Maps opens the directions preview instead of starting turn-by-turn navigation, and Waze ignores it. If no start location is provided, the current location of the device is used. ### Does the plugin work on the web? No, the `getAvailableApps` and `navigate` methods are only available on Android and iOS, and `getDefaultApp` is only available on Android. Launching installed navigation apps is a native capability that is not available in the browser. ## Related Plugins - [Android Intent Launcher](https://capawesome.io/docs/sdks/capacitor/android-intent-launcher/): Launch arbitrary Android intents. - [App Launcher](https://capawesome.io/docs/sdks/capacitor/app-launcher/): Check if an app can be opened and open it. - [Geocoder](https://capawesome.io/docs/sdks/capacitor/geocoder/): Convert addresses into coordinates and vice versa. ## 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/). ## Changelog See [CHANGELOG.md](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/maps-launcher/CHANGELOG.md). ## License See [LICENSE](https://github.com/capawesome-team/capacitor-plugins/blob/main/packages/maps-launcher/LICENSE). # Capacitor Media Session Plugin Capacitor plugin to interact with media controllers, volume keys and media buttons. **Attention**: This plugin relays media control events to your JavaScript code, so it is designed for playback that runs inside the web view, such as HTML5 `