CRUD Generator

Complete guide to using the module:make-crud command for rapid CRUD scaffolding in LaraDashboard modules. Automatically generates Models, Datatables, Livewire components, views, routes, and menu items.

CRUD Generator

The module:make-crud command provides rapid scaffolding for complete CRUD (Create, Read, Update, Delete) operations within LaraDashboard modules. It generates all necessary files including Models, Datatables, Livewire components, Blade views, routes, and menu items.

Prerequisites

Before using the CRUD generator, ensure you have:

  • An existing module created with php artisan module:make ModuleName
  • A database migration file defining your table structure (recommended)
  • Understanding of module development

Quick Start

Option A: One Command (Recommended)

Generate everything including migration in a single command:

# 1. Create module (if not exists)
php artisan module:make Blog

# 2. Generate CRUD with fields (creates migration + all files)
php artisan module:make-crud Blog --model=Post --fields="title:string,author:string,content:text,is_published:boolean"

# 3. Run migration
php artisan migrate

# 4. Clear cache and visit /admin/blog/posts
php artisan optimize:clear

Option B: From Existing Migration

If you already have a migration:

# 1. Create module
php artisan module:make Blog

# 2. Create migration manually
php artisan module:make-migration create_blog_posts_table Blog
# Edit the migration file with your columns

# 3. Run migration
php artisan migrate

# 4. Generate CRUD (auto-detects columns from migration)
php artisan module:make-crud Blog --migration=create_blog_posts_table

# 5. Clear cache
php artisan optimize:clear

Option C: Interactive Mode

Let the command guide you:

# 1. Create module
php artisan module:make Blog

# 2. Run CRUD generator (will prompt for fields)
php artisan module:make-crud Blog --model=Post
# Answer "Yes" when asked to define fields
# Enter field names and types interactively

# 3. Run migration and clear cache
php artisan migrate && php artisan optimize:clear

Access Your CRUD

Visit your new CRUD pages:

  • Index: /admin/blog/posts
  • Create: /admin/blog/posts/create
  • View: /admin/blog/posts/{id}
  • Edit: /admin/blog/posts/{id}/edit

Command Options

php artisan module:make-crud {module} [options]

Arguments

Argument Description
module The name of the module (e.g., Blog, Sample)

Options

Option Description
--migration= The migration file name to parse columns from (e.g., create_posts_table)
--model= The model name if not parsing from migration (e.g., Post, BlogPost)
--fields= Field definitions to create migration automatically (e.g., "title:string,content:text")

Examples

# Generate CRUD from migration (recommended for existing tables)
php artisan module:make-crud Sample --migration=create_sample_books_table

# Generate CRUD from model name (auto-detects migration)
php artisan module:make-crud Sample --model=Book

# Generate CRUD with fields - creates migration + all CRUD files in ONE command!
php artisan module:make-crud Blog --model=Post --fields="title:string,content:text,is_published:boolean"

# Interactive mode - prompts for field definitions
php artisan module:make-crud Blog --model=Article
# (Will ask if you want to define fields when no migration exists)

# Generate CRUD for a multi-word model
php artisan module:make-crud Blog --model=BlogPost --fields="title:string,excerpt:text,body:text"

Supported Field Types

When using --fields, you can use these types:

Basic Types

Type Database Column Form Input Example
string VARCHAR(255) Text input title:string
text TEXT Textarea content:text
integer INT Number input views:integer
boolean TINYINT(1) Checkbox is_active:boolean
date DATE Date picker published_date:date
datetime DATETIME Datetime picker expires_at:datetime
decimal DECIMAL Number input price:decimal
json JSON Textarea metadata:json

Advanced UI Types

Type Database Column Form Input Example
toggle TINYINT(1) Toggle switch is_featured:toggle
select VARCHAR(255) Dropdown status:select:Active|Inactive|Pending
editor TEXT Rich text editor (TinyMCE) body:editor
media FOREIGN KEY Media library selector featured_image:media

