Showing slug instead of id for crud in Laravel 8

Stefan Bogdanescu

Founder & Senior Architect · 2026-06-29

Laravel Company

Showing Slug Instead of ID for CRUD in Laravel 8: A Deep Dive into Route Parameter Handling

As developers building robust applications with Laravel, we frequently encounter scenarios where simply using database IDs feels insufficient. Users prefer human-readable identifiers like slugs instead of raw numeric IDs. Implementing this feature correctly—especially when dealing with edit and update operations—requires careful attention to how routes are defined, parameters are passed, and dependency injection works within the controller.

This post addresses a common hurdle: achieving a clean URL structure using slugs while avoiding tricky errors related to function arguments in Laravel controllers.

The Challenge: Mismatched Arguments in Route Handling

You have correctly identified the goal: redirecting to an edit view using the product's slug (e.g., /edit/my-awesome-product) instead of its database id. However, as your error message suggests: Too few arguments to function App\Http\Controllers\ProductController::edit(), 1 passed and exactly 2 expected, the issue lies in how Laravel is attempting to map the route parameters to your controller method signature.

This usually happens when you define routes that expect dynamic segments ({slug}) but the controller method isn't set up to receive those parameters correctly, or there’s a conflict in how you are trying to retrieve the necessary model instance and the slug itself.

Deconstructing the Route and Controller Flow

Let's analyze the structure you provided:

Your Routes:

Route::get('edit/{slug}', $url . '\productController@update'); // Route 1 (for updating)
Route::get('edit', $url . '\productController@edit');       // Route 2 (for editing view)

Your Controller Methods:

public function edit(Product $product, $slug) // Expects 2 arguments
{
    return view('edit', compact('product'));
}

public function update(Request $request, Product $product, $slug) // Receives the slug
{
    // ... logic using $product and $slug
}

The error occurs because when you hit Route::get('edit', ...) (Route 2), Laravel tries to resolve the parameters based on the path. If the route definition isn't perfectly aligned with how the controller expects arguments, it throws a fatal error during execution.

The Solution: Correct Parameter Passing and Route Binding

The key to fixing this is ensuring that your routes explicitly define what parameters are expected, and you leverage Laravel’s features for clean data retrieval. Instead of trying to pass the slug separately when fetching the model, we can streamline the process by binding the model directly using the slug in the route definition.

Step 1: Refine Route Definitions (The Clean Approach)

Instead of having a separate route for /edit and one for /edit/{slug}, consolidate your logic into one clear pattern that leverages Route Model Binding (RMB). For an edit operation, you only need to retrieve the model based on its unique identifier (the slug).

Modify your routes to bind the Product model directly using the slug:

// Use a single route definition for demonstration purposes.
// We use the 'edit' route to fetch the specific product by its slug.
Route::get('products/{product}/edit', [ProductController::class, 'edit'])->name('products.edit');
Route::post('products/{product}/update', [ProductController::class, 'update'])->name('products.update');

Step 2: Update the Controller Logic (Leveraging Route Model Binding)

By structuring the route this way, Laravel automatically resolves the $product parameter in your controller method using Eloquent's ability to find a model by its relationship or primary key. If you want to explicitly pass the slug, you can still do so, but it must be done consistently with the route definition.

If you insist on passing the slug explicitly (which is fine for demonstration, though less idiomatic when RMB is used), ensure your method signature perfectly matches what Laravel expects based on the route parameters:

use App\Models\Product;
use Illuminate\Http\Request;

class ProductController extends Controller
{
    // Ensure that if you pass other dynamic parts (like the slug), they are included.
    public function edit(Product $product, string $slug) // Laravel handles finding $product via route binding
    {
        // If you need the slug separately for context:
        $this->validate($request, [
            'title' => 'required',
            'image' => 'required'
        ]);

        return view('edit', compact('product'));
    }

    public function update(Request $request, Product $product, string $slug)
    {
        // Use the bound model ($product) for updates.
        $product->update([
            'product_title' => $request->input('title'),
            'product_slug' => $request->input('slug'), // Update the slug as well
            'product_image' => $request->file('image') ? $request->file('image')->store('images') : null
        ]);

        return redirect()->route('products.edit', $product->slug);
    }
}

This pattern ensures that Laravel correctly interprets the route parameters and injects the necessary data, resolving the "too few arguments" error by aligning the definition of the route with the expectations set by your controller methods. Understanding these routing conventions is crucial when building scalable applications on the Laravel framework, as discussed extensively in resources like laravelcompany.com.

Conclusion

The issue you faced was a classic case of parameter mismatch between the defined URL structure and the controller method signature. By adopting best practices—specifically using Route Model Binding where possible and ensuring strict alignment between routes and controllers—you can achieve clean, robust CRUD operations in Laravel. Focus on letting Eloquent handle the model retrieval based on your unique identifiers (like slugs), and let Laravel manage the routing complexity for you.