Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message
ptah-scaffold
jonytonet/ptah
claude-code
generator
crud
laravel
scaffolding
tailwind
livewire4

Scaffold a complete Ptah CRUD from an entity/table description. Orchestrates ptah:forge (fields or --db from an existing table, optional --api), migrate, the mandatory post-scaffold fixes, then ptah:config for the BaseCrud listing. Use whenever the user wants to create/generate a new entity, model, or CRUD with Ptah ("gerar entidade", "novo CRUD", "scaffold X", "cria um CRUD de …"), or hands over a table/fields to turn into a full screen.

Install
php artisan boost:add-skill jonytonet/ptah

Save this content to: .claude/skills/ptah-scaffold/SKILL.md

# Ptah — Scaffold a CRUD end-to-end

Turn "here is the entity/table" into a working web CRUD (Model + Service +
Repository + DTO + Livewire + migration + views) plus a configured BaseCrud
listing. This skill is the **runbook + guardrails**; for deep conventions
(SOLID layers, design tokens, performance rules, API, tests) defer to the
companion **`ptah-development`** skill (also shipped in the package at
`vendor/jonytonet/ptah/resources/boost/skills/ptah-development/SKILL.md`).

## Where the commands run

`ptah:*` are artisan commands of the **consuming Laravel app** (the app that
`composer require`d ptah), NOT the package itself. Run artisan from the app root
(the directory that contains the `artisan` file):
- working dir is the app root → `php artisan ptah:...`
- working dir is a parent workspace → `cd <app-dir> && php artisan ptah:...`
- **never** run `ptah:*` inside the ptah package / `vendor` directory (no `artisan` there).

If you edited the ptah package and the change isn't visible, run `composer dump-autoload` in the app.

## Step 0 — Collect the inputs (ask only what's missing)

- **Entity name** in PascalCase, optional subfolder → `Product` or `Catalog/Product`
  (subfolder = namespace `App\Models\Catalog\Product` and menu group).
- **Source of fields** — one of:
  - explicit fields: `name:string,price:decimal(10,2):nullable,category_id:unsignedBigInteger,is_active:boolean`
  - an **existing DB table** → use `--db` (ptah reads the columns for you).
- **Flags**: `--api` (web+API), `--api-only` (API without web views),
  `--no-soft-deletes`, `--no-menu`, `--table=` if it differs from the plural
  snake_case default.
- **Field types** (`--fields`): only `integer`/`bigInteger`/…/`unsigned*`,
  `foreignId`, `string`, `char`, `text`, `longText`, `decimal`, `float`,
  `double`, `boolean`, `date`, `datetime`, `timestamp`, `json`, `enum` (plus the
  aliases `int`, `bigint`, `uint`, `ubigint`, `bool`, `foreign`, `jsonb`). Any
  other type is **refused** by `ptah:forge` — nothing is generated. State
  `decimal(p,s)`; a bare `decimal` silently becomes `decimal(10,2)`.

If the user only gives a table that already exists, prefer `--db` — do not invent a `--fields` list.

## Step 1 — Generate

New entity from fields (SoftDeletes is ON by default — pass `--no-soft-deletes` to disable):
```bash
php artisan ptah:forge Catalog/Product \
  --fields="name:string,price:decimal(10,2),category_id:unsignedBigInteger,is_active:boolean"
```

From an existing table (introspects the DB — matches "just give the table"):
```bash
php artisan ptah:forge Catalog/Product --db --table=products
```

Web + API together:
```bash
php artisan ptah:forge Catalog/Product --fields="..." --api
```

Factory + demo seeder from the field types: add `--factory`.

In web mode (no `--api-only`) forge also **writes a default `crud_configs` row
to the database** (needs the `crud_configs` table from `ptah:install`) — the
screen already lists its columns before Step 3; `--force` overwrites that row.
Step 3 only refines it.

**Several related entities?** Write a spec and let `ptah:blueprint` order them
by foreign key and run forge → migrate → config → menu → permissions → seed
(`--dry-run` first). **A column on an existing entity?** `ptah:field Entity add
name:type[:modifiers]` edits migration, model, rules, DTO and config at once.
Both: `docs/AgentTools.md`.

## Step 2 — Post-scaffold fixes (in order)

1. **FK imports — only if `ptah:forge` printed a ⚠.** The generator resolves a
   foreign key's `use` itself when exactly one class of that name exists in
   `app/Models`, and lists each FK as `✔` (done) or `⚠` (not generated yet, or
   ambiguous). Scaffold related entities in dependency order and there is
   nothing to fix. Never commit a `// TODO:` line.
2. **Format:** `./vendor/bin/pint`
3. **Migrate:** `php artisan migrate`  ⚠️ plain `migrate` only — see Guardrails.
4. **Clear caches:** `php artisan view:clear && php artisan config:clear`
5. Unless you passed `--no-menu`, forge added a link to
   `database/seeders/MenuRegistry.php` (grouped when the entity has a subfolder,
   a flat root link when not) — sync the sidebar:
   `php artisan ptah:menu-sync` (see Guardrails re: `--fresh`).
