marko-php/marko/create-plugin
Claude Code and agent skill by marko-php/marko
- community
Checking that the SKILL.md file is still in the repository.
Create a Marko plugin — a class that intercepts methods on another class to modify behavior without subclassing. **Use this skill whenever the user wants to add a plugin, intercept a method, or extend a class's behavior.** Concrete triggers: "add a plugin for the OrderService", "intercept the save method on User", "extend the behavior of X without changing it", "modify Y's return value before it's returned", "I need a plugin", "create a Before/After plugin". Plugins use the #[Plugin], #[Before], #[After] attributes.
Install
npx vskill@latest install marko-php/marko/create-pluginSKILL.md
Create a Marko Plugin
This skill is the canonical specification for a Marko plugin. Do not inspect existing plugins in this project to infer structure — siblings may have drifted from spec. Copy the templates from `assets/` verbatim, substitute placeholders, and stop.
A plugin is a class that intercepts the input or output of a public method on any other class — without replacing that class. Plugins are Marko's fine-grained extensibility primitive. They are auto-discovered from any module's src/ directory; no manual registration is needed.
When to use
Use a plugin when the user wants to modify arguments to, or the result from, a public method on a class without rewriting or subclassing it. Common cases:
- Enrich a return value
- Validate or transform inputs before the method runs
- Short-circuit (cache hit, guard, redirect)
- Log or observe calls
- Chain transformations across modules
For total replacement of a class, use a Preference instead — that is a separate skill (marko-create-preference). Plugins and Preferences are complementary; only Preferences swap entire implementations.
Plugin model — two timings only
Marko supports exactly two plugin types. A third type that wraps the call is intentionally absent — anything it could do is expressed as a Before (short-circuit) or After (result transformation), keeping the call stack debuggable.
| Attribute | When | What it does | |
|---|---|---|---|
#[Before] | Before the target method | Modify args, short-circuit, or pass through | |
#[After] | After the target method | Receive and modify the return value |
Before return semantics
| Return value | Effect | |
|---|---|---|
null | Pass-through — original method runs with original args | |
array | Replace arguments — original method runs with these args | |
| Any other non-null value | Short-circuit — original method is NOT called |
After return semantics
After plugins receive $result (the return value of the original method or a prior After plugin) as their first parameter, followed by the (possibly modified) original arguments. Each After plugin's return value feeds the next After plugin in sort order — always return the (possibly modified) result.
sortOrder
sortOrder is defined on the method-level attribute, not the class. Lower numbers run first; negatives are valid; default is 0.
Runbook
Step 1 — Identify the target
Determine which class and public method to intercept. Plugins cannot intercept protected or private methods, and the target class must not be final.
Step 2 — Copy the plugin class template
Copy assets/PluginClass.php.tmpl verbatim. Substitute:
| Placeholder | Value | |
|---|---|---|
{{Vendor}} | The host module's namespace vendor segment, StudlyCase (e.g. App, Acme) | |
{{Name}} | The host module's name segment, StudlyCase (e.g., Blog) | |
{{TargetClass}} | Unqualified class name (e.g., PostRepository) |
`{{Vendor}}\{{Name}}` is inherited from the module that hosts the plugin — read it, do not re-derive it. A plugin lives inside an existing module, so its namespace must match that module exactly. Read the host module's composer.json autoload.psr-4 key and use its prefix verbatim:
- App-local module (
app/{name}/) → namespaceApp\{Name}(the vendor segment is always the literalApp, never the project directory name). - Distributable module (
modules/{vendor}/{name}/) → namespace{Vendor}\{Name}from that module's psr-4. - Framework monorepo package (
packages/{name}/, root containspackages/core/) → namespaceMarko\{Name}.
Never use `Marko` as the vendor outside the framework monorepo. Do not infer the vendor from the project's root composer.json name: a skeleton-derived project still carries marko/skeleton there. The host module's own psr-4 mapping is the source of truth.
Place the file at src/Plugins/{{TargetClass}}Plugin.php inside the module.
Add the correct use statement for the fully-qualified target class.
Remove any #[Before] or #[After] methods that are not needed for this plugin — the template shows both for illustration.
Step 3 — Implement the interceptor methods
Name each plugin method identically to the target method it intercepts (or use the method: argument on the attribute to override when names would collide).
Apply the correct return semantics from the table above.
Step 4 — Copy the test template
Copy assets/PluginTest.php.tmpl verbatim. Substitute the same placeholders.
Adjust test cases to reflect the actual behavior being intercepted (pass-through, argument modification, short-circuit, result enrichment).
Step 5 — Verify placement
- The plugin class lives anywhere under
src/—src/Plugins/is conventional. - Do not register it in
module.php— discovery is automatic.
Step 6 — LSP and MCP verification gate
After writing files, expect LSP diagnostics from marko-lsp. Resolve all diagnostics before declaring the plugin complete — diagnostics are the verification gate. Then call the find_plugins_targeting MCP tool with the target class to confirm the new plugin is discovered.
Constraints
- Targeted methods must be
publicon the target class - Target class must not be
final(Marko avoidsfinalfor this reason) - Plugin classes should be
readonlywhen they have no mutable state - Constructor property promotion always
declare(strict_types=1)always- No magic methods
- No traits
What this skill does not cover
- Creating a new module to host the plugin — see
marko-create-module - Replacing an entire class — that is a Preference, not a Plugin (use
marko-create-preference)
- Listening to events — that is
#[Observer], a different mechanism
See also
Security
- Security scanPassed, 95 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingpackages/claude-plugins/plugins/marko-skills/skills/create-plugin/SKILL.md
Popularity
- Repository stars
- 383
- Repository forks
- 37
- 7-day trend score
- 30
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/marko-php/marko/create-plugin)