Guide
Creating a registry
A registry is a JSON file, published at any URL you control, listing plugins other people can add to their copy of Map Extender. The extension ships with none pre-installed β anyone who wants more pastes in a registry's address themselves. This page is the format that file has to follow.
How it fits together
A registry is one JSON document with an array of plugins, each carrying its own code, match patterns, and settings inline. When a user pastes your registry's URL into the extension's gallery, it fetches that one file, shows them what's in it, and lets them add whichever plugins they want β each one still goes through the extension's own review screen before it's stored.
The official plugin gallery is itself just a registry published this way β its source repository is a working example and reasonable template to start from if you'd rather fork something than write the JSON by hand.
The document shape
{
"formatVersion": 1,
"name": "My Plugins", // shown in the gallery
"description": "A short list of my plugins.", // optional
"homepage": "https://github.com/you/repo", // optional
"updatedAt": "2026-08-17", // optional
"plugins": [
{
"id": "my-registry:bicycle-parking", // namespaced by you, see below
"name": "Bicycle Parking",
"description": "Shows bicycle parking from OpenStreetMap.",
"version": "1.1.0", // absent means 0.0.0
"author": "Jane Doe",
"homepage": "https://github.com/you/repo", // optional
"usage": "Zoom in, then pan to load", // optional, shown in the popup when enabled
"screenshots": [ // optional, https: or data:image/* only
"https://you.github.io/my-registry/parking.png"
],
"matchPatterns": ["*://*/*"],
"settingsSchema": [
{ "key": "color", "label": "Marker colour", "type": "text", "default": "blue" }
],
"settings": { "color": "blue" },
"code": "plugin.mapHook.onHook(function () { /* ... */ })"
}
]
}
The code is inlined directly in the JSON, deliberately β one fetch, one thing to validate, and no half-updated state if a follow-up request fails partway through.
Rules the extension enforces
A document failing any of these is rejected, with a message naming the plugin at fault:
formatVersionis1. A document declaring a higher number is refused with a message telling the user to update the extension.- Every
idis namespaced and unique within the document, and may never start withbuiltin:β that prefix is reserved for the plugins the extension seeds itself. versionismajor.minor.patch, compared numerically part by part, so1.10.0counts as newer than1.9.0. Bump it whenevercodechanges, or installs never see the update.codeis plain JavaScript for thepluginglobal described in the API reference β noimport,export, TypeScript, or JSX. It's injected as a source string, not loaded as a module.matchPatternsfollow the same wildcard syntax as a hand-written plugin (<all_urls>,*://*/*,*://*.example.com/*), matched per URL component β a bare hostname on its own is not a valid pattern.- Every
settingsSchemakey should have a matching value insettings. Missing ones are backfilled from the schema's defaults on save, but shipping them explicitly keeps the document readable. screenshotsmust behttps:URLs or inlinedata:image/*β anything else is silently dropped, and the user is shown how many images were hidden.
A document is also capped at 2 MB and 200 plugins β the extension's local storage is 10 MB total, shared with everything else it stores.
Hosting it
Any https: URL works β a GitHub Pages site, a raw file on GitHub, or your
own server. The extension re-checks that the response still comes from an
https: origin even after following redirects, sends no credentials with the
request, and uses ETag caching so refreshing an already-added registry is
cheap. Nothing is fetched until a user adds your URL themselves, and nothing refreshes
automatically β only when they ask.
Whoever hosts your registry's JSON β and, separately, whoever hosts any
https: screenshot URLs in it β sees a request each time someone loads it,
which discloses that person's IP address and that they're browsing plugins. If that
matters to you, embed screenshots as data: URIs instead of linking them.
What protects the user
An installed plugin is executable code running on every site its patterns match, so the extension treats adding one the same way a careful userscript manager would:
- The source is shown before anything is stored β code, match patterns, and settings, behind a plain warning that it runs with the extension's access.
- Adding never enables. Turning a plugin on for a given site stays a separate choice, made afterward.
- Updates are never silent. An update is only offered from the same registry URL a plugin was originally installed from β so a second, unrelated registry can't hijack it by publishing a plugin with the same id β and the user is shown a line-by-line diff of exactly what changed, not just the new code dropped in whole.
That last point is what actually matters: without a diff, "publish something useful, wait for it to be adopted, then push a malicious update" has nothing standing in its way.
Starting from a template
Fork the official registry
github.com/ssz360/map-extender-plugins already has a validation script and a contributing guide for adding a plugin folder β forking it and replacing its plugins with your own is the fastest path to a working registry.