6. **Verify:** `php artisan ptah:check` renders the screen and flags config
   that does not match the model/table (ghost columns, non-fillable fields).

## Step 3 — Configure the BaseCrud listing

`ptah:config` builds the listing config (columns/filters/actions/styles). It can
introspect the model/table to infer column types. Always pass `--non-interactive`
when driving it as an agent, and prefer `--dry-run` first to preview.

```bash
php artisan ptah:config "App\Models\Catalog\Product" --non-interactive \
  --column="name:text:label=Nome:sortable" \
  --column="price:number:label=Preço:renderer=money:sortable" \
  --column="is_active:select:label=Status:renderer=badge:badges=1|success|Ativo,0|danger|Inativo" \
  --filter="is_active:select:label=Ativos:options=1:Ativo,0:Inativo" \
  --set="itemsPerPage=15"

# preview / inspect:
php artisan ptah:config "App\Models\Catalog\Product" --dry-run --column="..."
php artisan ptah:config "App\Models\Catalog\Product" --list
```

Column/filter/action/style formats are documented in the package skill — reuse
them verbatim rather than guessing.

## Relationships

For an FK column (e.g. `category_id`), configure it as a relation/searchdropdown
so the form shows a picker and the listing shows the related label:
`--column="category_id:text:label=Categoria:relation=category:relation_display=name"`
para exibir o rótulo na listagem, ou
`--column="category_id:searchdropdown:label=Categoria:sd_model=App\Models\Catalog\Category:sd_value=id:sd_label=name"`
para ter o picker no formulário — `searchdropdown` sem `sd_model` (ou
`sd_service`) é recusado pelo validador.
Confirm the relation name and display column against the actual model before
committing — the inference is heuristic.

## Guardrails (organization security rules apply)

- **Never run destructive DB commands** as part of scaffolding: no `migrate:fresh`,
  `migrate:reset`, `db:wipe`, `migrate:rollback` on shared/prod. Use plain
  `php artisan migrate`. If a migration must be undone, a human runs it.
- **`ptah:menu-sync --fresh` rewrites the `menus` table** — treat as destructive:
  run plain `ptah:menu-sync`, and only use `--fresh` after explicit confirmation.
- **Confirm the environment** before migrating. Assume production unless told
  otherwise; back up first for anything beyond local.
- **Pint before any commit.** Commit/push only when the user asks.
- Secrets always via `.env`, never hardcoded.

## Quick end-to-end example

> "Cria um CRUD de Fornecedor (Catalog/Supplier) com nome, cnpj, ativo."

```bash
cd petplace
php artisan ptah:forge Catalog/Supplier --fields="name:string,cnpj:string,is_active:boolean"
# pint → migrate → clears (FK imports resolved by the generator)
./vendor/bin/pint && php artisan migrate && php artisan view:clear && php artisan config:clear
php artisan ptah:config "App\Models\Catalog\Supplier" --non-interactive \
  --column="name:text:label=Nome:sortable" \
  --column="cnpj:text:label=CNPJ" \
  --column="is_active:select:label=Status:renderer=badge:badges=1|success|Ativo,0|danger|Inativo" \
  --filter="is_active:select:label=Ativos:options=1:Ativo,0:Inativo"
```

Ptah — Scaffold a CRUD end-to-end

Turn "here is the entity/table" into a working web CRUD (Model + Service + Repository + DTO + Livewire + migration + views) plus a configured BaseCrud listing. This skill is the runbook + guardrails; for deep conventions (SOLID layers, design tokens, performance rules, API, tests) defer to the companion ptah-development skill (also shipped in the package at vendor/jonytonet/ptah/resources/boost/skills/ptah-development/SKILL.md).

Where the commands run

ptah:* are artisan commands of the consuming Laravel app (the app that composer required ptah), NOT the package itself. Run artisan from the app root (the directory that contains the artisan file):

  • working dir is the app root → php artisan ptah:...
  • working dir is a parent workspace → cd <app-dir> && php artisan ptah:...
  • never run ptah:* inside the ptah package / vendor directory (no artisan there).

If you edited the ptah package and the change isn't visible, run composer dump-autoload in the app.

Step 0 — Collect the inputs (ask only what's missing)

  • Entity name in PascalCase, optional subfolder → Product or Catalog/Product (subfolder = namespace App\Models\Catalog\Product and menu group).
  • Source of fields — one of:
    • explicit fields: name:string,price:decimal(10,2):nullable,category_id:unsignedBigInteger,is_active:boolean
    • an existing DB table → use --db (ptah reads the columns for you).
  • Flags: --api (web+API), --api-only (API without web views), --no-soft-deletes, --no-menu, --table= if it differs from the plural snake_case default.
  • Field types (--fields): only integer/bigInteger/…/unsigned*, foreignId, string, char, text, longText, decimal, float, double, boolean, date, datetime, timestamp, json, enum (plus the aliases int, bigint, uint, ubigint, bool, foreign, jsonb). Any other type is refused by ptah:forge — nothing is generated. State decimal(p,s); a bare decimal silently becomes decimal(10,2).

If the user only gives a table that already exists, prefer --db — do not invent a --fields list.

Step 1 — Generate

New entity from fields (SoftDeletes is ON by default — pass --no-soft-deletes to disable):

php artisan ptah:forge Catalog/Product \
  --fields="name:string,price:decimal(10,2),category_id:unsignedBigInteger,is_active:boolean"

From an existing table (introspects the DB — matches "just give the table"):

php artisan ptah:forge Catalog/Product --db --table=products

Web + API together:

php artisan ptah:forge Catalog/Product --fields="..." --api

Factory + demo seeder from the field types: add --factory.

In web mode (no --api-only) forge also writes a default crud_configs row to the database (needs the crud_configs table from ptah:install) — the screen already lists its columns before Step 3; --force overwrites that row. Step 3 only refines it.

Several related entities? Write a spec and let ptah:blueprint order them by foreign key and run forge → migrate → config → menu → permissions → seed (--dry-run first). A column on an existing entity? ptah:field Entity add name:type[:modifiers] edits migration, model, rules, DTO and config at once. Both: docs/AgentTools.md.

Step 2 — Post-scaffold fixes (in order)

  1. FK imports — only if ptah:forge printed a ⚠. The generator resolves a foreign key's use itself when exactly one class of that name exists in app/Models, and lists each FK as ✔ (done) or ⚠ (not generated yet, or ambiguous). Scaffold related entities in dependency order and there is nothing to fix. Never commit a // TODO: line.
  2. Format: ./vendor/bin/pint
  3. Migrate: php artisan migrate ⚠️ plain migrate only — see Guardrails.
  4. Clear caches: php artisan view:clear && php artisan config:clear
  5. Unless you passed --no-menu, forge added a link to database/seeders/MenuRegistry.php (grouped when the entity has a subfolder, a flat root link when not) — sync the sidebar: php artisan ptah:menu-sync (see Guardrails re: --fresh).
  6. Verify: php artisan ptah:check renders the screen and flags config that does not match the model/table (ghost columns, non-fillable fields).

Step 3 — Configure the BaseCrud listing

ptah:config builds the listing config (columns/filters/actions/styles). It can introspect the model/table to infer column types. Always pass --non-interactive when driving it as an agent, and prefer --dry-run first to preview.

php artisan ptah:config "App\Models\Catalog\Product" --non-interactive \
  --column="name:text:label=Nome:sortable" \
  --column="price:number:label=Preço:renderer=money:sortable" \
  --column="is_active:select:label=Status:renderer=badge:badges=1|success|Ativo,0|danger|Inativo" \
  --filter="is_active:select:label=Ativos:options=1:Ativo,0:Inativo" \
  --set="itemsPerPage=15"

# preview / inspect:
php artisan ptah:config "App\Models\Catalog\Product" --dry-run --column="..."
php artisan ptah:config "App\Models\Catalog\Product" --list

Column/filter/action/style formats are documented in the package skill — reuse them verbatim rather than guessing.

Relationships

For an FK column (e.g. category_id), configure it as a relation/searchdropdown so the form shows a picker and the listing shows the related label: --column="category_id:text:label=Categoria:relation=category:relation_display=name" para exibir o rótulo na listagem, ou --column="category_id:searchdropdown:label=Categoria:sd_model=App\Models\Catalog\Category:sd_value=id:sd_label=name" para ter o picker no formulário — searchdropdown sem sd_model (ou sd_service) é recusado pelo validador. Confirm the relation name and display column against the actual model before committing — the inference is heuristic.

Guardrails (organization security rules apply)

  • Never run destructive DB commands as part of scaffolding: no migrate:fresh, migrate:reset, db:wipe, migrate:rollback on shared/prod. Use plain php artisan migrate. If a migration must be undone, a human runs it.
  • ptah:menu-sync --fresh rewrites the menus table — treat as destructive: run plain ptah:menu-sync, and only use --fresh after explicit confirmation.
  • Confirm the environment before migrating. Assume production unless told otherwise; back up first for anything beyond local.
  • Pint before any commit. Commit/push only when the user asks.
  • Secrets always via .env, never hardcoded.

Quick end-to-end example

"Cria um CRUD de Fornecedor (Catalog/Supplier) com nome, cnpj, ativo."

cd petplace
php artisan ptah:forge Catalog/Supplier --fields="name:string,cnpj:string,is_active:boolean"
# pint → migrate → clears (FK imports resolved by the generator)
./vendor/bin/pint && php artisan migrate && php artisan view:clear && php artisan config:clear
php artisan ptah:config "App\Models\Catalog\Supplier" --non-interactive \
  --column="name:text:label=Nome:sortable" \
  --column="cnpj:text:label=CNPJ" \
  --column="is_active:select:label=Status:renderer=badge:badges=1|success|Ativo,0|danger|Inativo" \
  --filter="is_active:select:label=Ativos:options=1:Ativo,0:Inativo"
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ignibyte/aic-framework
glavweb/data-schema-bundle
glavweb/datagrid-bundle
tacticmedia/scim-server
uni-method/frame
neika-ai/sdk
adachsoft/php-code-style
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox