Toolmingo
Guides12 min read

Laravel Cheat Sheet: The Complete Quick Reference

A complete Laravel cheat sheet — Artisan commands, routing, Eloquent ORM, migrations, controllers, Blade templates, queues, and testing with copy-ready PHP code.

The Laravel patterns you need every day — Artisan commands, routing, Eloquent ORM, migrations, Blade templating, queues, and testing — with copy-ready code in every section.

Quick reference

Task Command / Code
Create project composer create-project laravel/laravel app
Start dev server php artisan serve
List all routes php artisan route:list
Create controller php artisan make:controller PostController
Resource controller php artisan make:controller PostController --resource
Create model php artisan make:model Post
Model + migration php artisan make:model Post -m
Create migration php artisan make:migration create_posts_table
Run migrations php artisan migrate
Rollback php artisan migrate:rollback
Fresh migrate + seed php artisan migrate:fresh --seed
Create seeder php artisan make:seeder PostSeeder
Create factory php artisan make:factory PostFactory
Create middleware php artisan make:middleware AuthMiddleware
Create request php artisan make:request StorePostRequest
Create job php artisan make:job ProcessPodcast
Create event php artisan make:event OrderShipped
Clear cache php artisan cache:clear
Clear config cache php artisan config:clear
Clear route cache php artisan route:clear
Tinker REPL php artisan tinker
Run tests php artisan test
Run queue worker php artisan queue:work

Routing

// routes/web.php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\PostController;

// Basic routes
Route::get('/posts', [PostController::class, 'index']);
Route::post('/posts', [PostController::class, 'store']);
Route::get('/posts/{post}', [PostController::class, 'show']);
Route::put('/posts/{post}', [PostController::class, 'update']);
Route::delete('/posts/{post}', [PostController::class, 'destroy']);

// Resource routes (all 7 CRUD routes in one line)
Route::resource('posts', PostController::class);

// API resource routes (excludes create/edit — no HTML forms)
Route::apiResource('posts', PostController::class);

// Route parameters
Route::get('/users/{id}/posts/{post}', function ($id, $post) {
    return "User {$id}, Post {$post}";
});

// Optional parameter
Route::get('/search/{query?}', function ($query = null) {
    return $query ?? 'all';
});

// Named routes
Route::get('/profile', [ProfileController::class, 'show'])->name('profile.show');
// Generate URL: route('profile.show')

// Route groups
Route::middleware(['auth'])->prefix('admin')->name('admin.')->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard');
    Route::resource('users', UserController::class);
});

// Redirect route
Route::redirect('/here', '/there');
// routes/api.php — API routes (stateless, no CSRF, /api/ prefix)
Route::middleware('auth:sanctum')->group(function () {
    Route::apiResource('posts', PostController::class);
});

Controllers

// app/Http/Controllers/PostController.php
namespace App\Http\Controllers;

use App\Models\Post;
use App\Http\Requests\StorePostRequest;
use Illuminate\Http\Request;

class PostController extends Controller
{
    public function index()
    {
        $posts = Post::with('user')->latest()->paginate(15);
        return view('posts.index', compact('posts'));
    }

    public function show(Post $post)  // Route model binding — auto-fetches by ID
    {
        return view('posts.show', compact('post'));
    }

    public function store(StorePostRequest $request)
    {
        $post = Post::create($request->validated());
        return redirect()->route('posts.show', $post)
            ->with('success', 'Post created!');
    }

    public function update(StorePostRequest $request, Post $post)
    {
        $post->update($request->validated());
        return redirect()->route('posts.show', $post);
    }

    public function destroy(Post $post)
    {
        $post->delete();
        return redirect()->route('posts.index');
    }
}

Form requests (validation)

// app/Http/Requests/StorePostRequest.php
namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class StorePostRequest extends FormRequest
{
    public function authorize(): bool
    {
        return $this->user()->can('create', Post::class);
    }

    public function rules(): array
    {
        return [
            'title'      => 'required|string|max:255',
            'body'       => 'required|string|min:10',
            'status'     => 'required|in:draft,published',
            'tags'       => 'nullable|array',
            'tags.*'     => 'string|max:50',
            'image'      => 'nullable|image|max:2048', // max KB
            'user_id'    => 'required|exists:users,id',
            'email'      => 'required|email|unique:users,email,' . $this->user()?->id,
        ];
    }

    public function messages(): array
    {
        return [
            'title.required' => 'A title is required.',
        ];
    }
}

Common validation rules:

