Integrations & capability bindings
An integration is an app-level feature — the geocoding orchestrator, the
routing orchestrator, a transit provider, a map overlay, an external data
source, photos, reviews, place enrichment. Each one ships a manifest.json that
declares its config schema, the secrets it needs, and the backend services it
requires. The admin panel under /admin/integrations is where you turn those
declarations into a working configuration: enable the features you want, fill in
their settings and credentials, and — when a feature needs a backend that more
than one service can supply — pick which one it uses.
If the service/integration/capability model is new to you, read How it works first. This page assumes it and focuses on the admin side. The panel and the API behind it are mapped in the Admin panel tour; installing third-party integrations is covered in Community extensions.
The integration list
/admin/integrations lists every loaded integration with its display name, the
domains it serves, whether it's enabled, its health, and whether its credentials
and required services are satisfied. Click into one to open its detail page,
which is organized into tabs:
- Overview — metadata, the services and other integrations it depends on, and the capability-binding picker.
- Config — the per-integration settings form, plus a read-only table showing every resolved value and where it came from.
- Credentials — the secret fields the integration declares, and their status.
- Health — the current probe result, with a button to re-run it.
Two controls sit outside the tabs. An enable / disable toggle flips whether the integration loads at all, and a Reload action re-runs the integration's setup so a config or binding change takes effect. Reload is queued as a background job — watch it finish under the panel's Activity view (Monitoring).
Configuring an integration
If an integration's manifest declares a configSchema, the Config tab
renders it as a form: one input per setting, typed from the schema — toggles for
booleans, dropdowns for enumerations, text and number inputs otherwise. An
integration with no schema (or one that exposes nothing beyond the on/off
switch) shows a note instead.
Saving writes your edits to the database and reloads the integration so the new
values apply right away — there's no separate restart step for integration
config, unlike backend services. The form rejects unknown keys and type
mismatches against the schema, and it refuses two kinds of field: secret fields
(those go through the Credentials tab) and the enabled switch (use the
enable / disable toggle). Every change is written to the audit log.
The config cascade
Each setting's effective value is resolved through a layered cascade. The
Config tab shows a source chip next to every field naming the layer that
won, and a field pinned by an environment variable is rendered disabled —
you can see the value but not edit it, because the environment would override
your edit anyway.
There are five layers. Highest priority wins:
| Priority | Source | Where it comes from |
|---|---|---|
| 5 | env | A host environment variable, INTEGRATION_<ID>_<KEY>. |
| 4 | config.json | A config.json file in the integration's own directory. |
| 3 | vault | An admin-set secret in the encrypted vault. |
| 2 | database | A value you saved in the Config form. |
| 1 | default | The configSchema default — nothing has overridden it. |
So an environment variable beats a local config.json, which beats a vaulted
secret, which beats a value saved in the admin panel, which beats the schema
default. Environment variables always win, which keeps a Docker deployment
predictable: a value pinned in infra/docker/.env can't be silently changed
from the UI, and the disabled field plus its env chip make that visible.
If you save a field in the form and it doesn't change, look at its source chip.
A higher layer — almost always an env override in infra/docker/.env — is
taking precedence. Remove that override (or change it there) to let the admin
value through. The same logic governs every layer of the cascade.
The environment-variable name follows a fixed pattern: INTEGRATION_, then the
integration's id uppercased with hyphens turned into underscores, then _, then
the config key uppercased. The key match is case-insensitive, so a camelCase
schema key like apiKey is set by INTEGRATION_<ID>_APIKEY:
# infra/docker/.env — overrides the `apiKey` setting of the `ev-charging` integration
INTEGRATION_EV_CHARGING_APIKEY=your-key-here
For the bigger picture of where settings live — .env, the admin panel, and how
they relate — see Configuration. The parallel
cascade for backend services uses the SERVICE_<ID>_<KEY> prefix and only
three layers; see Managing services.
Secrets
A config field marked "x-openmapx-secret": true in the manifest is a secret —
an API key, a token, a password. Secrets are never shown in the Config form or
returned in API responses; they live on the Credentials tab instead, where
each declared secret is listed with its status: stored in the vault,
supplied by an env override, or missing.
Set or rotate a secret from that tab. Its value is encrypted at rest in the
database vault, which requires OPENMAPX_SECRETS_KEY to be set on the API host
(generate one with openssl rand -hex 32). Without that key the vault is
unavailable and the panel says so; an integration that declares secret fields
will refuse to load at startup until the key is present, so this is a hard
requirement rather than a silent degrade.
You can also supply a secret entirely through the environment, with the same
INTEGRATION_<ID>_<KEY> variable used for any other setting — handy when you'd
rather keep credentials in infra/docker/.env than in the database. As always,
the env value wins over a vaulted one. An integration whose every required
secret is still missing is flagged as unconfigured in the list and on the
Overview dashboard.
Capability bindings
Integrations don't name a specific backend; they ask for a capability. The
routing integration requires a routing-engine; the geocoding providers require
a geocoder. Whether that capability is satisfied by Valhalla or OSRM, Photon or
Nominatim, is a deployment choice — and when more than one installed service can
satisfy a requirement, you choose which one, by setting a binding.
How a requirement resolves at load time:
- A
service:requirement names one specific service. It's satisfied if that service is installed and enabled — no choice to make. - A
capability:requirement with exactly one provider is auto-selected. Nothing to do; the single matching service is used. - A
capability:requirement with multiple providers is ambiguous. The integration won't resolve that requirement until you pick a binding — until then it logs a warning and the requirement is left unsatisfied. - An optional requirement with no provider falls back to the integration's public default (for example, a public routing endpoint) rather than failing.
You choose a binding on the integration's Overview tab. Each capability:
requirement shows a dropdown listing every installed service that provides it;
pick one to bind it, or choose (none — use fallback) to clear the binding and
let auto-select or the public fallback take over. The picker only offers
services that genuinely provide the capability — a service that doesn't is
rejected.
A binding is a stored (integration, capability) → service mapping. The
integration host loads all bindings once at startup and re-reads them on reload,
so after changing a binding, reload the integration (or restart the API) for it
to take effect. Like every admin action, setting a binding is audit-logged.
The same capability can be bound to different services for different integrations. Binding is also the only step you take in the panel — you still enable the backing service and render the stack separately; the binding just tells the integration which of the running providers to call.
Integration health
An integration whose manifest declares a healthCheck is probed periodically,
and the result drives the status shown in the list, on the Health tab, and on
the Overview dashboard. The host seeds the checks shortly after boot and refreshes
them in the background; the Health tab also has a button to run the probe on
demand.
A check resolves to one of three states:
- up — the probe succeeded.
- down — the probe failed (connection refused, a timeout, an HTTP error); the last error is shown.
- unconfigured — a required config key or secret is unset, or a self-hosted service the check points at isn't enabled. This is treated as "not set up yet," not a failure, so a feature you haven't configured doesn't show up as broken.
An integration with no health check is simply assumed healthy. Some integrations run several sub-checks (for example, one per country or per upstream); those roll up into a single signal — any sub-check down makes the integration down, otherwise any up makes it up. Probe history is retained, so the Monitoring view can show trends rather than just the current state.
Bulk operations
Configuring features one page at a time is tedious on a fresh deployment, so the
list's toolbar links to Bulk configure (/admin/integrations/bulk), a single
page that consolidates the same workflow. It renders every configurable
integration's settings form and credentials table inline, expanding on demand,
and drives the same save and credential endpoints as the per-integration pages
— so there's no separate "bulk" behavior to learn, just every form in one place.
The bulk page also publishes the complete environment-variable catalogue for
your installation: for each integration, the exact INTEGRATION_<ID>_<KEY>
variable name behind every setting and secret, with present / missing flags and
non-secret defaults. Copy the block you need into infra/docker/.env when you'd
rather pin configuration in the environment than click through forms. Secret
values are never exposed here — only their variable names and whether each is
currently set.
For acting on backend services in bulk (start, stop, recreate a whole preset), see Services administration and Managing services.
Where to go next
- Admin panel — the full tour of
/adminand how access is gated. - Services administration — enabling and operating the backends that satisfy capability requirements.
- Community extensions — installing additional integrations from the store or a Git URL.
- Configuration —
.env, secrets, and how the cascade fits the wider deployment. - Monitoring — health history, background jobs, and the audit log.