Enhanced Capacitor Watch Plugin with Full Bidirectional Messaging
Forked from @capacitor/watch with added support for Watch β Phone communication
This fork adds full bidirectional messaging between iPhone and Apple Watch:
β
NEW: Watch β Phone event listeners in JavaScript
β
NEW: messageReceived event for incoming watch messages
β
NEW: messageReceivedWithReply for interactive request/response patterns
β
NEW: Standard WatchConnectivity methods: sendMessage(), updateApplicationContext(), transferUserInfo()
β
NEW: getInfo() method to check watch pairing and reachability status
β
NEW: reachabilityChanged listener for connection status monitoring
β
100% Backward Compatible - All existing @capacitor/watch code still works
The original @capacitor/watch plugin only supports Phone β Watch communication. It has no way for JavaScript to receive messages from the watch. This fork adds the missing WCSessionDelegate event forwarding to enable true bidirectional communication.
Perfect for:
- Interactive watch apps that need to request data from the phone
- Apps that need to know when watch becomes reachable/unreachable
- Request/response patterns with reply handlers
- Real-time watch-to-phone notifications
- ENHANCEMENTS.md - Complete API reference and examples
- VETDRUGS_INTEGRATION.md - VetDrugs-specific integration guide
- README.md (below) - Original installation and setup guide
# Clone and build
git clone https://github.com/macsupport/CapacitorWatchEnhanced.git
cd CapacitorWatchEnhanced/packages/capacitor-plugin
pnpm install
pnpm run build
# Install in your app
cd /path/to/your/capacitor/app
npm install file:///path/to/CapacitorWatchEnhanced/packages/capacitor-plugin
npx cap sync iosimport { Watch } from '@macsupport/capacitor-watch-enhanced';
// Check if watch is available
const info = await Watch.getInfo();
if (info.isSupported && info.isPaired && info.isWatchAppInstalled) {
console.log('β
Watch is ready!');
}
// Listen for messages from watch
Watch.addListener('messageReceived', (message) => {
console.log('Received from watch:', message);
if (message.type === 'requestData') {
// Handle watch request
handleWatchRequest(message.payload);
}
});
// Send data to watch
await Watch.updateApplicationContext({
data: {
currentState: 'updated',
timestamp: Date.now()
}
});Zero breaking changes! Just update your import:
- import { Watch } from '@capacitor/watch';
+ import { Watch } from '@macsupport/capacitor-watch-enhanced';All existing methods (updateWatchUI, updateWatchData, runCommand listener) work exactly the same.
updateWatchUI()- Legacy UI definition (backward compatible)updateWatchData()- Legacy data updates (backward compatible)sendMessage()- Interactive messaging with reply handlers [NEW]updateApplicationContext()- Latest-value-only sync [NEW]transferUserInfo()- Queued background transfers [NEW]getInfo()- Check watch status [NEW]
messageReceived- Simple messages from watch [NEW]messageReceivedWithReply- Messages expecting reply [NEW]applicationContextReceived- Context updates from watch [NEW]userInfoReceived- Background transfers from watch [NEW]reachabilityChanged- Watch connection status [NEW]activationStateChanged- Session state changes [NEW]runCommand- Legacy command listener (backward compatible)
- CapWatchDelegate.swift - Enhanced with
notifyListeners()calls to forward all WCSessionDelegate events to JavaScript - WatchPlugin.swift - Added new plugin methods and reply handler dictionary
- WatchPlugin.m - Updated Objective-C bridge with new method registrations
- definitions.ts - Complete TypeScript definitions with JSDoc
- Weak Reference Pattern - Plugin β Delegate β Plugin to prevent retain cycles
- All delegate callbacks forwarded to JavaScript on main thread
- Reply handlers stored in thread-safe dictionary
- Automatic cleanup after reply sent
- β iOS - Full support (iOS 14+)
- β watchOS - Full support (watchOS 7+)
β οΈ Web - Stubs only (returns "not supported")- β Android - Not supported (no WatchConnectivity on Android)
Found a bug or want to add features?
- Open an issue: https://github.com/macsupport/CapacitorWatchEnhanced/issues
- Submit a PR with tests
- Update documentation
MIT (same as original @capacitor/watch)
- Enhanced by: Michael Sozanski DVM DABVP/ @macsupport
- Original plugin: Ionic Team - @capacitor/watch
- Use case: VetDrugs veterinary drug calculator with Apple Watch support
Below is the original setup guide from the Ionic Team's @capacitor/watch plugin.
CapacitorLABS - This project is experimental. Support is not provided. Please open issues when needed.
The Capacitor Watch plugin allows you to define a UI for a watch in your web code and show it on a paired watch.
This currently only supports iOS. This guide assumes you've already added iOS to your capcacitor project.
Also note - all of this will only work with an actual Apple Watch. Simulators don't allow the app<->watch communcation like real devices do.
Step 1
Add the watch plugin to your capacitor project, and then open the Xcode project:
npm install @capacitor/watch
npx cap sync
npx cap open iosStep 2
Go to add capabilities:
Add the 'Background Modes' and 'Push Notification' capabilities. Then in the Background Modes options, select 'Background Fetch', 'Remote Notifications', and 'Background Processing'. Your App target should look like this:
Step 3
Open AppDelegate.swift and add import WatchConnectivity and import CapactiorWatch to the top of the file, and the following code inside the application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) method:
assert(WCSession.isSupported(), "This sample requires Watch Connectivity support!")
WCSession.default.delegate = CapWatchSessionDelegate.shared
WCSession.default.activate()Step 4
Select File -> New -> Target in Xcode, and then the watchOS tab, and 'App':
Click 'Next' then fill out the options like so:
This dialog can be a little confusing, the key thing is your 'Bundle Identifier' must be [your apps bundle ID].watchapp for the watch<->app pairing to work. You must also pick SwiftUI for the Interface and Swift for the language. The project should be App.
Step 5
We're going to add the code that makes Capacitor Watch work in the watch application.
If you are using Xcode 15 or beyond you then need to add the Capacitor Watch Swift Package from your node_modules:
First go to the project package dependancies
Then choose 'Add Local'
Then navigate into the node_modules/@capacitor/watch/CapWatch-Watch-SPM folder and click 'Add Package'
Then in the column on the right pick your watch app to be the target and click 'Add Package'
Once this is done your Package Dependancies should look like this:
With Xcode 14 you will need to go here https://github.com/ionic-team/CapacitorWatch/tree/main/packages/iOS-capWatch-watch/Sources/iOS-capWatch-watch and copy all the files into your Watch project and make sure the target selected is your watch app. It should look like so:
Step 6
Then open the watch app's 'Main' file which should be watchappApp.swift. Add the lines import WatchConnectivity and import iOS_capWatch_watch above the @main statement. Then replace the line that says ContentView() with this:
The finished file should look like this:
import SwiftUI
import WatchConnectivity
import iOS_capWatch_watch
@main
struct watchddgg_Watch_AppApp: App {
var body: some Scene {
WindowGroup {
CapWatchContentView()
.onAppear {
assert(WCSession.isSupported(), "This sample requires Watch Connectivity support!")
WCSession.default.delegate = WatchViewModel.shared
WCSession.default.activate()
}
}
}
}Step 7
Add the 'Background Modes' capability to the watch app target, and enable 'Remote Notifications':
You should be ready to develop for Capcacitor Watch now!
You can still develop your iOS app like a normal capacitor app, but getting things to run on the watch requires you to change the target and destination in Xcode. You can change this with the 'Target Dropdown' near the center-top of Xcode:
The right half of this bar lets you pick the destination device or simulator. You will need to pick the watch paired with the phone and then hit the 'Run' button or use the 'cmd+r' run shortcut.
There can be some challenges in syncing the watch and phone apps. Sometimes you will get an error in the xcode console complaining the compainion app is not present. The best solution in this case is to re-build and re-install the apps on both devices.
You will use a long string to define the watch UI. A newline delimits components. Currently this plugin only supports a vertical scroll view of either Text or Button components.
Once you've defined your UI you can send it to the watch using the updateWatchUI() method:
async uploadMyWatchUI() {
const watchUI =
`Text("Capacitor WATCH")
Button("Add One", "inc")`;
await Watch.updateWatchUI({"watchUI": watchUI});
}Will produce this:
This article provides a great summary on the native methods and their implications: https://alexanderweiss.dev/blog/2023-01-18-three-ways-to-communicate-via-watchconnectivity
On the phone side, you can implement these methods using the Capacitor Background Runner Plugin (https://github.com/ionic-team/capacitor-background-runner). Currently the watch plugin will mainly handle the didReceiveUserInfo method, and you can recieve envents from the watch while your app is in the background using the following code in your runner.js:
addEventListener("WatchConnectivity_didReceiveUserInfo", (args) => {
console.log(args.message.jsCommand);
})You can also implment the runCommand event listener for foreground procesing:
Watch.addListener("runCommand", (data: {command: string}) => {
console.log("PHONE got command - " + data.command);
})The commands are the 2nd paramter in the Button() definition of the watch UI. This can be any string.
You can add variables to Text() elements by using a $ variable and updating with the updateWatchData command:
Text("Show my $number")
This example will update $number when executed:
var stateData = {
number: 0
}
async function counterIncrement() {
stateData.counter++
await Watch.updateWatchData({"data": convertValuesOfObjectToStringValues(stateData)})
}Capacitor Watch will persist the last UI you sent with updateWatchUI(). State from updateWatchData() is NOT preserved.












