makinacorpus/autocomplete-bundle
A Symfony bundle that adds AJAX-powered autocomplete form fields, with reusable form types, controllers, and Doctrine integration to serve typeahead suggestions from your application's data.
1. Install and enable
composer require makinacorpus/autocomplete-bundle
Register the bundle (namespace/class name — confirm with composer show makinacorpus/autocomplete-bundle and the README, this package predates a lot of conventions):
// config/bundles.php (Symfony 4+)
return [
// ...
MakinaCorpus\AutocompleteBundle\MakinaCorpusAutocompleteBundle::class => ['all' => true],
];
// Legacy Symfony 3: add to AppKernel::registerBundles()
Then publish the widget assets and check routing:
bin/console assets:install --symlink public
bin/console debug:router | grep -i autocomplete
bin/console debug:form | grep -i autocomplete
2. Where to look first
With one star and no dependents, the README is the only contract you have. Read these in order:
README.md — the form type's options and the expected JSON payload of the autocomplete endpoint.src/Form/Type/ — the form type you extend; debug:form gives you the authoritative option list.src/Controller/ — whether the bundle ships its own endpoint or expects you to provide one.Resources/public/ — the JavaScript; this defines the AJAX contract (query param name, response shape) and is the thing you'll most likely end up overriding.composer.json — Symfony version constraints. This is the make-or-break detail.3. First use case
An entity picker in a form: a Document field where the user types a title and picks from a server-side search, optionally uploading a new file inline instead of selecting an existing one. Wire one field, hit the endpoint with curl, then wire the JS.
Form type with an external endpoint
The common pattern is: the bundle gives you the widget and the form type, you give it a route.
use MakinaCorpus\AutocompleteBundle\Form\Type\AutocompleteType;
$builder->add('document', AutocompleteType::class, [
'route' => 'app_autocomplete_documents',
'min_length' => 2,
'multiple' => false,
// 'upload' => true, // only if you actually want the upload widget
]);
Your endpoint
Keep it dumb and fast — it is called on every keystroke:
#[Route('/_autocomplete/documents', name: 'app_autocomplete_documents', methods: ['GET'])]
public function documents(Request $request, DocumentRepository $repo): JsonResponse
{
$q = trim((string) $request->query->get('q', ''));
if (mb_strlen($q) < 2) {
return $this->json([]); // don't run a LIKE on 1 char
}
$items = $repo->searchByTitle($q, 20); // LIMIT 20, indexed column
return $this->json(array_map(fn (Document $d) => [
'id' => $d->getId(),
'text' => $d->getTitle(),
], $items));
}
Verify the exact key names against Resources/public/*.js — bundles of this vintage vary between id/text, value/label, and {results: [...]}. A 200 with the wrong shape looks identical to a broken widget.
Never trust the submitted value. The widget submits an id (or a title) that the client chose. Either build a ChoiceType with a choice_loader and let the bundle's type delegate, or add a PRE_SUBMIT/POST_SUBMIT listener that re-fetches the entity and rejects anything outside the allowed set. 'id' => 999999 in a POST body is trivial.
Entity relationship pattern
Pair a hidden id field with a DataTransformer so the entity hydrates on PRE_SET_DATA and serialises back on transform(). This keeps the rest of your form code typed instead of juggling scalars.
Upload variant
If you use the upload widget: upload to a non-public temp directory, persist a reference, and clean up orphans with a scheduled command. Do mime/size validation server-side (File constraint), never in JS only. Treat the autocomplete-of-existing-files as a search, not an authorisation check.
Theming and assets
Override the template (form_themes in config/packages/twig.yaml) rather than editing vendor files, and if you're on Encore/AssetMapper, prefer re-implementing the JS against your endpoint over fighting assets:install — the widget is the least valuable part of this bundle.
"makinacorpus/autocomplete-bundle": "1.0.x"), read the last-commit date, and assume you own the code. If composer.json caps Symfony at 3.x/4.x, budget for forking or writing a 60-line Stimulus controller instead — an autocomplete field is genuinely cheap to rebuild.debug:form failing or the type not appearing is your first signal; don't debug Twig before you've confirmed the bundle booted.#[IsGranted], rate-limit it (symfony/rate-limiter), and cap results. This is the most common way such a bundle becomes a DoS vector.LIKE '%…%'. Profile the endpoint with the Symfony profiler; a leading wildcard on a large table is a sequential scan on every keystroke.GET; the upload endpoint is not. Mixing them up produces silent 405s in the browser console and nothing in the PHP log.curl -s 'https://app.test/_autocomplete/documents?q=foo' -H 'Accept: application/json' | jq → check shape; browser Network tab → check the actual query param name the JS sends (often term, not q); bin/console debug:router → confirm the name matches the route option exactly.textContent rather than innerHTML, or escape server-side.role="combobox" / aria-activedescendant. If a11y is in scope, plan to replace the widget and keep the form type purely as a validation/hydration shim.form_themes, (3) replace the JS entirely, (4) fork. Avoid editing vendor/ — you will forget, and composer install on deploy will revert it.How can I help you explore Laravel packages today?