MainSDK
Search⌘K
How-to

Use location in your app

Get a coarse location hint from the declared platform's headers and geocode addresses through network, never for access decisions.

Building for 0.7

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

You will read a location hint for a default, geocode an address and see what MainSDK refuses. It takes about 15 minutes. The hint is for defaults only: a place guessed from a network address is wrong for people on a VPN or roaming, so it never decides who gets in.

You need

  • A MainSDK app with a main.yaml (see Thirty-minute path).
  • For geocoding, a Mapbox or Google Maps Platform key kept in your secrets manager.

1. Declare your platform

geo.of reads the headers of one platform, the one your app declares. Set deploy.target to vercel, cloudflare or fly. On Docker or Node behind CloudFront, set geo.edge instead.

main.yaml
deploy:
  target: vercel
geo:
  driver: platform-headers

Headers from any other platform are ignored, so a visitor cannot give your app a place by sending them. Fly sends no country header, so set x-mainsdk-country from your own GeoIP step.

2. Use the hint for a default

app/api/defaults/route.ts
import { geo } from "mainsdk/geo";

export async function GET(request: Request) {
  const { country, timezone } = geo.of(request);
  return Response.json({ currency: country === "IN" ? "INR" : "USD", timezone: timezone ?? "UTC" });
}

The hint holds a country, region, city and time zone, each checked, and never an IP address. When the platform sends nothing the fields are missing, so always have a fallback.

3. Geocode an address

Switch the driver and add the key as a secret reference.

main.yaml
geo:
  driver: mapbox
  key: secret:MAPBOX_TOKEN
app/checkout/actions.ts
"use server";
import { geo } from "mainsdk/geo";

export async function placeFor(address: string) {
  return geo.geocode(address);   // a Place, or null
}

The address goes through network to a host that serves the tenant's region, and the place is cached for the tenant. Your code does not change when you swap mapbox for google-maps. When the provider is down you get null: show the typed address and carry on. A tenant in a region the driver does not serve fails with MAIN-GEO-0004 before anything is sent.

4. Keep the hint out of access

Do not pass the hint to policy, access or auth.require. They throw MAIN-GEO-0001, and mainsdk check reports MAIN-GEO-0002 for geo.of used in access code. For country rules on protected files use protect policy.

5. Test it with no network

app/checkout/geo.test.ts
import { createTestApp } from "@mainsdk/testing";
import { geo } from "mainsdk/geo";

createTestApp({ yaml: "schema: 1\ngeo:\n  driver: local\n" });
const place = await geo.geocode("1 Main St, Springfield");   // from the kit's table, no network

Check it worked

Run npx mainsdk check. It passes when geo.driver is one of platform-headers, mapbox or google-maps, a provider driver has key: secret:NAME, a platform is declared where geo.of is used, and no access code reads the hint.

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet