googlemaps-kit
v0.4.0
Published
TypeScript SDK + Bubble Tea TUI for Google Maps consumer surfaces
Maintainers
Keywords
Readme
googlemaps-kit
TypeScript SDK for programmatic access to Google Maps consumer surfaces.
Site: googlemapskit.vaandeetttt.com · npm: googlemaps-kit
Agent bootstrap (Cursor / Claude / Codex): use Copy Agent Instruction on the site, or:
npx skills add KumarVandit/googlemaps-kit --skill googlemaps-kit --agent '*'Overview
Intent-first client for search, places, reviews, directions, photos, geocoding, tiles, and more. No Maps Platform API key. No browser dependency — works out of the box from Node 18+ with automatic client initialization.
Disclaimer
Not an official Google product. Endpoints are undocumented and can change, throttle, or block without notice. Intended for research and personal tooling, not bulk scraping.
How it works
- Zero config —
sdk()initializes automatically; no API keys or manual setup. - Same surfaces as Maps — Search, place details, reviews, directions, and photos use the same HTTP and RPC paths as the Maps web app, merged into typed results.
- Intent + namespaces — call
discover/profile/routefor common flows, or use domain namespaces (places,travel,map, …) for full control. - No headless browser — except
passiveAssist, which needs a viewport token you supply externally.
Quick start
npm install googlemaps-kitimport { sdk } from 'googlemaps-kit';
const maps = sdk({
locale: { hl: 'en', gl: 'in' },
});
const { places } = await maps.discover({
query: 'cafes in indiranagar',
near: { lat: 12.98, lng: 77.64 },
});
const top = places[0]!;
const { place } = await maps.profile(top, { depth: 'card' });
const reviews = await maps.opinions(top, { pages: 1 });
console.log(place.name, place.rating, reviews.reviews.length, 'reviews');Package layout
| Import | Use for |
|--------|---------|
| googlemaps-kit | Apps & agents — sdk(), Intent API, namespaces, result types |
| googlemaps-kit/advanced | Under the hood — HTTP client, protobuf builders, RPC, parsers |
Intent API
| Method | Input | Output | Latency notes |
|--------|-------|--------|---------------|
| discover({ query, near }) | query + coords (near or location) | { places, timingMs, mode, pagination } | Default mode:'fast' ~400 ms; pass offset to paginate |
| discoverPages(…) | same + maxPages | async iterable of DiscoverResult | Streams pages; dedupes across pages |
| resolve({ query \| url, near? }) | text or URL | { hexId?, name?, lat?, lng?, source } | Identity only — check hexId before profile |
| profile(ref, { depth? }) | PlaceRef | { place, depth, reviews?, … } | Use place.name (not top-level .name) |
| profileMany(refs) | PlaceRef[] | PlaceProfile[] | Bounded concurrency + onProgress |
| route({ from, to }) | coords / address / PlaceRef | DirectionsResult | Default metrics only |
| opinions(ref) | PlaceRef | ReviewsResult | reviewCount = page size; totalReviews needs aggregates |
| opinionsPages(ref) | PlaceRef | async iterable of review pages | Streams Boq pages |
| media(ref) | PlaceRef | { photos[], photoCount, nextPageToken? } | Flat PlacePhoto[] |
| mediaMany(refs) | PlaceRef[] | MediaResult[] | Bounded concurrency |
| pipeline({ discover, … }) | discover + optional profile/opinions | enriched rows | One-shot lead scrape |
| tools() | — | agent tool map | Same as createMapsTools(maps) |
| capabilities() | — | capability flags | Async; cookie presence only |
Common mistakes
| Mistake | Do this instead |
|---------|-----------------|
| const p = await maps.profile(…); p.name | p.place.name |
| route({ from: hexId }) | Pass coords or address — bare ids need lat/lng |
| reviews.reviewCount as place total | Use totalReviews with includeAggregates: true |
| place.photos[0].normalizedUrl after profile | Profile photos are URL strings; use media() for PlacePhoto |
| session: 'authenticated' without cookies | Throws at create — cookies are the real gate |
Namespaces
| Namespace | Contains |
|-----------|----------|
| maps.places | search, suggest, details, get(), reviews, photos, knowledge, localPosts |
| maps.location | geocode, timezone, reveal, passiveAssist |
| maps.travel | directions, distanceMatrix, elevation, transit, traffic |
| maps.map | tiles, staticMap, panorama |
| maps.meta | categories, ugcAggregates, lists, links, batchUrl |
| maps.agent | ask(), askMaps (signed-in) |
| maps.auth | status(), summarize() |
| maps.surfaces | list(), working(), get(name) |
await maps.places.search.searchText({ query: 'coffee', near, mode: 'fast' });
await maps.travel.directions.get({ origin: 'A', destination: 'B' });
await maps.location.geocode.geocode('HSR Layout, Bengaluru');Auth tiers
| Tier | How | Unlocks |
|------|-----|---------|
| Anonymous (default) | Session warms itself | Search, places, Boq reviews, photos, directions, traffic, categories |
| Authenticated | cookies / GMAPS_COOKIES | Ask Maps, reviews source:'rpc', private lists |
Signed-in surfaces throw AuthRequiredError when cookies are missing.
Configuration
sdk() loads .env.local then .env from the working directory.
| Option | Env var | Default | Description |
|--------|---------|---------|-------------|
| locale.hl / hl | GMAPS_HL | en | Language |
| locale.gl / gl | GMAPS_GL | us | Region |
| session | — | anonymous | Capability profile |
| cookies | GMAPS_COOKIES | — | Optional signed-in cookie string |
| performance.mode | — | fast | Default discover() search mode |
| requestDelayMs | GMAPS_REQUEST_DELAY_MS | 0 | Min delay between request starts |
| concurrency | GMAPS_CONCURRENCY | 6 | Max parallel in-flight requests |
| debug | GMAPS_DEBUG | false | Log requests/responses |
| hooks | — | — | onAction / onRetry / onError lifecycle callbacks |
| cache | — | off | Optional TTL cache for discover / profile (card) |
DX extras
const maps = sdk({
hooks: {
onAction: ({ type, status, durationMs }) => console.log(type, status, durationMs),
},
cache: { ttlMs: 60_000 },
});
// cancel in-flight work
const ac = new AbortController();
await maps.discover({ query: 'coffee', near, signal: ac.signal });
// stream pages
for await (const page of maps.discoverPages({ query: 'coffee', near, maxPages: 3 })) {
console.log(page.places.length, page.pagination.hasMore);
}
// batch + pipeline
await maps.profileMany(hits, { concurrency: 4, onProgress: console.log });
await maps.pipeline({ discover: { query: 'coffee', near }, maxPlaces: 5, profile: { depth: 'card' } });
// agent tools
const tools = maps.tools(); // or createMapsTools(maps)
await tools.discover.execute({ query: 'coffee', nearLat: near.lat, nearLng: near.lng });CLI (pretty tables on TTY, JSON when piped) + interactive TUI:
# Interactive (Bubble Tea TUI)
npx googlemaps-kit
npx googlemaps-kit tui
# Scripted
npx googlemaps-kit discover "cafes in indiranagar" --near 12.98,77.64 --limit 5
npx googlemaps-kit resolve --query "Cubbon Park Bangalore"
npx googlemaps-kit profile --query "Third Wave Coffee Indiranagar" --near 12.98,77.64
npx googlemaps-kit route --from "Cubbon Park, Bangalore" --to "Indiranagar, Bangalore"
npx googlemaps-kit opinions --query "Third Wave Coffee Indiranagar" --near 12.98,77.64
npx googlemaps-kit media --query "Third Wave Coffee Indiranagar" --near 12.98,77.64
npx googlemaps-kit pipeline "cafes" --near 12.98,77.64 --max 3TUI is built with Bubble Tea (TypeScript port). Use --json / --format json|csv|geojson for machine output. Export helpers: toCsv(places), toGeoJSON(places).
Advanced (under the hood)
When you need protobuf pb= builders, batchexecute RPC IDs, or raw parsers:
import {
HttpClient,
buildSearchPb,
extractBusinesses,
BATCH_EXECUTE_PATH,
} from 'googlemaps-kit/advanced';Organized modules: HTTP transport, auth/session, RPC/protobuf, parsers, service classes.
Limitations
- Undocumented consumer surfaces; no stability guarantee.
- Signed-in features need cookies you supply — the kit does not perform Google login.
passiveAssistneeds a viewportpsiyou supply externally.- Map tiles: 256px roadmap layer verified; other layers may return HTTP 400.
Development
npm test
npm run build
npm run examples:all
npm run verify:allWebsite: googlemapskit.vaandeetttt.com
License
MIT
