Tutorial : Multi Page / Step Form in Laravel with Validation

Multi Page / Step Form in Laravel with Validation

In this tutorial we will go over Example of Multi Page / Step Form in Laravel with Validation. This tutorial does not use any javascript component to create multi step form.

Instead we will create multiple form pages and will use Laravel session to save the intermediate data.

Before starting on to this tutorial make sure you have a Laravel Setup Ready and you have connected it to a Database. You can take help from below tutorials for the setup to get started.

We are creating a Multi Page Form to Insert products into the database. You can use this example for any of your Multi-Step Form Submission with Validation in Laravel.

To give a brief about the Multi-Step Form. The Form will consist of three pages before the results are stroed in the database

  1. First page will have the form fields to get basic information about the product (name, description etc.)
  2. Second page will have the ability to upload image for the product
  3. Third page is a review screen where user can see the information added and can submit the Form to database.

Once you are ready with the setup, Let’s dive into the steps.

 

Create Model, Controller and a Migration File.

Let’s start with Creating a Model, Controller and a Migration File for our Form Submission Example. Go to Terminal (Command Line) and Navigate to your project root directory.

Run following artisan command.

php artisan make:model Product -c -m

This artisan command will generate a Model file Product.php and since we have appended the command with additional parameters it will also generate a Controller ProductController.php and a migration file create_products_table.php

Here is how the File Looks

Product.php will be generated under app directory.

Product Model File Blank

 

ProductController.php will be generated under app > Http > Controllers directory

ProductController blank file

create_products_table.php migration file will be created under database > migrations folder.

products migration file.

Generate Database Table

As the next step, Let’s update our migration file to include the additional fields to store the Product properties, we will use this migration file to generate a database table for our example project.

Open create_products_table.php migration file and update the up() function as given below to add additional product parameters.

    public function up()
    {
        Schema::create('products', function (Blueprint $table) {
            $table->increments('id');
            $table->string('name');
            $table->string('company');
            $table->longText('description');
            $table->float('amount');
            $table->boolean('available');
            $table->string('productimg');
            $table->timestamps();
        });
    }

Now open your terminal, move to project root directory and run the following command to generate database tables form migration files.

php artisan migrate

php artisan migrate command

This will generate products table in the database along with the other default tables provided by Laravel.

Here is how the generated products table structure looks

products table structure multi-step laravel


Create Routes, Controller Method and Blade Page for Step 1 of Multi-Step Process

The first page of multi page Laravel form will have form fields to input information about the product.This will require a route entry , a corresponding Controller Method and a View file that will be returned by the Controller. Let’s do it one by one.

Open your web.php route file located under routes folder and add following entry into the file.

Route::get('/products/create-step1', 'ProductController@createStep1');
Route::post('/products/create-step1', 'ProductController@postCreateStep1');

