An updated Laravel CRUD Generator version of ibex/crud-generator now generates CRUD in Tailwind CSS in the blade with the blank Laravel installation.
1- Install Laravel
composer create-project laravel/laravel laravel-11-crud
2- Create Migration
php artisan make:model Posts –migration
For example posts table has the following columns in migration below.
<?php use Illuminate\Database\Migrations\Migration; use Illuminate\Database\Schema\Blueprint; use Illuminate\Support\Facades\Schema; return new class extends Migration { public function up(): void { Schema::create('posts', function (Blueprint $table) { $table->id(); $table->string('title'); $table->string('description'); $table->timestamps(); }); } public function down(): void { Schema::dropIfExists('posts'); } };
3- Install ibex/crud-generator
php artisan make:crud posts tailwind
A single command will generate all CRUD operations in a second. This will include a resource PostController, Post (Model), PostRequest, and all Blade views in Tailwind CSS.
4- Add route in web.php
Route::resource(‘posts’, PostController::class);
Below is the code generated for PostController
<?php namespace App\Http\Controllers; use App\Models\Post; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use App\Http\Requests\PostRequest; use Illuminate\Support\Facades\Redirect; use Illuminate\View\View; class PostController extends Controller { public function index(Request $request): View { $posts = Post::paginate(); return view('post.index', compact('posts')) ->with('i', ($request->input('page', 1) - 1) * $posts->perPage()); } public function create(): View { $post = new Post(); return view('post.create', compact('post')); } public function store(PostRequest $request): RedirectResponse { Post::create($request->validated()); return Redirect::route('posts.index') ->with('success', 'Post created successfully.'); } public function show($id): View { $post = Post::find($id); return view('post.show', compact('post')); } public function edit($id): View { $post = Post::find($id); return view('post.edit', compact('post')); } public function update(PostRequest $request, Post $post): RedirectResponse { $post->update($request->validated()); return Redirect::route('posts.index') ->with('success', 'Post updated successfully'); } public function destroy($id): RedirectResponse { Post::find($id)->delete(); return Redirect::route('posts.index') ->with('success', 'Post deleted successfully'); } }
5- Create view files.
6-Create view file- create.blade.php
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Create') }} {{modelTitle}} </h2> </x-slot> <div class="py-12"> <div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="w-full"> <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> <h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Create') }} {{modelTitle}}</h1> <p class="mt-2 text-sm text-gray-700">Add a new {{ __('{{modelTitle}}') }}.</p> </div> <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a> </div> </div> <div class="flow-root"> <div class="mt-8 overflow-x-auto"> <div class="max-w-xl py-2 align-middle"> <form method="POST" action="{{ route('{{modelRoute}}.store') }}" role="form" enctype="multipart/form-data"> @csrf @include('{{modelView}}.form') </form> </div> </div> </div> </div> </div> </div> </div> </x-app-layout>
7- Create view file- edit.blade.php
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('Update') }} {{modelTitle}} </h2> </x-slot> <div class="py-12"> <div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="w-full"> <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> <h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Update') }} {{modelTitle}}</h1> <p class="mt-2 text-sm text-gray-700">Update existing {{ __('{{modelTitle}}') }}.</p> </div> <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a> </div> </div> <div class="flow-root"> <div class="mt-8 overflow-x-auto"> <div class="max-w-xl py-2 align-middle"> <form method="POST" action="{{ route('{{modelRoute}}.update', ${{modelNameLowerCase}}->id) }}" role="form" enctype="multipart/form-data"> {{ method_field('PATCH') }} @csrf @include('{{modelView}}.form') </form> </div> </div> </div> </div> </div> </div> </div> </x-app-layout>
8-Create view file- form-field.blade.php
<div> <x-input-label for="{{column_snake}}" :value="__('{{title}}')"/> <x-text-input id="{{column_snake}}" name="{{column}}" type="text" class="mt-1 block w-full" :value="old('{{column}}', ${{modelNameLowerCase}}?->{{column}})" autocomplete="{{column}}" placeholder="{{title}}"/> <x-input-error class="mt-2" :messages="$errors->get('{{column}}')"/> </div>
9-Create view file- form.blade.php
<div class="space-y-6"> {{form}} <div class="flex items-center gap-4"> <x-primary-button>Submit</x-primary-button> </div> </div>
10-Create view file- index.blade.php
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ __('{{modelTitlePlural}}') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="w-full"> <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> <h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('{{modelTitlePlural}}') }}</h1> <p class="mt-2 text-sm text-gray-700">A list of all the {{ __('{{modelTitlePlural}}') }}.</p> </div> <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <a type="button" href="{{ route('{{modelRoute}}.create') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Add new</a> </div> </div> <div class="flow-root"> <div class="mt-8 overflow-x-auto"> <div class="inline-block min-w-full py-2 align-middle"> <table class="w-full divide-y divide-gray-300"> <thead> <tr> <th scope="col" class="py-3 pl-4 pr-3 text-left text-xs font-semibold uppercase tracking-wide text-gray-500">No</th> {{tableHeader}} <th scope="col" class="px-3 py-3 text-left text-xs font-semibold uppercase tracking-wide text-gray-500"></th> </tr> </thead> <tbody class="divide-y divide-gray-200 bg-white"> @foreach (${{modelNamePluralLowerCase}} as ${{modelNameLowerCase}}) <tr class="even:bg-gray-50"> <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-semibold text-gray-900">{{ ++$i }}</td> {{tableBody}} <td class="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-gray-900"> <form action="{{ route('{{modelRoute}}.destroy', ${{modelNameLowerCase}}->id) }}" method="POST"> <a href="{{ route('{{modelRoute}}.show', ${{modelNameLowerCase}}->id) }}" class="text-gray-600 font-bold hover:text-gray-900 mr-2">{{ __('Show') }}</a> <a href="{{ route('{{modelRoute}}.edit', ${{modelNameLowerCase}}->id) }}" class="text-indigo-600 font-bold hover:text-indigo-900 mr-2">{{ __('Edit') }}</a> @csrf @method('DELETE') <a href="{{ route('{{modelRoute}}.destroy', ${{modelNameLowerCase}}->id) }}" class="text-red-600 font-bold hover:text-red-900" onclick="event.preventDefault(); confirm('Are you sure to delete?') ? this.closest('form').submit() : false;">{{ __('Delete') }}</a> </form> </td> </tr> @endforeach </tbody> </table> <div class="mt-4 px-4"> {!! ${{modelNamePluralLowerCase}}->withQueryString()->links() !!} </div> </div> </div> </div> </div> </div> </div> </div> </x-app-layout>
11- Create view file- show.blade.php
<x-app-layout> <x-slot name="header"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> {{ ${{modelNameLowerCase}}->name ?? __('Show') . " " . __('{{modelTitle}}') }} </h2> </x-slot> <div class="py-12"> <div class="max-w-full mx-auto sm:px-6 lg:px-8 space-y-6"> <div class="p-4 sm:p-8 bg-white shadow sm:rounded-lg"> <div class="w-full"> <div class="sm:flex sm:items-center"> <div class="sm:flex-auto"> <h1 class="text-base font-semibold leading-6 text-gray-900">{{ __('Show') }} {{modelTitle}}</h1> <p class="mt-2 text-sm text-gray-700">Details of {{ __('{{modelTitle}}') }}.</p> </div> <div class="mt-4 sm:ml-16 sm:mt-0 sm:flex-none"> <a type="button" href="{{ route('{{modelRoute}}.index') }}" class="block rounded-md bg-indigo-600 px-3 py-2 text-center text-sm font-semibold text-white shadow-sm hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600">Back</a> </div> </div> <div class="flow-root"> <div class="mt-8 overflow-x-auto"> <div class="inline-block min-w-full py-2 align-middle"> <div class="mt-6 border-t border-gray-100"> <dl class="divide-y divide-gray-100"> {{viewRows}} </dl> </div> </div> </div> </div> </div> </div> </div> </div> </x-app-layout>
12-Create view file- view-field.blade.php
<div class="px-4 py-6 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-0"> <dt class="text-sm font-medium leading-6 text-gray-900">{{title}}</dt> <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0">{{ ${{modelNameLowerCase}}->{{column}} }}</dd> </div>