Note: The media type integrates with the Media Library. It creates a foreign key to the media table and uses the <x-media-selector> component for selecting images from the media library.

Select with Options

For select fields, you can define options inline using the pipe (|) separator:

# Format: field_name:select:Option1|Option2|Option3
--fields="status:select:Active|Inactive|Pending,priority:select:Low|Medium|High|Critical"

This generates a dropdown with the specified options.

Media Library Integration

The media type integrates with LaraDashboard's Media Library:

# Format: field_name:media
--fields="featured_image:media,gallery_image:media"

What it generates:

  1. Migration: Creates a foreign key column (featured_image_id) referencing the media table
  2. Model: Adds a belongsTo relationship and URL accessor
  3. Form: Uses <x-media-selector> component with image preview
  4. Datatable: Shows image thumbnail with preview
  5. Show view: Displays the image with click-to-open

Generated Model Code:

use App\Models\Media;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

public function featuredImage(): BelongsTo
{
    return $this->belongsTo(Media::class, 'featured_image_id');
}

public function getFeaturedImageUrlAttribute(): ?string
{
    return $this->featured_image_id && $this->featuredImage
        ? asset('storage/media/' . $this->featuredImage->file_name)
        : null;
}

Ready-to-Use Examples

Copy and paste these commands to quickly scaffold common CRUD modules. Each example creates everything you need in one command.

Blog / Posts

php artisan module:make-crud Blog --model=Post --fields="title:string,slug:string,excerpt:text,content:text,author:string,category:string,is_published:boolean,published_at:datetime" && php artisan migrate && php artisan optimize:clear

Visit: /admin/blog/posts

Products

php artisan module:make-crud Shop --model=Product --fields="name:string,sku:string,description:text,price:decimal,stock:integer,category:string,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/shop/products

Expenses / Finance

php artisan module:make-crud Finance --model=Expense --fields="title:string,amount:decimal,category:string,description:text,expense_date:date,is_paid:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/finance/expenses

Tasks / Todos

php artisan module:make-crud Project --model=Task --fields="title:string,description:text,priority:string,status:string,due_date:date,is_completed:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/project/tasks

Contacts / Leads

php artisan module:make-crud CRM --model=Contact --fields="name:string,email:string,phone:string,company:string,notes:text,source:string,is_customer:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/crm/contacts

Events / Calendar

php artisan module:make-crud Calendar --model=Event --fields="title:string,description:text,location:string,start_date:datetime,end_date:datetime,is_all_day:boolean,is_public:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/calendar/events

FAQs

php artisan module:make-crud Support --model=Faq --fields="question:string,answer:text,category:string,sort_order:integer,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/support/faqs

Testimonials

php artisan module:make-crud Marketing --model=Testimonial --fields="name:string,company:string,content:text,rating:integer,is_featured:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/marketing/testimonials

Team Members

php artisan module:make-crud Company --model=TeamMember --fields="name:string,role:string,bio:text,email:string,phone:string,sort_order:integer,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/company/team-members

Services

php artisan module:make-crud Business --model=Service --fields="name:string,description:text,price:decimal,duration:string,category:string,is_featured:boolean,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/business/services

Portfolio / Projects

php artisan module:make-crud Portfolio --model=Project --fields="title:string,description:text,client:string,category:string,url:string,completed_at:date,is_featured:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/portfolio/projects

Invoices

php artisan module:make-crud Billing --model=Invoice --fields="invoice_number:string,client_name:string,amount:decimal,tax:decimal,status:string,due_date:date,paid_at:datetime,notes:text" && php artisan migrate && php artisan optimize:clear

Visit: /admin/billing/invoices

Subscribers / Newsletter

php artisan module:make-crud Newsletter --model=Subscriber --fields="email:string,name:string,source:string,subscribed_at:datetime,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/newsletter/subscribers

Categories (Generic)

php artisan module:make-crud Catalog --model=Category --fields="name:string,slug:string,description:text,sort_order:integer,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/catalog/categories

Tags

