How I log multi-platform ad spend to Airtable with n8n
To pull ad spend from Meta, Google, TikTok, and Taboola into Airtable, you run one n8n flow on a timer. It reads each platform's daily spend, cleans up the numbers, and writes one row per campaign per day. It matches on a saved row id, so running it again just refreshes the spend and never makes copies.
I got tired of opening four ad dashboards every morning and pasting the numbers into one sheet by hand. So I built this flow, and I still run it. Here is the whole thing: why it matters, the usual ways people do it, how mine works, and how to point it at your own accounts.
The problem
Every ad platform keeps its numbers in its own dashboard. None of them were built to talk to each other.
If you run ads on Meta, Google, TikTok, and Taboola, your spend sits behind four logins. To see the whole picture you export four reports, paste them into one spreadsheet, and fix the columns so they line up. It takes twenty minutes every morning, it breaks the week a platform renames a field, and the sheet is already stale by lunch because yesterday's spend keeps moving as the platforms settle the final numbers.
This is common enough that Google added native Meta and TikTok ad-cost imports to Analytics in late 2025, aimed at teams who had been stitching platforms together by hand.
How most teams deal with it today
There are three common ways to bring ad spend together. Each has a catch.
Copy and paste by hand
Free, and it works on day one. But it eats time every morning, a tired person makes typos, and it falls apart the week you add a fifth account or a new platform.
A paid connector
Tools like Supermetrics or Funnel pull the data for you. They are solid, but they bill by the data source every month, the setup is theirs and not yours, and you keep paying whether you use it or not.
A full BI stack
A data warehouse plus a dashboard tool can do everything. It is also weeks of setup and a bill to match, which is a lot if all you really want is daily spend in one table.
The fix
You own it, it costs almost nothing to run, and it bends to whatever you need.
A single n8n flow does the boring part for you. On a timer it visits each platform, reads that day's spend for every campaign, and drops it all into one Airtable table. Run it every hour and the table stays close to live. Here is what it does, in three parts.
Reads daily spend per campaign
On a timer it hits each platform's reporting API and reads spend, impressions, and clicks for the day, campaign by campaign. Meta is the branch that ships in the template. Google, TikTok, and Taboola follow the same shape.
Writes one row per campaign per day
Every campaign becomes a dated Airtable row tagged with a Source field, so FACEBOOK, GOOGLE, TIKTOK, and TABOOLA sit side by side. That one table becomes the source you build a spend dashboard or a blended ROAS view on.
Stays current, never doubles up
It updates rows, it does not add them. Yesterday's spend keeps moving as the platforms finalize numbers, so the flow re-runs and refreshes the same rows. Run it hourly and the table is always close to live.
Want this running on your accounts?
Take the file and set it up yourself, or book a free call and we wire it into your stack for you.
Build it step by step
Every node in the Meta branch, in order, and how to set each one up. The other platforms copy this same shape.
- 1Schedule Trigger · Schedule
Starts the flow on a timer so nobody has to press run.
Set it up: Add a Schedule Trigger and set it to every 60 minutes. That is the only trigger in the flow.
- 2Format date · Code
Works out which day to ask each platform about.
Set it up: Add a Code node. Build today's date as YYYY-MM-DD, plus a PST-adjusted date (subtract 7 or 8 hours for daylight saving) and yesterday's PST date. Return them as fields the later nodes can read.
- 3Get FB Campaigns - Account 1 · HTTP Request
Pulls your list of campaigns from Meta.
Set it up: Add an HTTP Request to the Graph API campaigns endpoint for your ad account id. Ask for name, id, and status, and pass your access token. Duplicate this node per ad account if you run more than one.
- 4Get Airtable records · Airtable (Search)
Reads the rows Airtable already has for today.
Set it up: Add an Airtable node set to Search. Filter by today's Date so the flow knows which campaigns already have a row it can update.
- 5Extract FB campaigns · Code
Keeps the campaigns you care about and links each to its saved row.
Set it up: In a Code node, filter campaigns by your name keywords, then match each Campaign ID to the Airtable rows from the last step to grab its saved record id.
- 6Loop Over FB Campaigns · Split In Batches
Handles one campaign at a time.
Set it up: Add a Split In Batches node with batch size 1. Everything after it runs once per campaign, then loops back for the next one.
- 7Get Spend Data · HTTP Request
Reads that one campaign's spend for the day.
Set it up: Add an HTTP Request to the Meta insights endpoint for the current campaign id. Ask for spend, impressions, and clicks, using the date from the Format date step.
- 8Compile Ads data · Code
Shapes the row and throws out the empty ones.
Set it up: Add a Code node set to run once per item. Attach the spend to the campaign, skip any campaign with zero spend, and add a Source field set to FACEBOOK.
- 9If · If
Lets only real rows through.
Set it up: Add an If node that passes items where campaign_id is set, so nothing empty reaches Airtable.
- 10Airtable3 · Airtable (Upsert)
Writes or updates the daily row.
Set it up: Add an Airtable node set to Upsert. Match on the hidden id column, and map Date, Campaign Name, Campaign ID, Ad Account, Spend, Impressions, Clicks, and Source.
- 11If1 · If
Checks there is a row id before the snapshot.
Set it up: Add an If node that passes items where the record id is present, so the snapshot only runs for saved rows.
- 12Airtable · Airtable (Create)
Saves an intraday snapshot for history.
Set it up: Add a second Airtable node set to Create. Write a timestamped row into a Daily Snapshot table so you can see how spend moved through the day.
What made it tricky?
Three things ate most of the build time. If you rebuild this, they are where it goes wrong first.
The per-campaign loop
I use Split In Batches at size 1 so each campaign reads its own spend and carries its own saved row id into the write. The Compile Ads data node runs once per campaign and matches the spend back to the right campaign by campaign_id. A bulk call would be faster, but it would break that id matching.
Updating instead of duplicating
The Airtable write matches on a hidden id column, not on the name or date. That id is read up front from the existing rows, so a re-run finds the same row and refreshes it. Get this wrong and you get a fresh copy every hour. The search-then-match step is what keeps it clean.
The timezone trap
Ad platforms bill in the account's timezone, not UTC. Near midnight a UTC date grabs the wrong day. My Format date node builds a PST-adjusted date with a -7 or -8 offset for daylight saving, plus yesterday's PST date, so the day it asks for lines up with how the platform reports.
How do I point it at my own accounts?
The Meta branch is the working template. Adding a platform means copying that branch and swapping the two things that change.
| Platform | What you swap | Source tag |
|---|---|---|
| Meta | Graph API campaigns and insights endpoints, your token and ad account id | |
| Google Ads | Google Ads report query, customer id and OAuth login, map cost_micros to spend | |
| TikTok Ads | TikTok Business report endpoint, advertiser id and token | TIKTOK |
| Taboola | Taboola Backstage campaign summary endpoint and account id | TABOOLA |
Every branch ends at the same Airtable table with the same columns: Date, Campaign Name, Campaign ID, Ad Account, Spend, Impressions, Clicks, and Source. To point it at your setup, swap in your base and table ids, connect your Airtable token, and paste each platform's API login into its HTTP Request node. Change the timer if hourly is more or less than you need. If you would rather not touch the API logins yourself, this is exactly the kind of build you can hire an n8n expert to finish. It pairs well with my catch Meta creative fatigue and my triage and reply to Meta ad comments.
Wondering what that costs? See what an n8n expert costs in 2026.
Ad spend tracking questions, straight answers.
How do I log ad spend to Airtable automatically with n8n?
Run an n8n Schedule Trigger on a timer, read each platform's campaigns and their daily spend over the API with an HTTP Request node, clean up the numbers in a Code node, then write one row per campaign per day into Airtable. Match on a saved row id so re-runs update the same row instead of adding a copy.
Why update the row instead of adding a new one?
The flow runs many times a day, and yesterday's spend keeps changing as the platforms settle the final numbers. My Airtable node updates the row when it already exists, matched on a hidden id column, and creates one only when it is missing. That keeps the table current and free of duplicates.
How does the workflow avoid duplicate rows for the same campaign?
Before it touches spend, it reads the day's existing Airtable rows, then a Code node matches each campaign to its saved row id by Campaign ID. That id becomes the match key on the write. The same campaign on the same date lands on the same row every run, so nothing gets duplicated.
How do I handle the ad-platform timezone problem?
Ad platforms report spend in the account's timezone, not UTC, so a plain UTC date grabs the wrong day near midnight. My Format date Code node builds a plain date and a PST-adjusted date (offset -7 or -8 for daylight saving), plus yesterday's PST date, so it asks for the exact day the platform bills against.
Can this pull Google Ads, TikTok and Taboola too, or only Meta?
Yes. The pattern is one fetch, clean, and write branch per platform, tagged with a Source field (FACEBOOK, GOOGLE, TIKTOK, TABOOLA). Each platform has its own reporting endpoint, so you copy the branch and swap the API call and the field mapping. Airtable stays the one destination for all of them.
Why loop over campaigns one at a time instead of all at once?
Spend is read and written per campaign, and the write needs each campaign's own saved row id. I use a Split In Batches node with batch size 1 so every campaign runs its own read, clean, and write cycle. It is slower than one bulk call, but it keeps the id matching and the error handling clean.
How often should the ad spend sync run?
My Schedule Trigger runs every 60 minutes. Because it updates rows instead of adding them, frequent runs are safe and just refresh the same daily rows as the numbers settle. Hourly gives you a near-live table. If you only need a morning report, once a day at your reporting time is plenty.
Want this running on your ad accounts?
30 minutes, free. Bring your platforms and your Airtable base. You leave with a plan to get every campaign's daily spend into one table, whether you build it or we do.
Keep reading.
Catch a tired ad before it wastes money
Every morning it checks each ad against its own past clicks and flags the ones going stale in Slack.
Get the right jobs in front of a candidate
It gathers jobs all day, scores each one for the person, and emails only the roles that really fit.
Read every application in seconds
It scores each applicant against your must-haves, saves everyone for a human, and hands HR the top three.
Rather have it built for you?
Skip the build. A free 30-minute call and we set this up in your stack, live in a week or two.