AssemblyScript is the recommended language for developers familiar with TypeScript. It produces the smallest binaries (3-10 KB) and has the fastest development cycle.
npm install @signalk/assemblyscript-plugin-sdk
npm install --save-dev assemblyscript
Create assembly/index.ts:
import {
Plugin,
Delta,
Update,
PathValue,
emit,
setStatus
} from '@signalk/assemblyscript-plugin-sdk/assembly'
class MyPlugin extends Plugin {
name(): string {
return 'My AssemblyScript Plugin'
}
schema(): string {
return `{
"type": "object",
"properties": {
"updateRate": {
"type": "number",
"default": 1000
}
}
}`
}
start(config: string): i32 {
setStatus('Started')
// Emit a test delta
const pathValue = new PathValue('test.value', '"hello"')
const update = new Update([pathValue])
const delta = new Delta('vessels.self', [update])
emit(delta)
return 0 // Success
}
stop(): i32 {
setStatus('Stopped')
return 0
}
}
// Export for Signal K
const plugin = new MyPlugin()
export function plugin_name(): string {
return plugin.name()
}
export function plugin_schema(): string {
return plugin.schema()
}
export function plugin_start(configPtr: usize, configLen: usize): i32 {
const configBytes = new Uint8Array(configLen)
for (let i = 0; i < configLen; i++) {
configBytes[i] = load<u8>(configPtr + i)
}
const configJson = String.UTF8.decode(configBytes.buffer)
return plugin.start(configJson)
}
export function plugin_stop(): i32 {
return plugin.stop()
}
Note on Plugin IDs: The plugin ID is automatically derived from your package.json name. For example:
@signalk/example-weather-plugin → _signalk_example-weather-pluginmy-simple-plugin → my-simple-pluginThis ensures unique plugin IDs (npm guarantees package name uniqueness) and eliminates discrepancies between package name and plugin ID.
Create asconfig.json:
{
"targets": {
"release": {
"outFile": "plugin.wasm",
"optimize": true,
"shrinkLevel": 2,
"converge": true,
"noAssert": true,
"runtime": "incremental",
"exportRuntime": true
},
"debug": {
"outFile": "build/plugin.debug.wasm",
"sourceMap": true,
"debug": true,
"runtime": "incremental",
"exportRuntime": true
}
},
"options": {
"bindings": "esm"
}
}
Important: exportRuntime: true is required for the AssemblyScript loader to work. This exports runtime helper functions like __newString and __getString that the server uses for automatic string conversions.
npx asc assembly/index.ts --target release
{
"name": "my-wasm-plugin",
"version": "0.1.0",
"keywords": ["signalk-wasm-plugin"],
"wasmManifest": "plugin.wasm",
"wasmCapabilities": {
"dataRead": true,
"dataWrite": true,
"storage": "vfs-only"
}
}
Important: What makes a WASM plugin?
The
wasmManifestfield is the key identifier that tells Signal K this is a WASM plugin (not a Node.js plugin). It must point to your compiled.wasmfile.The package name can be anything - scoped (
@myorg/my-plugin) or unscoped (my-wasm-plugin). Choose a name that makes sense for your plugin and avoids conflicts on npm.
Option 1: Symlink (Recommended for Development)
Symlinking your plugin directory allows you to make changes and rebuild without copying files:
# From your Signal K node_modules directory
cd ~/.signalk/node_modules
ln -s /path/to/your/my-wasm-plugin my-wasm-plugin
# Now any changes you make and rebuild will be picked up on server restart
Option 2: Direct Copy
mkdir -p ~/.signalk/node_modules/my-wasm-plugin
cp plugin.wasm package.json ~/.signalk/node_modules/my-wasm-plugin/
# If your plugin has a public/ folder with web UI:
cp -r public ~/.signalk/node_modules/my-wasm-plugin/
Option 3: NPM Package Install
# If you've packaged with `npm pack`
npm install -g ./my-wasm-plugin-1.0.0.tgz
# Or install from npm registry (if published)
npm install -g my-wasm-plugin
Note: Symlinking is the most efficient method for development - changes are picked up on server restart without copying files. Use npm install for production deployments or when distributing plugins.
Important: If your plugin includes static files (like a web UI in the public/ folder), make sure to copy that folder as well. Static files are automatically served at /plugins/your-plugin-id/ when the plugin is loaded.
After installing your plugin, verify it appears in the Admin UI:
Navigate to Plugin Configuration: Open the Admin UI at http://your-server:3000/@signalk/server-admin-ui/ and go to Server → Plugin Config
Check Plugin List: Your WASM plugin should appear in the list with:
name() export)package.json)schema() export)Verify Configuration Persistence:
~/.signalk/plugin-config-data/your-plugin-id.json{
"enabled": true,
"enableDebug": false,
"configuration": {
"updateRate": 1000
}
}
Troubleshooting:
package.json has the signalk-wasm-plugin keyword and wasmManifest fieldschema() export returns valid JSON Schema~/.signalk/plugin-config-data/Important: The Admin UI shows all plugins (both Node.js and WASM) in a unified list. WASM plugins integrate seamlessly with the existing plugin configuration system.
PluginAbstract base class for all plugins.
Methods to implement:
id(): string - Unique plugin identifiername(): string - Human-readable nameschema(): string - JSON schema for configurationstart(config: string): i32 - Initialize pluginstop(): i32 - Clean shutdownDeltaRepresents a Signal K delta message.
const delta = new Delta('vessels.self', [update])
UpdateRepresents an update within a delta. The server automatically adds $source and timestamp.
const update = new Update([pathValue])
PathValueRepresents a path-value pair.
const pathValue = new PathValue('navigation.position', positionJson)
PositionGPS position with latitude/longitude.
const pos = new Position(60.1, 24.9)
const posJson = pos.toJSON()
NotificationSignal K notification.
const notif = new Notification(NotificationState.normal, 'Hello!')
const notifJson = notif.toJSON()
emit(delta: Delta): voidEmit a delta message to Signal K server.
emit(delta)
Requires capability: dataWrite: true
setStatus(message: string): voidSet plugin status (shown in admin UI).
setStatus('Running normally')
setError(message: string): voidReport an error (shown in admin UI).
setError('Sensor connection failed')
debug(message: string): voidLog debug message to server logs.
debug('Processing data: ' + value.toString())
getSelfPath(path: string): string | nullRead data from vessel.self.
const speedJson = getSelfPath('navigation.speedOverGround')
if (speedJson !== null) {
const speed = parseFloat(speedJson)
}
Requires capability: dataRead: true
getPath(path: string): string | nullRead data from any context.
const posJson = getPath('vessels.self.navigation.position')
Requires capability: dataRead: true
readConfig(): stringRead plugin configuration.
const configJson = readConfig()
saveConfig(configJson: string): i32Save plugin configuration.
const result = saveConfig(JSON.stringify(config))
if (result !== 0) {
setError('Failed to save config')
}
import {
createSimpleDelta,
getCurrentTimestamp
} from '@signalk/assemblyscript-plugin-sdk'
// Quick delta creation
const delta = createSimpleDelta('my-plugin', 'test.value', '"hello"')
emit(delta)
The SDK includes assemblyscript-json for parsing JSON data. This is useful when working with configuration, API responses, or resource provider requests.
import { JSON } from '@signalk/assemblyscript-plugin-sdk/assembly'
// Parse a JSON string
const jsonStr = '{"name": "My Boat", "speed": 5.2}'
const parsed = JSON.parse(jsonStr)
if (parsed.isObj) {
const obj = parsed as JSON.Obj
// Get string values
const nameValue = obj.getString('name')
if (nameValue !== null) {
const name = nameValue.valueOf() // "My Boat"
}
// Get number values
const speedValue = obj.getNum('speed')
if (speedValue !== null) {
const speed = speedValue.valueOf() // 5.2 (as f64)
}
}
Available methods on JSON.Obj:
getString(key) - Returns JSON.Str | nullgetNum(key) - Returns JSON.Num | nullgetBool(key) - Returns JSON.Bool | nullgetObj(key) - Returns JSON.Obj | nullgetArr(key) - Returns JSON.Arr | nullgetValue(key) - Returns JSON.Value | nullNote: Plugins using resource providers or parsing complex JSON should add assemblyscript-json to their dependencies:
npm install assemblyscript-json
Values must be JSON-encoded strings:
// Numbers
const pathValue = new PathValue('temperature', '25.5')
// Strings (note the quotes)
const pathValue = new PathValue('name', '"My Boat"')
// Objects
const pathValue = new PathValue(
'position',
'{"latitude":60.1,"longitude":24.9}'
)
// Use helper classes
const pos = new Position(60.1, 24.9)
const pathValue = new PathValue('position', pos.toJSON())
WASM plugins can register as resource providers to serve data via the Signal K REST API.
package.json:{
"wasmCapabilities": {
"resourceProvider": true
}
}
start():import {
registerResourceProvider,
ResourceGetRequest
} from '@signalk/assemblyscript-plugin-sdk/assembly/resources'
start(config: string): i32 {
if (registerResourceProvider('weather')) {
debug('Registered as weather resource provider')
}
return 0
}
// List all resources - GET /signalk/v2/api/resources/weather
export function resources_list_resources(queryJson: string): string {
return '{"current":' + cachedData.toJSON() + '}'
}
// Get specific resource - GET /signalk/v2/api/resources/weather/{id}
export function resources_get_resource(requestJson: string): string {
const req = ResourceGetRequest.parse(requestJson)
if (req.id === 'current') {
return cachedData.toJSON()
}
return '{"error":"Not found"}'
}
Once registered, your resources are available at:
curl http://localhost:3000/signalk/v2/api/resources/weather
curl http://localhost:3000/signalk/v2/api/resources/weather/current
AssemblyScript plugins can make HTTP requests using the as-fetch library with Asyncify support.
npm install as-fetch @signalk/assemblyscript-plugin-sdk
asconfig.json:{
"options": {
"bindings": "esm",
"exportRuntime": true,
"transform": ["as-fetch/transform"]
}
}
package.json:{
"wasmCapabilities": {
"network": true
}
}
import { fetchSync } from 'as-fetch/sync'
const response = fetchSync('https://api.example.com/data')
if (response && response.status === 200) {
const data = response.text()
// Process data...
}
Asyncify enables synchronous-style async code in WASM:
fetchSync() is calledThe Signal K runtime handles all state transitions automatically.
fetchSync hangs or doesn't work:
"transform": ["as-fetch/transform"] is in asconfig.jsonimport { fetchSync } from 'as-fetch/sync'"network": true in wasmCapabilitiesRequest fails:
See the example-weather-plugin for a complete implementation.
AssemblyScript is a strict subset of TypeScript. Notable differences:
any typeSee AssemblyScript documentation for details.
Check that:
wasmManifest points to correct filesignalk-wasm-plugin keyword is presentfile plugin.wasmCommon issues:
Check server logs:
DEBUG=signalk:wasm:* npm start