These route entries will handle the show form as well as form post method, Let’s go ahead and add corresponding controller methods

    /**
     * Show the step 1 Form for creating a new product.
     *
     * @return \Illuminate\Http\Response
     */
    public function createStep1(Request $request)
    {
        $product = $request->session()->get('product');
        return view('products.create-step1',compact('product', $product));
    }

    /**
     * Post Request to store step1 info in session
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function postCreateStep1(Request $request)
    {

        $validatedData = $request->validate([
            'name' => 'required|unique:products',
            'amount' => 'required|numeric',
            'company' => 'required',
            'available' => 'required',
            'description' => 'required',
        ]);

        if(empty($request->session()->get('product'))){
            $product = new Product();
            $product->fill($validatedData);
            $request->session()->put('product', $product);
        }else{
            $product = $request->session()->get('product');
            $product->fill($validatedData);
            $request->session()->put('product', $product);
        }

        return redirect('/products/create-step2');

    }

createStep1 function is to display step-1 form page to the user and postCreateStep1 method will handle the post request from the step-1 form. The data is validated using Laravel’s inbuilt validation system and then stored into the session. User is then redirected to step-2 form page.

Create a new file create-step1.blade.php under resources > views > products and add following contents to create a HTML form.

@extends('layout.layout')

@section('content')
    <h1>Add New Product - Step 1</h1>
    <hr>
    <form action="/products/create-step1" method="post">
        {{ csrf_field() }}
        <div class="form-group">
            <label for="title">Product Name</label>
            <input type="text" value="{{{ $product->name or '' }}}" class="form-control" id="taskTitle"  name="name">
        </div>
        <div class="form-group">
            <label for="description">Product Company</label>
            <select class="form-control" name="company">
                <option {{{ (isset($product->company) && $product->company == 'Apple') ? "selected=\"selected\"" : "" }}}>Apple</option>
                <option {{{ (isset($product->company) && $product->company == 'Google') ? "selected=\"selected\"" : "" }}}>Google</option>
                <option {{{ (isset($product->company) && $product->company == 'Mi') ? "selected=\"selected\"" : "" }}}>Mi</option>
                <option {{{ (isset($product->company) && $product->company == 'Samsung') ? "selected=\"selected\"" : "" }}}>Samsung</option>
            </select>
        </div>
        <div class="form-group">
            <label for="description">Product Amount</label>
            <input type="text"  value="{{{ $product->amount or '' }}}" class="form-control" id="productAmount" name="amount"/>
        </div>
        <div class="form-group">
            <label for="description">Product Available</label><br/>
            <label class="radio-inline"><input type="radio" name="available" value="1" {{{ (isset($product->available) && $product->available == '1') ? "checked" : "" }}}> Yes</label>
            <label class="radio-inline"><input type="radio" name="available" value="0" {{{ (isset($product->available) && $product->available == '0') ? "checked" : "" }}}> No</label>
        </div>
        <div class="form-group">
            <label for="description">Product Description</label>
            <textarea type="text"  class="form-control" id="taskDescription" name="description">{{{ $product->description or '' }}}</textarea>
        </div>
        @if ($errors->any())
            <div class="alert alert-danger">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif
        <button type="submit" class="btn btn-primary">Add Product Image</button>
    </form>
@endsection

Once you have completed these steps, navigate to /products/create-step1 url of your project. You should be able to see a Create products form in your application.

Multi page Laravel step-1


Create Routes, Controller Method and Blade Page for Step 2 of Multi-Step Process

Let’s go ahead and create necessary code to create second page of our multi step form in laravel.

Open your web.php route file located under routes folder and add following entry into the file.

Route::get('/products/create-step2', 'ProductController@createStep2');
Route::post('/products/create-step2', 'ProductController@postCreateStep2');
Route::post('/products/remove-image', 'ProductController@removeImage');

These route entries will handle the show form as well as form post method, Let’s go ahead and add corresponding controller methods

 /**
     * Show the step 2 Form for creating a new product.
     *
     * @return \Illuminate\Http\Response
     */
    public function createStep2(Request $request)
    {
        $product = $request->session()->get('product');
        return view('products.create-step2',compact('product', $product));
    }

    /**
     * Post Request to store step1 info in session
     *
     * @param  \Illuminate\Http\Request  $request
     * @return \Illuminate\Http\Response
     */
    public function postCreateStep2(Request $request)
    {
        $product = $request->session()->get('product');
        if(!isset($product->productImg)) {
            $request->validate([
                'productimg' => 'required|image|mimes:jpeg,png,jpg,gif,svg|max:2048',
            ]);

            $fileName = "productImage-" . time() . '.' . request()->productimg->getClientOriginalExtension();

            $request->productimg->storeAs('productimg', $fileName);

            $product = $request->session()->get('product');

            $product->productImg = $fileName;
            $request->session()->put('product', $product);
        }
        return redirect('/products/create-step3');

    }

    /**
     * Show the Product Review page
     *
     * @return \Illuminate\Http\Response
     */
    public function removeImage(Request $request)
    {
        $product = $request->session()->get('product');
        $product->productImg = null;
        return view('products.create-step2',compact('product', $product));
    }

Create a new file create-step2.blade.php under resources > views > products and add following contents to create a HTML form for second page of multi-step form.

@extends('layout.layout')

@section('content')
    <h1>Add New Product - Step 2</h1>
    <hr>
    @if(isset($product->productImg))
    Product Image:
    <img alt="Product Image" src="/storage/productimg/{{$product->productImg}}"/>
    @endif
    <form action="/products/create-step2" method="post" enctype="multipart/form-data">
        {{ csrf_field() }}
        <h3>Upload Product Image</h3><br/><br/>

        <div class="form-group">
            <input type="file" {{ (!empty($product->productImg)) ? "disabled" : ''}} class="form-control-file" name="productimg" id="productimg" aria-describedby="fileHelp">
            <small id="fileHelp" class="form-text text-muted">Please upload a valid image file. Size of image should not be more than 2MB.</small>
        </div>
        <button type="submit" class="btn btn-primary">Review Product Details</button>
        @if ($errors->any())
            <div class="alert alert-danger">
                <ul>
                    @foreach ($errors->all() as $error)
                        <li>{{ $error }}</li>
                    @endforeach
                </ul>
            </div>
        @endif
    </form><br/>
    @if(isset($product->productImg))
    <form action="/products/remove-image" method="post">
        {{ csrf_field() }}
    <button type="submit" class="btn btn-danger">Remove Image</button>
    </form>
    @endif
