---
title: An Alternative to the Official Capacitor Watch Plugin
description: Looking for a Capacitor watch plugin alternative? Compare the experimental, iOS-only @capacitor/watch with the Capacitor Watch plugin and map its API.
date:
  created: 2026-10-19
  updated: 2026-10-19
authors:
  - robingenz
categories:
  - Capacitor
  - Guides
  - SDKs
links:
  - Capacitor Watch: sdks/capacitor/watch.md
faq: true
---

# An Alternative to the Official Capacitor Watch Plugin

Teams searching for a Capacitor watch plugin alternative usually run into the same three limits of the official plugin: it declares Capacitor 5 as its peer dependency, it supports iOS only, and the watch screen is a text-defined list of labels and buttons rather than a native UI. That plugin is `@capacitor/watch` from the ionic-team/CapacitorWatch repository, labeled experimental and last released in April 2024. This post compares it with the [Capacitor Watch plugin](../../sdks/capacitor/watch.md), maps each of its three methods to the new API, and lists what changes in the Xcode setup when you switch. The Capacitor Watch plugin is part of [Capawesome Insiders](../../insiders/index.md).

<!-- more -->

<div class="capawesome-z29o10a">
  <a href="https://capawesome.io/" target="_blank">
    <img alt="Thousands of teams ship faster with Capawesome Cloud Native Builds and Live Updates" src="https://capawesome.io/assets/banners/cloud-teams-ship-faster-with-capacitor.png" />
  </a>
</div>

**Key takeaways:**

- `@capacitor/watch` 0.1.12 is the last release, published on April 8, 2024, with `@capacitor/core ^5.0.0` as its peer dependency and a CapacitorLABS label that states "Support is not provided".
- Its whole JavaScript API is `updateWatchUI(...)`, `updateWatchData(...)` and a `runCommand` listener; there is no `sendMessage` or `transferUserInfo` method.
- The Capacitor Watch plugin is a communication bridge with three channels, `sendMessage(...)`, `updateState(...)` and `transferUserInfo(...)`, and ships watch-side SDKs for watchOS and Wear OS.
- `updateWatchData(...)` maps to `updateState(...)`, `runCommand` maps to `messageReceived` with an optional reply, and `updateWatchUI(...)` has no equivalent because the watch UI becomes a SwiftUI view.
- The Capacitor Watch plugin requires Capacitor 8, needs no `AppDelegate` edits or extra capabilities, and is a paid Insiders plugin.

## Maintenance status

