Practical tips to improve your Signal K plugin's registry score. Most fixes take less than 5 minutes.
| Tier | Points | How to pass |
|---|---|---|
| Install | 20 | npm install --ignore-scripts succeeds |
| Load | 15 | Module exports a function that returns {id, name, start, stop} |
| Activate | 15 | start(config) completes without error — config is populated from your schema defaults |
| Schema | 5 | plugin.schema returns a JSON Schema object |
| Tests | 25 | npm test passes (biggest single tier — see below) |
| Security | 20 | npm audit finds no high or critical vulnerabilities |
| Changelog | −5 if missing | Ship a CHANGELOG.md or publish a GitHub Release matching the version tag |
| Screenshots | −5 if missing | Declare signalk.screenshots (array of package-relative paths) in package.json |
The registry looks for a CHANGELOG.md in the published package first, and falls back to the public GitHub Releases feed for the repo — so either path works. The recommended approach is GitHub Releases driven by a tag push, with softprops/action-gh-release@v2 and generate_release_notes: true. A plain CHANGELOG.md at the repo root (Keep a Changelog style) is equally accepted.
Declare them in package.json:
"signalk": {
"displayName": "My Plugin",
"appIcon": "./assets/icon-128.png",
"screenshots": [
"./docs/screenshots/main.png",
"./docs/screenshots/config.png"
]
}
Paths must be package-relative and the files must be included in the published tarball (check your files field or .npmignore). The AppStore shows the first screenshot as the hero image.
npm audit
npm audit fix
Most issues come from transitive dependencies. Update your direct dependencies first. If a vulnerability is in a deep transitive dep you don't control, consider whether you really need that dependency.
Every property in your schema should have a default value. The registry extracts these and passes them to start(). If your plugin crashes without them, it loses 15 points.
schema: {
type: 'object',
properties: {
interval: {
type: 'number',
title: 'Update interval (seconds)',
default: 60
}
}
}
See Plugin Configuration & Schemas for full details.
Even with schema defaults, defensive coding helps:
start(config) {
const interval = config.interval ?? 60
const items = config.items || []
}
The registry clones your source repo and runs npm test. The easiest approach uses Node's built-in test runner — zero dependencies needed.
Create test/plugin.test.ts:
import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import pluginFactory from '../src/index'
describe('plugin', () => {
const app = { debug: () => {}, error: () => {} } as any
const plugin = pluginFactory(app)
it('has required interface', () => {
assert.equal(typeof plugin.start, 'function')
assert.equal(typeof plugin.stop, 'function')
assert.ok(plugin.id)
})
it('starts and stops without error', () => {
plugin.start({}, () => {})
plugin.stop()
})
})
Add to package.json:
"scripts": {
"build": "tsc",
"test": "tsc && node --test dist/test/plugin.test.js"
}
The registry clones your source repo, runs npm install and npm run build, then npm test — so typescript from your devDependencies is available.
Create test/plugin.test.js:
const { describe, it } = require('node:test')
const assert = require('node:assert/strict')
const pluginFactory = require('../plugin/index.js')
describe('plugin', () => {
const app = { debug: () => {}, error: () => {} }
const plugin = pluginFactory(app)
it('has required interface', () => {
assert.equal(typeof plugin.start, 'function')
assert.equal(typeof plugin.stop, 'function')
assert.ok(plugin.id)
})
it('starts and stops without error', () => {
plugin.start({}, () => {})
plugin.stop()
})
})
Add to package.json:
"scripts": {
"test": "node --test test/plugin.test.js"
}
Why node:test? Published npm packages don't include devDependencies, so jest/mocha won't be available when the registry installs your plugin. The registry clones your source repo to run tests, but node:test is built into Node and always available.
Your start() assumes config has nested objects that don't exist yet. Add default values to nested properties in your schema, or use optional chaining (config.options?.speed ?? 5).
Your test runner (jest, mocha, vitest) isn't installed because devDependencies aren't available. Switch to node:test (built-in) or ensure the test command works after a fresh npm install.
Run npm audit locally. Usually it's a transitive dependency. Try npm audit fix or update the parent dependency that pulls it in.
The registry retests when a new version is published to npm. Bump your version and publish. Alternatively, results older than 7 days are automatically retested on the nightly run.