Laravel 12 Dynamic Auto-Suggest Field PHP JavaScript Guide

Yogesh Kushwaha
By Yogesh Kushwaha

September 3, 2025 05:22 PM

Auto-Suggest Field PHP JavaScript Guide

Dynamic auto-suggest field PHP JavaScript guide in #Laravel 12 & #PHP 8 for dynamic search and better user experience.

Auto-suggest (a.k.a. typeahead) makes search inputs feel instant and helpful. In this post we’ll build a production-ready auto-suggest text field in Laravel 12 (PHP 8+) with zero JS frameworks.

What we’re building

  • A /states-name-autosuggest endpoint returning lightweight JSON
  • A Blade view with a <input type="text"> and a dropdown list of suggestions
  • Vanilla JS

Prerequisites

  • Laravel 12 (works on 10/11)
  • PHP 8.3+
  • MySQL 8+ or MariaDB 10.4+ (example use MariaDB)

1. Database migration

Migration (cities) - We are going to create a migration for our City model.

// database/migrations/2025_09_03_120317_create_cities_table.php
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('cities', function (Blueprint $table) {
            $table->id();
            $table->foreignId('state_id')->constrained('states');
            $table->string('name', 45);
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('cities');
    }
};

2. Model

// app/Models/CIty.php
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;

class City extends Model
{
    public $timestamps = false;
    protected $fillable = ['name', 'state_id'];

    public function state()
    {
        return $this->belongsTo(State::class);
    }
}

3. Route to get autosuggestion values

// routes/web.php
Route::get('/states-name-autosuggest', [App\Http\Controllers\AutoSuggestController::class, 'statesNameAutoSuggest'])
    ->name('states.name.autosuggest');

4. Controller

// app/Http/Controllers/AutoSuggestController.php

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;

class AutoSuggestController extends Controller
{
    public function statesNameAutoSuggest(Request $request) {
        $searchTerm = $request->input('query');
        
        // Assuming you have a State model to fetch states from the database
        $states = \App\Models\State::where('name', 'LIKE', '%' . $searchTerm . '%')
            ->take(10)
            ->get(['id', 'name']);
        
        return response()->json($states ?? []);        
    }
}

Tip: for Postgres, replace FULLTEXT with ILIKE '%term%' or a trigram index.

5. Blade view

<!-- resources/views/cities/create.blade.php -->
<x-app-component :$pageTitle>
    <div class="container">
        <!-- Display any validation errors -->
        @if ($errors->any())
        <div class="alert alert-danger">
            <ul>
                @foreach ($errors->all() as $error)
                <li>{{ $error }}</li>
                @endforeach
            </ul>
        </div>
        @endif

        <form action="{{ route('cities.store') }}" method="POST">
            @csrf
            <div class="mb-3">
                <label for="name" class="form-label">City Name</label>
                <input type="text" name="name" id="name" value="{{ old('name') }}" class="form-control">
            </div>
            <div class="mb-3">
                <label for="stateName" class="form-label">State Name</label>
                <input type="text" list="stateListId" name="state_name" id="stateName" value="{{ old('state_name') }}" class="form-control">
                <input type="hidden" name="state_id" id="stateId" value="{{ old('state_id') }}">
                <ul class="list-group" id="stateListId"></ul>
            </div>
            <button type="submit" class="btn btn-primary">Create City</button>
        </form>
    </div>

    @push('page_scripts')
        @vite('resources/js/state_autosuggest.js')
    @endpush
</x-app-component>

 

6. Vanilla JS

const stateInput = document.getElementById("stateName");
stateInput.addEventListener("keyup", function () {
    const query = this.value;
    if (query.length < 2) {
        return; // Only trigger autosuggest for queries with 2 or more characters
    }

    fetch(`/states-name-autosuggest?query=${encodeURIComponent(query)}`)
        .then((response) => response.json())
        .then((data) => {
            const datalist = document.getElementById("stateListId");
            datalist.innerHTML = ""; // Clear previous suggestions
            data.forEach(state => {
                const listItem = document.createElement('li');
                listItem.classList.add('list-group-item', 'd-flex', 'justify-content-between', 'align-items-center');
                listItem.textContent = state.name;
                const spanElement = document.createElement('span');
                spanElement.classList.add('badge', 'bg-primary', 'rounded-pill');
                spanElement.textContent = state.id;
                listItem.appendChild(spanElement);
                datalist.appendChild(listItem);
            });
        })
        .catch((error) =>
            console.error("Error fetching state suggestions:", error)
        );
});

// Put the selected state ID into the hidden input field
const stateList = document.getElementById("stateListId");
stateList.addEventListener("click", function (event) {
    if (event.target.tagName === "LI") {
        const selectedStateId = event.target.querySelector("span").textContent;
        stateInput.value = event.target.textContent.replace(selectedStateId, "").trim(); // Set the input value to the state name
        document.getElementById("stateId").value = selectedStateId; // Set the hidden input value to the state ID
        stateList.innerHTML = ""; // Clear suggestions after selection
    }
});

7. City Create route

// routes/web.php

Route::resource('cities', App\Http\Controllers\CityController::class)
    ->names('cities');

8. Performance notes

  • Indexing: keep the name B-Tree index for prefix queries and a FULLTEXT index for contains searches.
  • Caching: Use caching if you want to suggest from large data sets.
  • Payload: return only id, name (avoid large blobs).
  • Limits: 8–10 suggestions is plenty.

9. Security & reliability

  • Throttle with a custom limiter
  • Validate min length server-side.
  • Escape/encode output
  • CORS: If serving from a different domain, add CORS rules to the API group.
  • Accessibility: add proper accessibility rules

Wrap-up

You have an example of Auto-Suggest Field PHP JavaScript Guide where you can create cities & State Name can be fetched from another model and list them as autosuggest to choose while creating a City Entity.