Skip to Content
PluginsWhich pattern?

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:

AxisValuesWhat it controls
Build styledeclarative | PHP (contract)Whether the plugin ships a provider service class + src/ tree, or is manifest-only.
Locationin-monorepo | extractedWhether the plugin’s code lives inside this repo (core Composer/Vite build) or ships as a separate artifact outside it.
Trustfirst-party-signed | third-partyWho authored/signed it — decides whether its server code is permitted to run at all.
UI deliverydeclarative-render | Inertia | client-fetchHow 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/v1 HTTP surface (§3 of ARCHITECTURE.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-tree plugins/*/manifest.json files;
  • a test-based guard forbidding Inertia::render in 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 styleLocationUI deliverySigning
A first-party feature bundled with every deploy, with real server logicPHP (contract)in-monorepoInertianone needed — in_tree is always trusted
A first-party feature bundled with every deploy, pure CRUD-over-KVdeclarativein-monoreporenderernone needed
A third-party/community submission with no server logicdeclarativeuploaded (or marketplace)renderernone required — unsigned declarative installs
A third-party/publisher submission that needs real server logicPHP (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)extractedclient-fetch against /api/v1signed 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.


Last updated on