oh/google-map-form-type-bundle
A Symfony bundle that adds a Google Maps form type for selecting locations directly in your forms. Let users pick a point on an interactive map and store latitude/longitude with minimal configuration. Ideal for address, venue, or geolocation fields.
First, the framing: despite the "Laravel developer" lens, oh/google-map-form-type-bundle is a Symfony bundle, not a Laravel package. There is no service provider, no config publish tag, nothing Composer-autoloadable into a Laravel form builder. If you are on Laravel, treat it as a reference implementation (read the JS + Twig to see how a draggable-marker → lat/lng pair is wired) and rebuild it as a Blade component + small Alpine/Stimulus controller. Everything below assumes Symfony.
Install
composer require oh/google-map-form-type-bundle
Expect the constraint solver to fight you — the last release is 2016-07-21, so the symfony/form / symfony/framework-bundle constraints target Symfony 2.x–3.x. On any modern project you will be forking the repo or using --ignore-platform-reqs purely to read the code.
Register it
config/bundles.php → Oh\GoogleMapFormTypeBundle\OhGoogleMapFormTypeBundle::class => ['all' => true]app/AppKernel.php → new Oh\GoogleMapFormTypeBundle\OhGoogleMapFormTypeBundle()Configure the API key (Google has required a key + billing account on the Maps JS API since 2016, which this bundle predates):
# config/packages/oh_google_map_form_type.yaml
oh_google_map_form_type:
api_key: '%env(GOOGLE_MAPS_API_KEY)%'
Look here first, not at the README — the docs are thin and the code is small:
Form/Type/GoogleMapType.php — the compound type; this defines lat, lng, and every option you can pass.Resources/views/Form/*.twig — the widget template; confirms required asset names, DOM ids, and data-* attributes the JS reads.Resources/public/js/*.js — the initialiser; check the Maps API version and whether the <script> URL includes &key=.Publish/install assets
php bin/console assets:install --symlink public/
Or, if you use Webpack Encore, copy the bundle JS into assets/ and import it yourself — cleaner and avoids assets:install in deploy pipelines.
First use case — a venue/address form where the user drops a pin:
use Oh\GoogleMapFormTypeBundle\Form\Type\GoogleMapType;
$builder
->add('name')
->add('latlng', GoogleMapType::class, [
'lat_options' => ['label' => 'Latitude'],
'lng_options' => ['label' => 'Longitude'],
'map_options' => ['width' => '100%', 'height' => '320px', 'zoom' => 12],
]);
{% form_theme form 'OhGoogleMapFormTypeBundle:Form:google_map_widget.html.twig' %}
{{ form_start(form) }}
{{ form_row(form.name) }}
{{ form_row(form.latlng) }}
{{ form_end(form) }}
If you see two plain text inputs and no map, the form theme or the JS asset is missing — see Gotchas.
Wrap it in a domain type. Don't scatter GoogleMapType across every form. Make a LocationType that composes it, so the map options stay in one place:
class LocationType extends AbstractType
{
public function buildForm(FormBuilderInterface $b, array $o): void
{
$b->add('latlng', GoogleMapType::class, [
'label' => false,
'map_options' => ['height' => '320px', 'zoom' => 14],
]);
}
}
Map to your entity. The type is compound, so your entity needs two writable properties (latitude, longitude). If you model the point as a single column (PostGIS/MySQL POINT, or a "lat,lng" string), either:
'property_path' => false on the child fields plus a PRE_SUBMIT listener that parses the pair into your value object, orDataTransformer on the parent so location ⇄ [lat, lng].Store coordinates as DECIMAL(10,7) / DECIMAL(11,8), not FLOAT. Float rounding on lng at 7 decimals is ~1cm, which is fine for pins but bites you the moment you do distance filters or equality checks. Also decide up front what null means (no pin dropped) versus 0,0 (a real place in the Gulf of Guinea).
Validate server-side, always. The map is a UI convenience; the POST body is attacker-controlled.
->add('latitude', NumberType::class, [
'constraints' => [new Assert\Range(['min' => -90, 'max' => 90])],
])
Default centre / zoom for empty state. When the entity has no coordinates yet, centre on a configured fallback (city HQ, country centroid) rather than letting Google default to the whole world. Pass it through map_options (the type typically accepts a lat/lng/zoom seed) — verify the exact option names against GoogleMapType::configureOptions, since they changed between versions.
Don't expect geocoding. This bundle places a marker and reads coordinates back out. It does not do address → lat/lng in either direction. For a real address workflow you need:
map.setCenter() on your map instance. You'll have to expose the map object from the bundle's initialiser or re-initialise yourself.Progressive enhancement. The lat/lng inputs exist as real form fields. Keep them visible (or at least present) so the form is submittable and correct without JS — that also makes it testable with $client->submitForm() in WebTestCase, where the map never renders.
Admin panels. For EasyAdmin/Sonata, this is a custom field/configurator job rather than a drop-in add('latlng', GoogleMapType::class). The cost of wiring is usually higher than a hand-rolled field with three lines of JS — worth measuring before you commit.
Multiple pins (e.g. a delivery route) is out of scope for this bundle. One map, one marker. For N points, write your own widget; the bundle's marker-centering logic doesn't generalise.
A grey/blank map is almost always the API key. The bundle's JS emits a <script src="https://maps.googleapis.com/maps/api/js"> built before keys were mandatory. Check the rendered HTML: if there's no &key=..., you must override the script tag yourself in your layout or patch the template. A key without a billing account enabled also fails, silently-ish, with a console error.
Hardened key restrictions. Set HTTP-referrer restrictions on the key and do not use it server-side. The key is in your page source by definition — restrict it and monitor it.
The Twig form theme is not auto-registered. form_theme must appear in the template that renders the form (or in twig.yaml globally). Miss it and you get two unstyled <input type="text">, which looks like "the bundle is broken".
Field name collisions with the JS. The initialiser typically locates map containers and inputs by generated ids derived from the form field name (form_latlng_lat). Render two of these forms on one page and the ids collide, or the JS picks the wrong one. Either namespace your form names (createNamed) or initialise per-container in your own script.
Asset serving in production. assets:install output isn't committed in most setups; if you skip it in the deploy script the map 404s while everything else works. With Encore, import the file so it gets fingerprinted — but note the bundle JS may assume it runs after the Maps API callback, so order imports deliberately.
HTTPS-only. Mixed-content blocking of the Maps API script produces a console error and a blank canvas that looks identical to a bad key.
Debugging checklist, in order: (1) view source — is the API script tag present and does it contain key=; (2) is the widget markup present (data-*/container div), meaning the form theme applied; (3) browser console for ReferenceError: google is not defined (script order) vs InvalidKeyMapError (key) vs RefererNotAllowedMapError (restrictions); (4) Network tab for a 404 on the bundle's JS.
Extension points, most sustainable first:
templates/bundles/OhGoogleMapFormTypeBundle/Form/... to control markup and add your own data- hooks.GoogleMapType to add options or change defaults; register it as the GoogleMapType::class alias if you want every call site upgraded at once.repositories with vcs type is a legitimate, low-risk move for a package this small.Plan the exit. This is a UI convenience of a few hundred lines. Budget a half-day to inline it rather than carrying an unmaintained Symfony 2-era dependency through your next major framework upgrade — you'll spend less time than on one Composer constraint fight.
How can I help you explore Laravel packages today?