Rule Description
required Must be present and non-empty
nullable May be null
string Must be a string
integer / numeric Must be a number
email Valid email format
min:n / max:n Length or value constraints
unique:table,col Must not exist in DB column
exists:table,col Must exist in DB column
in:a,b,c Must be one of the listed values
confirmed Field must have _confirmation match
image JPEG, PNG, GIF, BMP, SVG, or WEBP
array Must be an array
date / date_format:Y-m-d Valid date
boolean true, false, 1, 0, "true", "false"
url Valid URL format
regex:/pattern/ Must match regex
sometimes Only validate if field is present

Eloquent ORM

Model definition

// app/Models/Post.php
namespace App\Models;

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

class Post extends Model
{
    use HasFactory, SoftDeletes;

    protected $fillable = ['title', 'body', 'status', 'user_id'];
    // OR use $guarded = [] to allow all (less safe)

    protected $casts = [
        'published_at' => 'datetime',
        'settings'     => 'array',      // JSON column → PHP array
        'is_featured'  => 'boolean',
        'price'        => 'decimal:2',
    ];

    protected $hidden = ['deleted_at'];  // Excluded from JSON

    // Computed attribute (accessor)
    public function getExcerptAttribute(): string
    {
        return str($this->body)->limit(150);
    }

    // Modern attribute (Laravel 9+)
    use Illuminate\Database\Eloquent\Casts\Attribute;

    protected function excerpt(): Attribute
    {
        return Attribute::make(
            get: fn ($value, $attributes) => str($attributes['body'])->limit(150),
        );
    }

    // Relationships
    public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(User::class);
    }

    public function comments(): \Illuminate\Database\Eloquent\Relations\HasMany
    {
        return $this->hasMany(Comment::class);
    }

    public function tags(): \Illuminate\Database\Eloquent\Relations\BelongsToMany
    {
        return $this->belongsToMany(Tag::class)->withTimestamps();
    }

    // Query scope
    public function scopePublished($query)
    {
        return $query->where('status', 'published');
    }
}

Eloquent CRUD

// CREATE
$post = Post::create(['title' => 'Hello', 'body' => 'World', 'user_id' => 1]);
$post = new Post(['title' => 'Hello']);
$post->body = 'World';
$post->save();

// READ
$post  = Post::find(1);                         // null if not found
$post  = Post::findOrFail(1);                   // 404 if not found
$posts = Post::all();
$posts = Post::where('status', 'published')->get();
$post  = Post::where('slug', $slug)->first();   // null if not found
$post  = Post::firstOrCreate(['slug' => $slug], ['title' => $title]); // find or create
$post  = Post::firstOrNew(['slug' => $slug]);   // find or build (not saved)
$count = Post::where('status', 'published')->count();
$exists = Post::where('slug', $slug)->exists();

// UPDATE
$post = Post::findOrFail($id);
$post->update(['title' => 'New title']);        // mass update (needs $fillable)
$post->title = 'New title';
$post->save();
Post::where('status', 'draft')->update(['status' => 'published']); // bulk update

// DELETE
$post->delete();                                // soft delete if trait enabled
Post::destroy([1, 2, 3]);                       // delete by IDs
Post::where('created_at', '<', now()->subYear())->delete(); // bulk delete

// RESTORE (soft delete)
Post::withTrashed()->find($id)->restore();

Querying

// Ordering, limiting, pagination
Post::orderBy('created_at', 'desc')->take(10)->get();
Post::latest()->paginate(15);          // paginate() returns LengthAwarePaginator
Post::latest()->simplePaginate(15);    // simpler prev/next pagination
Post::latest()->cursorPaginate(15);    // efficient cursor-based pagination

// Filtering
Post::where('status', 'published')
    ->where('user_id', auth()->id())
    ->whereNotNull('published_at')
    ->whereBetween('created_at', [now()->subMonth(), now()])
    ->whereIn('status', ['published', 'draft'])
    ->get();

// OR conditions
Post::where('title', 'like', "%{$search}%")
    ->orWhere('body', 'like', "%{$search}%")
    ->get();

// whereHas — filter by relationship
Post::whereHas('comments', function ($q) {
    $q->where('approved', true);
})->get();

// Eager loading (prevents N+1)
Post::with('user', 'tags')->get();
Post::with(['comments' => function ($q) {
    $q->latest()->take(5);
}])->get();

// Lazy eager loading
$posts = Post::all();
$posts->load('user');

// Select specific columns
Post::select('id', 'title', 'user_id')->get();

// Raw expressions
Post::selectRaw('count(*) as total, status')->groupBy('status')->get();
Post::whereRaw('DATE(created_at) = ?', [today()->toDateString()])->get();

