Skip to main content

Community extensions

OpenMapX ships with a large set of first-party services and integrations, but its plugin model is open: anyone can publish an extension and you can install it into your own deployment. Extensions arrive through two channels, one per plugin layer:

  • Community service repositories add backend services — a database, a custom routing engine, an alternative geocoder, a data processor. They are registered by Git URL and land in the service catalog alongside the built-ins.
  • Community integrations add app-level features — map overlays, POI data sources, transit or geocoding providers, extra API routes. They are installed through the Store as prebuilt artifacts.

Both are managed from the admin panel (or the openmapx CLI) and both can be installed by the same operator into the same stack. This page covers installing third-party extensions. Writing your own is a separate topic — see the Developer section.

You are running third-party code

A community extension is code you did not write, running on your server. A service is a Docker container you choose to start; an integration's backend runs in-process inside the API server, with the same filesystem, network, and secrets access as a built-in. Neither channel is audited by the OpenMapX project. Install only from authors you trust, and read the install preview before you confirm.

Community service repositories

A community service repository is a Git repository that contains one or more <slug>/service.json manifests at its top level. Each manifest follows the same service manifest schema as a built-in service. Once you register the repository, every valid manifest in it appears in the service catalog and can be enabled, rendered, and started just like a first-party service.

Registering a repository

In the admin panel, open Services → Repositories → Add repository and paste the Git URL. The same flow is available from the CLI:

pnpm openmapx repos add https://github.com/someone/openmapx-services-foo
pnpm openmapx repos list

The URL is validated before anything is cloned. It must be https://, and the host must be on a short allowlist of public Git hosts: github.com, gitlab.com, codeberg.org, bitbucket.org, and git.sr.ht (plus the www. forms of GitHub and GitLab). This is a defense-in-depth gate — the endpoint is already admin-only, but the allowlist keeps an admin from coercing a clone of a file://, ssh://, or intranet URL into the service tree.

The install preview

Before you commit, OpenMapX performs a shallow clone, validates every service.json against the schema, and shows you an install preview — one card per service in the repository. The preview surfaces exactly what the service will do to your host:

  • the service id, name, version, and quality tier;
  • the capabilities it provides (so you know what it adds to your stack);
  • host port bindings — and any binding that is not loopback-only is flagged in red as publicly accessible, because that port will be reachable from the network, not just from the host;
  • reverse-proxy exposure, if the service asks Traefik to route a public path prefix to it;
  • requested Linux capabilities and devices beyond default container privileges;
  • validation errors, if any manifest is malformed.

If any manifest in the repository fails validation, the whole repository is refused — there are no partial installs. When the preview is clean, you confirm by checking the acknowledgment box (the CLI sets the equivalent acknowledgeRisks flag automatically), and the action is recorded in the audit log.

The manifest validator also constrains what a community service may request: quality must be community (or community-verified), and a community manifest may not use @-prefixed bind mounts (no Docker socket, no cross-service file sharing), networkMode: "host", or privileged: true. Plain relative-path bind mounts under the service's own directory are fine.

Where they land on disk

A registered repository is shallow-cloned into a directory keyed by a hash of its URL:

services/.community/<url-hash>/
├── foo-engine/
│ └── service.json
└── bar-database/
└── service.json

This directory is gitignored, and the service registry scans it on every render right after services/. Registering the same URL twice collapses to a single directory. A small service_repository row in the database records the URL, the hash, a display name, and the last-fetched commit.

Refreshing and removing

A registered repository tracks the remote's default branch. Refreshing pulls the latest commit and re-validates every manifest; if the upstream pushed a change that now fails validation, the refresh is rejected and your previous state is kept:

pnpm openmapx repos refresh <hash> # git fetch + reset, then re-validate
pnpm openmapx repos remove <hash> # unregister and delete the local clone

Both are also available under Services → Repositories in the admin panel.

Removing a repo does not stop its containers

repos remove deletes the clone and the database row and reloads the catalog, but it does not stop any container that was started from one of its services. Stop those services first if you want a clean tear-down. Once a community service is in the catalog you enable, build, and run it exactly like a built-in — see Managing services.

Community integrations (the Store)

A community integration is an application feature installed through the admin Store. Unlike services, integrations are installed as prebuilt artifacts — a single .tar.gz containing the manifest, a bundled backend ESM module, a bundled frontend ESM module, and a metadata file with checksums. The API server never builds at runtime; it only ever unpacks and loads finished artifacts. This keeps the app-api image immutable and makes installs survive a container restart.

Browsing the catalog and installing

Open Store in the admin panel. It lists entries from one or more catalog sources — HTTPS URLs that return a JSON array of available integrations. The default source is the OpenMapX community catalog; you can register additional sources under Store → Sources (HTTPS only, no credentials in the URL). The merged catalog is cached for 24 hours and can be refreshed on demand.

Each entry shows a trust-and-risk disclosure before you install — whether the integration runs a frontend bundle in the browser, executes backend code in the API process, makes outbound network calls, uses stored secrets, and which services it requires. There are two ways to install:

  • From the catalog — an entry that publishes an artifact URL installs with one click. Entries without a published artifact are shown for discovery but cannot be installed from the admin surface.
  • From an artifact URL — the Install from URL dialog accepts a manual HTTPS link to a .tar.gz, with an optional SHA-256 checksum. This is useful for a release candidate or an artifact hosted in a private location. When you supply a checksum, it is verified before the archive is extracted.

The admin install path is artifact-only by design — you cannot install an integration from a Git source URL through the admin panel, because that would require build tooling inside the API image. Building an integration from source is a developer workflow handled by the CLI.

Where they land on disk

Installed integrations live in:

custom_integrations/<id>/
├── manifest.json
└── dist/
├── frontend/index.js # loaded in the browser, when present
└── backend/index.mjs # imported by the API server, when present

This directory is gitignored and bind-mounted into the app-api container, so installs persist across restarts. A row in the database tracks each installed integration's id, source, and version.

After installing

In production, an integration's backend code is loaded into the API server's module cache at startup. After installing, updating, or removing one, restart the API server so the new code takes effect — the Store's job log surfaces this hint when it applies:

pnpm openmapx services restart app-api

Updates and removals are also driven from the Store. A catalog-managed integration that has a newer published version shows an available update; artifact-URL installs are refreshed by re-installing from a fresh URL.

Install-time hardening

Artifact installs are guarded on several fronts: only HTTPS artifact URLs are accepted from the admin surface; an optional SHA-256 is checked before extraction; artifacts are capped at 200 MB; tar extraction blocks path-escape (zip-slip), malicious symlinks and hardlinks, and absolute paths; and an artifact that ships a node_modules/ directory is rejected outright. These reduce the blast radius of a malformed archive — they are not a substitute for trusting the author of the code you run.

Choosing a channel

You want to add…Use…
A backend daemon — database, routing engine, geocoder, workerA community service repository
An app feature — overlay, data source, transit/geocoding provider, API routeA community integration via the Store

Where to go next

  • Managing services — enable, render, run, and configure any community service after you have registered its repository.
  • Admin panel — where the Store and Repositories sections live, and how admin access works.
  • How it works — the two plugin layers, in the overview.
  • Developer section — authoring your own service repository or community integration, the manifest schemas, and the artifact packaging workflow.