Custom CLI Schematics

Assegai's built-in generators help you move quickly through the framework's common workflows.

Custom schematics let you go further: they let you teach the CLI your own architecture, naming rules, and domain patterns.

That means your team can generate commands like:

  • assegai g loyalty-program rewards
  • assegai g menu-sync ubereats
  • assegai g tenant-onboarding enterprise

without forking the CLI or hand-copying files across projects.

This is one of the easiest ways to turn Assegai from "a framework with generators" into "a framework that understands how our team builds software."

Why this matters

Once an application grows beyond a demo, teams usually repeat the same patterns:

  • service classes
  • DTOs
  • provider integrations
  • config files
  • docs stubs
  • support files in TypeScript, JSON, YAML, or Markdown

Without custom schematics, that repetition becomes manual work.

With custom schematics, one command can generate the same structure consistently every time.

That gives you:

  • faster setup for repeatable features
  • fewer naming mistakes
  • less copy-paste drift
  • codebases that stay predictable across teams and projects

What a schematic actually is

A schematic is a recipe for generation.

At the simplest level, the recipe defines:

  1. which arguments and options the command accepts
  2. which template files to read
  3. where the generated files should be written
  4. which placeholder values should be replaced

If that is all you need, a declarative schematic is usually enough.

If your generator needs branching logic, custom path rules, or content assembly in PHP, you can move to a class-backed schematic.

Start with the easiest path

If you are new to custom schematics, start with a declarative schematic.

Generate a starter:

assegai schematic:init loyalty-program

That gives you a folder like this:

schematics/loyalty-program/
  schematic.json
  templates/
    service.php.stub

That is enough for a working custom generator.

A first declarative example

Example manifest:

{
  "name": "loyalty-program",
  "aliases": ["lp"],
  "description": "Generate loyalty program scaffolding.",
  "requiresWorkspace": true,
  "kind": "declarative",
  "arguments": [
    {
      "name": "name",
      "description": "The feature name to generate.",
      "required": true
    }
  ],
  "options": [
    {
      "name": "provider",
      "description": "The loyalty provider name.",
      "acceptValue": true,
      "valueRequired": true,
      "default": "internal"
    }
  ],
  "templates": [
    {
      "source": "templates/service.php.stub",
      "target": "__SOURCE_ROOT__/__NAME__/__NAME__Service.php"
    }
  ]
}

Example template:

<?php

namespace __CURRENT_NAMESPACE__;

class __NAME__Service
{
  public string $provider = '__OPTION_PROVIDER__';
}

Run it:

assegai g loyalty-program rewards --provider=partner-plus

Result:

src/Rewards/RewardsService.php

The generated file will contain:

<?php

namespace Assegaiphp\BlogApi\Rewards;

class RewardsService
{
  public string $provider = 'partner-plus';
}

Even this small example shows the real value: the CLI resolves names, paths, namespaces, and inputs for you, so the command generates something that already fits the project structure.

How to read the manifest

If you have never built a schematic before, this is the part that matters most.

  • name: the command name after assegai g
  • aliases: shorter names such as lp
  • description: help text shown in the CLI
  • requiresWorkspace: whether the schematic only works inside an Assegai app
  • kind: declarative for template-only generation, class for PHP-backed generation
  • arguments: positional inputs such as the feature name
  • options: named flags such as --provider=...
  • templates: for declarative schematics, the files to copy and where to write them
  • handler: for class-backed schematics, the PHP class that performs the generation

The most important pair is:

  • source: where the template file lives inside the schematic folder
  • target: where the generated file should be written in the app

Once those two are clear, the rest usually becomes straightforward.

How template tokens work

Tokens are simple text placeholders.

When the schematic runs, Assegai replaces them with resolved values.

Built-in naming tokens include:

  • __NAME__
  • __SINGULAR__
  • __PLURAL__
  • __CAMEL__
  • __KEBAB__
  • __PASCAL__
  • __BASE_NAMESPACE__
  • __CURRENT_NAMESPACE__
  • __SOURCE_ROOT__