// Chunking large datasets
Post::chunk(100, function ($posts) {
    foreach ($posts as $post) {
        // process
    }
});

// Using query scope
Post::published()->latest()->paginate(15);

Relationships

// One-to-Many
class User extends Model {
    public function posts(): HasMany { return $this->hasMany(Post::class); }
}
class Post extends Model {
    public function user(): BelongsTo { return $this->belongsTo(User::class); }
}
// Usage
$user->posts()->create(['title' => 'New post']);
$post->user;   // access related model

// Many-to-Many (with pivot table: post_tag)
class Post extends Model {
    public function tags(): BelongsToMany { return $this->belongsToMany(Tag::class); }
}
$post->tags()->attach($tagId);
$post->tags()->detach($tagId);
$post->tags()->sync([1, 2, 3]);     // sync (removes unlisted)
$post->tags()->toggle([1, 2]);      // add if missing, remove if present
$post->tags()->attach($tagId, ['order' => 1]);  // pivot data

// Has One Through / Has Many Through
class Country extends Model {
    // Country → has many Users → has many Posts
    public function posts(): HasManyThrough {
        return $this->hasManyThrough(Post::class, User::class);
    }
}

// Polymorphic
class Comment extends Model {
    public function commentable(): MorphTo { return $this->morphTo(); }
}
class Post extends Model {
    public function comments(): MorphMany { return $this->morphMany(Comment::class, 'commentable'); }
}

Migrations

// database/migrations/2024_01_01_000000_create_posts_table.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();                              // BIGINT UNSIGNED AUTO_INCREMENT PK
            $table->foreignId('user_id')->constrained()->cascadeOnDelete();
            $table->string('title');                   // VARCHAR(255)
            $table->string('slug')->unique();
            $table->text('body');
            $table->longText('content')->nullable();
            $table->enum('status', ['draft', 'published'])->default('draft');
            $table->json('meta')->nullable();
            $table->unsignedInteger('views')->default(0);
            $table->decimal('price', 10, 2)->nullable();
            $table->boolean('is_featured')->default(false);
            $table->timestamp('published_at')->nullable();
            $table->timestamps();                      // created_at + updated_at
            $table->softDeletes();                     // deleted_at (for SoftDeletes)

            $table->index('status');
            $table->index(['user_id', 'created_at']);
            $table->fullText('body');                  // MySQL FULLTEXT index
        });
    }

    public function down(): void
    {
        Schema::dropIfExists('posts');
    }
};
// Modify existing table
Schema::table('posts', function (Blueprint $table) {
    $table->string('subtitle')->nullable()->after('title');
    $table->dropColumn('old_column');
    $table->renameColumn('old', 'new');
    $table->index('subtitle');
});

Column types quick reference:

Method SQL type
id() BIGINT UNSIGNED PK AUTO_INCREMENT
string('col', 100) VARCHAR(100)
text() / longText() TEXT / LONGTEXT
integer() / bigInteger() INT / BIGINT
unsignedBigInteger() BIGINT UNSIGNED
decimal(8, 2) DECIMAL(8,2)
boolean() TINYINT(1)
date() / dateTime() DATE / DATETIME
timestamp() TIMESTAMP
timestamps() created_at + updated_at TIMESTAMP
json() JSON
uuid() CHAR(36)
foreignId('user_id') BIGINT UNSIGNED + FK helper

Blade templates

{{-- resources/views/posts/index.blade.php --}}
@extends('layouts.app')

@section('title', 'Posts')

@section('content')
    <h1>All Posts</h1>

    {{-- Variables (auto-escaped XSS) --}}
    <p>{{ $post->title }}</p>

    {{-- Unescaped HTML (only for trusted content) --}}
    {!! $post->body !!}

    {{-- Control structures --}}
    @if ($posts->isEmpty())
        <p>No posts found.</p>
    @elseif ($posts->count() === 1)
        <p>One post.</p>
    @else
        <p>{{ $posts->count() }} posts.</p>
    @endif

    @unless ($user->isAdmin())
        <p>Access restricted.</p>
    @endunless

    {{-- Loops --}}
    @foreach ($posts as $post)
        <div>
            <a href="{{ route('posts.show', $post) }}">{{ $post->title }}</a>
            @if ($loop->first) <span>New!</span> @endif
            @if ($loop->last) <hr> @endif
            <small>{{ $loop->index }} of {{ $loop->count }}</small>
        </div>
    @endforeach

    @forelse ($posts as $post)
        <p>{{ $post->title }}</p>
    @empty
        <p>No posts.</p>
    @endforelse

    {{-- Pagination --}}
    {{ $posts->links() }}

    {{-- Auth checks --}}
    @auth
        <a href="{{ route('posts.create') }}">New post</a>
    @endauth

    @can('update', $post)
        <a href="{{ route('posts.edit', $post) }}">Edit</a>
    @endcan
