Documentation / Exploitation / Guides pratiques / Declare and install plugins

Declare and install plugins

This recipe adds a plugin to a running Meridian instance: declare it in the instance manifest and let boot resolve it, either from a registry (by version range) or from a local build (path: override). It also covers PLUGINS_PATH, the filesystem-only alternative that bypasses the manifest's plugins: list entirely.

It assumes you already have an instance manifest (see First instance) and, if you're pulling from a registry, a reachable one (see Set up a plugin registry). For the full manifest schema, see Instance manifest.

Add a plugin from the registry

  1. Make sure registry: is set in the manifest, or MERIDIAN_REGISTRY is exported — the manifest key wins over the env var (apps/api/src/registry.ts, ensureInstancePlugins):

    apiVersion: meridian/instance-v1
    registry: gs://meridian-plugins        # or a local dir, or file:///srv/plugins
    
  2. Add an entry under plugins:, keyed by the plugin's package name. The shorthand form is a bare semver range; the long form is an object with a version key (libs/shared/src/plugin/instance.ts, PluginRequirement):

    plugins:
      "@posos/demo": "^0.1.0"
      "@posos/geriatrie": { version: "0.1.0" }
    

    The range is resolved with the npm semver package against the versions published on the registry — highest match wins (libs/plugin-cli/src/registry/registry.ts, ensurePlugin / maxSatisfying). Any range semver accepts works (^, ~, 1.2.x, >=1.0.0 <2.0.0, an exact version, *).

  3. Optionally confirm a matching version is actually published before you restart:

    meridian-plugin versions "@posos/demo" --registry gs://meridian-plugins
    
  4. Restart the process. Resolution happens once at boot, before plugins are loaded (apps/api/src/server/api.ts and apps/api/src/restate/server.ts both call loadPlugins(await ensureInstancePlugins(instance))):

    pnpm --filter @meridian/api run server    # catalog/API process
    pnpm --filter @meridian/api run restate    # workflow engine process
    

    Each process resolves plugins: independently. A version missing from the local cache is downloaded from the registry into MERIDIAN_PLUGIN_CACHE (default <repo>/.data/plugins, relative paths resolved from the repo root); a version already cached is reused without contacting the registry. The resulting folder becomes an extra discovery root passed to loadPlugins.

Pin an exact version instead of a range

Use an exact version string ("1.4.2") instead of a range when you need reproducible boots regardless of what gets published later — maxSatisfying against a single exact version only matches that version. There's no separate "pinning" syntax: it's the same version field, just a range with one match.

Override with a local path (skip the registry)

Use this for local development against an unpublished plugin, or a hotfix you don't want to push through the registry yet.

  1. Build the plugin so it has a dist/ (meridian-plugin build, esbuild — see Build and bundle). The registry-based flow above downloads this same shape of folder; path: just points at one directly instead.

  2. Replace the version with a path: for that plugin's entry:

    plugins:
      "@acme/vitals": { path: ../vitals/dist }
    

    A requirement declares version or path, never both — the manifest schema rejects an entry that sets neither or sets both (libs/shared/src/plugin/instance.ts, PluginRequirement.refine).

  3. Relative paths are resolved from the repository root, not from the manifest file's own location — the same rule INSTANCE_CONFIG itself uses (apps/api/src/registry.ts, fromRepoRoot).

  4. Restart the process. No registry is contacted for this entry at all — the path is used as-is, so nothing is downloaded and nothing is cached (apps/api/src/registry.ts, ensureInstancePlugins: the req.path branch returns before a registry store is even opened).

Load a plugin without declaring it (PLUGINS_PATH)

PLUGINS_PATH is independent of the manifest's plugins: list — it's a discovery root the plugin host scans on every boot regardless of what the instance declares (apps/api/src/plugins.ts, pluginRoots). Use it to mount externally delivered plugins onto the host filesystem (on-prem, air-gapped, container image layering) without touching the instance manifest at all. There is no version resolution here: whatever is on disk at that path loads, as-is.

  1. Point it at one or more compiled plugin directories — each one either a dist/ folder itself (contains plugin.yaml directly) or a parent folder whose immediate subfolders each contain one. Separate multiple entries with a comma or the OS path-list delimiter (: on Linux/macOS, ; on Windows):

    export PLUGINS_PATH=/opt/acme/plugins/vitals
    # or a parent of several plugin folders:
    export PLUGINS_PATH=/opt/acme/plugins
    
  2. Restart the process:

    PLUGINS_PATH=/opt/acme/plugins pnpm --filter @meridian/api run restate
    

    PLUGINS_PATH folders are added alongside whatever the instance's plugins: resolved to; both kinds of roots are loaded the same way — there is no implicit in-repo root. See Plugin host loading for how discovery and load order work across these roots.

Verify

  1. Watch the boot log: each resolved plugins: entry logs its plugin name, resolved version, whether it was downloaded or served from cache (registry entries) or its local directory (path: entries), and a final line listing every plugin actually loaded (apps/api/src/registry.ts and apps/api/src/plugins.ts, loadPlugins).

  2. A plugin that is listed in plugins: but cannot be resolved — no registry configured, range matches no published version, path doesn't exist — fails the boot loudly. Treat that the same as an invalid manifest: it's a configuration error, not a runtime warning.

75 documents6 sectionssource : /docs · généré au build