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.
- How to Install Laravel with XAMPP ( I am using Laravel 5.6 for this Project)
- Connecting your Project to Database in Laravel
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
- First page will have the form fields to get basic information about the product (name, description etc.)
- Second page will have the ability to upload image for the product
- 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.
ProductController.php
will be generated under app > Http > Controllers directory
create_products_table.php
migration file will be created under database > migrations folder.
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
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
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.
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.
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
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
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.
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
yes i got it fixed
just change $product->name to $product[‘name’]
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.
hi i have used it but it always update my record not insert a new record in databse
@Hersh
You can find these HTML files here:
https://github.com/5balloons/multi-step-form-laravel/blob/master/resources/views/products/create-step2.blade.php
Hope it is not too late 🙂
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
Thanks Catherine, Glad this tutorial helped.