---
title: Announcing the Capacitor Watch Plugin
description: The new Capacitor Watch plugin connects your app to Apple Watch and Wear OS with one API for live messages, state sync, and queued transfers.
date:
  created: 2026-09-01
  updated: 2026-09-01
authors:
  - robingenz
categories:
  - Announcements
  - Capacitor
  - SDKs
links:
  - Capacitor Watch: sdks/capacitor/watch.md
faq: true
---

# Announcing the Capacitor Watch Plugin

The wrist has been the point where Capacitor projects stop. The official watch plugin supports only iOS and is labeled experimental, and for Wear OS the standing answer has long been that it's not possible at all. Today we're releasing the [Capacitor Watch plugin](../../sdks/capacitor/watch.md), the first Capacitor watch plugin to bridge both Apple Watch and Wear OS: one TypeScript API for live messages, state sync, and queued transfers, with native watch-side SDKs included. It's available today to all Capawesome [Insiders](../../insiders/index.md).

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Build and deploy your Capacitor app with Capawesome Cloud" src="https://capawesome.io/assets/banners/cloud-build-and-deploy-capacitor-apps.png?t=1" />
  </a>
</div>

**Key takeaways:**

- `@capawesome-team/capacitor-watch` is the first Capacitor plugin to connect an app with both Apple Watch and Wear OS, built on `WCSession` on iOS and the Google Play services Data Layer on Android.
- Three channels with distinct guarantees: `sendMessage(...)` for live interaction with optional replies, `updateState(...)` for latest-wins state, `transferUserInfo(...)` for queued, guaranteed delivery.
- Data received while the app is closed is replayed once your listeners register. On Android it's persisted and survives an app restart.
- Native watch-side SDKs ship with the plugin: a Swift Package for watchOS and a Kotlin library for Wear OS. No `AppDelegate` or `MainActivity` changes are required.
- Payloads are capped at about 100 KB per message on every channel by the operating systems.
- Requires Capacitor 8 or later, Android and iOS only, part of the Capawesome Insiders subscription.

## Why a Capacitor Watch Plugin for Both Platforms?

