npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

gmaps-sdk

v0.3.0

Published

TypeScript client for the gmaps.dev places API. Not affiliated with Google.

Readme

gmaps-sdk

Find businesses on the map from your code: bakeries in Curitiba, dentists near a point. Each business is a place, with its name, phones, website, address, rating and a link to it on Google Maps.

Start

Create a key at https://console.gmaps.dev/keys. Then:

npm i gmaps-sdk
import { createClient } from "gmaps-sdk";

const gmaps = createClient({ apiKey: process.env.GMAPS_API_KEY });

const result = await gmaps.places.search({
    keywords: ["bakery"],
    geo: { location: "Curitiba, PR" },
});
console.log(result.places.map((place) => place.identity.name));

That search is free. It answers at once with the places we have already saved for that area. New places come back in Portuguese unless you pass lang: "en". A saved place keeps the language of the search that last collected it.

Guides: https://gmaps.dev/docs/sdk

When we have not saved enough yet

If the saved places do not fill limit (20 by default, up to 120), the search also starts a collection to find the rest, and result.collection is that collection. Each new place it finds costs 1 credit, and it stops at limit, so a search never costs more than limit credits.

A collection takes a few minutes. Watch the new places arrive:

if (result.collection) {
    for await (const frame of gmaps.watchCollection(result.collection.id)) {
        if (frame.type === "place") console.log(frame.data);
    }
}

The loop ends when the collection does. Or run the same search again later: the new places come back, and this time they are free. To wait inside the search itself, pass wait: 60. The answer then holds for up to 60 seconds while the collection works.

When you are out of credits, or already running as many collections as your plan allows, the search still answers with the saved places. result.collection is then null, and result.collectionError says why.

What it costs

  • A place we already saved: nothing. place.meta.cached is true.
  • A new place a collection found for you: 1 credit.
  • Emails for a new place, on plans that include them: 1 more credit per place, only when we find at least one.
  • Searching, quoting, exporting and reading your usage: nothing.
const usage = await gmaps.usage.get();
console.log(usage.creditsRemaining); // 1000

A whole city

For more than one page, ask for a collection. Quote it first: a quote is free and says how many places we have saved there and the most the collection can cost.

const ask = { keywords: ["dentist"], geo: { location: "Curitiba, PR" }, maxPlaces: 500 };

const quote = await gmaps.collections.quote(ask);
console.log(`${quote.placesKnown} places are free. The most it costs: ${quote.maxCost} credits.`);

const collection = await gmaps.collections.create(ask);

A collection is safe to ask for twice. Pass your own key, such as the id of the job that asked, and a second call with that key gets back the same collection. Nothing starts or costs credits twice, even if the first call got no answer:

const collection = await gmaps.collections.create(ask, { idempotencyKey: "job-1042" });

Without a key, the client makes one for each call. That keeps its own retries safe, but not one of yours after your process restarts.

Wait for it to end, then turn its places into a file. The link opens a csv, json or xlsx file without a key, for 24 hours:

for await (const frame of gmaps.watchCollection(collection.id)) {
    if (frame.type === "progress") console.log(frame.data);
}

const file = await gmaps.exports.create({ collectionId: collection.id, format: "csv" });
console.log(file.url);

On a paid plan, a webhook can tell your server when a collection ends, so nothing has to stay connected:

const hook = await gmaps.webhooks.create({ url: "https://example.com/gmaps" });

Store hook.secret: we show it only once. Your server checks every request against it before trusting it. Check the raw body, before any JSON parsing, and await the check:

import { verifyWebhook, type WebhookEvent } from "gmaps-sdk";

const raw = await request.text();
const signature = request.headers.get("x-gmaps-signature");
if (!(await verifyWebhook(process.env.GMAPS_WEBHOOK_SECRET, raw, signature))) {
    return new Response("Not from gmaps.dev", { status: 400 });
}
const event: WebhookEvent = JSON.parse(raw);
if (event.type === "collection.completed") console.log(event.data.collection.stopReason);

stopReason is null when a collection finished cleanly. Otherwise it says why it stopped: daily_cap when you reached today's limit, quota when this month's credits ran out, engine when we could not finish the searching, empty when the searches found nothing.

Before you ship

  • A place is a business listing. It is not permission to contact anyone. Check the law where you and the people you contact are before you send anything.
  • When a business asks us to leave it out, we drop it from every search and export from then on. Files you saved before still have it, so search again instead of reusing an old file.
  • Email addresses come only on paid plans, after your account accepts the acceptable use policy.

Errors

Every error is a GmapsError with a message that says what to do next, plus code, status, requestId, retryAfter (seconds), details, messageKey, params and idempotencyKey.

  • 429 QUOTA_EXCEEDED: details.scope says which limit: day, month or concurrency (too many collections at once). Wait for it to reset, or change plans.
  • 429 RATE_LIMIT_EXCEEDED: too many requests this minute. Retry after retryAfter seconds.
  • 402 PLAN_GATE: the plan does not include that feature. details.upgradeTo names the plan that does.
  • 404 PLACE_NOT_FOUND: we have not saved that place yet. Search its area first.
  • TIMEOUT, NETWORK_ERROR, or a 5xx on a call that is not a read: the call may have run and been charged. Call again only if a second run is fine.
  • The same failure on collections.create: call again with the same idempotencyKey (error.idempotencyKey has it). If the first call started a collection, you get that collection back.
  • 409 CONFLICT on collections.create: that key already started a collection for a different ask. Send the original ask, or use a new key.

Details

createClient() without options calls https://api.gmaps.dev/v1. Pass { baseUrl: "http://localhost:3000/v1" } to call a local API instead. Methods return data, not the response envelope. The list of open-source projects we build on needs no key: createClient().attributions.list().

The package uses standard fetch, has no runtime dependencies, and ships ESM JavaScript and TypeScript declarations. Use Node 22+, Bun, or a browser with AbortSignal.any and AbortSignal.timeout.

Each try at a call times out after 30 seconds, including reading the response body. A search with wait gets its wait plus 15 seconds. Pass { timeoutMs: 5000, signal } to any method to change that for one call.

If a call gets no answer, or an error on our side (a 5xx), the client sends it again about 1 second later, and again about 2 seconds after that. It does this only for calls that are safe to run twice: reads, quotes, coverage checks, cancels, webhook changes and deletes, and a new collection, which every try sends with the same idempotency key. A search, a new export, a new webhook and a webhook test are never sent again this way, because the first try may already have run, and a second could cost credits, send something twice, or make a second export in your list. After a 408, 425 or 429 that asks for a wait of 10 seconds or less, any call waits and goes again. Pass maxRetries: 0 to turn retries off.

The API catalog generates the methods and types. From the repository root, run bun run sdk:gen to update them and bun run sdk:check to check for drift.

gmaps.dev is not affiliated with Google or Alphabet.

O gmaps.dev não tem vínculo com o Google nem com a Alphabet.