Customising Martis Generators
Every Martis make-command (martis:resource, martis:action, martis:lens, martis:tool and the rest) renders a class from a .stub template in the package. This page shows how a project overrides those templates in stubs/martis/.
The defaults aim for "drop-in" with sane Laravel-style headers and docblocks. Once a project has its own conventions (custom file headers, opinionated docblocks, organisation imports, project-specific traits) the default stubs become a friction point.
martis:stubs publishes every template into stubs/martis/ so consumers can edit them in place.
Stub overrides vs component overrides. This page is about scaffolding — the source code emitted once when you run
php artisan martis:resource Foo. It does not affect runtime rendering. To swap a React component the panel uses live, see the Override System (componentRegistry,layoutRegistry).
Quick start
php artisan martis:stubsOutput (truncated; the package ships 60 stubs):
resource.stub ............................................... PUBLISHED action.stub ................................................. PUBLISHED action.destructive.stub ..................................... PUBLISHED lens.stub ................................................... PUBLISHED ... 60 stubs published; 0 skipped. Edit them in stubs/martis/ to customise generator output.When you re-run with --force, the status column reads OVERWRITTEN instead of PUBLISHED so the overwrite is visible in the log.
The folder is created at base_path('stubs/martis/'). From now on every martis:* make-command reads its template from there first via StubResolver::path(), falling back to the package default only if the override file is missing.
What gets published
| Stub | Used by |
|---|---|
resource.stub | martis:resource |
action.stub, action.destructive.stub | martis:action |
lens.stub | martis:lens |
dashboard.stub | martis:dashboard |
metric.value.stub, metric.trend.stub, metric.partition.stub, metric.progress.stub, metric.activity-feed.stub, metric.endpoint-table.stub | martis:value, martis:trend, martis:partition, martis:progress, martis:activity-feed, martis:endpoint-table |
filter.boolean.stub, filter.date.stub, filter.select.stub | martis:filter |
card.stub, component-card.tsx.stub | martis:card |
tool.stub, tool-component.tsx.stub | martis:tool |
field.stub, field.tsx.stub | martis:field |
policy.stub | martis:policy: deny-by-default since v2.4.0 (every ability returns false with a // TODO); keep that default in a published copy so a forgotten edit fails closed |
theme.css.stub | martis:theme |
component-{type}.tsx.stub (12 variants) | martis:component --type={type} (see table below) |
agents/AGENTS.md.stub | martis:agents: the guidelines primer for AI coding agents, published to stubs/martis/agents/ (v1.38.1+; see Agent guidelines → Customising the primer) |
martis:component types
martis:component accepts a --type= flag and resolves a dedicated stub per type. The stub-resolver fallback to component-generic.tsx.stub keeps the command working when --type= is omitted or unrecognised.
--type= | Stub | Override key registered by the generated component |
|---|---|---|
shell | component-shell.tsx.stub | layout:shell |
sidebar | component-sidebar.tsx.stub | layout:sidebar |
topbar | component-topbar.tsx.stub | layout:topbar |
footer | component-footer.tsx.stub | layout:footer |
card | component-card.tsx.stub | (used by martis:card) |
field | component-field.tsx.stub | (used by martis:field) |
generic (default fallback) | component-generic.tsx.stub | — |
login-page | component-login-page.tsx.stub | auth:login |
register-page | component-register-page.tsx.stub | auth:register |
forgot-password-page | component-forgot-password-page.tsx.stub | auth:forgot-password |
reset-password-page | component-reset-password-page.tsx.stub | auth:reset-password |
email-verify-notice-page | component-email-verify-notice-page.tsx.stub | auth:email-verify-notice |
password-change-page | component-password-change-page.tsx.stub | auth:password-change |
Install / SSO / Roles stubs
martis:stubs also publishes the templates that martis:install, martis:sso, and martis:roles use to write migrations + helper classes:
| Stub | Used by |
|---|---|
create_martis_action_events_table.php.stub, alter_martis_action_events_morph_ids_to_string.php.stub, fix_martis_action_events_morph_ids_string_v2.php.stub, create_user_preferences_table.php.stub, drop_dashboards_layout_from_user_preferences_table.php.stub, create_martis_notifications_table.php.stub, create_martis_cache_state_table.php.stub, add_profile_picture_column.php.stub, add_two_factor_columns.php.stub, create_sessions_table.php.stub | martis:install |
add_provider_group_column_to_roles_table.php.stub | martis:sso --with-migration |
MartisServiceProvider.php.stub | martis:install (provider scaffold) |
roles-permission-resource.stub, roles-role-resource.stub, roles-user-resource.stub, roles-policy.stub, roles-seeder.stub | martis:roles |
Since v1.8.8 these all route through StubResolver::path(), so editing the published copy in stubs/martis/ produces a custom output exactly like the regular generators. Caveats: the migrations describe schema Martis depends on at runtime — change column names or types only if you also update the matching app code, and re-run martis:install after. Because the copies in stubs/martis/ win, an upstream fix to one of these stubs (for example a migration correction) does not reach your installs until you re-publish it with martis:stubs --force or delete your copy. The extension scaffold (stubs/extensions/*: the Vite config, the tsconfig files, index.ts and the shims) is not override-aware: martis:install always reads it from the package (StubResolver::packagePath()).
Editing a stub
php artisan martis:stubsnano stubs/martis/resource.stub # editphp artisan martis:resource Order # output reflects the editsNo cache to clear, no rebuild, no restart. The generator reads the file from disk every invocation.
Stubs use simple {{ placeholder }} substitution, the same syntax Laravel core uses. Keep the placeholders intact unless you also customise the generator class.
Re-publishing after a package upgrade
When a Martis upgrade adds new generators or changes a stub shape, the new templates ship in the package but are not auto-copied into your stubs/martis/. Two options:
- Keep your customisations. Run
php artisan martis:stubsagain; existing files are skipped (output:SKIPPED). New stubs from the upgrade are added. Then manually port any upstream changes you care about. - Reset to the latest defaults. Run
php artisan martis:stubs --forceto overwrite every file with the latest package default. Use a clean working tree so the diff is reviewable in git.
php artisan martis:stubs # additive: skip existing, copy newphp artisan martis:stubs --force # destructive: overwrite everythingInternals — Martis\Stubs\StubResolver
Every make-command resolves its stub through:
\Martis\Stubs\StubResolver::path('resource.stub');The resolver exposes three static methods:
| Method | Returns |
|---|---|
path(string $name): string | Override-aware path — base_path('stubs/martis/<name>') when present, else the package default. This is the path every make-command uses. |
packagePath(string $name): string | Always the bundled package default, regardless of any override. Useful when your custom command needs the unmodified template (e.g. to diff against a customer override). |
packageDirectory(): string | The package's stubs/ root, derived from the resolver's own location (dirname(__DIR__, 2).'/stubs'). Drives the martis:stubs command itself. |
If you ship a custom artisan command of your own, the same resolver gives you the override-aware path:
use Martis\Stubs\StubResolver; protected function getStub(): string{ return StubResolver::path('my-custom.stub');}You publish your own stubs into stubs/martis/ from your own service provider's boot() if you want them to participate in the same flow.
Removing customisations
Drop the override file from stubs/martis/ and the resolver falls back to the package default on the next run. Removing the entire folder reverts everything.
rm -rf stubs/martis/ # full resetrm stubs/martis/resource.stub # revert just one