Because until now, watch support in the Capacitor ecosystem depended on which platform you asked about. [ionic-team/CapacitorWatch](https://github.com/ionic-team/CapacitorWatch){:target="_blank"} "currently only supports iOS" and ships under the CapacitorLABS label with an explicit note that the project is experimental and support is not provided. For Wear OS, the assumption that Capacitor apps are locked out went unchallenged for years, because Wear OS devices have no WebView to run one. Our guide on [how to build a Wear OS app for your Capacitor app](./how-to-build-a-wear-os-app-for-your-capacitor-app.md) took that assumption apart: the watch UI has to be native, and the communication between phone and watch doesn't.

The Capacitor Watch plugin is built on that separation. It's a communication bridge, deliberately. You build the watch UI with SwiftUI on watchOS and Jetpack Compose (or whatever you prefer) on Wear OS, with the full power of each platform, and the plugin handles everything about getting data across: sending, receiving, queueing, replaying, and reachability. Where watchOS and Wear OS behave differently, the plugin keeps the platform-true semantics and documents the difference instead of hiding it.

## Installation

To install the Capacitor Watch plugin, please refer to the [Installation](../../sdks/capacitor/watch.md/#installation) section in the plugin documentation. The plugin is published to the Capawesome npm registry and requires the license key that comes with a [Capawesome Insiders](../../insiders/index.md) subscription.

What you won't find in the setup steps is boilerplate. On iOS, the plugin activates the `WCSession` automatically when it loads, so your `AppDelegate` stays untouched. On Android, it registers its own `WearableListenerService` in the plugin manifest, which means data from the watch is received even while your app is closed, without any manifest changes on your side. The one Android prerequisite is Google Play services: the Data Layer is part of it, and on devices without Play services all plugin methods reject as unavailable.

## Usage

Both watch platforms expose the same three communication channels, each with a different delivery guarantee, and the plugin maps them onto three methods. Which one you reach for depends on whether you need an answer right now, only the newest value, or every single item.

### Check the connection first

Before building UI around the watch, ask what's actually there. [`getConnectionInfo()`](../../sdks/capacitor/watch.md#getconnectioninfo) reports the pairing status, whether the watch app is installed, and whether the watch is currently reachable:

```typescript
import { Watch } from '@capawesome-team/capacitor-watch';

const getConnectionInfo = async () => {
  const { reachable, paired, watchAppInstalled } = await Watch.getConnectionInfo();
  return { reachable, paired, watchAppInstalled };
};
```

On Android, `paired` is always `null` because the platform doesn't expose pairing information, and `watchAppInstalled` reflects whether a watch app declaring the configured capability was found. To react to the watch coming and going, subscribe to [`reachabilityChange`](../../sdks/capacitor/watch.md#addlistenerreachabilitychange-):

```typescript
await Watch.addListener('reachabilityChange', (event) => {
  console.log('Watch reachable:', event.reachable);
});
```

### Messages: live with an optional reply

[`sendMessage(...)`](../../sdks/capacitor/watch.md#sendmessage) is the interactive channel. It delivers immediately, and it can wait for an answer: set `expectsReply: true` and the promise resolves with the watch's reply:

```typescript
import { Watch } from '@capawesome-team/capacitor-watch';

const startWorkout = async () => {
  const { reply } = await Watch.sendMessage({
    data: { command: 'startWorkout' },
    expectsReply: true,
  });
  console.log('Watch confirmed:', reply);
};
```

The price of immediacy is reachability: if the watch is not connected right now, the call rejects with `WATCH_NOT_REACHABLE`. That makes messages the right channel for remote-control interactions where a stale command would be worse than a failed one, and the wrong channel for anything that must eventually arrive.

Receiving works through the [`messageReceived`](../../sdks/capacitor/watch.md#addlistenermessagereceived-) listener. When the event carries a `messageId`, the watch sent the message expecting a reply and is waiting on [`replyToMessage(...)`](../../sdks/capacitor/watch.md#replytomessage):

```typescript
await Watch.addListener('messageReceived', async (event) => {
  if (event.messageId) {
    await Watch.replyToMessage({
      data: { status: 'ok' },
      messageId: event.messageId,
    });
  }
});
```

### State: only the latest value wins

[`updateState(...)`](../../sdks/capacitor/watch.md#updatestate) shares a value where only the newest matters: the current step count, the workout status, today's task list. If the watch isn't reachable, the state is delivered as soon as it is again, and if you updated it five times in between, only the fifth arrives:

```typescript
import { Watch } from '@capawesome-team/capacitor-watch';

const updateState = async () => {
  await Watch.updateState({ data: { steps: 8421, goal: 10000 } });
};
```

On the receiving end there are two paths. The [`stateReceived`](../../sdks/capacitor/watch.md#addlistenerstatereceived-) event fires when an update comes in, and [`getReceivedState()`](../../sdks/capacitor/watch.md#getreceivedstate) reads the last received state at any time. The state is persisted, so it's still available after an app restart. One caveat: an update identical to the previous one may not be redelivered, so the state channel is no heartbeat.

### Transfers: queued until delivered

[`transferUserInfo(...)`](../../sdks/capacitor/watch.md#transferuserinfo) is the channel with a delivery guarantee. Every transfer is queued and delivered, even if the watch is out of reach for hours, and the queue survives relaunches:

```typescript
import { Watch } from '@capawesome-team/capacitor-watch';

const logLap = async () => {
  await Watch.transferUserInfo({ data: { lap: 3, completedAt: Date.now() } });
};
```

Incoming transfers arrive through the [`userInfoReceived`](../../sdks/capacitor/watch.md#addlisteneruserinforeceived-) listener. Ordering differs per platform: iOS delivers transfers in the order they were queued, while Android makes no ordering guarantee and keeps the 100 most recent undelivered transfers. If order matters on Android, put a timestamp or sequence number in the payload.

### How the three channels compare

Each method maps directly onto a platform API, and the guarantees are the platforms' own:

| Method                                                                        | Semantics             | iOS                        | Android                  | Guarantee                                      |
| ----------------------------------------------------------------------------- | --------------------- | -------------------------- | ------------------------ | ---------------------------------------------- |
| [`sendMessage(...)`](../../sdks/capacitor/watch.md#sendmessage)               | Live, optional reply  | `WCSession.sendMessage`    | `MessageClient`          | Requires reachability, not queued              |
| [`updateState(...)`](../../sdks/capacitor/watch.md#updatestate)               | Latest state wins     | `updateApplicationContext` | `DataItem` (fixed path)  | Replaces undelivered state, survives restarts  |
| [`transferUserInfo(...)`](../../sdks/capacitor/watch.md#transferuserinfo)     | Queued, all delivered | `transferUserInfo`         | `DataItem` (unique path) | Queued until delivered, even across relaunches |

All payloads must be JSON-serializable, `null` values are not supported, and the operating systems cap each payload at about 100 KB on every channel.

## The Watch Side Ships With the Plugin

A phone-side API alone would leave the harder half as an exercise. The plugin therefore ships native SDKs for both watch platforms, mirroring the same three channels on the wrist.

### watchOS: a Swift Package for SwiftUI

The watchOS SDK wires up the `WCSession` and exposes an `ObservableObject`, so the connection state drops straight into SwiftUI:

```swift
import SwiftUI
import CapawesomeWatchSDK

struct ContentView: View {
    @ObservedObject private var watch = CapawesomeWatch.shared

    var body: some View {
        VStack {
            Text(watch.reachable ? "Phone reachable" : "Not reachable")
            Button("Send") {
                watch.sendMessage(["text": "Hello from the watch!"])
            }
        }
        .onAppear {
            watch.activate()
        }
    }
}
```

The package lives inside the installed npm package, and the [watchOS setup](../../sdks/capacitor/watch.md#watchos) in the plugin documentation walks through adding a watch target to your Xcode project, including a complete minimal example app.

### Wear OS: a Kotlin library

On Wear OS, the SDK provides a `WatchListenerService` base class with one override per channel, plus a `CapawesomeWatch` class with suspend functions for sending:

```kotlin
class MyWatchListenerService : WatchListenerService() {
    override fun onMessageReceived(data: JSONObject, reply: ((JSONObject) -> Unit)?) {
        reply?.invoke(JSONObject().put("status", "ok"))
    }

    override fun onStateReceived(data: JSONObject) {}

    override fun onUserInfoReceived(data: JSONObject) {}
}
```

Two requirements come from the Google Play services Data Layer rather than from the plugin: the Wear OS module must use the same `applicationId` as your phone app, and both must be signed with the same certificate. The full module setup, from `settings.gradle` to the capability declaration, is covered step by step in [How to Build a Wear OS App for Your Capacitor App](./how-to-build-a-wear-os-app-for-your-capacitor-app.md).

## What Happens While Your App Is Closed?

Watch data doesn't wait for your app to be open, so the plugin treats the closed app as a normal case. On Android, the plugin's manifest-registered `WearableListenerService` receives data while your app is not running, persists it, and replays the events as soon as your listeners register, even after an app restart. On iOS, a message from the watch wakes your app in the background, and the events are held in memory until your listeners register. The asymmetry is honest: if iOS terminates the app before that happens, those events are gone, so register your listeners as early in startup as you can.

In the other direction, a phone-to-watch [`sendMessage(...)`](../../sdks/capacitor/watch.md#sendmessage) never launches the watch app's UI. On Android it starts the watch app's listener service, which is enough to receive and process the data. On iOS the watch app must be running to receive a live message, which is another reason to prefer state or transfers for anything that isn't interactive.

## Limits to Plan Around

A few constraints are set by the platforms and worth knowing before you design the data flow:

- **Payload size**: about 100 KB per payload on every channel. For anything larger, send a reference such as a URL or record id and fetch the content from your backend.
- **Payload types**: JSON-serializable data without `null` values. On iOS, payloads must additionally be property list compatible.
- **No cross-platform pairing**: an Apple Watch pairs only with an iPhone, and the Data Layer connects only Android phones with Wear OS watches. A Wear OS watch paired to an iPhone cannot communicate with your app. That's a platform limitation, no plugin can lift it.
- **Google Play services**: required on Android. Without it, all plugin methods reject as unavailable.

## Companion Plugins

Many watch experiences are fitness experiences, and there the plugin pairs well with the [Capacitor Health plugin](../../sdks/capacitor/health.md), which reads the steps, workouts, and heart rate data a watch writes to Apple Health or Health Connect. Our guide on [how to build a heart rate monitor with Capacitor](./how-to-build-a-heart-rate-monitor-with-capacitor.md) shows what a phone-side health experience looks like before you extend it to the wrist. For location-driven apps, the [Capacitor Background Geolocation plugin](../../sdks/capacitor/background-geolocation.md) and the [Capacitor Geofences plugin](../../sdks/capacitor/geofences.md) cover the tracking side while the watch acts as the display and remote.

## FAQ

### Does Capacitor support Wear OS?

A Capacitor app can't run on a Wear OS watch, since Wear OS has no WebView. It can pair with one: using the [Capacitor Watch plugin](../../sdks/capacitor/watch.md), your Capacitor app on the phone exchanges messages, state, and queued transfers with a native Wear OS app over the Google Play services Data Layer.

### Does the Capacitor Watch plugin render the watch UI?

No, by design. The watch app is fully native, typically SwiftUI on watchOS and Jetpack Compose on Wear OS, so you get the complete platform toolkit. The plugin covers the communication with it, including the native SDKs for the watch side.

### Can a Wear OS watch communicate with an iPhone app?

No. The Google Play services Data Layer connects Android phones with Wear OS watches only, and an Apple Watch pairs only with an iPhone. Both are platform limitations.

### Why does `sendMessage(...)` reject with `WATCH_NOT_REACHABLE`?

Messages are a live channel and require a reachable watch. On Android, also verify that the watch app declares the configured capability (default: `capawesome_watch`). When you need delivery rather than immediacy, use [`transferUserInfo(...)`](../../sdks/capacitor/watch.md#transferuserinfo) instead.

### Is the Capacitor Watch plugin free?

No. It's part of the Capawesome [Insiders](../../insiders/index.md) subscription, which also covers all other Insiders plugins and priority support.

### Does it work with Ionic, Angular, React, or Vue?

Yes. The phone-side API is plain TypeScript and framework-agnostic, so it works in any Capacitor app, including Ionic with Angular, React, or Vue, as well as plain JavaScript projects.

## Availability

The Capacitor Watch plugin is available today to all Capawesome [Insiders](../../insiders/index.md) and requires Capacitor 8 or later. It supports Android and iOS; there is no web implementation. The subscription covers every Insiders plugin, so there's no separate license to buy.

New plugins and notable releases are announced in the Capawesome newsletter first.

[Subscribe to the Capawesome Newsletter](https://capawesome.io/newsletter/){ .md-button .md-button--primary }

## Conclusion

The question "can my Capacitor app have a watch app?" used to get two different unsatisfying answers, one per platform. What it needed was a plugin that accepts the watch UI as native and solves the actual problem: moving data between phone and wrist with the right guarantee for each kind of data. That's what the [Capacitor Watch plugin](../../sdks/capacitor/watch.md) does, on both platforms, with the watch-side SDKs included.

**Further reading:**

- [How to Build a Wear OS App for Your Capacitor App](./how-to-build-a-wear-os-app-for-your-capacitor-app.md) — the complete Wear OS walkthrough, from Gradle module to first message
- [API Reference](../../sdks/capacitor/watch.md#api) — every method, option, event, and error code

**Missing a feature?** [Create a feature request](https://github.com/capawesome-team/capacitor-plugins/issues/new/choose){:target="_blank"} in our GitHub repository.

If you have any questions, join us on the [Capawesome Discord server](https://discord.gg/VCXxSVjefW){:target="_blank"}. To stay updated on the latest news, subscribe to the [Capawesome newsletter](https://capawesome.io/newsletter/){:target="_blank"}.
