Skip to content

Packages

Installable, signed bundles of sub-flows, example flows, app templates, OpenAPI specs and event types: install from the catalogue or a file, trust rules, upgrade and uninstall, and how to build and sign your own.

Updated View as Markdown

A package is the community-node equivalent: one JSON file that adds ready-made pieces to a connector without a rebuild. It can bundle any of these:

Content What the install does
Sub-flows Reusable flow tabs. They appear in the editor palette under Packages and run through a subflow node (calls: "pkg:<name>/<id>").
Examples Flows that join the Examples gallery, with their inputs, mock services and sample record.
Apps App definitions saved as drafts (templates), never published for you.
OpenAPI specs Registered as <package name>.<id>, for an http node’s openapi: "<package name>.<id>#operationId".
Event types Added to the business-event catalogue.

Packages are listed at flow.opexcg.com/packages. Open Packages in the editor’s menu (or Ctrl/Cmd+K, Packages…) to browse what is installed, fetch from the catalogue, or install a downloaded file.

Trust

Every package carries an Ed25519 signature over its contents. A connector decides what to do with it:

Trust Meaning Installs
official Signed by the OpexCG publisher key built into the connector. Always
trusted Signed by a key in packages.trusted_publishers. Always
unsigned No signature. Only with packages.allow_unsigned: true, shown with a warning
unknown_signer Signed with a key this connector does not know, so the signature was not checked. Only with packages.allow_unsigned: true, shown with a warning

A signature that does not match the contents is always refused, with or without allow_unsigned: the file was changed after it was signed. A verified package is only upgraded by a package signed with the same key.

A package can carry scripts (JavaScript transforms), HTTP and SQL calls and printer jobs, and they run inside your flows. Read an unsigned or third-party package before you install it. Trust is checked again every time the connector starts: remove a publisher key from the config and its packages become inactive (still listed, their sub-flows stop resolving, and a deploy that calls them says so).

packages:
  allow_unsigned: false                                  # default
  catalogue_url: https://flow.opexcg.com/api/packages    # "" = offline: install from a file only
  trusted_publishers:                                    # extra signers besides OpexCG
    - id: acme
      public_key: <base64 raw 32-byte Ed25519 public key>

Install, upgrade, uninstall

Installing needs the admin role and is audited (package.install, package.upgrade, package.uninstall). Everything that can fail is checked before anything changes: signature and trust, requires (the lowest opex-flow that runs the package; a newer requirement is refused with both versions), the contents, and conflicts. A package never overwrites what exists: an event type name, an app id or slug, an example id or a loaded OpenAPI spec that is already taken refuses the install and names it.

  • Install adds the contents. The same package twice is refused: upgrade it instead.
  • Upgrade replaces the contents with a newer version (same or older versions are refused). Sub-flows, specs and event types the new version drops are removed, so an upgrade is refused while a deployed flow uses one of them. A flow that calls the package runs the new sub-flow after its next deploy (the response says redeploy: true). An app you edited since it was installed is kept as it is.
  • Uninstall removes everything the package added. It is refused (409 in_use) while a deployed flow calls one of its sub-flows, uses one of its OpenAPI specs or one of its event types; the answer lists the nodes. Apps you edited stay.

Installing from the catalogue downloads the file and checks its SHA-256 against the listing before it reads it; a download must come from the catalogue’s own host. For a machine with no internet access, download the .opexpkg.json file elsewhere, copy it over and use Packages > From file (or POST /api/packages/install {bundle}).

State lives in the data directory: packages.json (the index) and packages/<name>.json (the verified bundle).

Using what a package adds

A sub-flow shows up in the palette under Packages; dropping it creates a subflow node whose calls is pkg:<package name>/<sub-flow id>. It behaves exactly like a sub-flow tab of your own flow (see Flows): each call site runs its own copy with its own counters, and an error inside follows the call node’s error wire. The package’s own nodes are not on your canvas; a problem inside one is reported on the call node.

REST and MCP

Method Path
GET /api/packages Installed packages, the palette sub-flows and the policy (viewer)
GET /api/packages/catalogue The catalogue with installed_version and update_available (viewer)
GET /api/packages/{name} One installed package (viewer)
POST /api/packages/inspect {name, version?} or {bundle}: trust, warnings, install or upgrade, changes nothing (admin)
POST /api/packages/install Same body: installs (admin, audited)
POST /api/packages/{name}/upgrade {version?} or {bundle} (admin, audited)
DELETE /api/packages/{name} Uninstall (admin, audited)

Errors carry code: bad_signature and requires and invalid (422), unsigned_refused (403), already_installed, not_newer, conflict and in_use (409), catalogue_unavailable (502). MCP tools: list_packages (with catalogue: true to fetch the catalogue), install_package (with upgrade: true to upgrade) and uninstall_package; list_examples and get_example include the examples of installed packages.

Build and sign your own

A package is a directory:

manifest.json                 name, version, title, description, author, license, requires, homepage?
subflows/<id>.json            { title, description?, nodes, edges }: one subflow_in, one subflow_out, no sources
examples/<id>.json            an example file, as in apps/connector/examples/
apps/<id>.json                an app definition
openapi/<id>.json             an OpenAPI 3.x document
events/<name>.json            an event type: { description, owner, schema, ... }
{
  "name": "com.acme.payments",
  "version": "1.0.0",
  "title": "ACME payments",
  "description": "What it does, in a sentence or two.",
  "author": "ACME",
  "license": "MIT",
  "requires": "2.1.0"
}

The name is reverse-DNS (lower-case words and dots, at least two parts), the version semver. Inside a package, a subflow node calls a sibling by its local id ("calls": "other"). Node ids must not contain :: or #.

opex-flow package keygen --out ~/.config/opex-flow/acme.key       # once; keep it out of git
opex-flow package pack ./my-package -o my-package.opexpkg.json   # validates, writes an UNSIGNED bundle
opex-flow package sign my-package.opexpkg.json --key ~/.config/opex-flow/acme.key --key-id acme
opex-flow package verify my-package.opexpkg.json --public-key <b64> --key-id acme   # exit 0 signed, 3 unsigned/unknown, 1 altered
opex-flow package info my-package.opexpkg.json                    # one line of JSON: manifest, counts, sha256

pack runs the install-time checks (shapes, ids, every sub-flow called once by the real expander, the examples rendered with their defaults, specs indexed), so a package that packs installs. scripts/sign-package.sh DIR does pack, sign and verify in one go; the key comes from OPEX_PACKAGE_KEY_FILE or OPEX_PACKAGE_KEY (base64 PKCS#8), never from the repository. Give a customer the public key from opex-flow package pubkey --key FILE for their trusted_publishers.

The OpexCG catalogue

flow.opexcg.com serves GET /api/packages (every package, newest version plus versions, each with sha256, size, signed, key_id, requires and a download path) and GET /api/packages/{name}/{version} (the bundle, with its SHA-256 in X-Checksum-Sha256). Packages live in R2 with a row in D1; scripts/publish-package.sh FILE verifies the signature against the OpexCG key, uploads the file and registers it, and --withdraw NAME VERSION hides a version. The signed packages of this repository are in packages/dist/ with their sources in packages/src/.

Navigation

Type to search…

↑↓ navigate↵ selectEsc close