atcliff/laravel-crud-forms
A Laravel package for scaffolding CRUD forms with minimal boilerplate. Define fields and validation once, then render create, edit, and delete interfaces via Blade components, ready to drop into any Eloquent resource.
1. Confirm the package is installable before you build on it. There's no tagged release since December 2019 and zero dependents, so treat the install as a spike, not a commitment:
composer require atcliff/laravel-crud-forms --dev
composer show atcliff/laravel-crud-forms # inspect version, requires, autoload
If Composer resolves against a dev-master branch, pin the exact commit in composer.json ("atcliff/laravel-crud-forms": "dev-master#<sha>") so a force-push upstream can't silently change your code. If it hard-requires Laravel 5.x/6.x and you're on 10/11, stop here and fork it — don't fight the constraint with --ignore-platform-reqs in a real app.
2. Find the service provider and publish assets. Open vendor/atcliff/laravel-crud-forms/composer.json → extra.laravel.providers (or check for package discovery). Then:
php artisan vendor:publish --provider="AtCliff\CrudForms\CrudFormsServiceProvider"
php artisan list | grep -i crud # look for generator/install commands
Read the published config/crud-forms.php first — that's where field-type→Blade-partial mappings, route prefix/middleware, and layout/view paths will live, and it tells you more about the package's design than the README does.
3. Read three files before writing any code: the base controller (or trait) the package expects you to extend, the field-rendering Blade partials, and the generator command if one exists. Those define the contract you're coding against.
4. First use case — scaffold one trivial resource end to end. Pick a Post (title:string, body:text, published_at:datetime, status:enum) and get /posts/create rendering and storing. Do it by hand first (controller + form definition + one Blade view) rather than via the generator, so you understand what the generator produces. Only then run the generator on a second model and diff the output against your manual version.
Declarative form definitions per model. The package's core idea is a field map that drives both create and edit rendering plus validation. Keep it in a dedicated class or config file, not inline in the controller:
// app/Forms/PostForm.php
return [
'title' => ['type' => 'text', 'label' => 'Title', 'rules' => 'required|max:255'],
'body' => ['type' => 'textarea', 'label' => 'Body', 'rules' => 'required'],
'status' => ['type' => 'select', 'options' => Post::STATUSES, 'rules' => 'required|in:...'],
'published_at' => ['type' => 'datetime', 'rules' => 'nullable|date'],
];
The win is that store and update share one definition — the day-to-day value is not retyping validation rules in two FormRequests.
Thin controller + resource routes. Extend the package's base controller and set the model/form as properties; register Route::resource('posts', PostController::class) inside your auth/admin middleware group. The package almost certainly does not ship authorization, so gate the whole group — don't rely on it to check policies per action.
Custom field types via partials. For belongsTo, belongsToMany, file uploads, or WYSIWYG, publish the views and add a partial, then register the type in config:
'fields' => ['author_id' => ['type' => 'belongsTo', 'model' => User::class, ...]]
belongsToMany (multi-select + sync) and polymorphic relations are usually the first things a generic CRUD package gets wrong — expect to write those by hand in the controller's store/update after the base save.
Wrap the views in your layout. Set the layout/view-path config to your app layout and let the generated views be @section/@yield content. Publish views once (--tag=views) and commit them — treat them as your code from that point, not vendor code.
Override per-model when needed. For one-off fields, drop a model-specific partial (resources/views/vendor/crud-forms/fields/post-seo.blade.php) rather than forking the whole package view tree.
Feature-test the two write paths immediately. $this->post(route('posts.store'), $payload)->assertRedirect() and the update equivalent, including an invalid-payload case. Since the package is unmaintained, these tests are your regression net for when you eventually replace it.
app/ or a private VCS repo if you plan to use it past a prototype. Do not make it a load-bearing dependency in a client project without a replacement plan.str_slug, array_get), $dates vs $casts, Blade @php changes, and the Laravel 9+ Symfony response changes. Run the full test suite on the target framework before anything else.$request->all() or $request->validated(). If it's the former, you must set $fillable correctly on every model — a stray is_admin field in a form config becomes a privilege escalation.Rule::unique(...)->ignore($post) is expressible; if not, override rules() in the controller.<form> includes enctype="multipart/form-data" and that the disk is configurable. Run php artisan storage:link. Test with a real upload, not just text fields.config/crud-forms.php, run php artisan config:clear — a stale cached config is the #1 "my field type isn't registering" cause.destroy on a soft-deleting model should delete() not forceDelete(); check the base controller and override if needed.php artisan route:list --path=posts, dd(config('crud-forms')), dd($formDefinition) in the controller, and check the rendered HTML for missing CSRF/@error blocks.How can I help you explore Laravel packages today?