Migrate from Geocoding v3 to v4

European Economic Area (EEA) developers

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.

Migrate from v3 Forward Geocoding

If you use v3 Geocoding to geocode addresses, you should migrate to the v4 Geocode an address 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 to specify the fields you want returned in the response.

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.
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 method.

Response field changes

v3 Field v4 Field Notes
status, error_message Removed v4 uses HTTP status codes and error bodies.
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
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
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
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
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)
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)
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 to turn coordinates into addresses, you should migrate to the v4 Reverse geocode a location 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 to specify the fields you want returned in the response.

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 method.

Response field changes

v3 Field v4 Field Notes
status, error_message Removed v4 uses HTTP status codes and error bodies.
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 method (POST /v4/geocode/destinations), which returns rich spatial context directly within each 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[] 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[] & 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 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 to specify the fields you want returned in the response.

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.
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 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.

Entrances

To get the entrances associated with a destination, use the field destination.entrances.

Each 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 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.

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

Each 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 ComputeRoutes method (Waypoint.navigationPointToken). See Route using navigation point tokens.
  • 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 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 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 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.

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, which is the GeoJSON format, using the RFC 7946 format.

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, which is the GeoJSON format, using the RFC 7946 format.

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 method requires a field mask, as explained in Choose fields to return. 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 -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
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
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
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
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
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
/**
 * 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 method (Waypoint.navigationPointToken) or the Navigation SDK. 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
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
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.

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: