grafite/crudmaker
Grafite's CrudMaker scaffolds complete Laravel CRUD from a simple YAML config, generating models, migrations, controllers, routes, views, and tests in seconds. Customize the templates to match your conventions and skip repetitive boilerplate.
CrudMaker is a scaffolding generator, not a runtime dependency. You install it, generate a slice of CRUD (migration, model, controller, routes, views, factory, tests), then own the output. Everything below assumes Laravel 8-ish; the package is archived, so confirm the composer.json constraint against your app version before committing to it (or plan to fork and vendor the stubs).
1. Install (dev only)
composer require grafite/crudmaker --dev
Auto-discovery registers Grafite\CrudMaker\CrudMakerServiceProvider. If you're on an older Laravel, add it to config/app.php manually.
2. Publish and read the config first
php artisan vendor:publish --provider="Grafite\CrudMaker\CrudMakerServiceProvider"
Open config/crudmaker.php. This is the single most important file: it controls namespaces (App\Models vs App), output paths for models/controllers/migrations/seeds/views, the layout your generated Blade @extends, the section name, pagination size, and template flavor (Bootstrap vs Tailwind). Set namespaces and layout before generating anything — regenerating after the fact won't fix already-written files.
3. Generate your first resource
php artisan crudmaker:new Post --with-migration --with-views
Flags drift between versions, so confirm with php artisan crudmaker:new --help before scripting anything. The common set is --with-migration, --with-views, --with-api, --with-factory, --with-seeder, --with-tests, --with-policies, --with-translations, --with-forms, --with-routes.
4. Make it real
php artisan migrate
php artisan route:list | grep posts
Hit /posts in the browser. You should have index/create/store/show/edit/update/destroy wired to the generated controller.
Where to look first, in order:
config/crudmaker.php — fix namespaces and view layout.vendor/grafite/crudmaker/src/ — the service class and stubs. When output doesn't match your house style, this is what you're going to edit or override.First real use case: a back-office admin table for an existing model you're tired of hand-writing. Generate index + create/edit views against your existing layout, then throw away the generated controller and write a thin one on top of your own action classes.
Staged generation, not one-shot. Generate in passes so failures are cheap:
# Pass 1: schema only, so you can review the migration
php artisan crudmaker:new Invoice --with-migration
# Pass 2: after editing the migration and confirming it runs
php artisan crudmaker:new Invoice --with-factory --with-seeder --with-tests
# Pass 3: last, once the model and validation are settled
php artisan crudmaker:new Invoice --with-views --with-routes
Each pass overwrites; see the gotchas. Commit between passes.
Scaffold → slim down. The generated controller is deliberately verbose (inline validation, $request->all(), pagination inline). The day-to-day pattern is to immediately refactor:
public function store(StoreInvoiceRequest $request)
{
$invoice = Invoice::create($request->validated());
return redirect()->route('invoices.show', $invoice)
->with('message', 'Invoice created.');
}
Move the inline rules from the generated controller into a FormRequest and delete them. This is the highest-value 10 minutes of the whole workflow.
Naming conventions drive everything. CrudMaker derives table name (plural snake), variable name (singular camel), view path (plural kebab), and route names (plural kebab) from the StudlyCase argument. php artisan crudmaker:new BlogPost yields blog_posts, $blogPost, blog-posts.index, blog-posts/* views. Get the name right the first time — renaming afterward means touching six files.
API variant for SPAs and mobile. --with-api produces a separate API controller and a resource route set. For an Inertia/Vue front end, generate both and delete the Blade views — or generate API-only and point your front end at it. Keep API and web controllers separate; merging them is where the generated code stops being useful.
Relationships. The generator infers a belongsTo from *_id column names and will render a select box for those in generated forms if it can resolve the related model. It's a guess. After generating, explicitly define belongsTo/hasMany on the model and confirm the form's options query is what you want (usually you need to scope it, e.g. only active records).
Views integration. The generated Blade extends whatever crudmaker.views.extends says and injects into crudmaker.views.section. If your layout uses @yield('content'), set the config to content before generating, otherwise you get blank pages with no error.
Pairs with FormMaker. --with-forms delegates form rendering to grafite/formmaker. Nice if you're already in the Grafite ecosystem (dashboard, etc.); otherwise skip it — plain Blade plus your component library is less indirection.
Team golden path. Wrap the whole thing in a composer script or Makefile so everyone generates identically:
"scripts": {
"crud": "php artisan crudmaker:new"
}
Better: write your own make:resource command that calls the CrudMaker service programmatically with your org's flags and then applies your post-generation edits. That turns a one-off tool into a repeatable convention, which is the only way scaffolding survives a code review culture.
Generated tests as a starting point. --with-tests gives you feature tests keyed to factories. Run them immediately after generating — before you customize anything. If they're red on a fresh scaffold, it's usually a factory/naming mismatch you want to fix once, centrally.
crudmaker:new Post --with-views after you've edited the views destroys your work. Always git status clean before generating. Consider aliasing the command locally to add git stash guardrails.composer.json constraints and the stubs, and depend on your fork via a VCS repository. Don't expect upstream patches.config/crudmaker.php, run php artisan config:clear. A stale cached config silently generates into the wrong namespace.App\Models but the config still says models live in App, you get controllers importing App\Post that doesn't exist. Set it once, verify on the first scaffold, then never touch it.*_id → foreign key, *_at → timestamp, is_*/has_* → boolean, otherwise string. Review every generated migration before running it — nullable, length, indexes, and softDeletes are usually missing.required|max:255-ish. They'll pass bad data and reject legitimate data (e.g. unique emails on update without Rule::unique()->ignore()). Rewrite before production.routes/web.php unchecked. Duplicate blocks on regeneration, and nothing is placed behind auth middleware. Move routes into a guarded group manually and delete stragglers.@csrf and @method('PUT'/'DELETE') exist in update/delete forms. Older laravelcollective-based output can surprise you here.crudmaker.paginate sets the default paginate() argument in every generated controller. Override per-controller rather than editing global config.-v, check storage/logs/laravel.log, and confirm the destination directories exist and are writable. A missing resources/views/admin/ directory fails quietly in some versions.vendor/ is a dead end because composer install wipes it; fork or copy stubs into your own command.require-dev and make sure it isn't installed on deploy targets.How can I help you explore Laravel packages today?