Which plugin pattern?
A VCTRbase plugin can be shaped along four axes — how it is distributed, how its UI is delivered, whether it runs server code, and how it is built. Use them to choose the right pattern for what you are building.
The four axes
A plugin sits somewhere on four orthogonal axes. They were historically conflated (“contract vs declarative” tried to describe all of them at once); keep them separate:
| Axis | Values | What it controls |
|---|---|---|
| Build style | declarative | PHP (contract) | Whether the plugin ships a provider service class + src/ tree, or is manifest-only. |
| Location | in-monorepo | extracted | Whether the plugin’s code lives inside this repo (core Composer/Vite build) or ships as a separate artifact outside it. |
| Trust | first-party-signed | third-party | Who authored/signed it — decides whether its server code is permitted to run at all. |
| UI delivery | declarative-render | Inertia | client-fetch | How its screens actually reach the browser. |
The rule that ties location to UI delivery: location gates UI delivery, not trust.
- In-monorepo → Inertia. The plugin’s PHP is on the core Composer autoload map and
its React lives in the core Vite build, so
Inertia::render()and server-computed props work exactly like any other first-party page. - Extracted → client-fetch only. Once a plugin’s React leaves the core Vite build,
Inertia’s server-props model is physically impossible — the extracted plugin’s UI can
only reach the host by calling the
/api/v1HTTP surface (§3 ofARCHITECTURE.md). That decoupling is why extraction works at all. - Declarative → renderer. A declarative plugin ships no UI code of its own; the host
draws its screens from the manifest’s
ui.nav[]/ui.views[](below). Location and trust don’t apply to its UI — there’s no code to place anywhere.
Trust is orthogonal to all of this: a first-party-signed plugin uses Inertia while it lives in-monorepo, and would use client-fetch if the same plugin were later extracted to ship outside the monorepo. Signedness doesn’t change which UI mechanism is available — location does.
Where this repo stands today: the extraction rollout is complete. Of the 20
functional first-party plugins, 8 have been extracted to signed external marketplace
repositories that ship their React as client-fetch UI against /api/v1 — they are live and
core-removed, no longer under plugins/. The 12 functional plugins that remain
in-monorepo (plugins/*, alongside the hello/sample-esm samples) use Inertia (or the
declarative renderer). The foundation this depended on shipped in Round 18 and every
extraction used it — it is built, not pending:
- a
distribution(monorepo|extracted) marker on the manifest — present on all 14 in-treeplugins/*/manifest.jsonfiles; - a test-based guard forbidding
Inertia::renderin extracted plugins (tests/Feature/Plugins/ExtractionGuardTest.php, proven against a bad-plugin fixture); - a shared client-fetch kit (
packages/plugin-ui).
Build against the in-monorepo path (below) unless you’re specifically doing extraction
work; the full extraction procedure lives in
docs/superpowers/plugin-extraction-playbook.md.
A targeted client-side fetch inside a core Inertia page (e.g. a live-polling widget) is not a violation of this rule — the rule is about a page’s primary data path, not every network call it happens to make.
Which pattern do I use?
| You’re building… | Build style | Location | UI delivery | Signing |
|---|---|---|---|---|
| A first-party feature bundled with every deploy, with real server logic | PHP (contract) | in-monorepo | Inertia | none needed — in_tree is always trusted |
| A first-party feature bundled with every deploy, pure CRUD-over-KV | declarative | in-monorepo | renderer | none needed |
| A third-party/community submission with no server logic | declarative | uploaded (or marketplace) | renderer | none required — unsigned declarative installs |
| A third-party/publisher submission that needs real server logic | PHP (contract) | uploaded (or marketplace) | (Inertia only if merged in-monorepo; otherwise client-fetch) | required — must be signed by a keyring key, or the upload is refused |
| A first-party plugin being moved out of the monorepo (extraction rollout) | PHP (contract) | extracted | client-fetch against /api/v1 | signed as first-party |
Start from plugins/hello/ for a contract build, or
plugins-external/appointment-reminders.zip for a declarative one — both are exercised
by passing tests today.