Marketplace policy
What may be published, what is refused, how versions move, and how a dispute is handled.
This page is the policy for the plugin marketplace at /plugins: what it accepts, what it refuses, what a publisher may contribute, how a version moves through it, and what happens when somebody reports a listing. It is written for the person publishing, but it is also the honest answer to "what is this catalog actually policing?" for the person installing.
What is accepted
The marketplace is deliberately broad. If your plugin extends the office in a way that its own documentation can describe, it belongs here.
- Plugins that contribute any of the eight things a manifest may contribute — a provider, models, skills, routing rules, tool names, role templates, pipelines or UI panels. The full list with the shape each one must have is the table below.
- Both kinds. Declarative plugins ship validated data and no code. Code plugins export an
activatefunction and register tools or subscribe to events. Both are first-class: a skill pack and a tool that talks to your own service are equally welcome. - Commercially motivated plugins, including ones that exist to make a paid service easier to use, provided the manifest is honest about it. Declare the provider you add, name the environment variable the key comes from in
keyEnvVar, and say in the description that the service costs money. A plugin that quietly routes your prompts to a vendor's endpoint is not acceptable; the same plugin with a manifest that says so is. - Plugins under any licence you choose. Declare it in the
licensefield so the listing shows it accurately. Proprietary plugins are allowed; so are AGPL ones. - Plugins that are small. A single skill, one routing rule, one panel. There is no minimum size and no expectation that a plugin be a product.
- Plugins that duplicate an idea. Two publishers may both make a cost tracker. That is a conversation about naming and clarity, not a rejection.
What a publisher may contribute
A plugin manifest's contributes object has exactly eight keys. Anything else in it is not read by the host, and a publisher who puts a contribution under an invented key has shipped a manifest that silently contributes nothing. This table is the complete list, with the fields the host actually requires.
| Key | What it adds | What the host requires |
|---|---|---|
providers |
A whole model provider: its base URL, its protocol and the name of the environment variable holding its key | An id (lowercase slug), a label, a baseUrl and a kind of exactly openai-compat or anthropic. The URL must be https, except a loopback address with "keyless": true, which is how a local runtime is added. keyEnvVar, if present, must look like an environment variable name. A provider that names neither a key variable nor keyless would never count as configured and is dropped. |
models |
Model entries merged into the catalog the router chooses from | id, providerId and a valid tier — one of nano, small, standard, strong, max. Context window and token caps default when omitted, and unrated costs default to zero. |
skills |
Skills added to the catalogue, which a floor can then enable | id, name, description and a non-empty body. tags and taskClasses are optional and are what the per-turn selection scores against. |
routingRules |
Rules that reorder the router's candidate list | An id; optionally taskClass, tier, preferProviderIds, preferModelIds and avoidModelIds. A rule expresses a preference and cannot add a candidate — it can never rescue a model that failed capability filtering. A rule that names a tier does move the walk, and a rule without a taskClass applies to every task class. |
toolNames |
Tools registered with the host, grantable per role | Code plugins only — a declarative plugin that asks for the tools permission without shipping code gets a warning, because it cannot register anything. Names are namespaced to the plugin: a plugin with id dev3d.office-echo registering a tool called echo becomes dev3d_office_echo_echo, so two plugins cannot collide. |
roleTemplates |
Role definitions offered in the hire form | id, displayName and title. Offering a template does not hire anyone; a person still has to do that. |
pipelines |
Pipeline definitions offered to every floor | id, name and a stages array. A pipeline is data, so contributing one reaches no file on its own. |
uiPanels |
Panels rendered in the console, in one of four placements: inspector, runs, office-overlay or settings |
Widgets of exactly six kinds — metric, keyValue, table, list, bars, note. A panel that reads live data needs an https or http source URL, and its refresh interval is clamped between 5 seconds and one hour. There are caps on how much a panel may draw: 24 widgets, 60 rows, 60 list items and 8 columns. |
One clarification that catches people out: settings is a top-level manifest field, not a contributes key. A plugin that wants configurable options declares a settings schema beside entry, not inside contributes. And permissions are a separate list again — there are ten of them, and they are not contribution keys at all.
For the full field-by-field reference, including the types each contribution accepts and the defaults that apply when a field is omitted, see Plugin manifest.
What is refused
Some of these the site can enforce mechanically. Others are policy, enforced by review and by reports, and saying which is which is more useful than implying a scanner that does not exist.
Refused automatically, at upload
- Bundles over 32 MB, or with more than 2048 entries. Both limits exist because a small archive can expand into something enormous, and they match the limits the dev3d host itself enforces. Refused rather than truncated.
- Archives containing an absolute path, a
..segment, a link or a device node. The whole archive is rejected rather than partially salvaged, because a partially understood archive is exactly the case where a later step trusts the wrong file. - Anything that is not a readable
.tar.gz, is empty, or has noplugin.jsonat its root or inside exactly one wrapping directory. Deeper nesting is refused: at that point the bundle is not a plugin. - An
apiVersionwhose major version is not1. The host refuses a mismatched major, so accepting it here would publish something that cannot be installed. - A plugin id that is not lowercase reverse-dns — at least one dot, no uppercase, no spaces.
- An
entrythat points outside the plugin directory, or aplugin.jsonthat is not valid JSON.
Refused as policy
- A manifest that omits
entrywhile the bundle ships code. The presence ofentryis what tells the host to import a module and what tells an installer that this plugin runs code at all. Publishing a code plugin as declarative is a misrepresentation of the most consequential kind. Note the honest limit: the site classifies a bundle by its manifest and cannot see code that noentrypoints at, so this is a rule you are held to rather than a check that catches you. - Declared permissions that understate what the code does. The list is the disclosure a stranger reads before enabling the plugin, and it is a gate for two capabilities (tool registration and the event stream). A plugin that opens outbound connections without declaring
network, or reads outside its workspace without declaringfiles, has broken the promise the listing makes — and the host will not catch it, becauseactivate()runs with the orchestrator's authority. - Anything that ships a credential. Not an API key, not a token, not a private key, not a base64 blob that decodes to one. The host reads provider keys from its own environment; a plugin can name a variable but never carry a secret.
- Anything that names another publisher's environment variable. If your plugin needs a key, it names a variable that belongs to your service, and the operator of a host is the one who sets it.
- Anything that tries to shadow a built-in provider id. Provider ids are first-come in a literal sense: the first declaration of an id wins and the built-in
DEV3D_*configuration is always first, so a manifest cannot take over a provider the operator has already configured. Attempting it is a refusal, not merely an ineffective plugin. - Anything that ships code no one can read. Minified, packed, encoded or otherwise obfuscated modules are refused. The manifest is the entire consent mechanism here — a listing whose code cannot be reviewed makes the marketplace's central promise worthless. If you build from source, publish a readable bundle.
- Payloads unrelated to the office. Cryptocurrency miners, ad injection, traffic interception, credential harvesting, sandbox-escape attempts, and anything that reaches for data the plugin's own description does not mention.
- Content that is illegal, infringing, or distributes someone else's work without permission. This includes bundling a third-party library whose licence you are violating.
- Listings that impersonate another publisher, reuse another plugin's id, or copy a listing's name and description closely enough to be mistaken for it.
Versioning, and what a publisher can and cannot do about a bad release
Versions here are compared by three numbers and an optional pre-release tag. It is deliberately not a full semantic-versioning implementation: the only question the host ever asks is "is this newer?", a missing part counts as zero, so 1.2 and 1.2.0 are the same version rather than one being invalid, and a pre-release sorts below the release it leads up to — 2.0.0-beta.1 is older than 2.0.0.
From that follow four rules that shape what publishing here is like:
- An upgrade only moves forward. A host checking for updates offers a marketplace version only when it is strictly newer than what is installed. Offering the same version again is answered with an error that says so.
- A marketplace plugin never replaces one that ships with the office. The refusal is explicit: such a plugin is updated by updating dev3d. This keeps the checkout and the loaded set from disagreeing.
- There is no downgrade in the host, and no pinning to a specific version. An operator cannot say "stay on 1.1.0 because 1.2.0 broke us".
- A yanked version stops being offered, and the marketplace skips it when deciding which version to serve. That is the publisher's tool for withdrawing something broken.
Put together, those four rules mean one thing, and it is the honest headline of this section: a publisher who ships a bad version cannot take it back from installations that already have it. The catalog can stop serving it and the update check can stop offering it, but a host that already installed it keeps it. The operator's real answer is to disable the plugin, and the publisher's real answer is to publish a fixed version with a higher number as quickly as possible. This is a genuine limitation of the platform, it is documented as one rather than dressed up, and publishers should assume that anything they publish may be in use the moment it appears.
Publish accordingly. Test a version on your own installation before you upload it; write the changelog so an operator can tell whether they want it; keep a version's behaviour within what its listing already says. A version number is a promise that the next one is better, not a promise that it is safe.
Reports, disputes and takedowns
Everything comes through contact. There is one route on purpose: a report that lands in an inbox someone reads is worth more than a form that files it into a queue nobody owns.
A report about a malicious bundle is acted on first and discussed afterwards. A bundle that ships something its manifest does not disclose — a credential exfiltration, an unannounced network call, code that no one can read, a payload unrelated to the office — is removed from the catalog first, and the publisher is asked about it second. If that turns out to be a mistake, a version can be restored. The asymmetry is deliberate: the cost of a listing being briefly unavailable is small, and the cost of a malicious bundle staying available is not.
A report about naming, description or a duplicated idea is a conversation. Nothing is removed while it happens. Two cost trackers can coexist; the question is whether one is impersonating the other, and that is a matter of evidence rather than urgency.
To make a report actionable, include:
- The plugin id and the version — both are shown on the listing, and the version matters because the report is about specific bytes.
- The sha256 of the bundle, which the listing publishes and which pins the report to exactly what you downloaded.
- What you observed, and how — a log line, a network request, a file that appeared where the manifest said nothing would be written. "This plugin seems sketchy" is not something anyone can act on.
- Whether you still have the plugin installed. If you do, mention it: it changes what an operator can reasonably suggest as a next step.
What the site can do about a report: archive a listing, yank a version, or ask the publisher to fix something. What it cannot do: uninstall anything from anyone's dev3d installation, inspect a running host, or recover data a plugin already exfiltrated. Any policy that claimed those powers would be lying about the architecture — the marketplace is a file server with a catalog, and its reach ends at the HTTP response.
Before you publish: a short checklist
| Check | Why it matters |
|---|---|
Is entry set exactly when the bundle ships code, and absent otherwise? | It is the single field that decides whether installing your plugin imports a module, and it is refused as a misrepresentation when it is wrong |
| Does every permission you declare correspond to something the plugin actually does? | Yes, and be careful in the other direction too: two of the ten are actually enforced, so omitting tools or events makes the plugin fail rather than merely look dishonest. For the rest, the list is the disclosure a stranger reads before enabling you, and understating it is the policy violation people actually get removed for |
| Is every contribution under one of the eight real keys? | A contribution under an invented key is silently ignored by the host, so it ships as nothing |
Is the license field filled in and correct? | The listing shows it; a missing field makes the site fall back to a default that is not your choice |
| Does the description say what the plugin does when it costs money or sends data somewhere? | Commercial plugins are welcome; undisclosed routing of prompts to a vendor is not |
| Is the bundle under 32 MB with fewer than 2048 entries, and free of links and absolute paths? | All three are checked at upload and the whole archive is refused rather than repaired |
| Have you tested it on your own installation first? | Because a bad version cannot be recalled from installations that already have it |
Related reading: Publishing to the marketplace, Plugin manifest, Building a plugin, and the terms for what you promise by publishing.
Where to go next
Linked from
Did this page answer your question?