📄 SKILL.md

← Vault

name: supabase-management-api-cors-proxy

description: Proxy Supabase Management API calls through TanStack Start server route to avoid browser CORS blocks


Problem

The Supabase Management API (api.supabase.com/v1/projects/...) blocks browser-based fetch calls with CORS, even when the OPTIONS preflight returns 204. POST requests with JSON body fail because the Access-Control-Allow-Origin header is not included in the response.

This happens because:

Step 2: Call from the frontend

`typescript

async function doSomething() {

const res = await fetch("/api/public/your-action", {

method: "POST",

headers: { "Content-Type": "application/json" },

body: JSON.stringify({ someField }),

});

const data = await res.json();

if (!res.ok || data.error) {

return toast.error(data.error || "Erro");

}

return data;

}

`

Why not use Management API directly from browser?

Management API CORS preflight responds with 204 but the actual POST request (with JSON body and Bearer token) gets blocked by the browser's CORS policy. The proxy route avoids this entirely.

Critical Limitation — No Parameterized Queries

The POST /v1/projects/{ref}/database/query endpoint does NOT support $1, $2 etc. parameter placeholders in the params array. All queries are rejected with 42P02: there is no parameter $1.

Always use direct string interpolation with SQL escaping:

`typescript

const safeValue = value.trim().replace(/'/g, "''");

body: JSON.stringify({

query: SELECT ... WHERE token = '${safeToken}' AND name = '${safeName}',

})

`

Tokens (SHA256 hex, 64 chars) are inherently safe from injection. For user text fields, always apply replace(/'/g, "''").

WRONG (silently fails with 400/404):

`typescript

body: JSON.stringify({

query: SELECT ... WHERE token = $1,

params: [token],

})

`

Alternative: Use Supabase REST API with anon key

For tables where RLS allows anonymous inserts, use supabase.from('table').insert(...) directly from the browser without CORS issues. Only use the proxy for Management API calls.