Track YouTube Views Daily in Google Sheets With Apps Script
On this page, 10 sections
To track YouTube views daily in Google Sheets, write an Apps Script that calls the YouTube Data API’s videos.list for your video IDs and appends one row per video, then put it on a daily time-driven trigger.
Google’s docs put one videos.list call at 1 quota unit, against a default allowance of 10,000 units a day, so a daily log is far inside the limit.
One thing to know before you build it: the number you get is the lifetime view count, not the views from yesterday. Daily views are the difference between two days of readings. If you want per-day numbers straight from YouTube instead, that is the Analytics API, which I covered in connecting YouTube Analytics to Google Sheets.
How I checked this: I read the videos.list reference, the quota pages, the Apps Script YouTube service, trigger and quota docs, and Google’s API discovery document (revision 20261006) on 11 October 2026.
The script below passes node --check, and I ran it in a test harness with stand-ins for the Sheets, YouTube and trigger services. I did not run it against a real YouTube key or a real channel, so treat the first live run as your test.
What the Data API gives you, and what it does not
GET https://www.googleapis.com/youtube/v3/videos takes a required part parameter and an id parameter that is a comma-separated list. Asking for part=statistics returns the VideoStatistics object. In the discovery document its fields are viewCount, likeCount, commentCount, favoriteCount (marked deprecated) and dislikeCount.
Two details from that same document change how you write the code. The counts are typed as strings (unsigned 64-bit integers), so convert them with Number() before they go in a sheet. And maxResults is not supported together with id, so you cannot ask for “the first 50 of my videos”; you must list the IDs you want.
The Data API describes what a video is and its running totals. It does not give “views on 4 November”. That is why this post logs a reading each day. For when to pick which API, see YouTube Analytics API vs Data API.
Set up the sheet
- Make a tab called
Videos. Put a header in A1 and one video ID per row below it. The ID is the part afterv=in a watch URL. - Go to Extensions, Apps Script, and add the YouTube Data API advanced service. Google’s page says an advanced service must be enabled before use and exposes the same objects, methods and parameters as the public API.
- Paste the script, run
logViewsonce by hand, and approve the permissions it asks for.
Google’s advanced services guide (last updated 3 September 2026) says that with a default Cloud project the API is enabled automatically when you add the service.
It does not say how Data API quota is counted for that default project, and I could not find it on the pages I read. I cover what is and is not documented in the Data API quota guide for Sheets.
The script
It reads the IDs, asks for statistics in batches, and appends one row per video to a Log tab. The call shape, YouTube.Videos.list('statistics', { id: '...' }), follows Google’s advanced service page, which shows the same style for YouTube.Search.list.
const VIDEOS_TAB = 'Videos'; // column A: one video ID per row, header in row 1
const LOG_TAB = 'Log';
const CHUNK = 50;
function logViews() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const idsSheet = ss.getSheetByName(VIDEOS_TAB);
const lastRow = idsSheet.getLastRow();
if (lastRow < 2) return;
const ids = idsSheet.getRange(2, 1, lastRow - 1, 1).getValues()
.map(row => String(row[0]).trim())
.filter(Boolean);
let log = ss.getSheetByName(LOG_TAB);
if (!log) log = ss.insertSheet(LOG_TAB);
if (log.getLastRow() === 0) {
log.appendRow(['read_at', 'video_id', 'views', 'likes', 'comments']);
}
const readAt = new Date();
const rows = [];
const returned = new Set();
for (let i = 0; i < ids.length; i += CHUNK) {
const chunk = ids.slice(i, i + CHUNK);
const res = YouTube.Videos.list('statistics', { id: chunk.join(',') });
(res.items || []).forEach(item => {
const s = item.statistics || {};
returned.add(item.id);
rows.push([
readAt,
item.id,
s.viewCount === undefined ? '' : Number(s.viewCount),
s.likeCount === undefined ? '' : Number(s.likeCount),
s.commentCount === undefined ? '' : Number(s.commentCount)
]);
});
}
ids.filter(id => !returned.has(id)).forEach(id => {
rows.push([readAt, id, 'not returned', '', '']);
});
if (rows.length) {
log.getRange(log.getLastRow() + 1, 1, rows.length, rows[0].length).setValues(rows);
}
}
function createDailyTrigger() {
ScriptApp.getProjectTriggers()
.filter(t => t.getHandlerFunction() === 'logViews')
.forEach(t => ScriptApp.deleteTrigger(t));
ScriptApp.newTrigger('logViews').timeBased().everyDays(1).atHour(6).create();
}
Choices worth knowing about:
- Batches of 50. The
videos.listpage does not state a limit on the number of IDs in one call. I chose 50 as a cautious size and did not test larger ones. - One write per run. It builds all rows first and writes them with a single
setValues, rather than callingappendRowin a loop.appendRowis used only once, for the header. - “not returned” rows. An ID the API does not return (a typo, a deleted or private video) gets a row saying so, not a silent gap. In my harness, one of three fake IDs was left out of the response and produced that row.
- Blank, not zero. If a statistic is absent from the response, the cell stays empty so a missing value cannot be mistaken for 0 views.
Run it every day
Run createDailyTrigger once. It removes any existing trigger for logViews, then creates one with ScriptApp.newTrigger('logViews').timeBased().everyDays(1).atHour(6).create(). The ClockTriggerBuilder reference says frequency is required when you use atHour(), which is why everyDays(1) is there. It also offers inTimezone() if you want to pin a time zone.
The installable triggers page says time-driven triggers can run as often as every minute and that the time may be slightly randomised: a 9 AM trigger fires between 9 and 10 AM. So 6 means “sometime from 6 to 7”, and that is fine for a daily total.
The Apps Script quotas page (last updated 3 September 2026) lists the ceilings that apply: triggers total runtime of 90 minutes a day on a consumer account and 6 hours on Google Workspace, 6 minutes per execution, and 20 triggers per user per script.
A run that reads a few hundred videos is not the thing that hits those, but I did not time one, so I do not have a measured duration to give you.
The quota arithmetic
Google’s getting-started and quota-cost pages (read 11 October 2026) say the default is 10,000 units a day for most endpoints, “subject to change”, that the quota resets at midnight Pacific Time, and that even invalid requests cost at least 1 unit. Calls needed is the number of videos divided by 50, rounded up. I did the sums in Python:
- 50 videos: 1 call, so 1 unit, for one daily read, or 24 units a day if you read hourly (0.24% of 10,000).
- 500 videos: 10 calls, so 10 units, for one daily read of the whole list (0.1% of 10,000), or 240 units if you read hourly (2.4%).
- 5,000 videos: 100 calls a day, or 2,400 units hourly (24%).
Without batching, one call per video makes the 500-video daily log 500 units, which is 5% of the allowance for no benefit. The assumption behind all of this is that cost is per call and not per ID, which matches the 1 unit per read that the quota table states. If your project shares quota with other tools, subtract what they use.
Turning totals into daily views
Your Log tab is a long table: a date, an ID and three totals per row. For daily views, take each video’s reading and subtract the previous day’s reading for the same ID.
A pivot table with IDs down the side and dates across makes the subtraction a copy-across formula. Because the first day has nothing to subtract from, expect your first useful day to be the second.
What I could not verify
- A run against a real key or channel. The harness proves the logic, not the live API response.
- How Data API quota is counted for Apps Script’s default Cloud project. Not documented on the pages I read.
- The maximum number of IDs per call. Not stated on the reference page.
Questions people ask
Do I need an API key?
Not for this version. It uses the Apps Script advanced service, which handles authorisation through scopes you approve on first run. If you call the REST endpoint yourself instead, you manage the credentials. The discovery document lists the scopes videos.list accepts, including youtube.readonly.
Can I track videos I do not own?
The method takes IDs, and statistics is a documented part. I did not test other channels’ videos, so confirm with a single ID before building around it.
Why is my sheet missing a day?
Check the Executions page in Apps Script for an error that day, and check the trigger still exists. The trigger limits are in the quotas page above.
Do this today
Put five video IDs in a Videos tab, paste the script, run logViews once and look at the Log tab. If the five rows look right, run createDailyTrigger. The first useful daily number is tomorrow’s.
Resources
- YouTube Data API: videos.list, read 11 October 2026
- YouTube Data API: quota cost, read 11 October 2026
- YouTube Data API: getting started and quota, read 11 October 2026
- Apps Script: YouTube advanced service, read 11 October 2026
- Apps Script: installable triggers, read 11 October 2026
- Apps Script: ClockTriggerBuilder, last updated 13 April 2026, read 11 October 2026
- Apps Script: quotas, last updated 3 September 2026, read 11 October 2026