Laravel's session system underlies authentication (which is why a logged-in user stays logged in across requests), but it's also directly usable for any data that needs to persist briefly, per-visitor, without going into the database — flash messages, a multi-step form's partial progress, or an unauthenticated shopping cart.
1. Storing and retrieving session data
// Via the Session facade
Session::put('cart_id', $cartId);
$cartId = Session::get('cart_id');
// Via the request object — functionally identical
$request->session()->put('cart_id', $cartId);
$cartId = $request->session()->get('cart_id');
// Via the global session() helper
session(['cart_id' => $cartId]);
$cartId = session('cart_id');
All three forms read and write the exact same underlying session store — which one to use is purely a matter of context and style (the helper is convenient in a Blade view or a quick script; the facade or request object reads more explicitly in a controller).
2. Flash data — available for exactly the next request, then gone
Session::flash('success', 'Product created successfully.');
return redirect()->route('products.index');
@if (session('success'))
<div class="alert">{{ session('success') }}</div>
@endif
Flash data is specifically designed for the redirect-after-post pattern — set right before a redirect, it survives exactly one subsequent request (the page the redirect lands on) and is then automatically removed. This is why "flash" messages don't need any manual cleanup: Laravel handles the expiration after that single next request automatically.
3. Checking for and removing session data
if (Session::has('cart_id')) {
// key exists and its value is not null
}
if (Session::exists('cart_id')) {
// key exists at all, even if its value is null — a subtly different check than has()
}
Session::forget('cart_id');
Session::flush(); // clears everything in the session
4. Choosing a session driver
// .env
SESSION_DRIVER=file // simplest, fine for a single-server setup
SESSION_DRIVER=database // needs a sessions table (php artisan session:table, then migrate)
SESSION_DRIVER=redis // fast, the standard choice once scaling beyond one server
The file driver stores each session as a file on that specific server's disk — this breaks in a load-balanced, multi-server deployment, since a user's session data only exists on whichever single server happened to handle their first request. database or redis centralizes session storage so any server in the pool can read the same session data, which is a genuine requirement once an app runs behind a load balancer across more than one server.
5. A multi-step form example
// Step 1 controller
public function stepOne(Request $request)
{
$request->validate(['name' => 'required']);
Session::put('signup.name', $request->name);
return redirect()->route('signup.step-two');
}
// Step 2 controller
public function stepTwo(Request $request)
{
$request->validate(['email' => 'required|email']);
Session::put('signup.email', $request->email);
// Final step: read everything back and actually create the record
User::create([
'name' => Session::get('signup.name'),
'email' => Session::get('signup.email'),
]);
Session::forget('signup');
return redirect()->route('signup.complete');
}
Storing partial form progress in the session across several separate requests, then clearing it once the final record is actually created, is a standard pattern for a multi-step wizard-style form — it avoids either cramming everything into one giant form or persisting genuinely incomplete data to the database as an intermediate state.
6. Session security basics
// .env
SESSION_ENCRYPT=true // encrypts the session cookie's contents
SESSION_SECURE_COOKIE=true // only send the session cookie over HTTPS
SESSION_SECURE_COOKIE should be true in production on any site served over HTTPS — without it, the session cookie can still be sent over an accidental plain-HTTP connection, which is a real exposure risk for session hijacking on a network that isn't fully trusted.