You also get tokens for custom inputs:

  • __ARG_<NAME>__
  • __OPTION_<NAME>__

Examples:

  • __ARG_NAME__
  • __OPTION_PROVIDER__

These tokens are what make schematics practical instead of rigid. They let one command produce output that matches the app's naming conventions and folder structure automatically.

Can you combine tokens?

Yes.

Token replacement is text-based, so you can combine tokens anywhere they make sense.

Examples:

__SOURCE_ROOT__/__NAME__/DTOs/Create__SINGULAR__DTO.php
__SOURCE_ROOT__/__NAME__/__PASCAL____OPTION_PROVIDER__Client.php
docs/__KEBAB__-integration.md

If the user runs:

assegai g loyalty-program rewards --provider=PartnerPlus

then a target like this:

__SOURCE_ROOT__/__NAME__/__PASCAL____OPTION_PROVIDER__Client.php

becomes:

src/Rewards/RewardsPartnerPlusClient.php

This is where custom schematics start feeling powerful: they do not just copy files, they resolve the repeated naming work that teams usually do by hand.

Can you generate files that are not PHP?

Yes.

A schematic is not limited to PHP files. It can generate any text file.

For example:

{
  "templates": [
    {
      "source": "templates/client.ts.stub",
      "target": "__SOURCE_ROOT__/__NAME__/clients/__KEBAB__.client.ts"
    },
    {
      "source": "templates/config.json.stub",
      "target": "config/__KEBAB__.json"
    },
    {
      "source": "templates/notes.md.stub",
      "target": "docs/__KEBAB__.md"
    }
  ]
}

That means one schematic can generate PHP, TypeScript, JSON, Markdown, YAML, or plain text files together.

This is especially useful when your real workflow spans more than backend code. A single command can generate:

  • backend service classes
  • DTOs
  • frontend support files
  • config files
  • internal docs
  • integration notes

That is a much better fit for real teams than a one-file generator.

When to move to a class-backed schematic

Use a class-backed schematic when the generator needs logic, not just templates.

Typical reasons:

  • some files should only be generated when an option is present
  • the output path depends on custom rules
  • you want to assemble content in PHP before writing the file
  • you want more control than declarative templates provide

Starter command:

assegai schematic:init menu-sync --php

That creates a manifest plus a handler class that extends AbstractCustomSchematic.

A good rule of thumb is:

  • start declarative when your output is mostly template-driven
  • move to class-backed when your generation rules start branching

Share schematics through Composer packages

If your team uses the same schematic across projects, you can ship it in a package.

In the package composer.json:

{
  "extra": {
    "assegai": {
      "schematics": [
        "resources/menu-sync/schematic.json"
      ]
    }
  }
}

Once the package is installed in the workspace, assegai g can discover it.

This makes custom schematics useful beyond one repository. They can become part of your team's internal platform, shared standards, or reusable package ecosystem.

See what the CLI found

Use:

assegai schematic:list

This shows:

  • built-in schematics
  • local schematics from schematics/
  • package schematics from installed Composer packages

It is the quickest way to debug discovery issues.

Discovery config

The workspace config lives in assegai.json:

{
  "cli": {
    "schematics": {
      "paths": ["schematics"],
      "discoverPackages": true,
      "allowOverrides": false
    }
  }
}

This gives you explicit control over where custom schematics come from and whether package discovery is enabled in the current workspace.

Practical takeaway

Custom schematics are one of the strongest ways to extend Assegai without extending the framework itself.

Use them when your team wants to:

  • encode repeatable feature patterns
  • keep naming consistent
  • scaffold more than one file at a time
  • reduce copy-paste setup work
  • make internal conventions executable

In other words: custom schematics let you turn your preferred architecture into a command.

Next step

If you want the full token reference, more complete manifest examples, and guidance for generating multi-file company scaffolds, continue with Custom CLI Schematics In Depth.