@endsection

@push('scripts')
    <script>console.log('page loaded');</script>
@endpush
{{-- resources/views/layouts/app.blade.php --}}
<!DOCTYPE html>
<html>
<head>
    <title>@yield('title', 'My App')</title>
    @stack('styles')
</head>
<body>
    @include('partials.nav')
    <main>@yield('content')</main>
    @stack('scripts')
</body>
</html>
{{-- Blade components (Laravel 7+) --}}
{{-- resources/views/components/alert.blade.php --}}
<div class="alert alert-{{ $type ?? 'info' }}">
    {{ $slot }}
    @if (isset($title)) <h4>{{ $title }}</h4> @endif
</div>

{{-- Usage --}}
<x-alert type="danger">
    <x-slot name="title">Error</x-slot>
    Something went wrong.
</x-alert>

Authentication (Laravel Breeze / Sanctum)

# Install Breeze (simple auth scaffolding)
composer require laravel/breeze --dev
php artisan breeze:install blade
php artisan migrate

# Install Sanctum (API tokens / SPA auth)
composer require laravel/sanctum
php artisan vendor:publish --provider="Laravel\Sanctum\SanctumServiceProvider"
php artisan migrate
// API token authentication with Sanctum
// routes/api.php
Route::post('/login', function (Request $request) {
    $request->validate(['email' => 'required|email', 'password' => 'required']);

    if (!Auth::attempt($request->only('email', 'password'))) {
        return response()->json(['message' => 'Invalid credentials'], 401);
    }

    $token = $request->user()->createToken('api-token')->plainTextToken;
    return response()->json(['token' => $token]);
});

Route::middleware('auth:sanctum')->group(function () {
    Route::get('/user', fn (Request $request) => $request->user());
    Route::post('/logout', function (Request $request) {
        $request->user()->currentAccessToken()->delete();
        return response()->json(['message' => 'Logged out']);
    });
});
// Auth helpers
auth()->user()           // current user (null if guest)
auth()->id()             // current user ID
auth()->check()          // is logged in?
auth()->guest()          // is guest?
Auth::login($user)       // log in a user
Auth::logout()           // log out

Service providers and dependency injection

// app/Providers/AppServiceProvider.php
use App\Repositories\PostRepositoryInterface;
use App\Repositories\EloquentPostRepository;

public function register(): void
{
    // Bind interface to concrete implementation
    $this->app->bind(PostRepositoryInterface::class, EloquentPostRepository::class);

    // Singleton — same instance every time
    $this->app->singleton(MyService::class, function ($app) {
        return new MyService($app->make(Config::class));
    });
}

public function boot(): void
{
    // Boot logic — runs after all providers registered
    Post::observe(PostObserver::class);
    Gate::define('update-post', fn ($user, $post) => $user->id === $post->user_id);
}
// Controller constructor injection
class PostController extends Controller
{
    public function __construct(private PostRepositoryInterface $posts) {}

    public function index()
    {
        return view('posts.index', ['posts' => $this->posts->all()]);
    }
}

Queues and jobs

// app/Jobs/SendWelcomeEmail.php
namespace App\Jobs;

use App\Models\User;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;

class SendWelcomeEmail implements ShouldQueue
{
    use InteractsWithQueue, Queueable, SerializesModels;

    public int $tries = 3;
    public int $timeout = 60;

    public function __construct(public User $user) {}

    public function handle(): void
    {
        $this->user->notify(new WelcomeNotification());
    }

    public function failed(\Throwable $e): void
    {
        // Log or alert on permanent failure
        logger()->error('SendWelcomeEmail failed', ['user' => $this->user->id]);
    }
}

// Dispatch
SendWelcomeEmail::dispatch($user);
SendWelcomeEmail::dispatch($user)->delay(now()->addMinutes(10));
SendWelcomeEmail::dispatchIf($user->wants_email, $user);

// Run worker
// php artisan queue:work --queue=high,default --tries=3

Testing

// tests/Feature/PostTest.php
namespace Tests\Feature;

use App\Models\Post;
use App\Models\User;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Tests\TestCase;

class PostTest extends TestCase
{
    use RefreshDatabase;  // Runs migrations + rolls back after each test

    public function test_guest_cannot_create_post(): void
    {
        $response = $this->postJson('/api/posts', ['title' => 'Test']);
        $response->assertStatus(401);
    }

