Google Places Proxy — Supabase Edge Function Setup

Agent Builder

google-places-proxy Edge Function

This Edge Function proxies all Google Places API calls server-side — the API key is stored as a Supabase secret and never sent to the browser. Required for the Place Search flow node to work in production.

API key never in browser Deno Edge Runtime CORS-safe
How It Works

Browser
chat.html
Sends search query
(no API key)

Edge Function
google-places-proxy
Injects API key
from Supabase secret

Google Places
maps.googleapis.com
Receives full
authenticated request
The browser only ever calls your Supabase project URL with the anon key (already public by design). The Google Places API key is read from your platform_settings database table using SUPABASE_SERVICE_ROLE_KEY (auto-injected by Supabase) — completely server-side, never visible in network logs. Update the key any time from Superadmin, no redeployment needed.
Proxy Endpoints
All calls are POST /functions/v1/google-places-proxy with a JSON body.
endpoint fieldRequired body paramsMaps to Google API
"autocomplete" input (string)
optional: types, components, sessiontoken
place/autocomplete/json
"details" place_id (string)
optional: fields, sessiontoken
place/details/json
"test" none Health check — returns {"ok":true,"status":"KEY_VALID"}
If you deployed from the Supabase Dashboard editor — one more step required.
The dashboard editor deploys the function but does not disable JWT verification by default. This causes every browser call to fail with a CORS/401 error ("Failed to fetch").

Fix it in the Supabase Dashboard:
  1. Go to Edge Functions in your Supabase dashboard
  2. Click on google-places-proxy
  3. Click the ⚙ Settings (or Details) tab
  4. Toggle "Verify JWT"OFF
  5. Save — then click Test Proxy Function below again
No secrets needed — key is read from your database.
The Edge Function reads google_places_api_key from your platform_settings table at runtime using SUPABASE_SERVICE_ROLE_KEY (auto-injected by Supabase into every Edge Function — you don't set it).

To update your key: Superadmin → Platform Settings → Google Places API Key → Save. No redeployment needed.
Deploy Steps
Run these commands once from your local machine. Recommended — sets JWT flag automatically. No secrets to set.
1

Install Supabase CLI (if not already)

Terminal
# macOS
brew install supabase/tap/supabase

# Windows (scoop)
scoop bucket add supabase https://github.com/supabase/scoop-bucket.git
scoop install supabase

# npm (any platform)
npm install -g supabase
2

Log in & link your project

Terminal
supabase login
supabase link --project-ref vjdxsvxznkeiorpphbkw
3

Deploy — --no-verify-jwt is critical

Terminal
supabase functions deploy google-places-proxy --no-verify-jwt
--no-verify-jwt lets the chatbot call the function with just the anon key — no user login required. Without this flag every call fails with 401 → "Failed to fetch".
4

Save your API key in Superadmin

Go to Superadmin → Platform Settings → Google Places API Key, paste your key and click Save Google Places Key. The Edge Function reads it from the database — no redeployment needed when you change the key.

5

Test the deployment below ↓

Use the Live Test panel below to confirm the function is reachable and the key works.

Live Test
Testing URL:

Test Edge Function


Live Autocomplete Test

Edge Function Source
The function is already in your project at supabase/functions/google-places-proxy/index.ts. You don't need to create it manually — only needed if deploying from the Dashboard editor.
supabase/functions/google-places-proxy/index.ts
// Proxies Google Places API
// API key read from platform_settings table — no secrets needed
// Deploy: supabase functions deploy google-places-proxy --no-verify-jwt

// ⚠️ NO import needed — uses built-in Deno.serve()
// (avoids deno.land/std version mismatch errors)

let _cachedKey = null;

async function getPlacesKey() {
  if (_cachedKey) return _cachedKey;
  const sbUrl      = Deno.env.get("SUPABASE_URL") ?? "";
  const serviceKey = Deno.env.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
  const r = await fetch(`${sbUrl}/rest/v1/platform_settings?id=eq.global&select=google_places_api_key&limit=1`,
    { headers: { apikey: serviceKey, Authorization: `Bearer ${serviceKey}` } }
  );
  const rows = await r.json();
  const key  = rows?.[0]?.google_places_api_key ?? "";
  if (!key) throw new Error("google_places_api_key is empty in platform_settings");
  _cachedKey = key;
  return key;
}

// ✅ Deno.serve() — modern built-in, no import needed
Deno.serve(async (req) => {
  const cors = { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type, Authorization, apikey" };
  if (req.method === "OPTIONS") return new Response(null, { status: 204, headers: cors });
  const body       = await req.json();
  const PLACES_KEY = await getPlacesKey();

  // endpoint: "autocomplete" | "details" | "mapembed" | "test"
  if (body.endpoint === "autocomplete") { /* proxies autocomplete/json */ }
  if (body.endpoint === "details")      { /* proxies place/details/json */ }
  if (body.endpoint === "mapembed")     { /* returns Maps Embed URL server-side */ }
  if (body.endpoint === "test")         { /* health check */ }
});
Deployment Checklist