@endsection

 

Once you have completed these steps, enter information in step-1 of create product form and you should be redirected to step-2 page which should look like below.

multi step page 2 laravel

If you face difficulty in uploading the file / images and making them public, Please follow steps in following tutorial Example of File Upload with Validation in Laravel 5.6


Create Routes, Controller Method and Blade Page for Step 3 of Multi-Step Process

Let’s go ahead and create necessary code to create third page of our multi step form in laravel. In this step user can review the information added in first two steps and if required can go back to edit the information.

Open your web.php route file located under routes folder and add following entry into the file.

Route::get('/products/create-step3', 'ProductController@createStep3');
Route::post('/products/store', 'ProductController@store');

These route entry will handle the GET request to the review page of our multi step form and will also handle the final POST request to put data into database.

Open your ProductController.php and add corresponding controller methods into the database.

    /**
     * Show the Product Review page
     *
     * @return \Illuminate\Http\Response
     */
    public function createStep3(Request $request)
    {
        $product = $request->session()->get('product');
        return view('products.create-step3',compact('product',$product));
    }

    /**
     * Store product
     *
     * @return \Illuminate\Http\Response
     */
    public function store(Request $request)
    {
        $product = $request->session()->get('product');
        $product->save();
        return redirect('/products');
    }

Create a new file create-step3.blade.php under resources > views > products and add following contents to create a HTML form for second page of multi-step form.

@extends('layout.layout')

@section('content')
    <h1>Add New Product - Step 3</h1>
    <hr>
    <h3>Review Product Details</h3>
    <form action="/products/store" method="post" >
        {{ csrf_field() }}
        <table class="table">
            <tr>
                <td>Product Name:</td>
                <td><strong>{{$product->name}}</strong></td>
            </tr>
            <tr>
                <td>Product Amount:</td>
                <td><strong>{{$product->amount}}</strong></td>
            </tr>
            <tr>
                <td>Product Company:</td>
                <td><strong>{{$product->company}}</strong></td>
            </tr>
            <tr>
                <td>Product Available:</td>
                <td><strong>{{$product->available ? 'Yes' : 'No'}}</strong></td>
            </tr>
            <tr>
                <td>Product Description:</td>
                <td><strong>{{$product->description}}</strong></td>
            </tr>
            <tr>
                <td>Product Image:</td>
                <td><strong><img alt="Product Image" src="/storage/productimg/{{$product->productImg}}"/></strong></td>
            </tr>
        </table>
        <a type="button" href="/products/create-step1" class="btn btn-warning">Back to Step 1</a>
        <a type="button" href="/products/create-step2" class="btn btn-warning">Back to Step 2</a>
        <button type="submit" class="btn btn-primary">Create Product</button>
    </form>
@endsection

Once you upload image in the second space and move to review the information added. You should be redirected to the third and final step and it should look something like below

multi-step-3-laravel

Click on Create Product and it should trigger the store method of the ProductController and the information should have be added to the database.


Show All Products

As a bonus, Let’s create a index page, where we will show all the products.

Open your web.php route file and add following entry for the index page.

Route::get('/products', 'ProductController@index');

Add the corresponding index method to ProductController

 /**
     * Display a listing of the prducts.
     *
     * @return \Illuminate\Http\Response
     */
    public function index()
    {
        $products = Product::all();
        return view('products.index',compact('products',$products));
    }

We make use of Eloquent all() method to get all the products in the database. Further we make use of compact method to pass the products object to index page.

Create a new page index.blade.php and put following contents into the file.

@extends('layout.layout')

