Skip to main content
OpenClaw handles plugin dependencies at install/update time only. Runtime loading never runs a package manager, repairs a dependency tree, or mutates the OpenClaw package directory.

Responsibility split

Plugin packages own their dependency graph:
  • Runtime dependencies live in the plugin package’s dependencies or optionalDependencies.
  • SDK/core imports are peer or supplied OpenClaw imports.
  • Local development plugins bring their own already-installed dependencies.
  • npm and git plugins install into OpenClaw-owned package roots.
OpenClaw owns only the plugin lifecycle:
  • Discover the plugin source.
  • Install or update the package when explicitly requested.
  • Record install metadata.
  • Load the plugin entrypoint.
  • Fail with an actionable error when dependencies are missing.

Install roots

OpenClaw uses stable per-source roots:
  • npm packages install into per-plugin projects under ~/.openclaw/npm/projects/<encoded-package>.
  • git packages clone under ~/.openclaw/git.
  • Local/path/archive installs are copied or referenced without dependency repair.
npm installs run in that per-plugin project root with:
openclaw plugins install npm-pack:<path.tgz> uses the same per-plugin npm project root for a local npm-pack tarball: OpenClaw reads the tarball’s npm metadata, adds it to the managed project as a copied file: dependency, runs the normal npm install above, then verifies the installed lockfile metadata before trusting the plugin. This path exists for package-acceptance and release-candidate proof, where a local pack artifact should behave like the registry artifact it simulates. Use npm-pack: when testing official or external plugin packages before publish. A raw archive or path install is useful for local debugging, but it does not prove the same dependency path as an installed npm or ClawHub package. npm-pack: proves the managed package install shape; it is not, by itself, proof that the plugin is catalog-linked official content. When behavior depends on bundled-plugin or trusted official plugin status, pair the local package proof with a catalog-backed official install or a published package path that records official trust. Privileged helper access and trusted-official scope handling should be validated on that trusted install path, not inferred from a local tarball install. If a plugin fails at runtime with a missing import, fix the package manifest instead of repairing the managed project by hand. Runtime imports belong in the plugin package dependencies or optionalDependencies; devDependencies are not installed for managed runtime projects. A local npm install inside ~/.openclaw/npm/projects/<encoded-package> can unblock a temporary diagnostic, but it is not package-acceptance proof because the next install or update recreates the project from package metadata. npm may hoist transitive dependencies to the per-plugin project’s node_modules beside the plugin package. OpenClaw scans the managed project root before trusting the install, and removes that project on uninstall, so hoisted runtime dependencies stay inside that plugin’s cleanup boundary. OpenClaw-owned npm plugin packages never ship npm lockfiles. The repository uses pnpm-lock.yaml as its committed product dependency review boundary, then generates npm package locks only in temporary directories to validate the publishable dependency graph:
The checker strips plugin devDependencies, applies the workspace override policy, and rejects generated versions absent from pnpm-lock.yaml. Nothing is written into the checkout. Third-party plugin packages may still contain lockfiles according to their own packaging policy; OpenClaw’s installer leaves that npm behavior to the installed npm version. Before treating a local package as release-candidate proof, inspect the tarball that will be installed:
For dependency changes, also verify a production install can resolve the runtime packages without dev dependencies:
OpenClaw-owned npm plugin packages can also publish with explicit bundledDependencies. The npm publish path overlays the runtime dependency name list, strips dev-only workspace metadata from the published manifest, stages a separate package directory without source node_modules, and runs a script-free npm install there for runtime dependencies. It then packs or publishes the plugin tarball with those dependency files included and removes the staging directory. The pnpm-owned source dependency tree stays unchanged. When a direct runtime dependency has an approved workspace patch for its exact version, npm and ClawHub packaging include that dependency from the matching frozen pnpm install. Packaging verifies the installed patch identity and packs its bytes into the temporary dependency install, then restores the original public version specifier in the published manifest. This also applies when bundling all runtime dependencies is disabled; unrelated dependencies retain their normal install behavior. A stale source install or an explicit bundleRuntimeDependencies: false opt-out stops packaging rather than publishing an unpatched dependency. Native-heavy packages (Codex, ACPX, Copilot, llama.cpp, memory-lancedb, Microsoft Teams, Tlon) opt out with openclaw.release.bundleRuntimeDependencies: false; they still ship a precisely pinned manifest, but npm resolves runtime dependencies during install instead of embedding every platform binary in the plugin tarball. The root openclaw package also resolves dependencies at install time and does not bundle its full dependency tree. See dependency locking. Plugins that import openclaw/plugin-sdk/* declare openclaw as a peer dependency. OpenClaw does not let npm install a separate registry copy of the host package into a managed project, because a stale host package can affect npm’s peer resolution inside that plugin. Managed npm installs skip npm peer resolution/materialization, and OpenClaw reasserts plugin-local node_modules/openclaw links for installed packages that declare the host peer, after install or update. git installs clone or refresh the repository, then run:
The installed plugin then loads from that package directory, so package-local and parent node_modules resolution work the same way they do for a normal Node package.

Local plugins

Local plugins are developer-controlled directories. OpenClaw never runs npm install, pnpm install, or dependency repair for them; if a local plugin has dependencies, install them in that plugin before loading it. Third-party TypeScript local plugins load through Jiti as an emergency path. Packaged JavaScript plugins and bundled internal plugins load through native import/require instead.

Startup and reload

Gateway startup and config reload never install plugin dependencies. They read the plugin install records, compute the entrypoint, and load it. A missing dependency at runtime fails plugin load with an error that points the operator to an explicit fix:
doctor --fix removes dangling global plugin-runtime symlinks and can recover downloadable plugins that are missing from local install records when config still references them. Doctor does not repair dependencies for an already-installed local plugin.

Bundled plugins

Lightweight and core-critical bundled plugins ship as part of OpenClaw. They should either carry no heavy runtime dependency tree, or move out to a downloadable package on ClawHub/npm. For the current generated list of plugins that ship in the core package, install externally, or stay source-only, see Plugin inventory. Bundled plugin manifests must not request dependency staging. Large or optional plugin functionality should be packaged as a normal plugin and installed through the same npm/git/ClawHub path as third-party plugins. Internal bundled plugins retain their dependency declarations in their own manifests. Runtime dependencies that are not compiled into dist must also be declared in the root OpenClaw package’s dependencies or optionalDependencies, because the root package ships their runtime. External plugins keep their runtime dependencies plugin-local. In source checkouts, use pnpm install followed by pnpm build. OpenClaw prefers dist/extensions, then dist-runtime/extensions, and falls back to extensions when neither built tree is available. pnpm owns the source dependency trees: postinstall and build preparation preserve plugin-local versions and workspace links. Native Node imports resolve from each plugin package; packaged bundled runtime still uses the root runtime declarations above. Rebuild to pick up source edits when using a built tree. Source checkout development is pnpm-only; plain npm install at the repository root does not prepare the pnpm workspace. For the global npm row, use npm install -g openclaw --allow-scripts=openclaw on npm 12 or npm 11.16+. On npm 11.15 and earlier, omit --allow-scripts=openclaw. Plugin dependency convergence remains intentionally script-disabled and continues to use the --ignore-scripts commands above.

Native imports from a standalone source build

To import an already-built extensions/<package>/dist directly with Node, use the host link installed by pnpm. If that link is missing, explicitly prepare it from the source checkout root:
This requires existing root SDK output in dist/plugin-sdk and the selected package’s standalone runtime output. If the package output is missing, build it first with node scripts/lib/plugin-npm-runtime-build.mjs extensions/<package>. The preparation command does not rebuild either output or execute plugin code. It only links the checkout as node_modules/openclaw for a real immediate source package that declares openclaw in peerDependencies or dependencies. It does not install third-party dependencies; those must already be available through the pnpm workspace. Preparation refuses symlinked package paths, unsafe manifests, and conflicting dependency paths instead of reporting success. Ordinary package builds remain artifact-only. Postinstall and root build preparation preserve source plugin-local node_modules, including this link. Runtime loading never performs this setup or runs a package manager.

Legacy cleanup

Older OpenClaw versions generated bundled-plugin dependency roots at startup or during doctor repair. Packaged postinstall now cleans only its own installation: obsolete bundled-plugin node_modules and .openclaw-install-stage* directories under dist/extensions, dist files absent from the packaged inventory, and empty dist directories. doctor --fix removes global Node-prefix package symlinks into plugin-runtime-deps only when the alias itself is genuinely dangling. Live aliases are preserved. Neither Doctor nor postinstall deletes shared plugin-runtime-deps roots or mirrors, which may still serve another installation or profile. The deprecated core/doctor/legacy-plugin-dependencies selector is informational only; it no longer scans shared roots for removal. Older npm installs also used a shared ~/.openclaw/npm/node_modules root. Current install, update, uninstall, and doctor flows still recognize that legacy flat root for recovery and cleanup only. New npm installs create per-plugin project roots instead.