Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

Browse all series

Laravel From Scratch (2026 Edition)

Welcome to the latest iteration of the longest running Laravel introductory video series in the world. Like clockwork, we refresh this series every two years. This latest 2026 iteration covers Laravel 12 and beyond.

You'll learn not only the basic fundamentals of Laravel, but also how to leverage more advanced features like authentication, authorization, queues, testing, and more. We'll even review the full deployment process from scratch.

Series trailer thumbnail

Progress

Series Info

Episodes
43
Run Time
9h 40m
Difficulty
Beginner
Last Updated
Jan 14, 2026
Version
Latest

Series Episodes

  1. The Fundamentals (13)
    1. Welcome Aboard

      My name is Jeffrey Way, and welcome...to Laravel From Scratch: 2026 edition!
    2. Set Up Your Development Environment

      Before we start writing any Laravel code, we need to get our development environment in order so we will use Laravel Herd to install PHP, Composer and Node and to scaffold a new project with the Laravel installer. Once the project is created we will open routes/web.php and the welcome view and tweak the URI and the view to get our feet wet and see how requests map to views.
    3. Routing 101

      Let's explore how Laravel's expressive API lets us register routes and return views with things like Route::get and the view function. We already saw the default welcome homepage so now we will add an /about route and create a simple /contact view to practice linking between pages.
    4. Layout Files

      We're going to clean up duplicated page markup by moving shared HTML into a reusable Blade layout component and wiring simple routes with Route::view. Along the way we'll learn how to slot page specific content with $slot, pass a title prop to the layout and merge any extra classes with $attributes->merge() so our components stay flexible.
    5. Pass Data to Views

      Now that we've learned how to register a route and load a view, let's tackle passing data into that view using the third parameter of Route::view or by returning view('welcome', [...]) from a closure. We'll also read values from the query string with request('person', 'world') and see how Blade automatically escapes output unless we choose raw output with {!! $var !!}.
    6. Blade Directives

      Okay, let's spend a few minutes exploring Blade directives and how they make our view code much nicer to write. We'll look at debugging with @dump and @dd and common control flow directives like @if, @foreach and @forelse.
    7. Forms

      Let's build a tiny digital notebook and get comfortable with form handling in Laravel by accepting user input and saving it for later. We'll send a POST to an ideas route, include the @csrf token, grab the value with the request helper, push it into the session, and redirect back to the homepage.
    8. Databases, Migrations, and Eloquent

      Okay, now that we've been keeping ideas in the session, we're going to switch over to a real database and learn how migrations and models let us version and query that data. We'll run php artisan make:migration to add an ideas table with a description and state, run php artisan migrate, create an Idea model, and use Idea::create() plus queries like where('state', 'pending')->get() to store and filter our ideas.
    9. HTTP Requests and REST

      Now that we've met Eloquent let's build out a simple CRUD flow for the ideas resource so we can list show edit update and delete individual items. Along the way we'll lean on Laravel conventions like route model binding and method spoofing so our routes and forms behave correctly and we can focus on the important logic not boilerplate.
    10. Controllers

      We are going to pull the create form out of the index view and give it its own ideas/create page so creating ideas is clearer. Then we will create an IdeaController with the seven resourceful actions and move our route closures into controller methods so our routes file becomes a single place that lists every route.
    11. Request Validation

      Okay, now that submitting an empty description can make us reach the database and fail, let's add validation so we catch those problems earlier and more gracefully. We'll validate in the store action with request->validate, surface messages from the errors bag using the @error blade directive, and extract a reusable x-error component for tidy markup.
    12. Form Request Classes

      Okay, now that we have inline validation working, let's move the rules into a dedicated form request class so Laravel will run validation automatically when we type it into a controller. We'll scaffold a StoreIdeaRequest, use the authorize and rules methods to control access and validation, tweak messages if we want, and decide whether to keep separate classes for store and update or share a single IdeaRequest when the rules are identical.
    13. A Brief DaisyUI Detour

      Now that we have the basics wired up, let's spend ten minutes polishing the UI by pulling in tailwind and daisyui so we can rapidly assemble navigation areas, cards, and form controls. We'll extract the nav into its own component and turn each idea into a reusable idea-card so the layout looks real and we can reuse styles across the app.
  2. Authentication and Authorization (5)
    1. Authentication Explained

      Now that we have our UI scaffolding in place, let's implement authentication by wiring up the register and login routes and creating the RegisteredUserController and SessionsController to show forms and handle submissions. We will validate input with $request->validate(), hash passwords using Hash, use the @auth and @guest directives in the nav, and add a logout endpoint so we can sign users in and out.
    2. Require Authentication With Middleware

      Now we're going to associate each Idea with a user by adding a user_id foreign key using foreignIdFor and cascading deletes, and we'll tidy up access by applying the auth and guest middleware to the appropriate routes. Then we'll make new ideas use the current user via Auth::id() and filter the index so you only see your own ideas and next time we'll swap the long form queries for a proper Eloquent relationship so we can just call $user->ideas.
    3. Eloquent Relationships

      Let's wire up the Eloquent relationships so our Idea knows its creator with belongsTo and our User exposes their ideas with hasMany. Then we'll lean on those relationships to read and create records with auth()->user()->ideas()->create() and get ready to fix a security hole where users can currently view other people's ideas.
    4. Authorization Using Gates

      We're going to lock down parts of the app with Laravel authorization by defining gates in the AppServiceProvider and using the @can blade directive to only show links to people who should see them. Then we'll protect the endpoints themselves with the can route method or Gate::authorize and talk through simple choices like allowing optional guest checks and returning a 404 instead of a 403 when you want to hide an admin route.
    5. Authorization Using Policies

      Now that we've played with the Gate API, let's move on to policies and create an IdeaPolicy so we can centralize authorization for the Idea model. We'll implement an update rule and see how to call it from controllers with $this->authorize('update', $idea) or middleware and how to reference Idea::class when no model instance exists.
  3. Digging Deeper (4)
    1. Frontend Asset Bundling with Vite

      Now that we have been pulling Tailwind and Daisy UI from a CDN, let's switch to local bundling with vite so our assets are compiled and optimized with the rest of the app. We will wire up the @vite directive, edit resources/css/app.css and then use npm run dev for hot reloads or npm run build for production.
    2. Notifications

      Okay now that we can create ideas let's make sure the owner gets notified whenever a new idea is persisted using Laravel notifications. We'll scaffold an IdeaPublished notification with php artisan make:notification, preview the email with Mailpit, and dispatch it from the IdeaController@store action.
    3. When to Queue it Up

      Now that we've covered sending notifications and emails, let's touch on moving that work into the background with queued jobs by implementing ShouldQueue so users are not left waiting. We'll learn how to push tasks onto the queue, run a worker with php artisan queue:work and peek at the jobs table to see everything being processed.
    4. How to Get Started Testing Your Code

      Now that the bulk of the app is built we'll finish up by learning Pest PHP and browser testing so we can stop testing things manually and start automating real user flows. We'll wire up the Playwright plugin, run php artisan test or pest, use visit and debug to inspect failures and then write practical tests for registration and the /ideas pages including using a data-test attribute to target elements.
  4. Final Project: Build and Deploy an App (21)
    1. Final Project Setup

      We're going to harness everything we've learned and together build a simple but feature rich app called Idea so we can deploy it and keep learning from real world examples. Along the way we'll follow my exact workflow for initializing the repo, pushing to GitHub and deploying with Forge, then set up tooling like Pint, Rector, Code Rabbit and Laravel Boost so you can see how a modern Laravel project gets built and maintained.
    2. Design Your Model Layer

      Before we jump into the UI, let's get our domain and database shape sorted by scaffolding an Idea model with a migration and factory plus a companion Step model and an IdeaStatus enum. We'll wire up the relationships set sensible default attributes and add a few quick tests so the UI has a solid foundation to build on.
    3. Tailwind Theme Setup And Initial UI

      We're going to nail down our Tailwind theme and extract a few reusable UI pieces so building pages gets a lot less tedious. We'll create a layouts file with a nav, pull out a reusable form and field component and then wire up routes and controllers for register and login so users can sign up and sign in.
    4. Browser Testing Registration Forms With Pest

      Now that registration and login routes are in place we're going to write browser tests that exercise the register, login and logout flows using Pest PHP's browser helpers. We'll arrange users with a user factory, target elements with data-test attributes and use debug mode to verify both the happy path and the unhappy paths like a missing email behave as expected.
    5. Flash Messaging and Interactivity with AlpineJS

      Now we are going to make our flash messages feel alive by adding lightweight interactivity with Alpine.js. We'll wire a tiny x-data component that uses x-show, a timeout and a transition so the session success message automatically fades away after a few seconds.
    6. Idea Cards

      All right, we are going to build the page that displays our idea cards and wire up a route and controller to return a view for /ideas. We will extract a reusable Blade component like x-card and a status label component like x-idea-status so the list looks polished, and next time we will add filtering and pagination.
    7. Idea Filtering

      Now we're going to add filtering so we can show ideas by status from the query string and wire that up with an Eloquent scope. We'll also build the little filter pills that display counts per status and move the counting logic into the Idea model so the view stays clean.
    8. Show A Single Idea

      Now that we can click into an idea, let's build the single idea page by creating the show.blade.php view and wiring it to the IdeaController@show action. We will lay out the title and description, add a top navigation with component icons, render links inside a card, and wire up a delete form that posts to the idea.destroy route so the page is functional and ready for the next steps.
    9. Create A Functional Modal With AlpineJS

      Okay, now that we have the idea index with a big clickable card, let's build a modal for creating and editing ideas that opens when the card dispatches an open-modal event with the modal name. We'll wire it up with x-data and x-show, add x-transition plus click away and escape handlers, tighten up ARIA attributes and then extract it into a reusable <x-modal> component.
    10. Construct The Idea Form

      Okay now that we have basic modal support we're going to build the actual form for creating ideas and wire it up to the POST /ideas route and the store action. We'll make our field component textarea aware add an Alpine powered status selector that writes to a hidden input enforce the minimal client side constraints and persist the idea so new entries show up at the top of the list.
    11. Test The Create Idea Form

      Okay, we already have a nice modal for creating ideas but it only works when we test it by hand so in this lesson we will automate that flow with a browser test. We will add data-test attributes to elements like the create idea button and the button status options then write a test that visits the page, clicks and fills the form while actingAs a user and then asserts the idea was persisted.
    12. Allow For One or Many Links

      Alright, let's add support for attaching links to an idea by giving users an input with a plus button that appends whatever they typed into a links array. We'll track the typed value with x-model on new link, render each link as a named input like links[] so the traditional form submits them as an array and then validate them on the server.
    13. Actionable Steps

      We are adding actionable steps to ideas by reusing the modal markup, wiring Alpine inputs to a steps array, and validating the payload so we can persist those items into a dedicated steps table related to each idea. Then we will render the steps on the idea show page and let us toggle the completed state with small per step forms that hit a PATCH route handled by StepController@update.
    14. Upload Featured Images To Storage

      Let's add featured images above each idea by dropping a file input into the form and setting the form enctype to multipart/form-data so uploads are sent to the server. We'll persist the files to storage, expose them with php artisan storage:link and render them in the show and index views then start pulling idea creation logic out of the controller into a dedicated Action class.
    15. Action Classes

      We're going to pull the idea creation logic out of the controller and into a dedicated action class called CreateIdea that exposes a handle method so the code is reusable and easier to test. Along the way we'll decouple from the request by passing explicit attributes and using constructor injection with Laravel's CurrentUser attribute so the action can be invoked from anywhere.
    16. Authorization Is A Requirement

      We just ran a quick review of our refactor and added a test to prove that steps actually get created so issues like an undefined variable inside a transaction closure do not slip through. Now we are going to tackle authorization using gates and a simple IdeaPolicy, showing how to call authorize in the controller or use the can middleware on routes and adding tests to lock down access.
    17. The Edit Idea Modal

      We want to let users edit an existing idea by reopening the same modal we use to create ideas, so we'll start by writing a test that clicks the edit idea button. When that failed we discovered the modal is hard coded on the index page, so we'll extract it into its own Alpine component at idea/modal and make sure the open modal dispatch works on the show page.
    18. Update Idea Action

      Okay, now we are tackling updating an idea by adding a dedicated updateIdea action and hooking it into the update controller so we can manage image uploads, update attributes, and shift the form to an object based steps structure. To keep things simple we will sync the steps by wiping and rebuilding them instead of doing tricky upserts and we will lock it down with browser and unit tests including using assertValue to verify initial form values.
    19. Edit Your Profile

      Now that users can sign up we are adding an edit profile flow so they can visit /profile/edit, open a view populated from auth()->user() and reach it via a link in our navigation that points to a ProfileController. We will wire up edit and update actions, validate and conditionally update the password notify the original email address when the email changes and cover the behavior with tests using Notification::fake and on demand assertions.
    20. Deploy And Then Implement A Feature Request

      Let's finish up by running composer run format, running our test suites, and pushing the changes to Forge so we can watch a real deploy. Then we'll add a formatted_description accessor that uses Laravel's Str::markdown and bring in Tailwind's typography plugin so our markdown descriptions render nicely in production.
    21. Where To Go From Here

      Now that we have the app finished let's pick a path for improving it, like adding team support so people can collaborate and we can practice more advanced authorization. If you want to keep learning we can explore Laravel Livewire for PHP driven interactivity or try pairing Vue.js or React with Inertia.js to build a richer SPA experience.

Continue Learning