---
title: "Use location in your app"
description: "Get a coarse location hint from the declared platform's headers and geocode addresses through network, never for access decisions."
section: "guides"
status: "Building for 0.7"
spec: "geo"
canonical: "https://mainsdk.dev/docs/guides/use-geo"
updated: "2026-10-28"
---

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

Status: Building for 0.7 · Canonical: https://mainsdk.dev/docs/guides/use-geo · 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](https://mainsdk.dev/docs/getting-started/thirty-minutes)).
- 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.

```yaml title="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

```ts title="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.

```yaml title="main.yaml"
geo:
  driver: mapbox
  key: secret:MAPBOX_TOKEN
```

```ts title="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

```ts title="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.