    public function test_user_can_create_post(): void
    {
        $user = User::factory()->create();

        $response = $this->actingAs($user, 'sanctum')
            ->postJson('/api/posts', [
                'title' => 'My Post',
                'body'  => 'Some content here...',
            ]);

        $response->assertStatus(201)
            ->assertJsonPath('data.title', 'My Post');

        $this->assertDatabaseHas('posts', ['title' => 'My Post', 'user_id' => $user->id]);
    }

    public function test_post_index_returns_paginated_list(): void
    {
        Post::factory(20)->create();

        $response = $this->getJson('/api/posts');

        $response->assertOk()
            ->assertJsonStructure(['data', 'meta' => ['current_page', 'total']]);
    }
}
// database/factories/PostFactory.php
namespace Database\Factories;

use Illuminate\Database\Eloquent\Factories\Factory;

class PostFactory extends Factory
{
    public function definition(): array
    {
        return [
            'title'     => fake()->sentence(),
            'body'      => fake()->paragraphs(3, true),
            'status'    => fake()->randomElement(['draft', 'published']),
            'user_id'   => \App\Models\User::factory(),
        ];
    }

    // State modifier
    public function published(): static
    {
        return $this->state(['status' => 'published', 'published_at' => now()]);
    }
}

// Usage in tests
Post::factory(10)->published()->create();
Post::factory()->for($user)->create();

Common mistakes

Mistake Fix
Not setting $fillable on model Add $fillable or $guarded = [] to allow create()/update()
N+1 query problem Use ->with('relation') eager loading
Missing ->validated() in controller Use $request->validated() not $request->all()
Route::resource includes web-only routes in API Use Route::apiResource for APIs
Storing business logic in controllers Move to service classes or action classes
Not using findOrFail() for user-facing routes Use findOrFail() to get automatic 404
Querying in Blade templates Pass data from controller, never query in views
env() in config files only (not elsewhere) Use config('key') at runtime; env() only in config files

Laravel vs other frameworks

Feature Laravel (PHP) Django (Python) Express (Node.js)
ORM Eloquent (Active Record) Django ORM Sequelize/Prisma
Auth Sanctum/Passport/Breeze built-in Passport.js
Template engine Blade Django templates EJS/Handlebars
Migrations Artisan + Schema manage.py migrate Knex/Prisma
Queues built-in (Horizon) Celery Bull/BullMQ
CLI php artisan manage.py No built-in
Testing PHPUnit/Pest pytest/unittest Jest/Mocha
Ecosystem Composer / Packagist pip / PyPI npm

FAQ

What version of PHP does Laravel require? Laravel 11 requires PHP 8.2+. Laravel 10 requires PHP 8.1+. Always check the official docs for your version. Use php -v to check your current version.

What is the difference between $fillable and $guarded? $fillable is an allowlist — only listed fields can be mass-assigned with create() or update(). $guarded is a blocklist — all fields are mass-assignable except the listed ones. Using $guarded = [] allows everything (use only in controlled contexts like seeders). Never skip both — that opens a mass assignment vulnerability.

How do I fix the N+1 problem in Laravel? An N+1 happens when you loop over models and access a relationship that wasn't eager-loaded, firing one extra query per item. Fix it with ->with('relation'): Post::with('user')->get() loads all users in one query instead of one per post. Enable Model::preventLazyLoading(true) in AppServiceProvider::boot() during development to detect N+1 automatically.

How do route model binding work? When a controller parameter name matches a route segment and is type-hinted to a model, Laravel automatically fetches the record from the database. Route::get('/posts/{post}', [PostController::class, 'show']) + public function show(Post $post) auto-fetches Post::findOrFail($id). You can customize the lookup column with Route::get('/posts/{post:slug}', ...).

What is the difference between php artisan migrate and migrate:fresh? migrate runs only the pending (new) migrations. migrate:fresh drops all tables and reruns all migrations from scratch — never use it in production as it destroys data. migrate:rollback reverses the last batch of migrations. migrate:refresh rollbacks all and re-runs all (slower than fresh, less destructive than fresh for seeding).

How do I handle file uploads in Laravel? Use $request->file('photo')->store('photos', 'public') to save an upload to storage/app/public/photos/. Run php artisan storage:link to create the public symlink. Validate uploads with 'photo' => 'required|image|max:2048'. Access stored files via asset(Storage::url($path)).

Related tools

Keep reading

All Toolmingotools are free & run in your browser

No sign-up, no upload, no watermark. Your files never leave your device.

Browse all tools