`@capacitor/watch` has not had a release since April 2024, and its peer dependency pins it to Capacitor 5. The [ionic-team/CapacitorWatch](https://github.com/ionic-team/CapacitorWatch){:target="_blank"} repository is not archived and the npm package is not deprecated, so the numbers have to tell the story (all checked on 2026-10-18):

| Fact | Value |
| --- | --- |
| Latest release on npm | 0.1.12, published 2024-04-08 |
| Last push to the repository | 2024-04-08 |
| Peer dependency | `@capacitor/core: ^5.0.0` |
| Project label | CapacitorLABS, "This project is experimental. Support is not provided." |
| Platforms | iOS only |
| Open issues | 8 |
| Weekly downloads | 51 |

The open issues are version requests without a maintainer reply. Issue #41 asked for Capacitor 6 support in May 2024, and issue #46 asked for Capacitor 8 support in January 2026 and opens with the sentence "this plugin seems not to be maintained anymore". Issue #35, the longest thread with 21 comments, reports that `CapWatchContentView` cannot be found in scope after following the setup guide. Two further issues from 2025 and 2026 report spelling errors in the README, one of them in an import statement of the setup guide.

The peer dependency is the practical blocker. npm 7 and newer refuse to install a package whose peer range the project does not satisfy, so adding `@capacitor/watch` to a Capacitor 6, 7 or 8 project ends in a peer dependency conflict unless you pass `--legacy-peer-deps`, and then you run a plugin that was built and tested against Capacitor 5.1.

## Two models

The two plugins answer the question "where does the watch UI live?" differently. `@capacitor/watch` lets you define the UI in web code. You build a newline-delimited string of `Text("...")` and `Button("label", "command")` lines, send it with `updateWatchUI({ watchUI })`, and the watch renders it as a vertical scroll view. Values are injected through `$variable` placeholders and pushed with `updateWatchData({ data })`, where every value must be a string. Taps come back through `addListener('runCommand', ...)` with the command string from the button definition. The plugin persists the last UI on the watch, but not the data. The README also notes that two paired Simulators cannot talk to each other, so development needs a physical Apple Watch.

The Capacitor Watch plugin is a communication bridge and nothing else. You build the watch UI natively, with SwiftUI on watchOS and Jetpack Compose or views on Wear OS, and the plugin moves data between the phone and the watch over three channels: `sendMessage(...)` for live delivery with an optional reply, `updateState(...)` for latest-wins state that survives restarts, and `transferUserInfo(...)` for queued transfers that are delivered even when the watch is out of reach. The watch side ships with the plugin as a Swift Package for watchOS and a Kotlin library for Wear OS, and the same TypeScript API works on Android. [Announcing the Capacitor Watch Plugin](./announcing-the-capacitor-watch-plugin.md) walks through the channels in detail.

If your watch screens fit a scrolling list of labels and buttons, and your project still runs Capacitor 5, the string-defined UI is less code than a SwiftUI target. The moment you need a second screen type, a value that is not a string, a reply from the watch, or a Wear OS app, the official model has no answer, and the native route is the only one that scales.

## API mapping

The Capacitor Watch plugin replaces the three members of `@capacitor/watch` with a larger API, and two of the three have a direct counterpart. The table maps every method, event and setup element of `@capacitor/watch` 0.1.12 to the Capacitor Watch plugin:

| @capacitor/watch | Capacitor Watch |
| --- | --- |
| `import { Watch } from '@capacitor/watch'` | `import { Watch } from '@capawesome-team/capacitor-watch'` |
| `updateWatchUI({ watchUI })` with a `Text`/`Button` string | *No equivalent*; the UI is a SwiftUI view in the watch target |
| `updateWatchData({ data })`, string values only, not persisted on the watch | [`updateState({ data })`](../../sdks/capacitor/watch.md#updatestate), any JSON values, persisted by the system |
| `$variable` placeholders in `Text(...)` | SwiftUI bindings to the published `receivedState` dictionary of `CapawesomeWatch.shared` |
| `addListener('runCommand', ({ command }) => ...)` | [`addListener('messageReceived', ...)`](../../sdks/capacitor/watch.md#addlistenermessagereceived-) with `event.data`, plus [`replyToMessage(...)`](../../sdks/capacitor/watch.md#replytomessage) when the watch expects an answer |
| `WatchConnectivity_didReceiveUserInfo` event in a Background Runner script | [`addListener('userInfoReceived', ...)`](../../sdks/capacitor/watch.md#addlisteneruserinforeceived-); a watch-to-phone message wakes the app on iOS |
| *No equivalent* | [`sendMessage(...)`](../../sdks/capacitor/watch.md#sendmessage) with `expectsReply`, [`transferUserInfo(...)`](../../sdks/capacitor/watch.md#transferuserinfo), [`getConnectionInfo()`](../../sdks/capacitor/watch.md#getconnectioninfo), [`getReceivedState()`](../../sdks/capacitor/watch.md#getreceivedstate), `reachabilityChange` and `stateReceived` events |
| `WCSession` code in `AppDelegate.swift`, Background Modes and Push Notifications capabilities | Nothing; the plugin activates the `WCSession` when it loads |
| `CapWatchContentView` and `WatchViewModel.shared` as the `WCSession` delegate on the watch | `CapawesomeWatch.shared.activate()` and your own SwiftUI views |
| iOS only | iOS and Android (Wear OS) |

Two rows change semantics rather than names. `updateWatchData(...)` takes a dictionary of strings and the official README documents that this state is not preserved on the watch, while `updateState(...)` takes any JSON-serializable values without `null` and both operating systems persist the latest state on the receiving side. And `runCommand` is a one-way signal that carries a single string, while `messageReceived` carries a JSON object and, when the watch sent the message with a reply handler, a `messageId` that lets the phone answer the same message.

## Migrating the command flow

A counter with `@capacitor/watch` is a UI string, a data push and a command listener on the phone side:

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

let count = 0;

const setUpWatch = async () => {
  await Watch.updateWatchUI({
    watchUI: `Text("Count: $count")
Button("Add One", "inc")`,
  });
  await Watch.updateWatchData({ data: { count: String(count) } });
  await Watch.addListener('runCommand', async ({ command }) => {
    if (command === 'inc') {
      count++;
      await Watch.updateWatchData({ data: { count: String(count) } });
    }
  });
};
```

With the Capacitor Watch plugin, the phone keeps the state logic and loses the UI string. The watch sends a message when the button is tapped, the phone increments and publishes the new state, and an optional reply confirms the tap:

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

let count = 0;

const setUpWatch = async () => {
  await Watch.updateState({ data: { count } });
  await Watch.addListener('messageReceived', async event => {
    if (event.data.command === 'inc') {
      count++;
      await Watch.updateState({ data: { count } });
      if (event.messageId) {
        await Watch.replyToMessage({
          data: { count },
          messageId: event.messageId,
        });
      }
    }
  });
};
```

The UI string moves into the watch target as a SwiftUI view. `CapawesomeWatch.shared` publishes the last received state, so the label updates on its own whenever the phone calls `updateState(...)`:

```swift
import SwiftUI
import CapawesomeWatchSDK

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

    var body: some View {
        VStack {
            Text("Count: \(watch.receivedState["count"] as? Int ?? 0)")
            Button("Add One") {
                watch.sendMessage(["command": "inc"])
            }
        }
        .onAppear {
            watch.activate()
        }
    }
}
```

The count is an `Int` on both ends now, where the official plugin forced a `String(count)` conversion before every push. A watch-to-phone `sendMessage` also wakes the phone app in the background on iOS, so the tap is handled even when the Capacitor app is not in the foreground, which is the case the official README covers with a separate Background Runner script. [How to Build an Apple Watch App for Your Capacitor App](./how-to-build-an-apple-watch-app-for-your-capacitor-app.md) builds the full watch target around this view, including the Xcode steps.

## Setup differences

The Capacitor Watch plugin removes every phone-side setup step of `@capacitor/watch`. The official README asks you to add the Background Modes and Push Notifications capabilities to the iOS target, enable Background Fetch, Remote Notifications and Background Processing, and paste three lines into `AppDelegate.swift` that set `CapWatchSessionDelegate.shared` as the `WCSession` delegate. The Capacitor Watch plugin activates the session itself when Capacitor loads it, so the `AppDelegate` and the capabilities list stay as they are.

The watch target is created the same way in both cases, through **File > New > Target** with the watchOS tab, and the bundle identifier rule applies to both. The official README spells it as `[your apps bundle ID].watchapp`; the Capacitor Watch plugin documents the prefix rule and lets Xcode derive the suffix, which is `.watchkitapp` in current templates. Both plugins add their watch-side code as a local Swift Package from `node_modules`, with the difference that `@capacitor/watch` also needs its `CapWatchContentView` as the root view and `WatchViewModel.shared` as the session delegate, which is the step that issue #35 and its 21 comments are about.

Testing differs in what the documentation promises. The official README states that Simulators do not allow app-to-watch communication at all. The Capacitor Watch plugin's documentation makes no promise about the Simulator in either direction, and Apple's warning on [`transferUserInfo(_:)`](https://developer.apple.com/documentation/watchconnectivity/wcsession/transferuserinfo(_:)){:target="_blank"} is the documented limit: "Always test Watch Connectivity data transfers on paired devices. The Simulator app doesn't support the `transferUserInfo(_:)` method." Build the SwiftUI screens in the Simulator and verify every channel on a paired iPhone and Apple Watch before you ship.

Two requirements are new. The Capacitor Watch plugin requires Capacitor 8 or later, so a Capacitor 5 project upgrades first; the [Capacitor 8 upgrade guide](https://capacitorjs.com/docs/updating/8-0){:target="_blank"} covers that move. And a Wear OS app has two conditions set by the Google Play services Data Layer: the watch module must use the same `applicationId` as the phone app, and both apps must be signed with the same certificate. [How to Build a Wear OS App for Your Capacitor App](./how-to-build-a-wear-os-app-for-your-capacitor-app.md) covers the Android side end to end.

## What you gain

Switching adds four capabilities that `@capacitor/watch` does not have, and keeps the platform limits that no plugin can lift.

- **Replies**: `sendMessage(...)` with `expectsReply: true` resolves with the watch's answer, and the watch SDK's `onMessageReceived` closure hands you a `reply` callback for the other direction. The official plugin's `runCommand` has no return path.
- **Queued transfers**: `transferUserInfo(...)` delivers every payload even when the watch is out of range, in order on iOS. The official plugin exposes no queued channel from the phone.
- **Reachability**: `getConnectionInfo()` returns `reachable`, `paired` and `watchAppInstalled`, and the `reachabilityChange` event fires on every change, so the UI can show the connection state before a user taps.
- **Background reception**: data that arrives while the app is closed is replayed once your listeners register. On Android, the plugin's `WearableListenerService` persists it across app restarts; on iOS, it is held in memory and lost if the system terminates the app before the listeners register, so register them early in startup.

The limits come from the operating systems. The plugin documentation puts the payload cap at about 100 KB per payload on every channel, payloads must be JSON-serializable without `null` values (and property list compatible on iOS), and an Apple Watch pairs only with an iPhone while the Data Layer connects only Android phones with Wear OS watches. On Android, the plugin requires Google Play services and rejects every method as unavailable without them.

## FAQ

### Does @capacitor/watch support sendMessage?

No. The `WatchPlugin` interface of `@capacitor/watch` 0.1.12 has three members: `addListener('runCommand', ...)`, `updateWatchUI({ watchUI })` and `updateWatchData({ data })`. There is no `sendMessage`, `transferUserInfo` or `updateApplicationContext` method, and the README points to the Capacitor Background Runner plugin for receiving `didReceiveUserInfo` in the background. The [Capacitor Watch plugin](../../sdks/capacitor/watch.md) exposes `sendMessage(...)`, `updateState(...)` and `transferUserInfo(...)` on the phone and the matching methods on the watch.

### Is @capacitor/watch still maintained?

The last release is 0.1.12 from April 8, 2024, the last commit on the main branch is from the same day, and the requests for Capacitor 6 (issue #41) and Capacitor 8 (issue #46) have no maintainer reply as of 2026-10-18. The repository carries the CapacitorLABS label, which states that the project is experimental and support is not provided. It is not archived and the package is not marked as deprecated.

### Can I keep defining the watch UI in TypeScript?

Not with the Capacitor Watch plugin. It moves data and leaves the UI to a native watch target, so the screens are SwiftUI on watchOS and Compose or views on Wear OS. The data those screens display can still come entirely from TypeScript through `updateState(...)` and `sendMessage(...)`, and the watch SDK publishes it as an `ObservableObject` that binds into SwiftUI without glue code.

### Is the Capacitor Watch plugin free?

No. The plugin is part of the [Capawesome Insiders](../../insiders/index.md) subscription and is installed from the Capawesome npm registry with the license key that comes with it. `@capacitor/watch` is free under the MIT license.

### Does the Capacitor Watch plugin work in the Simulator?

Use it for the watch screens, not for verifying delivery. Apple documents that the Simulator does not support `transferUserInfo(_:)` or `transferFile(_:metadata:)` and asks that Watch Connectivity transfers are always tested on paired devices. Pair an iPhone Simulator with an Apple Watch Simulator to run both apps side by side, and test the message, state and queued transfer channels on physical devices before you ship.

## Build the Watch App in the Cloud

A watch target adds a second bundle identifier, a second provisioning profile and a watchOS SDK to every iOS build, and all of it has to be present on the machine that archives the app. Capawesome Cloud Native Builds runs that archive on hosted Macs with your signing assets, so the Capacitor app and its watch app build and upload to TestFlight from one pipeline.

[Try Capawesome Cloud Free](https://capawesome.io){ .md-button .md-button--primary }

## Conclusion

If your project is on Capacitor 6 or newer, the decision is made for you: `@capacitor/watch` does not install without `--legacy-peer-deps`, and the Capacitor Watch plugin is the maintained path. If you are still on Capacitor 5 and your watch UI is a list of labels and buttons, the official plugin works until the upgrade, and the mapping table above tells you how much code the move will touch, which for most apps is the `runCommand` listener and the data pushes. Plan the switch together with the Capacitor 8 upgrade, build the SwiftUI view first, and keep the phone-side state logic where it is. [How to Build an Apple Watch App for Your Capacitor App](./how-to-build-an-apple-watch-app-for-your-capacitor-app.md) is the step-by-step version of that plan.

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"}.
