**European Economic Area (EEA) developers**

> [!NOTE]
> If your billing address is in the European Economic Area, effective on 8 July 2025, the [Google Maps Platform EEA Terms of Service](https://cloud.google.com/terms/maps-platform/eea) will apply to your use of the Services. Functionality varies by region. [Learn more](https://developers.google.com/maps/comms/eea/faq.md.txt).

The Geocoding API v4 introduces several new methods that replace functionality
in v3 of the API. This guide shows you how to migrate your app to use the new v4
methods.

You can use your existing API keys with the new v4 methods. However, if you
have requested a quota uplift on v3 of the API, you must request an uplift on
the new v4 APIs.

> [!WARNING]
> **Warning:** Due to the security risks, you shouldn't call Geocoding API v4 methods directly from client-side JavaScript. If you are doing so against recommendations, be aware that requesting higher quotas increases the potential impact of API key abuse. All calls to Geocoding API v4 using API keys exposed on the client-side are at high risk. For more details, see [Security
> considerations](https://developers.google.com/maps/documentation/geocoding/geocoding-v4-migrate#security-considerations).

## Migrate from v3 Forward Geocoding

If you use [v3
Geocoding](https://developers.google.com/maps/documentation/geocoding/guides-v3/requests-geocoding) to
geocode addresses, you should migrate to the v4 [Geocode an
address](https://developers.google.com/maps/documentation/geocoding/geocoding) method, which accepts a GET
request.

The v4 API changes names, structure, and support for several parameters. We
strongly recommend that you [use a field
mask](https://developers.google.com/maps/documentation/geocoding/choose-fields) to specify the fields you
want returned in the response.

> [!WARNING]
> **Warning:** In all v4 methods, field names are formatted using lower camel case (e.g., addressComponents), in contrast to v3 methods, which used snake case (e.g., address_components). RPC responses continue to use snake case in v4, as they did in v3.

### Request parameter changes

| v3 Parameter | v4 Parameter | Notes |
|---|---|---|
| `address`, `components` | `address` | Unstructured address (v3 `address`) is now passed in URL path. Structured address components (v3 `components`) can be passed as `address.*` query parameters. See [Reproduce v3 component filters](https://developers.google.com/maps/documentation/geocoding/geocoding-v4-migrate.md.txt#reproduce-v3-component-filters). |
| `bounds` | `locationBias.rectangle` | Renamed; structure changed to object. |
| `language` | `languageCode` | Renamed. When omitted, v3 uses the `Accept-Language` header or defaults to `en`, whereas v4 defaults to the local language and script of the location. |
| `region` | `regionCode` | Renamed. When omitted, v3 defaults to `us`, whereas v4 has no default value. |
| `extra_computations` | *Removed* | Replaced by [SearchDestinations](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt) method. |

### Response field changes

| v3 Field | v4 Field | Notes |
|---|---|---|
| `status`, `error_message` | *Removed* | [v4 uses HTTP status codes and error bodies](https://google.aip.dev/193#http11json-representation). |
| `results.address_components.long_name` / `results.address_components.short_name` | `results.addressComponents.longText` / `results.addressComponents.shortText` | Renamed. |
| `results.geometry.location_type` | `results.granularity` | Renamed. |
| `results.geometry.location` | `results.location` | Field names: `lat`/`lng` -\> `latitude`/`longitude`. |
| `results.geometry.viewport` | `results.viewport` | Field names: `northeast`/`southwest` -\> `high`/`low`. |
| `results.postcode_localities` | `results.postalCodeLocalities` | Renamed. Now returned for one or more localities (v3 required \>1). |
| `results.partial_match` | *Removed* |   |
| *New* | `results.addressComponents.languageCode` | Language of the specific address component. |
| *New* | `results.bounds` | Explicit bounds using `high`/`low`. |
| *New* | `results.place` | Resource name for the place. |
| *New* | `results.postalAddress` | Structured `PostalAddress` object. |

### Reproduce v3 component filters

Forward Geocoding in Geocoding v3 included a `components` parameter that enabled
hard filtering of results for specific components (e.g.,
`components=country:US`). Forward Geocoding in v4 does not support this type of
hard filtering in the request parameters. In v4, you can provide address
information as either a single unstructured string in the path or as structured
query parameters like `address.addressLines`, `address.locality`,
`address.administrativeArea`, `address.postalCode`, and `address.regionCode`.
Structured parameters don't act as hard filters. Instead, all the provided
components are combined to form the complete address the API will attempt to
geocode. The API searches for the best match for the *entire address* specified
across all components. Providing components in a structured way can help the API
better understand the intent, especially when the address information is
collected from separate form fields. This can aid the API in handling minor
typos or ambiguities within each component, as the type of information in each
field is clear.

To achieve behavior similar to v3's hard component filters, you must implement
client-side filtering on the `results` array returned by the v4 API based on the
`postalAddress` and `addressComponents` objects in the response.

The following table shows the best match between the v3 `components` filters and
the v4 `postalAddress` fields:

| v3 component filter | v4 response field |
|---|---|
| `country` | `postalAddress.regionCode` |
| `postal_code` | `postalAddress.postalCode` |
| `administrative_area` | `postalAddress.administrativeArea` or `addressComponents` |

#### Client-side filtering examples

The following examples show how to filter results to achieve behavior similar to v3 component filtering for supported fields: country, administrative area, and postal code. It is not recommended to
filter on other fields as reliable filtering criteria does not exist.

##### Filter by Country

Match the `address.regionCode` from your request with the `postalAddress.regionCode` in each result. Note that while the API accepts lowercase region codes in the request, it always returns them in uppercase in the response, so you must normalize your input to uppercase before comparison.
Python Code Example

```python
def filter_by_country(results, region_code):
    return [
        res for res in results
        if res.get('postalAddress', {}).get('regionCode') == region_code.upper()
    ]

# Example usage:
# v4_response = ... # API call response
# filtered = filter_by_country(v4_response.get('results', []), 'US')
```
Node.js Code Example

```javascript
function filterByCountry(results, regionCode) {
    return results.filter(res => res.postalAddress?.regionCode === regionCode.toUpperCase());
}

// Example usage:
// const v4Response = ... # API call response
// const filtered = filterByCountry(v4Response.results, 'US');
```

##### Filter by Administrative Area

Match the `address.administrativeArea` from your request with the `postalAddress.administrativeArea` in each result. Similar to country codes, you should normalize your input to uppercase before comparison. This is only reliable when the `administrativeArea` in your request is provided with a standard 2-letter abbreviation. The `postalAddress.administrativeArea` in the response may not directly match ISO 3166-2 code suffixes for several reasons. Firstly, in some areas the subdivision corresponding the the ISO 3166-2 code is not part of standard representation of postal addresses. For example, in Spain, the administrative area level 1 (e.g., "CN" for Canarias) might be present in the `addressComponents`, but `postalAddress.administrativeArea` might contain the level 2 area (e.g., "Santa Cruz de Tenerife"). Secondly, in some areas the subdivision's
abbreviation is not commonly used and its represented in `postalAddress.administrativeArea`
with a longer name (for similar reasons, `addressComponents.shortText` for address component
corresponding to the subdivision may not match the subdivision's ISO 3166-2 code suffix). Additonally, the subdivision in some cases may be represented at the address component for `administrative_area_level_n` with n greater than 1.

For the most comprehensive results, you should check for a match in both `postalAddress.administrativeArea` and any `addressComponents` with an `administrative_area_level_n` type (e.g. `administrative_area_level_1`).
Python Code Example

```python
def filter_by_admin_area(results, admin_area):
    target = admin_area.upper()
    filtered = []
    for res in results:
        # Check postalAddress
        pa_aa = res.get('postalAddress', {}).get('administrativeArea', '')
        if pa_aa == target:
            filtered.append(res)
            continue
        # Check all administrative area levels in addressComponents
        for comp in res.get('addressComponents', []):
            is_admin_level = any(t.startswith('administrative_area_level_')
                               for t in comp.get('types', []))
            if is_admin_level:
                short = comp.get('shortText', '')
                if short == target:
                    filtered.append(res)
                    break
    return filtered

# Example usage:
# filtered = filter_by_admin_area(v4_response.get('results', []), 'CA')
```
Node.js Code Example

```javascript
function filterByAdminArea(results, adminArea) {
    const target = adminArea.toUpperCase();
    return results.filter(res => {
        // Check postalAddress.administrativeArea
        if (res.postalAddress?.administrativeArea === target) {
            return true;
        }
        // Check addressComponents for any administrative area level
        return res.addressComponents?.some(c => {
            const isAdmin = c.types?.some(t => t.startsWith('administrative_area_level_'));
            return isAdmin && c.shortText === target;
        });
    });
}

// Example usage:
// const filtered = filterByAdminArea(v4Response.results, 'CA');
```

##### Filter by Postal Code

Match the `address.postalCode` from your request with the `postalAddress.postalCode` in each result. You must normalize both the input and result postal codes before comparison. The normalization logic is highly dependent on the region. A basic approach involves removing spaces and hyphens and converting to a consistent case.

More advanced normalization might be needed to handle postal code prefixes (e.g., "L2" in the UK) or suffixes (e.g., "+4" in US ZIP codes). The specific normalization logic required will vary depending on the country and the formats of postal codes involved. You may need to adapt the provided normalization functions or implement more sophisticated logic for the regions you are targeting.

The provided example handle US ZIP codes and allow matching on the 5-digit base even if ZIP+4 is provided or returned.
Python Code Example (US ZIP Code focused)

```python
import re

def normalize_postal_code_us(pc):
    # Basic normalization for US: uppercase, alphanumeric only
    if not pc:
        return ""
    normalized = re.sub(r'[^A-Z0-9]', '', pc.upper())
    # Return only the first 5 digits for US ZIP codes
    return normalized[:5]

def filter_by_postal_code_us(results, postal_code):
    normalized_input = normalize_postal_code_us(postal_code)
    if not normalized_input:
        return []
    filtered_results = []
    for res in results:
        pa_pc = res.get('postalAddress', {}).get('postalCode', '')
        if normalize_postal_code_us(pa_pc) == normalized_input:
            filtered_results.append(res)
    return filtered_results

# Example usage:
# Matches '94043', '94043-1351', '940431351'
# filtered = filter_by_postal_code_us(v4_response.get('results', []), '94043')
# filtered = filter_by_postal_code_us(v4_response.get('results', []), '94043-1234')
```
Node.js Code Example (US ZIP Code focused)

```javascript
function normalizePostalCodeUs(pc) {
    // Basic normalization for US: uppercase, alphanumeric only
    const normalized = (pc || '').toUpperCase().replace(/[^A-Z0-9]/g, '');
    // Return only the first 5 digits for US ZIP codes
    return normalized.substring(0, 5);
}

function filterByPostalCodeUs(results, postalCode) {
    const normalizedInput = normalizePostalCodeUs(postalCode);
    if (!normalizedInput) {
        return [];
    }
    return results.filter(res => {
        const paPc = res.postalAddress?.postalCode || '';
        return normalizePostalCodeUs(paPc) === normalizedInput;
    });
}

// Example usage:
// Matches '94043', '94043-1351', '940431351'
// const filtered = filterByPostalCodeUs(v4Response.results, '94043');
// const filtered = filterByPostalCodeUs(v4Response.results, '94043-1234');
```

## Migrate from v3 Reverse Geocoding

If you use [v3 Reverse
Geocoding](https://developers.google.com/maps/documentation/geocoding/guides-v3/requests-reverse-geocoding)
to turn coordinates into addresses, you should migrate to the v4 [Reverse
geocode a location](https://developers.google.com/maps/documentation/geocoding/reverse-geocoding) method,
which accepts a GET request.

The v4 API changes names, structure, and support for several parameters. We
strongly recommend that you [use a field
mask](https://developers.google.com/maps/documentation/geocoding/choose-fields) to specify the fields you
want returned in the response.

> [!WARNING]
> **Warning:** In all v4 methods, field names are formatted using lower camel case (e.g., addressComponents), in contrast to v3 methods, which used snake case (e.g., address_components). RPC responses continue to use snake case in v4, as they did in v3.

### Request parameter changes

| v3 Parameter | v4 Parameter | Notes |
|---|---|---|
| `language` | `languageCode` | Renamed. When omitted, v3 uses the `Accept-Language` header or defaults to `en`, whereas v4 defaults to the local language and script of the location. |
| `region` | `regionCode` | Renamed. When omitted, v3 defaults to `us`, whereas v4 has no default value. |
| `result_type` | `types` | Renamed; uses repeated query parameters. |
| `location_type` | `granularity` | Renamed; uses repeated query parameters. |
| `extra_computations` | *Removed* | Replaced by [SearchDestinations](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt) method. |

### Response field changes

| v3 Field | v4 Field | Notes |
|---|---|---|
| `status`, `error_message` | *Removed* | [v4 uses HTTP status codes and error bodies](https://google.aip.dev/193#http11json-representation). |
| `results.address_components.long_name` / `results.address_components.short_name` | `results.addressComponents.longText` / `results.addressComponents.shortText` | Renamed. |
| `results.geometry.location_type` | `results.granularity` | Renamed. |
| `results.geometry.location` | `results.location` | Field names: `lat`/`lng` -\> `latitude`/`longitude`. |
| `results.geometry.viewport` | `results.viewport` | Field names: `northeast`/`southwest` -\> `high`/`low`. |
| *New* | `results.addressComponents.languageCode` | Language of the specific address component. |
| *New* | `results.bounds` | Explicit bounds using `high`/`low`. |
| *New* | `results.place` | Resource name for the place. |
| *New* | `results.postalAddress` | Structured `PostalAddress` object. |

## Migrate from v3 Address descriptors

In Geocoding API v3, passing `extra_computations=ADDRESS_DESCRIPTORS` returned an `address_descriptor` object containing `landmarks[]` and `areas[]`. In Geocoding API v4, this capability is replaced by the [`SearchDestinations`](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt) method (`POST /v4/geocode/destinations`), which returns rich spatial context directly within each [`Destination`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#destination) object.

The following table maps legacy v3 `address_descriptor` fields to their v4 `SearchDestinations` equivalents:

| v3 `address_descriptor` Field | v4 `SearchDestinations` Field | Migration Notes |
|---|---|---|
| `landmarks[]` | [`destinations[].landmarks[]`](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt#landmarks) | Request `destinations.landmarks` in your `X-Goog-FieldMask`. |
| `landmarks[].place_id` | `landmarks[].place.placeId` | Wrapped inside the `place` (`PlaceView`) object. |
| `landmarks[].display_name` | `landmarks[].place.displayName` | Localized text (`text` and `languageCode`). |
| `landmarks[].types` | `landmarks[].place.types` | Array of place type strings. |
| `landmarks[].spatial_relationship` | `landmarks[].relationalDescription` | Instead of a raw enum (`ACROSS_THE_ROAD`, `AROUND_THE_CORNER`), v4 returns a localized, human-readable phrase (e.g., `"Across from Central Park"`). |
| `landmarks[].straight_line_distance_meters` | `landmarks[].straightLineDistanceMeters` | Renamed to lower camelCase. |
| `landmarks[].travel_distance_meters` | `landmarks[].travelDistanceMeters` | Renamed to lower camelCase. |
| `areas[]` (`containment` enum) | [`destinations[].containingPlaces[]`](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt#containing-places) \& [`subDestinations[]`](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt#subdestinations) | Closest available equivalent for structural hierarchy: enclosing complexes, campuses, malls, and buildings are returned as structured `PlaceView` objects in `containingPlaces[]` and `subDestinations[]` (with `structureType` set to `GROUNDS` or `BUILDING` and optional `displayPolygon` outlines). Note that there is no direct 1:1 replacement for neighborhood/district proximity areas or the v3 `containment` enum (`NEAR`, `WITHIN`, `OUTSKIRTS`). |

## Migrate from v3 Place geocoding

If you use `place_id` to get the address for a specific Place ID with Geocoding
v3, you must migrate to the v4 [Place
Geocoding](https://developers.google.com/maps/documentation/geocoding/places-geocoding) method, which
accepts a GET request.

The v4 API changes names, structure, and support for several parameters. We
strongly recommend that you [use a field
mask](https://developers.google.com/maps/documentation/geocoding/choose-fields) to specify the fields you
want returned in the response.

> [!WARNING]
> **Warning:** In all v4 methods, field names are formatted using lower camel case (e.g., addressComponents), in contrast to v3 methods, which used snake case (e.g., address_components). RPC responses continue to use snake case in v4, as they did in v3.

### Request parameter changes

| v3 Parameter | v4 Parameter | Notes |
|---|---|---|
| `place_id` | `place` field in request proto | The Place ID is now provided as a path parameter `places/{place}`, for example: `https://geocode.googleapis.com/v4/geocode/places/ChIJj61dQgK6j4AR4GeTYWZsKWw`. This maps to the place field in the underlying request. |
| `language` | `languageCode` | Renamed. When omitted, v3 uses the `Accept-Language` header or defaults to `en`, whereas v4 defaults to the local language and script of the location. |
| `region` | `regionCode` | Renamed. When omitted, v3 defaults to `us`, whereas v4 has no default value. |

### Response field changes

| v3 Field | v4 Field | Notes |
|---|---|---|
| `status`, `error_message` | *Removed* | [v4 uses HTTP status codes and error bodies](https://google.aip.dev/193#http11json-representation). |
| `results` | *(root)* | v4 returns a single result object, not a `results` array. |
| `results.address_components.long_name` / `results.address_components.short_name` | `addressComponents.longText` / `addressComponents.shortText` | Renamed. |
| `results.geometry.location_type` | `granularity` | Renamed. |
| `results.geometry.location` | `location` | Field names: `lat`/`lng` -\> `latitude`/`longitude`. |
| `results.geometry.viewport` | `viewport` | Field names: `northeast`/`southwest` -\> `high`/`low`. |
| `results.postcode_localities` | `postalCodeLocalities` | Renamed. Now returned for one or more localities (v3 required \>1). |
| *New* | `addressComponents.languageCode` | Language of the specific address component. |
| *New* | `bounds` | Explicit bounds using `high`/`low`. |
| *New* | `place` | Resource name for the place. |
| *New* | `postalAddress` | Structured `PostalAddress` object. |

## Migrate from Geocoding Hyperlocal Data to Destinations

The following features in the Geocoding API v3 are being replaced by the
[SearchDestinations](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt)
method of the Geocoding API v4:

- Entrances
- Navigation points
- Building outlines
- Grounds

If you were using the Geocoding API v3 for the above features, use this document
to help you use the `SearchDestinations` method instead to get these features.
This document explains where in the `SearchDestinations` response to find
these features, and differences in how these features are represented in the API
responses between the Geocoding API v3 and the `SearchDestinations` method of
the Geocoding API v4.

> [!WARNING]
> **Warning:** Unlike Geocoding API v3 and other Geocoding API v4 methods, the SearchDestinations method may return results that don't have a precise primary address. If you require only results with precise primary addresses, set `placeFilter.addressability` to `PRIMARY` in your request.

### Entrances

To get the entrances associated with a
[`destination`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#destination),
use the field `destination.entrances`.

Each [`entrance`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#entrance)
in `destination.entrances` has the following fields (replacing the legacy v3 `buildings.entrances` schema):

- `displayName` - a new optional field that contains a human-readable name for the entrance, for example `"Gate B"`.
- `location` - a location of type [`LatLng`](https://developers.google.com/maps/documentation/geocoding/reference/rest/Shared.Types/LatLng) using `latitude` and `longitude` (replacing v3 `lat`/`lng`).
- `tags` - same as the `tags` field from Geocoding API v3 entrances (such as `PREFERRED`).
- `place` - resource name of the place (for example, `places/ChIJ...`), replacing the `building_place_id` field from Geocoding API v3. In v4, this place can represent any destination structure (building, campus, or park), not only a building.

### Navigation points

To get the navigation points associated with a `destination`, use the field
`destination.navigationPoints`.

Each [`navigationPoint`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#navigationpoint)
in `destination.navigationPoints` has the following fields (replacing the legacy v3 `navigation_points` schema):

- `navigationPointToken` - an encrypted token string used to route to this specific navigation point in the [Routes API](https://developers.google.com/maps/documentation/routes) [`ComputeRoutes`](https://developers.google.com/maps/documentation/routes/reference/rest/v2/TopLevel/computeRoutes.md.txt) method ([`Waypoint.navigationPointToken`](https://developers.google.com/maps/documentation/routes/reference/rest/v2/Waypoint.md.txt#FIELDS.navigation_point_token)). See [Route using navigation point tokens](https://developers.google.com/maps/documentation/geocoding/navigation-point-tokens.md.txt).
- `displayName` - a new optional field that contains a human-readable name for the navigation point, for example `"5th Ave"`.
- `location` - a location of type [`LatLng`](https://developers.google.com/maps/documentation/geocoding/reference/rest/Shared.Types/LatLng) using `latitude` and `longitude`.
- `travelModes` - similar to the `restricted_travel_modes` field from Geocoding API v3, except that v4 specifies **acceptable** travel modes (`DRIVE`, `WALK`) rather than restricted travel modes.
- `usage` - a new field specifying the supported arrival use cases for the navigation point (such as `PICKUP`, `DROPOFF`, or `PARKING`).

### Building outlines

To get the building outlines associated with a `destination`, use the
`displayPolygon` field of the
[`placeView`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#placeview)
objects in the `destination` that represent buildings (replacing the legacy v3 `buildings.building_outlines` array). For each `placeView`, check if it is a building with the
[`placeView.structureType`](https://developers.google.com/maps/documentation/geocoding/reference/rest/v4/geocode.destinations/searchDestinations.md.txt#placeview.structuretype)
field. If the structure type is `BUILDING`, you can get the outline from the
`placeView.displayPolygon` field. The `placeView` will also have additional
fields for the building that were not in the Geocoding API v3.

> [!NOTE]
> **Note:** Just looking for the building polygon at a certain location? You can use the `placeFilter` parameter in your request to narrow results. To return only building results, set `structureType` to `"BUILDING"` within `placeFilter`. If getting a polygon is your priority (even for locations without precise addresses), set `addressability` to `"ANY"` in `placeFilter` to return the most precise polygon available. Example
>
> ```json
> {
>   "locationQuery": {
>     "location": {
>       "latitude": 37.4224,
>       "longitude": -122.0841
>     },
>     "placeFilter": {
>       "structureType": "BUILDING",
>       "addressability": "ANY"
>     }
>   }
> }
>   
> ```

A `destination` can have a `placeView` object that represents a building in the
following fields:

- `destination.primary` - the primary place for the destination.
- `destination.containingPlaces` - a repeated field that holds larger places that "contain" the primary place. For example, if the primary place is a `subpremise`, `containingPlaces` usually holds the `placeView` representing the building.
- `destination.subDestinations` - a repeated field that holds sub-destinations of the primary place (for example, individual apartment units or suites within a building).

Note that the format of `placeView.displayPolygon` matches the [building outline
format in the Geocoding API
v3](https://developers.google.com/maps/documentation/geocoding/guides-v3/building-attributes), which is the
GeoJSON format, using the [RFC 7946
format](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6).

### Grounds

Similar to building outlines, to get the grounds associated with a
`destination`, use the `displayPolygon` field of the `placeView`
objects in the `destination` that represent grounds (replacing the legacy v3 `grounds` array). For each `placeView`, check if it is a grounds polygon with the `placeView.structureType` field. If the
structure type is `GROUNDS`, you can get the outline from the
`placeView.displayPolygon` field. The `placeView` will also have additional
fields for the grounds that were not in the Geocoding API v3.

A `destination` can have a `placeView` object that represents a grounds in the
following fields:

- `destination.primary`
- `destination.containingPlaces`
- `destination.subDestinations`

Note that the format of `placeView.displayPolygon` matches the [grounds outline
format in the Geocoding API v3](https://developers.google.com/maps/documentation/geocoding/guides-v3/grounds),
which is the GeoJSON format, using the [RFC 7946
format](https://datatracker.ietf.org/doc/html/rfc7946#section-3.1.6).

### Precision of results

In the Geocoding API v3, the `location_type` field in the response geometry
indicated the precision of the results, and some clients would rank or filter
results based on these values (`ROOFTOP`, `RANGE_INTERPOLATED`,
`GEOMETRIC_CENTER`, and `APPROXIMATE`). In standard Geocoding API v4 migrations,
this field is renamed `granularity`.

In the Destinations API (v4 `SearchDestinations`), there is no `location_type`
field. Instead, spatial information is handled differently:

- **No manual client-side filtering needed** : While standard Geocoding provides multiple results across different granularities, the `SearchDestinations` method minimizes ambiguity by resolving to a single, optimized destination whenever possible. This removes the need for clients to filter by location type to determine which result is best.
- **Spatial information represented by structure type and display polygons** : Spatial geometry and structure are indicated by:
  - The `displayPolygon` (for exact geometry).
  - The `structureType` field within the `placeView` object.
- **Structure type mapping** :
  - A `structureType` of `POINT`, `BUILDING`, or `SECTION` generally corresponds to what was called `ROOFTOP` previously.
  - A `structureType` of `GROUNDS` generally corresponds to `GEOMETRIC_CENTER`.

### Use a field mask to request these features

The [SearchDestinations](https://developers.google.com/maps/documentation/geocoding/search-for-destinations.md.txt)
method requires a field mask, as explained in [Choose fields to
return](https://developers.google.com/maps/documentation/geocoding/choose-fields). The field mask can be set
to `*` in order to return all fields, or you can set it to the specific fields
you want to receive. For example, the following API request sets the field mask
to receive all fields required to get the entrances, navigation points, building
outlines, and grounds of a destination:

```curl
curl -X POST -d '{"place": "places/ChIJG3kh4hq6j4AR_XuFQnV0_t8"}' \
  -H "X-Goog-Api-Key: API_KEY" \
  -H "Content-Type: application/json" \
  -H "X-Goog-FieldMask: destinations.entrances,destinations.navigationPoints,destinations.primary,destinations.containingPlaces,destinations.subDestinations" \
  https://geocode.googleapis.com/v4/geocode/destinations
```

## Common Migration Recipes \& Code Patterns

When migrating complex workflows from Geocoding v3 or Geocoding++ to `SearchDestinations`, use the following code recipes to extract common spatial and address structures.

### Extract the best building polygon

If you want to render the building footprint for a location or destination:

1. Check if the `primary` place has `structureType === 'BUILDING'` and contains a `displayPolygon`.
2. If not, inspect `containingPlaces` to find the enclosing building (useful when the primary destination is a subdestination of a larger building).
3. If no enclosing building is found, check `subDestinations` to see if individual buildings are listed.

Python Code Example

```python
def get_best_building_polygon(destination):
    primary = destination.get('primary', {})
    if primary.get('structureType') == 'BUILDING' and 'displayPolygon' in primary:
        return primary['displayPolygon']

    # Check containing places for the parent building
    for place in destination.get('containingPlaces', []):
        if place.get('structureType') == 'BUILDING' and 'displayPolygon' in place:
            return place['displayPolygon']

    # Fallback to sub-destinations if available
    for place in destination.get('subDestinations', []):
        if place.get('structureType') == 'BUILDING' and 'displayPolygon' in place:
            return place['displayPolygon']

    return None
```
Node.js Code Example

```javascript
function getBestBuildingPolygon(destination) {
    const { primary = {}, containingPlaces = [], subDestinations = [] } = destination;

    if (primary.structureType === 'BUILDING' && primary.displayPolygon) {
        return primary.displayPolygon;
    }

    const parentBuilding = containingPlaces.find(
        p => p.structureType === 'BUILDING' && p.displayPolygon
    );
    if (parentBuilding) {
        return parentBuilding.displayPolygon;
    }

    const subBuilding = subDestinations.find(
        p => p.structureType === 'BUILDING' && p.displayPolygon
    );
    return subBuilding?.displayPolygon || null;
}
```

### Extract grounds outline

If you need the boundary of an apartment complex, corporate campus, shopping center, or park:

1. If the `primary` place represents the entire complex (`structureType === 'GROUNDS'`), use `primary.displayPolygon`.
2. If the `primary` place is a building or entrance located inside a larger complex, find the enclosing grounds in `containingPlaces`.

Python Code Example

```python
def get_grounds_polygon(destination):
    primary = destination.get('primary', {})
    if primary.get('structureType') == 'GROUNDS' and 'displayPolygon' in primary:
        return primary['displayPolygon']

    for place in destination.get('containingPlaces', []):
        if place.get('structureType') == 'GROUNDS' and 'displayPolygon' in place:
            return place['displayPolygon']

    return None
```
Node.js Code Example

```javascript
function getGroundsPolygon(destination) {
    const { primary = {}, containingPlaces = [] } = destination;

    if (primary.structureType === 'GROUNDS' && primary.displayPolygon) {
        return primary.displayPolygon;
    }

    const groundsPlace = containingPlaces.find(
        p => p.structureType === 'GROUNDS' && p.displayPolygon
    );
    return groundsPlace?.displayPolygon || null;
}
```

### Select landmarks for address display versus last-mile arrival

Geocoding v3 `address_descriptor` returned a `spatial_relationship` enum (`WITHIN`, `BESIDE`, `BEHIND`, `NEAR`, `ACROSS_THE_ROAD`, `AROUND_THE_CORNER`). In `SearchDestinations`:

- There is no raw relationship enum. Instead, use `relationalDescription.text` directly. It contains localized, ready-to-display phrases like `"Across from Central Park"` or `"Behind the convention center"`. Don't prepend `"Near "` manually.
- Use `tags` to select the right landmark for your use case:
  - `ARRIVAL`: Hyperlocal landmarks situated immediately adjacent to, facing, or across the street from the destination (typically within 50--100 meters). Best for real-time driver arrival, courier delivery confirmation, or in-app arrival cards.
  - `ADDRESS`: Locally prominent landmarks within the surrounding neighborhood (typically within 100--500 meters). Best for customer-facing checkout, order review, or pre-trip destination disambiguation.
  - If a landmark has both `ADDRESS` and `ARRIVAL` tags, it is both well-known in the neighborhood and immediately adjacent to the destination.

Python Code Example

```python
def get_best_relational_landmark(destination, is_last_mile=True):
    """Select the best landmark based on user context.

    Args:
        destination: Destination object from SearchDestinations.
        is_last_mile: True for arrival and delivery guidance (prefers ARRIVAL tags).
                      False for address display and disambiguation (prefers ADDRESS tags).

    Returns:
        Dict with landmark description, distances, tags, and placeId, or None.
    """
    landmarks = destination.get('landmarks', [])
    if not landmarks:
        return None

    target_tag = 'ARRIVAL' if is_last_mile else 'ADDRESS'

    # Landmarks are ranked by relevance and proximity. Select the first candidate matching the target tag.
    for lm in landmarks:
        tags = lm.get('tags', [])
        if target_tag in tags or ('ARRIVAL' in tags and 'ADDRESS' in tags):
            description = lm.get('relationalDescription', {}).get('text')
            if description:
                return {
                    'description': description,
                    'travelDistanceMeters': lm.get('travelDistanceMeters'),
                    'straightLineDistanceMeters': lm.get('straightLineDistanceMeters'),
                    'tags': tags,
                    'placeId': lm.get('place', {}).get('placeId')
                }

    # Fallback to the first available landmark with a relational description if no tagged match is found
    for lm in landmarks:
        description = lm.get('relationalDescription', {}).get('text')
        if description:
            return {
                'description': description,
                'travelDistanceMeters': lm.get('travelDistanceMeters'),
                'straightLineDistanceMeters': lm.get('straightLineDistanceMeters'),
                'tags': lm.get('tags', []),
                'placeId': lm.get('place', {}).get('placeId')
            }

    return None
```
Node.js Code Example

```javascript
/**
 * Select the best landmark based on user context.
 *
 * @param {Object} destination Destination object from SearchDestinations.
 * @param {boolean} [isLastMile=true] True for arrival and delivery guidance
 *        (prefers ARRIVAL tags). False for address display and disambiguation
 *        (prefers ADDRESS tags).
 * @return {Object|null}
 */
function getBestRelationalLandmark(destination, isLastMile = true) {
    const landmarks = destination.landmarks || [];
    if (!landmarks.length) return null;

    const targetTag = isLastMile ? 'ARRIVAL' : 'ADDRESS';

    // Landmarks are ranked by relevance and proximity. Select the first candidate matching the target tag.
    const match = landmarks.find((lm) => {
        const tags = lm.tags || [];
        return (tags.includes(targetTag) || (tags.includes('ARRIVAL') && tags.includes('ADDRESS'))) &&
               lm.relationalDescription?.text;
    }) || landmarks.find((lm) => lm.relationalDescription?.text);

    if (match) {
        return {
            description: match.relationalDescription.text,
            travelDistanceMeters: match.travelDistanceMeters,
            straightLineDistanceMeters: match.straightLineDistanceMeters,
            tags: match.tags || [],
            placeId: match.place?.placeId || null,
        };
    }

    return null;
}
```

### Route to vehicle navigation points and preferred entrances

Last-mile dispatch and arrival workflows combine two complementary types of spatial access points:

- **Turn-by-turn driving** : Route vehicles to designated road drop-off points using `navigationPoints`. Navigation points are pre-ranked by proximity and relevance, so the first candidate with your desired travel mode (such as `'DRIVE'`) and usage tag (such as `'DROPOFF'` or `'PICKUP'`) provides the optimal vehicle waypoint. Send its `navigationPointToken` to the Routes API [`ComputeRoutes`](https://developers.google.com/maps/documentation/routes/specify_location.md.txt#navigation_point_token) method (`Waypoint.navigationPointToken`) or the [Navigation SDK](https://developers.google.com/maps/documentation/navigation/android-sdk/navigation-points.md.txt). If no road-snapped driving point exists, fall back to the building entrance or primary centroid.
- **Physical doorway access** : Guide the courier on foot or passenger to the building entrance using `destinations.entrances`. Check `tags` for `'PREFERRED'` to find entrances that likely provide direct physical access. If no entrance data is available, fall back to a pedestrian navigation point (`travelModes` including `'WALK'`) or the primary centroid.

Python Code Example

```python
def get_routing_and_arrival_points(destination, usage_tag=None):
    navigation_points = destination.get('navigationPoints', [])
    entrances = destination.get('entrances', [])
    primary_location = destination.get('primary', {}).get('location')

    # 1. Doorstep / Physical Entrance
    # Prefer PREFERRED entrance -> any entrance -> WALK nav point -> primary location
    preferred_entrance = None
    for ent in entrances:
        if 'PREFERRED' in ent.get('tags', []):
            preferred_entrance = ent
            break
    if not preferred_entrance and entrances:
        preferred_entrance = entrances[0]

    walk_nav_point = None
    for np in navigation_points:
        if 'WALK' in np.get('travelModes', []):
            walk_nav_point = np
            break

    if preferred_entrance:
        doorstep_location = preferred_entrance.get('location')
    elif walk_nav_point:
        doorstep_location = walk_nav_point.get('location')
    else:
        doorstep_location = primary_location

    # 2. Vehicle Routing Waypoint
    # Find a driving navigation point. If usage_tag (such as 'DROPOFF' or 'PICKUP')
    # is specified, prioritize a candidate with that usage tag; otherwise fall back
    # to the first available driving point.
    drive_nav_point = None
    fallback_drive_point = None
    for np in navigation_points:
        if 'DRIVE' in np.get('travelModes', []):
            if usage_tag and usage_tag in np.get('usages', []):
                drive_nav_point = np
                break  # Exact match for desired usage tag
            elif fallback_drive_point is None:
                fallback_drive_point = np  # First driving point as fallback

    if not drive_nav_point:
        drive_nav_point = fallback_drive_point

    if drive_nav_point:
        vehicle_location = drive_nav_point.get('location')
        nav_token = drive_nav_point.get('navigationPointToken')
    elif preferred_entrance:
        vehicle_location = preferred_entrance.get('location')
        nav_token = None
    else:
        vehicle_location = primary_location
        nav_token = None

    return {
        'vehicleRoutingWaypoint': {
            'navigationPointToken': nav_token,
            'location': vehicle_location,
        },
        'doorstepLocation': doorstep_location,
    }
```
Node.js Code Example

```javascript
function getRoutingAndArrivalPoints(destination, usageTag = null) {
    const navigationPoints = destination.navigationPoints || [];
    const entrances = destination.entrances || [];
    const primaryLocation = destination.primary?.location || null;

    // 1. Doorstep / Physical Entrance
    const preferredEntrance = entrances.find(ent => ent.tags?.includes('PREFERRED'))
        || entrances[0]
        || null;

    const walkNavPoint = navigationPoints.find(np => np.travelModes?.includes('WALK'))
        || null;

    const doorstepLocation = preferredEntrance?.location
        || walkNavPoint?.location
        || primaryLocation;

    // 2. Vehicle Routing Waypoint
    // Find a driving navigation point. If usageTag (such as 'DROPOFF' or 'PICKUP')
    // is specified, prioritize a candidate with that usage tag; otherwise fall back
    // to the first available driving point.
    const driveNavPoint = (usageTag && navigationPoints.find(np => np.travelModes?.includes('DRIVE') && np.usages?.includes(usageTag)))
        || navigationPoints.find(np => np.travelModes?.includes('DRIVE'))
        || null;

    let vehicleLocation = primaryLocation;
    let navToken = null;
    if (driveNavPoint) {
        vehicleLocation = driveNavPoint.location;
        navToken = driveNavPoint.navigationPointToken;
    } else if (preferredEntrance) {
        vehicleLocation = preferredEntrance.location;
    }

    return {
        vehicleRoutingWaypoint: {
            navigationPointToken: navToken,
            location: vehicleLocation,
        },
        doorstepLocation: doorstepLocation,
    };
}
```

## Security considerations

The Geocoding API v4 is designed as a server-to-server API. There is no direct
migration path for JavaScript users from v3 to v4. Calling the v4 methods
directly from client-side JavaScript (for example, in a browser) using an API
key exposes your API key to a high risk of theft and misuse.

HTTP referrer restrictions, while useful, are not sufficient protection for web
service endpoints as they can be trivially bypassed by attackers who forge the
`Referer` header in their requests.

### Recommended approach

The recommended way to use the Geocoding API v4 is from your own backend server.
Your client application should make requests to this intermediary server, which
then securely calls the Google API using a protected API key (for example, a key
stored in an environment variable or a secret manager). This ensures that your
API key is never exposed in the frontend code.

### Alternates for client-side needs

If you have client-side needs that require geocoding, consider using one of the
existing client-side solutions:

- **Geocoding Service in the Maps JavaScript API:** The [Geocoding
  Service](https://developers.google.com/maps/documentation/javascript/geocoding) continues to use the v3 backend and is designed for use in a browser environment.
- **Places UI Kit:** Use the [Places UI
  Kit](https://developers.google.com/maps/documentation/javascript/places-ui-kit/overview), including [Place Autocomplete
  elements](https://developers.google.com/maps/documentation/javascript/places-ui-kit/basic-autocomplete), for address-related user interface elements.