---
title: "PebbleKit JS"
canonical_url: https://developer.repebble.com/guides/communication/using-pebblekit-js/
last_updated: 2026-10-11
sdk_version: 4.33.1
---
# PebbleKit JS

Source: https://developer.repebble.com/guides/communication/using-pebblekit-js/

PebbleKit JS allows a JavaScript component (run in a sandbox inside the official Pebble mobile app) to be added to any watchapp or watchface in order to extend the functionality of the app beyond what can be accomplished on the watch itself.

Extra features available to an app using PebbleKit JS include:

- Access to extended storage with [`localStorage`](#using-localstorage).

- Internet access using [`XMLHttpRequest`](#using-xmlhttprequest).

- Location data using [`geolocation`](#using-geolocation).

- The ability to show a configuration page to allow users to customize how the app behaves. This is discussed in detail in [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md).

## Setting Up

PebbleKit JS can be set up by creating the `index.js` file in the project's `src/pkjs/` directory. Code in this file will be executed when the associated watchapp is launched, and will stop once that app exits.

The basic JS code required to begin using PebbleKit JS is shown below. An event listener is created to listen for the `ready` event - fired when the watchapp has been launched and the JS environment is ready to receive messages. This callback must return within a short space of time (a few seconds) or else it will timeout and be killed by the phone.

```
Pebble.addEventListener('ready', function() {// PebbleKit JS is ready!console.log('PebbleKit JS ready!');});
```

**Important**

A watchapp or watchface **must** wait for the `ready` event before attempting to send messages to the connected phone. See [_Advanced Communication_](https://developer.repebble.com/guides/communication/advanced-communication.md#waiting-for-pebblekit-js) to learn how to do this.

## Defining Keys

Before any messages can be sent or received, the keys to be used to store the data items in the dictionary must be declared. The watchapp side uses exclusively integer keys, whereas the JavaScript side may use the same integers or named string keys declared in `package.json`. Any string key not declared beforehand will not be transmitted to/from Pebble.

> Note: This requirement is true of PebbleKit JS **only** , and not PebbleKit Android or iOS.

Keys are declared in the project's `package.json` file in the `messageKeys` object, which is inside the `pebble` object. Example keys are shown as equivalents to the ones used in the hypothetical weather app example in [_Sending and Receiving Data_](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#choosing-keys).

```
"messageKeys": ["Temperature","WindSpeed","WindDirection","RequestData","LocationName"]
```

The names chosen here will be injected into your C code prefixed with `MESSAGE_KEY_`, like `MESSAGE_KEY_Temperature`. As such, they must be legal C identifiers.

If you want to emulate an array by attaching multiple "keys" to a name, you can specify the size of the array by adding it in square brackets: for instance, `"LapTimes[10]`" would create a key called `LapTimes` and leave nine empty keys after it which can be accessed by arithmetic, e.g. `MESSAGE_KEY_LapTimes + 3`.

## Sending Messages from JS

Messages are sent to the C watchapp or watchface using `Pebble.sendAppMessage()`, which accepts a standard JavaScript object containing the keys and values to be transmitted. The keys used **must** be identical to the ones declared earlier.

An example is shown below:

```
// Assemble data objectvar dict = {'Temperature': 29,'LocationName': 'London, UK'};// Send the objectPebble.sendAppMessage(dict, function() {console.log('Message sent successfully: ' + JSON.stringify(dict));}, function(e) {console.log('Message failed: ' + JSON.stringify(e));});
```

It is also possible to read the numeric values of the keys by `require`ing `message_keys`, which is necessary to use the array feature. For instance:

```
// Require the keys' numeric values.var keys = require('message\_keys');// Build a dictionary.var dict = {}dict[keys.LapTimes] = 42dict[keys.LapTimes+1] = 51// Send the objectPebble.sendAppMessage(dict, function() {console.log('Message sent successfully: ' + JSON.stringify(dict));}, function(e) {console.log('Message failed: ' + JSON.stringify(e));});
```

### Type Conversion

Depending on the type of the item in the object to be sent, the C app will be able to read the value (from the [`Tuple.value` union](https://developer.repebble.com/guides/communication/sending-and-receiving-data.md#data-types)) according to the table below:

| JS Type | Union member |
| --- | --- |
| String | cstring |
| Number | int32 |
| Array | data |
| Boolean | int16 |

## Receiving Messages in JS

When a message is received from the C watchapp or watchface, the `appmessage` event is fired in the PebbleKit JS app. To receive these messages, register the appropriate event listener:

```
// Get AppMessage eventsPebble.addEventListener('appmessage', function(e) {// Get the dictionary from the messagevar dict = e.payload;console.log('Got message: ' + JSON.stringify(dict));});
```

Data can be read from the dictionary by reading the value if it is present. A suggested best practice involves first checking for the presence of each key within the callback using an `if()` statement.

```
if(dict['RequestData']) {// The RequestData key is present, read the valuevar value = dict['RequestData'];}
```

## Using LocalStorage

In addition to the storage available on the watch itself through the [`Storage`](https://developer.repebble.com/docs/c/Foundation/Storage.md "Storage") API, apps can take advantage of the larger storage on the connected phone through the use of the HTML 5 [`localStorage`](http://www.w3.org/TR/webstorage/) API. Data stored here will persist across app launches, and so can be used to persist latest data, app settings, and other data.

PebbleKit JS `localStorage` is:

- Associated with the application UUID and cannot be shared between apps.

- Persisted when the user uninstalls and then reinstalls an app.

- Persisted when the user upgrades an app.

To store a value:

```
var color = '#FF0066';// Store some datalocalStorage.setItem('backgroundColor', color);
```

To read the data back:

```
var color = localStorage.getItem('backgroundColor');
```

> Note: Keys used with `localStorage` should be Strings.

## Using XMLHttpRequest

A PebbleKit JS-equipped app can access the internet and communicate with web services or download data using the standard [`XMLHttpRequest`](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest) object.

To communicate with the web, create an `XMLHttpRequest` object and send it, specifying the HTTP method and URL to be used, as well as a callback for when it is successfully completed:

```
var method = 'GET';var url = 'http://example.com';// Create the requestvar request = new XMLHttpRequest();// Specify the callback for when the request is completedrequest.onload = function() {// The request was successfully completed!console.log('Got response: ' + this.responseText);};// Send the requestrequest.open(method, url);request.send();
```

If the response is expected to be in the JSON format, data items can be easily read after the `responseText` is converted into a JSON object:

```
request.onload = function() {try {// Transform in to JSONvar json = JSON.parse(this.responseText);// Read datavar temperature = json.main.temp;} catch(err) {console.log('Error parsing JSON response!');}};
```

## Using Geolocation

PebbleKit JS provides access to the location services provided by the phone through the [`navigator.geolocation`](http://dev.w3.org/geo/api/spec-source.html) object.

Declare that the app will be using the `geolocation` API by adding the string `location` in the `capabilities` array in `package.json`:

```
"capabilities": ["location"]
```

Below is an example showing how to get a single position value from the `geolocation` API using the [`getCurrentPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/getCurrentPosition) method:

```
function success(pos) {console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);}function error(err) {console.log('location error (' + err.code + '): ' + err.message);}/\* ... \*/// Choose options about the data returnedvar options = {enableHighAccuracy: true,maximumAge: 10000,timeout: 10000};// Request current positionnavigator.geolocation.getCurrentPosition(success, error, options);
```

Location permission is given by the user to the Pebble application for all Pebble apps. The app should gracefully handle the `PERMISSION DENIED` error and fallback to a default value or manual configuration when the user has denied location access to Pebble apps.

```
function error(err) {if(err.code == err.PERMISSION\_DENIED) {console.log('Location access was denied by the user.');} else {console.log('location error (' + err.code + '): ' + err.message);}}
```

The `geolocation` API also provides a mechanism to receive callbacks when the user's position changes to avoid the need to manually poll at regular intervals. This is achieved by using [`watchPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition) in a manner similar to the example below:

```
// An ID to store to later clear the watchvar watchId;function success(pos) {console.log('Location changed!');console.log('lat= ' + pos.coords.latitude + ' lon= ' + pos.coords.longitude);}function error(err) {console.log('location error (' + err.code + '): ' + err.message);}/\* ... \*/var options = {enableHighAccuracy: true,maximumAge: 0,timeout: 5000};// Get location updateswatchId = navigator.geolocation.watchPosition(success, error, options);
```

To cancel the update callbacks, use the `watchId` variable received when the watch was registered with the [`clearWatch()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/clearWatch) method:

```
// Clear the watch and stop receiving updatesnavigator.geolocation.clearWatch(watchId);
```

## Account Token

PebbleKit JS provides a unique account token that is associated with the Pebble account of the current user, accessible using `Pebble.getAccountToken()`:

```
// Get the account tokenconsole.log('Pebble Account Token: ' + Pebble.getAccountToken());
```

The token is a string with the following properties:

- From the developer's perspective, the account token of a user is identical across platforms and across all the developer's watchapps.

- If the user is not logged in, the token will be an empty string ('').

## Watch Token

PebbleKit JS also provides a unique token that can be used to identify a Pebble device. It works in a similar way to `Pebble.getAccountToken()`:

```
// Get the watch tokenconsole.log('Pebble Watch Token: ' + Pebble.getWatchToken());
```

The token is a string derived from the watch and the developer ID of an appstore app, or the UUID of a sideloaded app, so apps from the same developer see the same watch token and apps from different developers do not.

**Important**

The watch token is dependent on the watch's serial number, and therefore **should not** be used to store sensitive user information in case the watch changes ownership. If the app wishes to track a specific user _and_ watch, use a combination of the watch and account token.

## Showing a Notification

A PebbleKit JS app can send a notification to the watch. This uses the standard system notification layout with customizable `title` and `body` fields:

```
var title = 'Update Available';var body = 'Version 1.5 of this app is now available from the appstore!';// Show the notificationPebble.showSimpleNotificationOnPebble(title, body);
```

> Note: PebbleKit Android/iOS applications cannot directly invoke a notification, and should instead leverage the respective platform notification APIs. These will be passed on to Pebble unless the user has turned them off in the mobile app.

## Getting Watch Information

Use `Pebble.getActiveWatchInfo()` to return an object of data about the connected Pebble.

This API is currently only available for SDK 3.0 and above. Do not assume that this function exists, so test that it is available before attempting to use it using the code shown below.

```
var watch = Pebble.getActiveWatchInfo ? Pebble.getActiveWatchInfo() : null;if(watch) {// Information is available!} else {// Not available, handle gracefully}
```

> Note: If there is no active watch available, `null` will be returned.

The table below details the fields of the returned object and the information available.

| Field | Type | Description | Values |
| --- | --- | --- | --- |
| `platform` | String | Hardware platform name. | `aplite`, `basalt`, `chalk`. |
| `model` | String | Watch model name including color. | `pebble_black`, `pebble_grey`, `pebble_white`, `pebble_red`, `pebble_orange`, `pebble_blue`, `pebble_green`, `pebble_pink`, `pebble_steel_silver`, `pebble_steel_black`, `pebble_time_red`, `pebble_time_white`, `pebble_time_black`, `pebble_time_steel_black`, `pebble_time_steel_silver`, `pebble_time_steel_gold`, `pebble_time_round_silver_14mm`, `pebble_time_round_black_14mm`, `pebble_time_round_rose_gold_14mm`, `pebble_time_round_silver_20mm`, `pebble_time_round_black_20mm`, `qemu_platform_aplite`, `qemu_platform_basalt`, `qemu_platform_chalk`. |
| `language` | String | Language currently selected on the watch. | E.g.: `en_GB`. See the [_Internationalization_](https://developer.repebble.com/guides/tools-and-resources/internationalization.md#locales-supported-by-pebble) for more information. |
| `firmware` | Object | The firmware version running on the watch. | See below for sub-fields. |
| `firmware.major` | Number | Major firmware version. | E.g.: `2` |
| `firmware.minor` | Number | Minor firmware version. | E.g.: `8` |
| `firmware.patch` | Number | Patch firmware version. | E.g.: `1` |
| `firmware.suffix` | String | Any additional firmware versioning. | E.g.: `beta3` |

## Differences in the Pebble Mobile App

The Pebble mobile app runs PebbleKit JS differently on each phone platform. On Android the JavaScript runs in a hidden WebView, so the full set of browser APIs is available. On iOS it runs in JavaScriptCore with a small set of APIs added by the app: `XMLHttpRequest`, `WebSocket`, `setTimeout` and `setInterval`, `localStorage`, `atob` and `btoa`, `navigator.geolocation`, and `console`. Anything else that a browser provides, such as `fetch`, `Blob` or the DOM, does not exist on iOS.

Code that follows the sections above works on both. The sections below list the behaviour that differs from the original Pebble apps, or between Android and iOS, so that existing apps can be checked against it. The source is at [github.com/coredevices/mobileapp](https://github.com/coredevices/mobileapp).

### Lifecycle

The JavaScript for an app starts when the app starts running on the watch and stops when the app exits. Launching the app again, or reconnecting the watch, starts a fresh JavaScript context, so state that must survive belongs in `localStorage`. Only one app's JavaScript runs at a time.

On iOS the `ready` event fires as soon as the app's script has been evaluated. On Android it fires once the script has loaded in the WebView. Messages from the watch that arrive before `ready` are held for up to six seconds.

On iOS the phone suspends the Pebble mobile app when it is in the background and no Bluetooth activity is pending. Timers do not fire while the app is suspended and run late when it resumes, so do not rely on `setInterval` for work that must happen while the user is not looking at the phone.

### Network Requests

Use `XMLHttpRequest`. It is the WebView's own implementation on Android and the app's implementation on iOS, where these limits apply:

- Event handlers receive an event with only a `type` property. Read the result from the request object, not from `event.target`.

- `responseType` can be `""`, `"text"`, `"json"` or `"arraybuffer"`. `"blob"` and `"document"` throw. An `arraybuffer` response is delivered as a `Uint8Array`.

- There is no `timeout` property and there are no progress events.

- Synchronous requests (`async` set to `false` in `open()`) still deliver their response asynchronously, so the response is not available when `send()` returns. Always use asynchronous requests.

`fetch()` is only available on Android. Apps that use it fail on iOS with `fetch is not defined`.

`WebSocket` is available on both platforms. On iOS `binaryType` is always `"arraybuffer"`.

### Storage

`localStorage` is separate for each app, keyed by the app's UUID, and keeps its contents across app launches and phone app restarts. Values are stored as strings, as in a browser.

On Android, values written through the `localStorage` object are saved when the app's JavaScript stops. Use `localStorage.setItem()` rather than assigning properties so that writes are not lost if the phone app is killed.

### Geolocation

`navigator.geolocation.getCurrentPosition()` and `watchPosition()` work on both platforms with these differences from a browser:

- `maximumAge` (default 0) and `timeout` (default 15 seconds) are honoured. When the timeout passes and a previous position is known, that position is returned instead of an error.

- `enableHighAccuracy` only affects `watchPosition()`.

- The position object has `coords` with `latitude`, `longitude`, `accuracy`, `altitude`, `heading` and `speed`, and no `timestamp`.

- Errors always have `code` set to `1` and a `message` string.

### Tokens

`Pebble.getAccountToken()` and `Pebble.getWatchToken()` are derived from the signed-in Pebble account (or, for the watch token, the watch serial) together with the developer ID of an appstore app or the UUID of a sideloaded app. Both tokens therefore change when an app that was tested by sideloading is installed from the appstore. Treat data stored against a token from a sideloaded build as test data.

`Pebble.getTimelineToken()` returns the token the appstore issued for the app. A sideloaded app gets a placeholder token while the _Emulate Timeline Webservice_ setting is on, which it is by default, and the failure callback when it is off. Neither token can be used with a server: the Pebble mobile app does not sync pins, so use [_local pins_](https://developer.repebble.com/guides/pebble-timeline/timeline-local-pins.md) instead.

### Watch Information

`Pebble.getActiveWatchInfo()` returns `platform`, `model`, `language` and `firmware` (`major`, `minor`, `patch`, `suffix`). The `platform` value is the connected watch's platform only when that platform is in the app's `targetPlatforms`. When the watch is running a build for an older platform, `platform` reports the platform of the build that is installed:

| Connected watch | Reported when not a target |
| --- | --- |
| flint (Pebble 2 Duo) | diorite if targeted, otherwise aplite |
| emery (Pebble Time 2) | basalt |
| gabbro (Pebble Round 2) | chalk |

Add the new platforms to `targetPlatforms` and rebuild to receive their real names.

### AppMessage

`Pebble.sendAppMessage()` follows these rules:

- Keys must be names declared in `messageKeys` in `package.json`, or numeric strings. Keys that are not declared are dropped without an error.

- Values can be strings, integers, booleans (sent as 0 or 1) and arrays of numbers (sent as a byte array). Fractional numbers are truncated to integers. Nested objects are not supported and stop the message from being sent.

- The call fails with `"Failed to connect to Pebble"` if the watch does not accept the message within five seconds. If the watch does not acknowledge the message within ten seconds, the failure callback receives an error of `"nack"`.

### Configuration Pages

`Pebble.openURL()` is only honoured in response to the `showConfiguration` event, and must be called within ten seconds of it. The page opens in a WebView inside the Pebble mobile app, not in the system browser. `data:` URLs are accepted, so a configuration page can be embedded in the app's JavaScript.

The page returns its result by navigating to `pebblejs://close#<data>`. `pebblejs://close/?<data>` and `pebblejs://close/<data>` are also accepted; `pebblejs://close?data=...` is not. The data is percent-decoded once and passed to the `webviewclosed` handler as `e.response`. It is not parsed, so call `JSON.parse(e.response)` as before. If the page is closed without data, or the user leaves it with the back button, `webviewclosed` does not fire. Read [_App Configuration_](https://developer.repebble.com/guides/user-interfaces/app-configuration.md) for the full flow.

### Plugins (Preview)

The `Pebble` object has four functions for the plugins preview. They do nothing useful until the preview is turned on in the Pebble mobile app, which is off by default, and the API will change; read [_Plugins (Preview)_](https://developer.repebble.com/guides/plugins.md) before using them.

- `Pebble.enumeratePlugins()` returns an array of the installed plugins with their sources and actions.

- `Pebble.subscribeToSource(config)` subscribes to a category and item and delivers updates to `config.onData`.

- `Pebble.invokeAction(config)` runs one action on one plugin and returns a Promise that resolves with the result.

- `Pebble.sendConfigMessage(message)` pushes a message to the app's own settings page while it is open.

### Other Differences

- `Pebble.showToast()` shows a toast on Android and does nothing on iOS.

- `Pebble.getVersionCode()` is not available. Calling it throws.

- `Pebble.timelineSubscribe()`, `timelineUnsubscribe()`, `timelineSubscriptions()` and `appGlanceReload()` do nothing and never call back. Server-pushed pins and glances are not supported.

- `console.log()` output appears in `pebble logs` prefixed with the app's name and the line number. The Pebble mobile app's own log, the one included in bug reports, replaces lines containing words such as "token" or "location" with `<REDACTED>` when the setting that obfuscates log content is on; the Developer Connection log is not redacted.
