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.
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.POST /functions/v1/google-places-proxy with a JSON body.| endpoint field | Required body params | Maps 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"} |
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).# 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
supabase login
supabase link --project-ref vjdxsvxznkeiorpphbkw
--no-verify-jwt is criticalsupabase 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".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.
Use the Live Test panel below to confirm the function is reachable and the key works.
supabase/functions/google-places-proxy/index.ts. You don't need to create it manually — only needed if deploying from the Dashboard editor.// 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 */ } });