@section('content')
    @if (Session::has('message'))
        <div class="alert alert-info">{{ Session::get('message') }}</div>
    @endif
    <table class="table">
        <thead class="thead-dark">
        <tr>
            <th scope="col">#</th>
            <th scope="col">Product Name</th>
            <th scope="col">Product Description</th>
            <th scope="col">Company</th>
            <th scope="col">Amount</th>
            <th scope="col">Available</th>
        </tr>
        </thead>
        <tbody>
        @foreach($products as $product)
            <tr>
                <th scope="row">{{$product->id}}</th>
                <td>{{$product->name}}</td>
                <td>{{$product->description}}</td>
                <td>{{$product->company}}</td>
                <td>{{$product->amount}}</td>
                <td>{{$product->available ? 'Yes' : 'No'}}</td>
            </tr>
        @endforeach
        </tbody>
    </table>
@endsection

Demo

Code Repo

I have used Bootstrap Template Layout integration in this project, You can Integrate Bootstrap into your Laravel Application for similar User Interface.

 

If you found this tutorial helpful and if you have any questions, Let me know in comments. You might also find other related tutorials useful.

Related Articles

22 comments On Tutorial : Multi Page / Step Form in Laravel with Validation

  • There is also an issue with redirecting the form back with validation errors. None of the data is stored so it returns an empty form, to be filled in again completely – frustrating for the user.

    • Martyn, Did you tried cloning and running the code directly from the code repo. The issues you mentioned are not replicating on my end.

  • Did anyone resolve the “Trying to get property ‘name’ of non-object” error for the creat-step1.blade.
    From what I can see $product = $request->session()->get(‘product’); in ProductController is the problem since the session() hasn’t been stored yet, it is sending an empty object. {{{ $product->name or ” }}} therefore produces the error

  • Hi there,

    I know this blog entry is a bit older but maybe still someone can help me.

    As far as I can judge, I still can access the second page by just entering the URL http://localhost/products/create-step2. My question would be how to avoid this so that in case the first site was not called, I neither can access the second page.

    An error I had was with the lines:
    name ?? ” }}}” class=”form-control” id=”taskTitle” name=”name”>

    I had to change ” to ??, not know what really is the difference.
    Thanks
    md

    • In the controller method of second step, check if you have the required data in session from the first step. If not then redirect user to the first page.

      • This is also the problem with the first page. There is no session therefore no object and consequently a non-object error. Please see above.

  • sanchit aggarwal

    hi i have used it but it always update my record not insert a new record in databse

  • Thanks for this great tutorial.

    > Create a new file create-step1.blade.phpunder resources > views > products and add following contents to create a HTML form.
    I don’t see the contents for creating the HTML form. Am I missing something?

  • it retrieves 1 Edgar Tek Torres

  • where do i change it Edgar Tek Torres? thanks i have the same problem, Trying to get property ‘name’ of non-object (View: /home/vagrant/code/sample/resources/views/products/create-step1.blade.php)

  • En caso de tener el problema de Ken “Trying to get property ‘name’ of non-object !”.
    Se soluciona cambiando la expresiones de acceso a una propiedad de un objeto “->” por las de acceso a propiedad de un arreglo “[ ‘ ‘ ]”.

    ___________________________________________________________________________________________________________________________________________________________
    In case of having the problem of Ken “Trying to get property ‘name’ of non-object!”.
    It is solved by changing the access expressions to a property of an object “->” by accessing property of an array “[ ‘ ‘ ]”.

  • I downloaded the source demo and it is working fine, but in my implementation in a project i am working on i get an error

    Trying to get property ‘name’ of non-object !

    view:
    name }}”>

    controller:
    public function checkoutAddress(Request $request){
    $oder = $request->session()->get(‘oder’);
    return view(‘shop.checkout.address’)->with(‘oder’, $oder);
    }

  • Thanks for tutorial.
    I have added a progression bar in the layout.
    It works but there is a short blink each time I click on the submits button; I mean it’s not flawless. It looks like the whole page is loaded every time
    How can I do to have the progression bar only load once ?

    Cheers

  • Fantastic job done.

  • Thank you sir

  • Great tutorial Sir.
    One question, Laravel ask me for the fields to be fillable on the model, it is ok? I am missing something.

    Cheers

    • Mass Assignment
      You may also use the create method to save a new model in a single line. The inserted model instance will be returned to you from the method. However, before doing so, you will need to specify either a fillable or guarded attribute on the model, as all Eloquent models protect against mass-assignment by default.

  • Thank you so much it help me a lot

  • This is beautiful, it helped me a lot. Thanks

Leave a reply:

Your email address will not be published.

Site Footer