Customizing services
The TomTom Maps SDK provides extensive configuration options to customize service behavior, optimize performance, and handle different application requirements. This guide covers request validation, custom configurations, error handling, and advanced service customization techniques.
Request validation
The SDK includes built-in request validation that verifies each service request adheres to the expected structure before sending it to the API. This validation system helps developers catch errors early and prevents faulty API calls that could result in unnecessary costs.
Benefits of Request Validation:
- Contextual error messages relevant to SDK types (easier debugging)
- Prevention of malformed API calls (cost optimization)
- Early error detection in development
Performance Considerations: If you're confident about your inputs and want to optimize CPU usage, you can disable validation on a per-service basis:
import { search, geocode, calculateRoute } from '@tomtom-org/maps-sdk/services';
// Disable validation for specific services
await search({
query: "Eiffel Tower",
validateRequest: false
});
await geocode({
query: "Amsterdam",
validateRequest: false
});
Recommendation: Keep validation enabled during development and testing phases. Only disable it in production when you're confident about input quality.
Service lifecycle hooks: onAPIRequest and onAPIResponse
The SDK allows you to hook into the service request lifecycle for advanced debugging, logging, or analytics. You can use the onAPIRequest and onAPIResponse callbacks to observe outgoing requests and incoming responses.
- onAPIRequest: Called immediately before the request is sent to the API.
- onAPIResponse: Called as soon as the raw response is received from the API, before parsing.
Example:
await search({
query: "Eiffel Tower",
onAPIRequest: (apiRequest) => {
console.debug('API Request:', apiRequest);
},
onAPIResponse: (apiRequest, apiResponse) => {
console.debug('API Response:', apiResponse);
}
});
These hooks are useful for debugging, analytics, or custom logging. They do not modify the request or response.
Cancelling a request
Every service that takes a parameters object accepts a signal, an
AbortSignal. Aborting it cancels
the underlying HTTP request and rejects the call with an SDKAbortError, whose name is
'AbortError'.
The two single-result convenience helpers are the exception: searchOne and geocodeOne take a
bare query string, so there is nowhere to pass a signal. Use search or geocode with limit: 1
when you need a cancellable single-result lookup.
This matters most when a new request supersedes an older one — a search-as-you-type box, or a search re-run on every map move. Without cancellation, a slow earlier response can arrive after a faster later one and overwrite it.
import { search, SDKAbortError } from '@tomtom-org/maps-sdk/services';
let controller;
const searchForQuery = async (query) => {
// Cancel whatever is still in flight from the previous keystroke
controller?.abort();
controller = new AbortController();
try {
return await search({ query, signal: controller.signal });
} catch (error) {
if (error instanceof SDKAbortError) return; // superseded, not a failure
throw error;
}
};
Aborting also interrupts the SDK while it waits to retry a rate-limited (429) response, so a
cancelled call never sits out a pending backoff.
To put a deadline on a call, compose it onto the same signal — see Deadlines.
Global service configuration
The TomTomConfig class provides centralized configuration management for all SDK services. This approach ensures consistent behavior across your application and simplifies configuration management.
import { TomTomConfig } from '@tomtom-org/maps-sdk/core';
TomTomConfig.instance.put({
apiKey: 'YOUR_API_KEY',
language: 'en-GB', // Default language for all responses
commonBaseURL: 'https://api.tomtom.com', // Custom API base URL for enterprise
retry: { timeoutMs: 40000 } // Budget for retrying rate-limited requests
});
Configuration Options:
- apiKey: Your TomTom API key (required)
- apiVersion: API version to call
- language: ISO language code for response localization
- commonBaseURL: Custom endpoint for enterprise or regional deployments
- trackingId: Identifier echoed on requests for your own tracing
- displayUnits: Unit system used when formatting distances and durations
- retry: Automatic retry of
429responses. See Global configuration
validateRequest is a per-call option rather than a global one — see
Request validation above.
To route service traffic through a proxy that holds your API key server-side — so the key never reaches the browser — see Proxying your API calls.
Comprehensive error handling
Robust error handling is crucial for production applications. The SDK provides structured error responses that help you implement appropriate user feedback and fallback strategies:
import { search, SDKAbortError } from '@tomtom-org/maps-sdk/services';
try {
const results = await search({
query: "coffee",
position: [4.9041, 52.3676]
});
console.log(results);
} catch (error) {
// A cancelled call carries no status, so match it before the status checks
if (error instanceof SDKAbortError) {
return; // superseded by a newer request — not a failure
}
// Handle specific error types
if (error.status === 401) {
console.error('Authentication failed - check API key');
// Redirect to login or show API key configuration
} else if (error.status === 429) {
console.error('Rate limit exceeded - implement retry logic');
// Implement exponential backoff retry
} else if (error.status >= 500) {
console.error('Server error - implement fallback');
// Use cached data or alternative service
} else {
console.error('Request failed:', error.message);
// Show user-friendly error message
}
}
Common Error Codes:
- 400: Bad Request - Invalid parameters or malformed request
- 401: Unauthorized - Invalid or missing API key
- 403: Forbidden - API key lacks required permissions
- 429: Too Many Requests - Rate limit exceeded
- 500: Internal Server Error - Service temporarily unavailable
Deadlines
There is no per-call timeout option. Compose a deadline onto the signal instead, which
lets one call carry both a deadline and your own cancellation:
// Give autocomplete a short deadline — a fast response is expected
await autocompleteSearch({
query: "coffee",
position: [4.9041, 52.3676],
signal: AbortSignal.timeout(3000)
});
// A longer one for complex routing, still cancellable by hand
await calculateRoute({
locations: [[4.9041, 52.3676], [2.3522, 48.8566]],
signal: AbortSignal.any([controller.signal, AbortSignal.timeout(15000)])
});
A deadline that fires rejects with SDKAbortError, the same as a manual abort. Check
error.cause if you need to tell the two apart.
The retry.timeoutMs global setting is unrelated. It caps how long the SDK keeps retrying a
rate-limited (429) response, and is not a limit on any single request. See
Global configuration.
Advanced customization
For advanced use cases, the SDK exposes the customizeService object, which provides low-level access to the internal components of each service. This enables you to deeply customize how requests are built, how responses are parsed, and how the service logic operates.
When to use customizeService:
- You need to transform requests or responses beyond standard SDK options.
- You want to integrate with custom API gateways, proxies, or non-standard endpoints.
- You require custom validation, error handling, or logging at the service layer.
- You need to adapt to new API versions or experimental features before official SDK support.
What does customizeService provide?
- Request builders: Functions to construct API requests with custom logic or parameters.
- Response parsers: Functions to transform or post-process API responses.
- Templates: Configuration objects that define service behavior.
- Validation schemas: Rules for input parameter validation.
Example usage:
import { mergeFromGlobal } from '@tomtom-org/maps-sdk/core';
import { customizeService } from '@tomtom-org/maps-sdk/services';
// Each service exposes its own builder, parser and template under a service-specific name
const { buildGeocodingRequest, parseGeocodingResponse } = customizeService.geocode;
// Request builders expect the global configuration to be merged in already,
// so the API key, base URL and API version are all present
const url = buildGeocodingRequest(mergeFromGlobal({ query: 'Amsterdam' }));
// Send the request yourself, then parse the raw API payload
const apiResponse = await fetch(url);
const places = parseGeocodingResponse(await apiResponse.json());
The member names are service-specific — customizeService.calculateRoute exposes
buildCalculateRouteRequest / parseCalculateRouteResponse / calculateRouteTemplate,
customizeService.reverseGeocode exposes buildRevGeoRequest / parseRevGeoResponse /
reverseGeocodingTemplate, and so on.
Note: Most developers will not need to use
customizeServicedirectly. The standard service functions (likegeocode,search,calculateRoute) are sufficient for typical use cases. Use advanced customization only when you need to modify internal service behavior.
Related guides and examples
Related guides
- Services Quickstart - Basic setup and usage patterns
- Places Services Quickstart - Places-specific configuration
- Routing Services Quickstart - Routing-specific configuration
- Core SDK Quickstart - Core configuration and setup
Configuration examples
- Map Config Playground - Interactive configuration exploration
- Traffic Config Playground - Traffic service customization
- Node.js Examples - Server-side configuration patterns
Advanced topics
- Service Customization - API Reference for advanced service customization options