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.