ブログのコンテンツは現在英語でご利用いただけます。翻訳は近日公開予定です。
A Google Sheet Template for Tracking Creative Fatigue Across 50+ Campaigns
Lucas Weber
Creative Strategy Director
Here's a pattern every media buyer recognizes but few track systematically: your winning ad slowly stops winning. CPA creeps up. CTR drifts down. You notice it a week too late, after burning $2,000 on a creative that peaked 10 days ago.
That's creative fatigue. And if you're managing 50+ campaigns, tracking it manually in Ads Manager is effectively impossible. You need a system.
This article gives you a 4-tab Google Sheet template for tracking creative fatigue, the exact signals to monitor, and three ways to automate the data — from simple CSV exports to a Meta API integration with Google Apps Script.
What Creative Fatigue Actually Looks Like (With Numbers)
Creative fatigue isn't a vague concept. It's measurable, and the thresholds are surprisingly consistent across verticals.
The numbers, based on aggregate data across performance campaigns:
- Frequency > 3.0 (prospecting): CTR drops ~40% on average compared to the first week. At frequency 5.0+, you're paying for irritation, not awareness.
- Frequency > 5.0 (retargeting): Even warm audiences degrade. Past 5.0, you're showing the same ad to the same person 5+ times in your lookback window. Diminishing returns is an understatement.
- Week-over-week CTR decline > 15%: If CTR drops more than 15% from one week to the next with stable spend, the creative is dying. Not "underperforming" — dying.
- CPA increase > 30% vs. first-week baseline: This is the money signal. When CPA inflates 30%+ above the creative's first full week of delivery, the economics have shifted.
The critical insight: CPA increase + frequency increase = fatigue. CPA increase + stable frequency = something else (audience saturation, seasonal shift, competition). Don't kill a creative for the wrong reason.
The Template: 4 Tabs, One System
Here's the Google Sheet structure. Each tab serves a specific purpose. I'll walk through the columns, the logic, and the conditional formatting.
Tab 1: Active Creatives Dashboard
This is your overview — one row per creative, color-coded by health status.
| Column | Content | Notes |
|---|---|---|
| A: Creative ID | Meta ad ID | Unique identifier |
| B: Creative Name | Descriptive name | Use a naming convention: [offer]-[variant]-[format]-[date] |
| C: Campaign | Campaign name | For filtering |
| D: Account | Ad account name | For multi-account tracking |
| E: Launch Date | Date creative went live | For calculating age |
| F: Days Active | =TODAY()-E2 | Auto-calculated |
| G: Status | Active / Watch / Replace / Paused | Manual or formula-driven |
| H: Current Frequency (7d) | Last 7-day frequency | From Tab 2 |
| I: Current CTR (7d) | Last 7-day CTR | From Tab 2 |
| J: CTR Change WoW | =(I2-[last week CTR])/[last week CTR] | Week-over-week % |
| K: Current CPA | Last 7-day CPA | From Tab 2 |
| L: CPA vs. Baseline | =(K2-[week 1 CPA])/[week 1 CPA] | % change from first week |
| M: Fatigue Score | Composite (see below) | 0-10 scale |
| N: Next Action | Replace / Iterate / Monitor | Decision output |
Fatigue Score formula (column M):
The score is a weighted composite of three signals:
= MIN(10, (
IF(H2>3, 3, IF(H2>2, 1, 0)) +
IF(J2<-0.15, 3, IF(J2<-0.08, 1, 0)) +
IF(L2>0.30, 4, IF(L2>0.15, 2, 0))
))
- Frequency weight: 0-3 points
- CTR decay weight: 0-3 points
- CPA inflation weight: 0-4 points (heaviest — this is the money metric)
Conditional formatting:
- Fatigue Score 0-3: Green background (healthy)
- Fatigue Score 4-6: Yellow background (watch)
- Fatigue Score 7-10: Red background (replace immediately)
Tab 2: Weekly Tracking Log
This is the raw data tab. One row per creative per week.
| Column | Content |
|---|---|
| A: Creative ID | Meta ad ID |
| B: Week | Week number or date range (e.g., 2026-W08) |
| C: Impressions | Total impressions for the week |
| D: Reach | Total unique reach for the week |
| E: Frequency | =C2/D2 (or from Meta directly) |
| F: Clicks | Link clicks |
| G: CTR | =F2/C2 |
| H: Conversions | Purchase/lead count |
| I: CPA | =[Spend]/H2 |
| J: Spend | Total spend for the week |
| K: Revenue | If available (postback/server tracking) |
| L: ROAS | =K2/J2 |
| M: CPM | =(J2/C2)*1000 |
Sort by Creative ID, then by Week descending. This gives you a time series per creative that feeds into Tab 1's calculations.
Key: The first row for each creative (the earliest week with meaningful data — at least 1,000 impressions) becomes the baseline. Tab 1's "CPA vs. Baseline" column references this row.
Tab 3: Fatigue Alerts
Auto-calculated tab that filters creatives meeting alert thresholds. Use Google Sheets' FILTER or QUERY functions.
| Column | Content |
|---|---|
| A: Creative ID | Auto-populated from Tab 1 where Fatigue Score >= 5 |
| B: Creative Name | Lookup from Tab 1 |
| C: Alert Type | "High Frequency" / "CTR Decay" / "CPA Inflation" / "Multiple Signals" |
| D: Fatigue Score | From Tab 1 |
| E: Days Since Launch | From Tab 1 |
| F: Current CPA | From Tab 1 |
| G: Baseline CPA | From Tab 2 (first week) |
| H: Estimated Daily Waste | =(F2-G2) * [daily conversions] — rough cost of running a fatigued creative |
| I: Priority | =IF(H2>50, "URGENT", IF(H2>20, "HIGH", "MEDIUM")) |
| J: Suggested Action | Based on signal type (see decision framework below) |
The FILTER formula for column A:
=FILTER('Active Creatives'!A:A, 'Active Creatives'!M:M >= 5)
Tab 4: Creative Rotation Schedule
Planning tab for replacement creatives.
| Column | Content |
|---|---|
| A: Fatigued Creative ID | Which creative needs replacement |
| B: Replacement Creative | New creative name/ID |
| C: Replacement Type | New concept / Iteration / Format change |
| D: Planned Launch Date | When the replacement goes live |
| E: Status | Planned / In Production / Ready / Launched |
| F: Days Until Fatigue | Estimated days before current creative hits Fatigue Score 7+ |
| G: Notes | Context for the creative team |
The goal is to have replacements ready before creatives die. Column F can be estimated from the rate of fatigue score increase in Tab 2 (linear projection from the last 3 weeks).
The 3 Fatigue Signals to Track
Signal 1: Frequency Runaway
What it is: The ratio of impressions to unique reach over a rolling 7-day window. When this number climbs, you're showing the same ad to the same people repeatedly.
How to calculate: Impressions (7d) / Reach (7d)
Thresholds:
- Prospecting campaigns: > 3.0 = warning, > 4.0 = replace
- Retargeting campaigns: > 5.0 = warning, > 7.0 = replace
- Broad targeting: > 2.5 = warning (broad audiences fatigue slower, but the threshold is lower because you're supposed to be reaching new people)
Why retargeting tolerates higher frequency: Your retargeting audience already knows you. Seeing your ad 5 times is annoying but not necessarily unproductive. Seeing a prospecting ad 4 times from a brand you've never heard of? That's where the block button lives.
The trap: Frequency alone doesn't confirm fatigue. A creative at frequency 4.0 with stable CTR and CPA is fine — it's working. Only flag it when frequency rises AND performance metrics degrade.
Signal 2: CTR Decay Rate
What it is: The week-over-week percentage change in click-through rate.
Formula: (This Week CTR - Last Week CTR) / Last Week CTR
Threshold: > -15% = the creative is losing engagement
A 5-8% decline can be noise (weekly seasonality, budget changes, audience rotation). A 15%+ decline sustained over 2 consecutive weeks is a signal. A 25%+ decline in a single week usually means the audience has seen the creative enough — the novelty is gone.
Important context: Always check against impression volume. If impressions dropped 50% because you reduced budget, CTR changes are unreliable. Only compare weeks with similar impression volumes (within 30% of each other).
Signal 3: CPA Inflation
What it is: How much your cost per acquisition has increased compared to the creative's first full week of delivery.
Formula: (Current Week CPA - Week 1 CPA) / Week 1 CPA
Threshold: > +30% = the economics have shifted. Time to act.
This is the most important signal because it directly measures money. CTR is a proxy. Frequency is a proxy. CPA is the outcome.
Baseline selection matters: Use the creative's first full week with at least 1,000 impressions and 5+ conversions. Don't use launch day — the first 24-48 hours are often volatile as Meta's algorithm explores delivery.
The confirmation test: Is CPA increasing because of fatigue or because of external factors? Check: if CPA is up AND frequency is up AND CTR is down, it's fatigue. If CPA is up but frequency is stable, look at auction dynamics (CPM changes), landing page issues, or offer-level problems.
Automating the Data
Manually copying numbers from Ads Manager into a spreadsheet works for 5 campaigns. It doesn't work for 50. Here are three approaches, from simple to automated.
Option 1: AdRow Report Export (Simplest)
If you're using AdRow, you can export campaign, ad set, and ad-level data as CSV with all the metrics you need — impressions, reach, clicks, CTR, conversions, CPA, spend, frequency, and revenue (if postback is configured).
Steps:
- Open AdRow Reports, filter by date range (last 7 days)
- Select the ad accounts you want
- Export to CSV
- Import into Tab 2 of your Google Sheet (File > Import > Upload)
AdRow's data syncs every 15 minutes, so your export reflects near-real-time performance. You can schedule this as a weekly Monday morning routine — 5 minutes to export, import, and review the Fatigue Alerts tab.
For a more automated approach, use AdRow's rule engine to skip the spreadsheet entirely: set up rules that auto-pause creatives when frequency exceeds your threshold AND CPA exceeds baseline + 30%. The rule engine supports 20+ metrics with AND/OR logic, so you can encode the exact fatigue signals described above.
Option 2: Meta Marketing API + Google Apps Script
If you want the data to flow automatically into your Google Sheet on a schedule, here's a Google Apps Script that pulls ad-level data from the Meta Marketing API.
Prerequisites:
- A Meta App with Marketing API access
- A long-lived access token (System User token recommended)
- The ad account IDs you want to track
/**
* Pulls ad-level performance data from Meta Marketing API
* and writes it to the "Weekly Tracking Log" tab.
*
* Set this up as a weekly time-driven trigger (Triggers > Add Trigger).
*/
const META_ACCESS_TOKEN = PropertiesService.getScriptProperties()
.getProperty('META_ACCESS_TOKEN');
const AD_ACCOUNT_IDS = [
'act_123456789',
'act_987654321',
// Add your ad account IDs here
];
const API_VERSION = 'v23.0';
function fetchWeeklyCreativeData() {
const sheet = SpreadsheetApp.getActiveSpreadsheet()
.getSheetByName('Weekly Tracking Log');
const today = new Date();
const weekAgo = new Date(today.getTime() - 7 * 24 * 60 * 60 * 1000);
const since = Utilities.formatDate(weekAgo, 'UTC', 'yyyy-MM-dd');
const until = Utilities.formatDate(today, 'UTC', 'yyyy-MM-dd');
const weekLabel = Utilities.formatDate(today, 'UTC', 'yyyy') + '-W' +
Math.ceil((today.getTime() -
new Date(today.getFullYear(), 0, 1).getTime()) /
(7 * 24 * 60 * 60 * 1000));
AD_ACCOUNT_IDS.forEach(function(accountId) {
const url = 'https://graph.facebook.com/' + API_VERSION + '/' +
accountId + '/ads?' +
'fields=id,name,campaign{name},' +
'insights.date_preset(last_7d){' +
'impressions,reach,clicks,ctr,cpc,' +
'spend,actions,cost_per_action_type,frequency' +
'}' +
'&filtering=[{"field":"effective_status",' +
'"operator":"IN",' +
'"value":["ACTIVE","PAUSED"]}]' +
'&limit=500' +
'&access_token=' + META_ACCESS_TOKEN;
try {
const response = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
const data = JSON.parse(response.getContentText());
if (data.error) {
Logger.log('API Error for ' + accountId + ': ' +
data.error.message);
return;
}
(data.data || []).forEach(function(ad) {
if (!ad.insights || !ad.insights.data ||
ad.insights.data.length === 0) return;
const insights = ad.insights.data[0];
const impressions = parseInt(insights.impressions) || 0;
const reach = parseInt(insights.reach) || 0;
const clicks = parseInt(insights.clicks) || 0;
const spend = parseFloat(insights.spend) || 0;
const frequency = parseFloat(insights.frequency) || 0;
const ctr = parseFloat(insights.ctr) || 0;
var conversions = 0;
var cpa = 0;
if (insights.actions) {
insights.actions.forEach(function(action) {
if (action.action_type === 'purchase' ||
action.action_type === 'offsite_conversion.fb_pixel_purchase') {
conversions = parseInt(action.value) || 0;
}
});
}
if (insights.cost_per_action_type) {
insights.cost_per_action_type.forEach(function(cost) {
if (cost.action_type === 'purchase' ||
cost.action_type === 'offsite_conversion.fb_pixel_purchase') {
cpa = parseFloat(cost.value) || 0;
}
});
}
var cpm = impressions > 0 ? (spend / impressions) * 1000 : 0;
var roas = 0;
sheet.appendRow([
ad.id,
weekLabel,
impressions,
reach,
frequency,
clicks,
ctr / 100,
conversions,
cpa,
spend,
'',
roas,
cpm
]);
});
} catch (e) {
Logger.log('Fetch error for ' + accountId + ': ' + e.message);
}
});
}
Setup instructions:
- Open your Google Sheet > Extensions > Apps Script
- Paste the code above
- Go to Project Settings > Script Properties > Add
META_ACCESS_TOKENwith your token value (never hardcode tokens in the script itself) - Update
AD_ACCOUNT_IDSwith your actual account IDs - Run
fetchWeeklyCreativeData()once manually to authorize - Set up a weekly trigger: Triggers > Add Trigger >
fetchWeeklyCreativeData> Time-driven > Week timer > Every Monday at 6-7 AM
Security note: Store your access token in Script Properties, not in the code. If you share the spreadsheet, the script code is visible but Script Properties are not. For production use, implement token refresh via a System User with a long-lived token.
Option 3: AdRow Rule Engine (Skip the Sheet Entirely)
If your goal is to act on creative fatigue rather than just track it, you can encode fatigue signals directly into AdRow's automation rules:
Rule 1: High-frequency pause
- Condition: Frequency (7d) > 3.5 AND CTR (7d) < [your threshold] AND Status = Active
- Action: Pause
- Apply to: Ads
- Notification: Telegram alert with metrics
Rule 2: CPA inflation pause
- Condition: CPA (7d) > [baseline * 1.3] AND Spend (7d) > $50 AND Frequency (7d) > 2.5
- Action: Pause
- Cooldown: 24 hours
- Notification: Telegram alert
Rule 3: Budget reduction on fatigued campaigns
- Condition: Frequency (7d) > 3.0 AND CPA (3d) > CPA (7d) * 1.15
- Action: Decrease budget by 20% (max 40% daily)
- Cooldown: 12 hours
The rule engine checks every 15 minutes. Combined with Telegram notifications, you know the moment a creative gets paused and why — without opening a spreadsheet.
When to Replace vs. Iterate: Decision Framework
Not every fatigued creative needs a completely new concept. Here's a decision framework:
Is the CONCEPT still working?
(Core message, offer, angle)
/ \
YES NO
/ \
ITERATE REPLACE
/ \ (New concept,
Format Copy/Visual new angle,
change refresh new hook)
/ \
Video->Static Same script,
Static->Video new visuals
Carousel->Reel New headline
New thumbnail
Color shift
Iterate when:
- The creative ran profitably for 2+ weeks before fatiguing (the concept works, the execution is stale)
- CTR decay is > 15% but CPA inflation is < 20% (engagement is dropping but conversions still happen)
- Frequency is high but limited to a specific placement (Instagram Stories fatigue faster than Feed)
- You can change the visual hook without changing the message
Replace when:
- CPA inflation is > 40% with no sign of recovery over 2 weeks
- The creative never had a strong week (Fatigue Score hit 5+ within the first 2 weeks — this isn't fatigue, it's a bad creative)
- The offer or angle has been exhausted across multiple creative variants
- Frequency > 5.0 on prospecting with broad targeting (the audience is saturated with this message, not just this visual)
The "3-iteration rule": If you've iterated on a creative concept 3 times and each iteration fatigues faster than the last, the concept is dead. Make a new one. Don't polish a corpse.
Key Takeaways
-
Creative fatigue is measurable. Track three signals: frequency runaway (> 3.0 prospecting), CTR decay (> -15% WoW), and CPA inflation (> +30% vs baseline). All three together = confirmed fatigue.
-
Build the system once, use it forever. The 4-tab Google Sheet (Dashboard, Weekly Log, Alerts, Rotation Schedule) takes 2-3 hours to set up and saves you from discovering fatigue a week too late.
-
Automate the data. Manual tracking doesn't scale past 10 campaigns. Use AdRow's CSV export for weekly imports, Google Apps Script for automatic API pulls, or AdRow's rule engine to skip the spreadsheet and auto-pause fatigued creatives directly.
-
CPA is the money metric. Frequency and CTR are leading indicators. CPA inflation is the one that actually costs you money. Weight it accordingly in your fatigue score.
-
Iterate before you replace. A format change or visual refresh extends creative life by 1-3 weeks at a fraction of the cost of producing a new concept. But after 3 iterations, move on.
-
Plan replacements before you need them. The Rotation Schedule tab exists so you have creatives ready before the current ones die. Reactive creative production always costs more than proactive production.
The goal isn't to eliminate creative fatigue — it's inevitable. The goal is to detect it within 48 hours instead of 2 weeks, and have a replacement ready when it happens. That's the difference between burning budget and managing it.
Need help generating replacement creatives at scale? Check out AdRow's AI Copilot — AI video generation, UGC avatars, and voiceovers integrated directly into your campaign launcher. Or automate fatigue detection with the rule engine and get Telegram alerts the moment a creative starts dying.
よくあるご質問
The Ad Signal
推測を拒否するメディアバイヤーのための週刊インサイト。1通のメール。シグナルのみ。
関連記事
The Media Buyer's Guide to Automating Meta Ads Rules
You went to bed at midnight. ROAS was 3.2x. You woke up to $2,400 burned. Here's how to build automation rules that protect your budget and scale your winners.
How to Launch 100+ Meta Campaigns in 5 Minutes with AdRow
Setting up a single Meta campaign takes 5-10 minutes. 100 campaigns = 8-16 hours. Here's how to do it in 5 minutes with combinatorial generation and parallel API execution.
AdRow vs Madgicx: Which Platform Scales Better for Agencies?
We built AdRow, so we're biased. What we won't do is write a hit piece. Here's a detailed, feature-by-feature comparison so you can figure out which tool fits your workflow.