php artisan module:make-crud Content --model=Tag --fields="name:string,slug:string,color:string,is_active:boolean" && php artisan migrate && php artisan optimize:clear

Visit: /admin/content/tags


Advanced Examples (Using New Field Types)

Blog with Rich Editor & Toggle

php artisan module:make-crud Blog --model=Article --fields="title:string,slug:string,excerpt:text,content:editor,category:select:Tech|Lifestyle|Business|News,is_featured:toggle,is_published:toggle,published_at:datetime" && php artisan migrate && php artisan optimize:clear

Visit: /admin/blog/articles

Products with Status Dropdown

php artisan module:make-crud Inventory --model=Product --fields="name:string,sku:string,description:editor,price:decimal,stock:integer,status:select:In Stock|Low Stock|Out of Stock|Discontinued,is_featured:toggle" && php artisan migrate && php artisan optimize:clear

Visit: /admin/inventory/products

Support Tickets with Priority & Status

php artisan module:make-crud Helpdesk --model=Ticket --fields="subject:string,description:editor,priority:select:Low|Medium|High|Critical,status:select:Open|In Progress|Resolved|Closed,is_urgent:toggle" && php artisan migrate && php artisan optimize:clear

Visit: /admin/helpdesk/tickets

Documents with File Upload

php artisan module:make-crud Documents --model=Document --fields="title:string,description:text,category:select:Contract|Invoice|Report|Other,attachment:file,is_public:toggle" && php artisan migrate && php artisan optimize:clear

Visit: /admin/documents/documents

Products with Media Library Image

php artisan module:make-crud Store --model=Product --fields="name:string,description:editor,featured_image:media,price:decimal,stock:integer,status:select:Draft|Published|Archived,is_featured:toggle" && php artisan migrate && php artisan optimize:clear

Visit: /admin/store/products

Comprehensive Example (All Field Types)

This example demonstrates all supported field types in a single command:

php artisan module:make-crud Demo --model=Item --fields="title:string,description:text,content:editor,featured_image:media,price:decimal,quantity:integer,status:select:Draft|Published|Archived,release_date:date,published_at:datetime,is_featured:toggle,metadata:json" && php artisan migrate && php artisan optimize:clear

Visit: /admin/demo/items

What this creates:

  • title - Text input (string)
  • description - Textarea (text)
  • content - Rich text editor with TinyMCE (editor)
  • featured_image - Media library selector with image preview (media)
  • price - Decimal number input (decimal)
  • quantity - Integer number input (integer)
  • status - Dropdown with Draft/Published/Archived options (select)
  • release_date - Date picker (date)
  • published_at - DateTime picker (datetime)
  • is_featured - Toggle switch (toggle)
  • metadata - JSON textarea (json)

Job Listings with Editor

php artisan module:make-crud Careers --model=Job --fields="title:string,location:string,type:select:Full-time|Part-time|Contract|Remote,description:editor,requirements:editor,salary_range:string,is_active:toggle" && php artisan migrate && php artisan optimize:clear

Visit: /admin/careers/jobs


Tip: After generating, customize the Datatable to add filters for category, status, or other enum-like fields. See Adding Filters section below.

Generated Files

The command generates the following files:

Model

modules/{Module}/app/Models/{Model}.php

<?php

declare(strict_types=1);

namespace Modules\Blog\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class Post extends Model
{
    use HasFactory;

    protected $table = 'blog_posts';

    protected $fillable = [
        'title',
        'author',
        'content',
        'category',
        'is_published',
    ];

    protected function casts(): array
    {
        return [
            'is_published' => 'boolean',
        ];
    }
}

Datatable Component

modules/{Module}/app/Livewire/Components/{Model}Datatable.php

Features:

  • Sortable columns
  • Searchable fields
  • Pagination
  • Action buttons (View, Edit, Delete)
  • Livewire-based deletion with confirmation

Livewire Components

  • modules/{Module}/app/Livewire/Admin/{Models}/Index.php - List page
  • modules/{Module}/app/Livewire/Admin/{Models}/Show.php - View page
  • modules/{Module}/app/Livewire/Admin/{Models}/Create.php - Create form
  • modules/{Module}/app/Livewire/Admin/{Models}/Edit.php - Edit form with delete

Blade Views

  • modules/{Module}/resources/views/livewire/admin/{models}/index.blade.php
  • modules/{Module}/resources/views/livewire/admin/{models}/show.blade.php
  • modules/{Module}/resources/views/livewire/admin/{models}/create.blade.php
  • modules/{Module}/resources/views/livewire/admin/{models}/edit.blade.php

Layout

modules/{Module}/resources/views/layouts/crud.blade.php

A clean layout extending the module's master layout, designed for CRUD pages with breadcrumb navigation.

Routes

Routes are automatically added to modules/{Module}/routes/web.php:

Route::get('posts', PostIndex::class)->name('posts.index');
Route::get('posts/create', PostCreate::class)->name('posts.create');
Route::get('posts/{post}', PostShow::class)->name('posts.show');
Route::get('posts/{post}/edit', PostEdit::class)->name('posts.edit');

Menu Item

A submenu item is automatically added to modules/{Module}/app/Services/MenuService.php:

// Posts submenu
$menu->setChildren(array_merge($menu->children, [
    (new AdminMenuItem())->setAttributes([
        'label' => __('Posts'),
        'icon' => 'lucide:list',
        'route' => route('admin.blog.posts.index'),
        'active' => Route::is('admin.blog.posts.*'),
        'id' => 'blog-posts',
        'permissions' => [],
    ]),
]));

Column Type Mapping

The generator automatically maps database column types to appropriate form inputs:

Database Type Form Input PHP Type Blade Component
string, char Text input string <x-inputs.input>
text, mediumText, longText Textarea string <textarea>
integer, bigInteger, tinyInteger Number input int <x-inputs.input type="number">
boolean Checkbox bool <input type="checkbox">
toggle Toggle switch bool <x-inputs.toggle>
select Dropdown string <x-inputs.select>
editor Rich text editor string <x-text-editor>
media Media library selector ?int <x-media-selector>
date Date input date <x-inputs.input type="date">
datetime, timestamp Datetime input datetime <x-inputs.input type="datetime-local">
foreignId Select (requires customization) int <x-inputs.select>

Customization Guide

Adding Permissions

Update the getActionCellPermissions() method in your Datatable:

public function getActionCellPermissions($item): array
{
    return [
        'view' => auth()->user()->can('post.view', $item),
        'edit' => auth()->user()->can('post.edit', $item),
        'delete' => auth()->user()->can('post.delete', $item),
    ];
}

Adding Filters

Add filter dropdowns to your Datatable for enum or category fields:

// Add property and queryString
public string $category = '';

public array $queryString = [
    ...parent::QUERY_STRING_DEFAULTS,
    'category' => ['except' => ''],
];

public function updatingCategory(): void
{
    $this->resetPage();
}

// Add getFilters method
public function getFilters(): array
{
    $categories = Post::query()
        ->whereNotNull('category')
        ->where('category', '!=', '')
        ->distinct()
        ->pluck('category', 'category')
        ->toArray();

    return [
        [
            'id' => 'category',
            'label' => __('Category'),
            'filterLabel' => __('Category'),
            'icon' => 'lucide:folder',
            'allLabel' => __('All Categories'),
            'options' => $categories,
            'selected' => $this->category,
        ],
    ];
}

// Update buildQuery to filter
protected function buildQuery(): QueryBuilder
{
    return QueryBuilder::for(Post::query())
        ->when($this->search, function ($query) {
            $query->where(function ($q) {
                $q->where('title', 'like', "%{$this->search}%")
                    ->orWhere('content', 'like', "%{$this->search}%");
            });
        })
        ->when($this->category, function ($query) {
            $query->where('category', $this->category);
        })
        ->orderBy($this->sort, $this->direction);
}

Customizing Form Fields

Edit the generated Blade views to customize form inputs:

{{-- Select dropdown for category --}}
<div>
    <label for="category" class="form-label">{{ __('Category') }}</label>
    <select wire:model="category" id="category" class="form-control">
        <option value="">{{ __('Select Category') }}</option>
        <option value="tech">{{ __('Technology') }}</option>
        <option value="lifestyle">{{ __('Lifestyle') }}</option>
        <option value="business">{{ __('Business') }}</option>
    </select>
    @error('category')
        <p class="mt-1 text-sm text-red-600 dark:text-red-400">{{ $message }}</p>
    @enderror
</div>

{{-- Toggle for is_published --}}
<label class="flex items-center gap-3 cursor-pointer">
    <input type="checkbox" wire:model="is_published" class="form-checkbox">
    <div>
        <span class="text-sm font-medium text-gray-700 dark:text-gray-300">
            {{ __('Published') }}
        </span>
        <p class="text-xs text-gray-500 dark:text-gray-400">
            {{ __('Make this post visible to the public') }}
        </p>
    </div>
</label>

Adding Relationships

For foreign key relationships, customize the form to include a select dropdown:

// In your Create/Edit component
public function mount(): void
{
    $this->categories = Category::all();
    // ... rest of mount
}

public function render(): View
{
    return view('blog::livewire.admin.posts.create', [
        'categories' => Category::all(),
    ]);
}
{{-- In your Blade view --}}
<x-inputs.select
    wire:model="category_id"
    name="category_id"
    label="{{ __('Category') }}"
    :options="$categories->pluck('name', 'id')"
    :required="true"
/>

Custom Column Rendering

Add custom column rendering in your Datatable:

public function renderStatusColumn($item): string
{
    $class = $item->is_published
        ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200'
        : 'bg-gray-100 text-gray-800 dark:bg-gray-700 dark:text-gray-300';

    $label = $item->is_published ? __('Published') : __('Draft');

    return "<span class='px-2 py-1 text-xs font-medium rounded-full {$class}'>{$label}</span>";
}

Best Practices

1. Always Create Migration First

The generator works best when it can parse your migration file to understand your table structure. This ensures:

  • Correct fillable fields in the model
  • Appropriate form inputs for each field type
  • Proper validation rules
  • Accurate datatable headers

2. Follow Naming Conventions

Use Laravel naming conventions for best results:

  • Table: module_models (e.g., blog_posts, sample_books)
  • Model: Singular PascalCase (e.g., Post, Book)
  • Migration: create_{table}_table

3. Run Tests After Generation

Always test your generated CRUD:

php artisan test --filter=Post

4. Customize Permissions

The generated code uses true for all permissions by default. Update these to use proper permission checks for production:

'view' => auth()->user()->can('post.view', $item),

5. Add Model Casts

Ensure your model has proper casts for boolean and date fields:

protected function casts(): array
{
    return [
        'is_published' => 'boolean',
        'published_at' => 'datetime',
    ];
}

Troubleshooting

Routes Not Found

Clear the route cache after generating CRUD:

php artisan optimize:clear

Menu Not Showing

Ensure your MenuService.php has been updated. The generator looks for return $menu; to insert the submenu. If your MenuService has a different structure, add the menu item manually.

Boolean Field Type Error

If you get "Cannot assign int to property" errors for boolean fields, ensure:

  1. Your model has proper boolean casts
  2. The Edit component casts the value: $this->is_published = (bool) $this->post->is_published;

Migration Not Detected

If the generator can't find your migration, specify the full migration name:

php artisan module:make-crud Blog --migration=2024_01_15_create_blog_posts_table

Example: Complete Blog Module

Here's a complete example of creating a Blog module with Posts:

# 1. Create module
php artisan module:make Blog

# 2. Create migration
php artisan module:make-migration create_blog_posts_table Blog
# Edit the migration file with your columns

# 3. Run migration
php artisan migrate

# 4. Generate CRUD
php artisan module:make-crud Blog --migration=create_blog_posts_table

# 5. Clear cache
php artisan optimize:clear

# 6. Visit /admin/blog/posts

See Also

/