Documentation menu
HomeDocsCustomizationCustomising generators

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:stubs

Output (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

StubUsed by
resource.stubmartis:resource
action.stub, action.destructive.stubmartis:action
lens.stubmartis:lens
dashboard.stubmartis:dashboard
metric.value.stub, metric.trend.stub, metric.partition.stub, metric.progress.stub, metric.activity-feed.stub, metric.endpoint-table.stubmartis:value, martis:trend, martis:partition, martis:progress, martis:activity-feed, martis:endpoint-table
filter.boolean.stub, filter.date.stub, filter.select.stubmartis:filter
card.stub, component-card.tsx.stubmartis:card
tool.stub, tool-component.tsx.stubmartis:tool
field.stub, field.tsx.stubmartis:field
policy.stubmartis: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.stubmartis:theme
component-{type}.tsx.stub (12 variants)martis:component --type={type} (see table below)
agents/AGENTS.md.stubmartis: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=StubOverride key registered by the generated component
shellcomponent-shell.tsx.stublayout:shell
sidebarcomponent-sidebar.tsx.stublayout:sidebar
topbarcomponent-topbar.tsx.stublayout:topbar
footercomponent-footer.tsx.stublayout:footer
cardcomponent-card.tsx.stub(used by martis:card)
fieldcomponent-field.tsx.stub(used by martis:field)
generic (default fallback)component-generic.tsx.stub—
login-pagecomponent-login-page.tsx.stubauth:login
register-pagecomponent-register-page.tsx.stubauth:register
forgot-password-pagecomponent-forgot-password-page.tsx.stubauth:forgot-password
reset-password-pagecomponent-reset-password-page.tsx.stubauth:reset-password
email-verify-notice-pagecomponent-email-verify-notice-page.tsx.stubauth:email-verify-notice
password-change-pagecomponent-password-change-page.tsx.stubauth: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:

StubUsed 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.stubmartis:install
add_provider_group_column_to_roles_table.php.stubmartis:sso --with-migration
MartisServiceProvider.php.stubmartis:install (provider scaffold)
roles-permission-resource.stub, roles-role-resource.stub, roles-user-resource.stub, roles-policy.stub, roles-seeder.stubmartis: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:stubs
nano stubs/martis/resource.stub # edit
php artisan martis:resource Order # output reflects the edits

No 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:

  1. Keep your customisations. Run php artisan martis:stubs again; existing files are skipped (output: SKIPPED). New stubs from the upgrade are added. Then manually port any upstream changes you care about.
  2. Reset to the latest defaults. Run php artisan martis:stubs --force to 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 new
php artisan martis:stubs --force # destructive: overwrite everything

Internals — Martis\Stubs\StubResolver

Every make-command resolves its stub through:

\Martis\Stubs\StubResolver::path('resource.stub');

The resolver exposes three static methods:

MethodReturns
path(string $name): stringOverride-aware path — base_path('stubs/martis/<name>') when present, else the package default. This is the path every make-command uses.
packagePath(string $name): stringAlways 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(): stringThe 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 reset
rm stubs/martis/resource.stub # revert just one