marko-php/marko/create-module
Claude Code and agent skill by marko-php/marko
- community
- ext:[{"type":"hook","description":"Pre/post lifecycle hooks"}]
Checking that the SKILL.md file is still in the repository.
Scaffold a new Marko module — a self-contained Composer package with composer.json, namespaced src/, and Pest tests. **Use this skill whenever the user asks to create, add, or scaffold a new Marko module or package.** Concrete triggers: 'create a module named payment', 'scaffold an acme/blog package', 'add a new module for X', 'break this code out into its own package', 'I need a new module', 'make me a Marko module'. Marko makes no distinction between core and third-party modules: layout is identical.
Install
npx vskill@latest install marko-php/marko/create-moduleSemi-extensibleMentions customization, but needs skill-specific knowledge. Read the extensible skills standard
SKILL.md
Create a Marko module
A Marko module is a Composer package that the framework auto-discovers via the extra.marko.module flag. Modules can live anywhere — app/{name}/ for application code, modules/{vendor}/{name}/ for a distributable package dropped into a host project, packages/{name}/ in the framework monorepo, or vendor/{vendor}/{name}/ once installed from Packagist. The layout is identical in every case. Directory and Composer-name segments are always lowercase; only the PHP namespace is StudlyCase.
This skill is the canonical specification for a Marko module. Do not inspect existing modules in this project to infer layout — siblings may have drifted from spec. Copy the templates from `assets/` verbatim, substitute placeholders, and stop.
Step 1 — Confirm the name, then pick a location
If the user did not give the module a name, ask for one before scaffolding anything. Do not invent a name, reuse a vendor as the name, or proceed with a placeholder.
Casing rule — read this before creating any directory
A module's identity has two forms that must stay in sync:
- Directory and Composer name → always lowercase. They are identical segment-for-segment, so a module can move between tiers (e.g.
modules/→vendor/) or be symlinked with zero path rewrites. - PHP namespace → always StudlyCase.
blog→Blog,my-payment→MyPayment.
So the name blog yields directory blog/, Composer name …/blog, and namespace …\Blog. Lowercase module directories are correct — do not capitalize them. The casing only flips to StudlyCase in the PHP namespace and the psr-4 autoload key. (If you have seen "lowercase module names" before and assumed it was a bug, it was not: the directory is supposed to be lowercase; only the namespace is StudlyCase.)
Pick the tier by where the module lives
| Tier | Directory | Composer name | Namespace | When | |
|---|---|---|---|---|---|
| App-local | app/{name}/ | app/{name} | App\{Name} | Application code, not distributed. The vendor segment is always the literal app — there is no project-derived vendor. | |
| Distributable | modules/{vendor}/{name}/ | {vendor}/{name} | {Vendor}\{Name} | A package you intend to publish or share, dropped into a host project. Two-segment, vendor-scoped. | |
| Framework monorepo | packages/{name}/ | marko/{name} | Marko\{Name} | Only when working inside the Marko monorepo itself (root contains packages/core/). |
Never author into `vendor/` — it is ephemeral and populated by Composer. The modules/ tier is the manual-install mirror of vendor/: same two-segment {vendor}/{name} shape, so a module can later move to vendor/ (or be symlinked) without changes.
Default tier: if the user does not specify, assume an app-local module (app/{name}/, namespace App\{Name}). Only use modules/{vendor}/{name}/ when the user signals the module is meant to be distributed/shared.
Choosing `{vendor}` (distributable modules only — app-local modules have no vendor): derive it from the host project's root directory name (a project in ~/Sites/acme → vendor acme). Do not read it from the project's composer.json name: a project scaffolded from marko/skeleton still carries marko/skeleton there, so the directory name is the reliable signal. Never use `marko` as the vendor for an application or third-party module — that vendor is reserved for packages inside the framework monorepo.
Step 2 — Write composer.json
Copy the appropriate template to <module-root>/composer.json and substitute all placeholders.
Required keys: name, type: marko-module, require.marko/core, psr-4 autoload, and extra.marko.module: true to flag it for the code indexer. Never set a `version` field — let Composer infer it from the branch.
Deterministic constraint selection rule
Resolve {{marko_constraint}} and choose the template using this ordered decision tree:
- Inside the Marko monorepo (root directory contains
packages/core/) → useassets/composer.json.monorepo.tmpl. Set{{marko_constraint}}toself.version. This ensures all packages in the monorepo resolve against each other at the same version without manual pinning.
- Otherwise (host project / standalone module) → use
assets/composer.json.tmpl. Determine{{marko_constraint}}by reading the host project's rootcomposer.json:
- Find the first
marko/*constraint that is already declared (e.g."marko/framework": "*"→ constraint is*;"marko/framework": "dev-develop"→ constraint isdev-develop;"marko/framework": "^1.2"→ constraint is^1.2). - If no
marko/*constraint is present in the hostcomposer.json, default to*.
Using * (or matching the host's existing constraint) ensures the scaffolded module resolves against whatever version of Marko the host project is consuming — whether that is a tagged release, a dev-develop branch alias, or a Composer path symlink. Never default to `^1.0`: that constraint is incompatible with any host consuming dev-develop or *, which is the most common case during active development (e.g. marko/skeleton requires marko/framework: *).
Step 3 — Create the directory layout
Copy assets/Pest.php.tmpl to tests/Pest.php. No placeholder substitution needed.
Step 4 — Decide whether you need module.php
module.php is optional. Only create it if the module needs explicit DI bindings (interface → concrete class wiring), singleton declarations, or boot callbacks for lifecycle hooks.
If the module is just classes that auto-resolve, omit `module.php` entirely. Do not create an empty manifest.
When you do need it, copy assets/module.php.tmpl to <module-root>/module.php and substitute {{Vendor}} and {{Name}} placeholders.
Step 5 — Add a slim README
Copy assets/README.md.tmpl to <module-root>/README.md and substitute {{vendor}} and {{name}} placeholders.
Per docs/DOCS-STANDARDS.md, package READMEs are slim pointers — title, install command, one quick example, and a link to the full docs page. Substantive documentation belongs in docs/src/content/docs/packages/{name}.md, not the README.
Step 6 — Verify the module is discovered
The module exists because you wrote it. The runtime discovers and autoloads it live on the next request — no composer dump-autoload, no index rebuild, no extra registration step. The MCP/LSP index self-refreshes (scoped to app/ and modules/), so list_modules and validate_module will reflect freshly created code automatically.
If you want to double-check structure, confirm:
composer.jsonhasextra.marko.module: true- The module's psr-4 namespace resolves correctly
You may call list_modules or validate_module as an optional cross-check, but these are not gates — the module is live as soon as the files are in place.
Verification
After writing files, expect LSP diagnostics from marko-lsp to surface in the same turn. Resolve all diagnostics before declaring the module complete — diagnostics are the verification gate, not optional warnings. Calling list_modules is an optional cross-check to confirm the MCP index reflects the new module; it is not required for the module to be active.
Conventions to enforce
- Every PHP file:
declare(strict_types=1); - Constructor property promotion always
- Type declarations on every parameter, return, and property
- No
finalclasses (blocks Preferences extensibility) - No magic methods — be explicit
- Use
readonlywhere immutability is appropriate, not as a blanket rule
What this skill does not cover
- Authoring plugins for the new module — see the
marko-create-pluginskill - Adding routes, observers, commands — see the relevant Marko docs pages
- Database migrations — see the
marko/databasepackage docs
See also
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
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-module/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.
Extension points
hookPre/post lifecycle hooks
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-module)