From 29b2748aedfd3aa3c3a3625737ea97d7a7ee8296 Mon Sep 17 00:00:00 2001 From: factory-davidgu Date: Fri, 12 Jun 2026 17:40:28 -0700 Subject: [PATCH] docs(building-plugins): document npm plugin sources Document the npm plugin source for marketplace manifests, including required/optional fields, the published package layout (Droid and Claude Code formats), install-time hardening defaults, and the wrapper-marketplace pattern for shipping a single npm-published plugin. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com> --- docs/guides/building/building-plugins.mdx | 136 +++++++++++++++++++++- 1 file changed, 135 insertions(+), 1 deletion(-) diff --git a/docs/guides/building/building-plugins.mdx b/docs/guides/building/building-plugins.mdx index ec2dd998..6259c2ef 100644 --- a/docs/guides/building/building-plugins.mdx +++ b/docs/guides/building/building-plugins.mdx @@ -327,10 +327,144 @@ Create `.factory-plugin/marketplace.json`: | `description` | No | Shown when browsing marketplaces | | `owner` | No | Contact information | | `plugins[].name` | Yes | Plugin identifier | -| `plugins[].source` | Yes | Relative path to plugin directory | +| `plugins[].source` | Yes | Where to fetch the plugin from. A relative path string or a source object. See [Plugin sources](#plugin-sources). | | `plugins[].description` | No | Shown in plugin browser | | `plugins[].category` | No | For organizing plugins | +### Plugin sources + +Each plugin entry's `source` field tells Droid where to fetch the plugin from. The default form, a relative path string like `"./plugin-one"`, points at a directory inside the marketplace repository. For plugins that live elsewhere, use a source object. + +| Source type | Fields | Use when | +| :---------- | :----- | :------- | +| Relative path | `"./path/to/plugin"` | Plugin lives inside the marketplace repository. | +| `github` | `repo`, `ref?`, `sha?` | Plugin is hosted in its own GitHub repository. | +| `url` | `url`, `ref?`, `sha?` | Plugin is hosted on a non-GitHub git host (GitLab, Bitbucket, self-hosted). | +| `git-subdir` | `url`, `path`, `ref?`, `sha?` | Plugin lives inside a subdirectory of a larger git repository, such as a monorepo. | +| `npm` | `package`, `version?`, `registry?` | Plugin is published as an npm package. | + +#### npm packages + +Distribute plugins as npm packages when you already ship to a private registry (Artifactory, CodeArtifact, GitHub Packages, Verdaccio, and so on) and want to reuse that channel for your Droid plugins. Public packages on the npm registry work the same way. + +```json +{ + "name": "pr-triage", + "source": { + "source": "npm", + "package": "@your-org/droid-pr-triage" + } +} +``` + +Pin to a specific version or range: + +```json +{ + "name": "pr-triage", + "source": { + "source": "npm", + "package": "@your-org/droid-pr-triage", + "version": "2.4.0" + } +} +``` + +Install from a private registry: + +```json +{ + "name": "pr-triage", + "source": { + "source": "npm", + "package": "@your-org/droid-pr-triage", + "version": "^2.0.0", + "registry": "https://npm.your-org.example" + } +} +``` + +| Field | Required | Description | +| :---- | :------- | :---------- | +| `package` | Yes | npm package name. Scoped packages (`@scope/name`) are supported. | +| `version` | No | npm version, range, or dist-tag (for example `2.4.0`, `^2.0.0`, `latest`). Defaults to `latest`. | +| `registry` | No | Custom registry URL. Defaults to the system npm registry. The setting is scoped to the install run and never written to your global `~/.npmrc`. | + +**Layout requirements.** The published package root must contain a plugin manifest. Both the native Droid layout (`.factory-plugin/plugin.json`, `droids/`, `mcp.json`) and the Claude Code layout (`.claude-plugin/plugin.json`, `agents/`, `.mcp.json`) are accepted. Claude Code layouts are translated into Droid form when the package is copied into the plugin cache. + +A typical published package looks like this: + +``` +@your-org/droid-pr-triage/ +├── .factory-plugin/ +│ └── plugin.json +├── package.json +├── skills/ +│ └── classify/ +│ └── SKILL.md +├── droids/ +│ └── triage-reviewer.md +└── README.md +``` + +Set the `files` field in `package.json` so only the plugin payload ships: + +```json +{ + "name": "@your-org/droid-pr-triage", + "version": "2.4.0", + "files": [ + ".factory-plugin", + "skills", + "droids", + "mcp.json", + "hooks", + "README.md" + ] +} +``` + +**Hardening.** Droid runs `npm install` in a per-plugin scratch directory with `--ignore-scripts`, `--no-save`, `--no-audit`, and `--no-fund`. Lifecycle scripts (`postinstall`, `preinstall`, and so on) are not executed, and your global npm configuration is not consulted or mutated. + + +`npm:` is not a valid marketplace source. Droid only accepts `npm` as a per-plugin source inside a marketplace's `marketplace.json`. To ship a single npm-published plugin to your team without standing up a full marketplace repo, publish a thin wrapper marketplace (see below). + + +##### Wrapper marketplace for a single npm plugin + +To distribute one npm-published plugin without a dedicated marketplace repo, commit a small `marketplace.json` to any folder. The folder name becomes the marketplace name. + +``` +your-org-plugins/ +└── .factory-plugin/ + └── marketplace.json +``` + +```json +{ + "name": "your-org-plugins", + "owner": { "name": "Your Org Platform Team" }, + "plugins": [ + { + "name": "pr-triage", + "description": "Triage and label new pull requests for the on-call rotation", + "source": { + "source": "npm", + "package": "@your-org/droid-pr-triage", + "version": "^2.0.0" + } + } + ] +} +``` + +Teammates add it like any other local marketplace: + +```bash +droid plugin marketplace add ./your-org-plugins +droid plugin install pr-triage@your-org-plugins +``` + ### Version management Use semantic versioning in your plugin manifest for documentation purposes: