Supabase has been gaining serious traction as an open-source Firebase alternative. It gives you PostgreSQL, auth, storage, and real-time subscriptions out of the box, and a growing number of teams are building their entire backend on it.
So when a customer asked “Can we feed Supabase with Supermetrics data?” I wanted to find out. Supabase isn’t one of our native destinations like BigQuery or Snowflake, but since it’s PostgreSQL under the hood, there had to be a way.
Turns out there is, and it only takes about 30 minutes to set up.
The approach: Edge Functions + Supermetrics Data API
Supabase offers Edge Functions, which are server-side TypeScript functions that run globally on Deno. They’re built for exactly this kind of use case: calling external APIs, processing the response, and writing to your database, all without exposing secrets to the client.
On the Supermetrics side, the Data API (part of Build On Supermetrics) gives you a single endpoint to pull data from over 100+ marketing platforms. Google Ads, Facebook Ads, LinkedIn, TikTok, GA4: same endpoint, same response format. All you have to do is change a parameter.
Put them together, and the architecture looks like this:
The Edge Function authenticates with the Supermetrics API, pulls the marketing data you’ve specified, parses the response, and inserts rows into a Postgres table. Your API key stays server-side, the function runs at the edge, and your marketing data lands in the same database as the rest of your app.
What you’ll need
- A Supermetrics API key, part of the Build On Supermetrics offering
- At least one data source authenticated in Supermetrics (Google Ads, Facebook Ads, etc.)
- A Supabase project (the free tier works for testing)
No CLI installation, no Docker, no local dev environment needed. Everything below is done from the Supabase Dashboard.
Building it: step by step
Step 1: Create a destination table
In the Supabase SQL Editor, create a table to hold your marketing data. I’m using Google Ads here, but you can adapt the schema for whatever source you’re pulling:
SQL
CREATE TABLE IF NOT EXISTS google_ads (
id BIGSERIAL PRIMARY KEY,
date DATE,
campaign_name TEXT,
impressions BIGINT,
clicks BIGINT,
cost NUMERIC(12,4),
fetched_at TIMESTAMPTZ DEFAULT NOW()
);
Step 2: Store your API key as a secret
Go to Edge Functions → Manage Secrets and add:
- SUPERMETRICS_API_KEY with your API key from the Supermetrics Hub
You don’t need to add SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY because those are automatically available in Edge Functions.
Step 3: Write the Edge Function
Go to Edge Functions → Deploy a new function → Via Editor. The entire function is under 50 lines:
TypeScript
import { createClient } from "https://esm.sh/@supabase/supabase-js@2";
Deno.serve(async (req) => {
try {
const API_KEY = Deno.env.get("SUPERMETRICS_API_KEY");
// Call the Supermetrics Data API
const smResponse = await fetch(
"https://api.supermetrics.com/enterprise/v2/query/data/json",
{
method: "POST",
headers: {
"Content-Type": "application/json; charset=utf-8",
"Authorization": `Bearer ${API_KEY}`,
},
body: JSON.stringify({
ds_id: "AW", // Google Ads
ds_accounts: "list.all_accounts", // all connected accounts
start_date: "2025-12-01",
end_date: "today",
fields: "Date,CampaignName,Impressions,Clicks,Cost",
max_rows: 1000,
}),
}
);
// Check for API errors before parsing
if (!smResponse.ok) {
const errText = await smResponse.text();
return new Response(
JSON.stringify({ error: "SM API error", body: errText }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
const smData = await smResponse.json();
const rows = smData?.data ?? [];
// First row is headers, rest is data
const dataRows = rows.slice(1).map((row) => ({
date: row[0],
campaign_name: row[1],
impressions: parseInt(row[2]) || 0,
clicks: parseInt(row[3]) || 0,
cost: parseFloat(row[4]) || 0,
}));
// Insert into Supabase
const supabase = createClient(
Deno.env.get("SUPABASE_URL"),
Deno.env.get("SUPABASE_SERVICE_ROLE_KEY")
);
const { error } = await supabase
.from("google_ads").insert(dataRows);
if (error) {
return new Response(
JSON.stringify({ error: "Insert failed", details: error }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}
return new Response(
JSON.stringify({ success: true, rows_inserted: dataRows.length }),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
} catch (err) {
return new Response(
JSON.stringify({ error: String(err) }),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
});
Step 4: Deploy and test
Click Deploy, then use the built-in tester to send a POST request. A successful response looks like this:
Response
{ "success": true, "rows_inserted": 432 }
Check your Table Editor and the google_ads table should now have real campaign data.
✓ Result
In my test, 432 rows of Google Ads campaign data (impressions, clicks, cost) landed in Supabase in about 1.2 seconds, including the round-trip to the Supermetrics API.
Switching Data sources
This is where the Data API really shines. To switch platforms, just change the ds_id parameter. The endpoint and response format stay exactly the same.
| ds_id | Data Source | Example Fields |
|---|---|---|
| AW | Google Ads | Date, CampaignName, Impressions, Clicks, Cost |
| FA | Facebook Ads | Date, campaign_name, Impressions, Clicks, Cost |
| GAWA | Google Analytics 4 | Date, Sessions, Users, Pageviews |
| LI | LinkedIn Ads | Date, CampaignName, Impressions, Clicks, Cost |
| TA | TikTok Ads | Date, CampaignName, Impressions, Clicks, Spend |
| IGI | Instagram Insights | Date, Followers, Impressions, Reach |
Change ds_id from "AW" to "FA" and you’re pulling Facebook Ads instead. You could create one Edge Function per source, or build a single function that accepts the data source as a parameter.
Note: field IDs vary between platforms. For example, Facebook Ads uses campaign_name (lowercase) while Google Ads uses CampaignName. If you switch sources, update the field mapping in the Edge Function to match.
Automating it with pg_cron
A manual trigger is fine for testing, but for production you’ll want this running on a schedule. Supabase supports pg_cron. Enable it in Database → Extensions, then run:
SQL
SELECT cron.schedule(
'daily-google-ads-sync',
'0 6 * * *', -- Every day at 6 AM UTC
$$
SELECT net.http_post(
url := 'https://<project-ref>.supabase.co/functions/v1/supermetrics-sync',
headers := jsonb_build_object(
'Authorization', 'Bearer ' || '<service-role-key>'
),
body := '{}'::jsonb
);
$$
);
Now, your marketing data refreshes every morning before anyone opens their laptop.
Debugging tips
While I was very happy with the results, I did hit a few stumbling blocks during testing. Here’s what to watch for (and how to fix them):
⚠ QUERY_ACCOUNT_UNAVAILABLE
This was the most common error. It means the data source account hasn’t been authenticated in Supermetrics, or the API key doesn’t have access to it. Use ds_accounts: "list.all_accounts" to discover available accounts, and check /enterprise/v2/ds/logins to verify your connected logins.
Facebook Ads permission errors. Even with a valid Supermetrics login, you might see “User may not have permissions to access account.” This is a Meta Business Manager issue. The Facebook user needs at least Analyst-level access on the ad account.
401 on curl testing. Make sure you’re using the Supabase project API key (eyJhbG...), not your personal access token (sbp_...). The easiest option is to use the built-in Dashboard tester, which handles auth automatically.
Can’t see error details in logs? Supabase logs don’t always surface response bodies. A good workaround is to temporarily return all debug info in the response body with status 200 so you can read it in the tester. Once everything works, switch back to proper error handling.
Limitations to keep in mind
- Execution time: Edge Functions have a timeout (typically 60s on the free tier). For large data pulls, use the Supermetrics API's async query pattern with sync_timeout: 0, which submits the query as a job and lets you poll for results.
- Maintenance: The customer owns this code. If the API response format changes, the function will need updating.
- Not real-time: This is pull-based. Data is fetched on demand or on a schedule, not streamed continuously.
- For heavy workloads: Our native destinations (BigQuery, Snowflake, Redshift) handle schema management, incremental loads, and backfills automatically. For high-volume production, it may be better to land data there first and sync to Supabase from the warehouse.
Wrapping up
Supabase and the Supermetrics Data API make a solid combination for teams that want marketing data alongside their app data. One Edge Function, one secret, 30 minutes. And since the Data API covers over 100 platforms through a single endpoint, you’re not locked into one source.
If you’re interested in building on Supermetrics, whether that’s feeding Supabase, powering a custom dashboard, or wiring up AI workflows on marketing data, check out Build On Supermetrics or dive into the API docs.
Questions? Feel free to reach out. Always happy to help debug an Edge Function or two.