Scheduling Video Publishing on YouTube
YouTube supports scheduled publishing with 15-minute time slot granularity.
15-Minute Time Slot Algorithm
Videos are scheduled to the next available 15-minute boundary (00, 15, 30, 45).
Rule: Always round UP to next slot (never current slot)
function calculateScheduledTime(baseTime?: Date): Date {
const now = baseTime || new Date();
const currentMinutes = now.getMinutes();
const remainder = currentMinutes % 15;
// If already on a 15-minute mark, go to the NEXT one
const minutesToAdd = remainder === 0 ? 15 : 15 - remainder;
const scheduledTime = new Date(now);
scheduledTime.setMinutes(now.getMinutes() + minutesToAdd);
scheduledTime.setSeconds(0);
scheduledTime.setMilliseconds(0);
return scheduledTime;
}
Examples
| Current Time | Scheduled Time |
|---|---|
| 09:07 | 09:15 |
| 09:15 | 09:30 |
| 09:38 | 09:45 |
| 09:45 | 10:00 |
| 09:52 | 10:00 |
| 10:00 | 10:15 |
Staggered Batch Processing
Multiple videos are staggered by 15 minutes each:
function scheduleMultipleVideos(count: number, baseTime?: Date): Date[] {
const scheduledTimes: Date[] = [];
let currentTime = calculateScheduledTime(baseTime);
for (let i = 0; i < count; i++) {
scheduledTimes.push(new Date(currentTime));
// Next video: 15 minutes later
currentTime = new Date(currentTime.getTime() + 15 * 60 * 1000);
}
return scheduledTimes;
}
Example output for 3 videos at 09:38:
Video 1: 09:45
Video 2: 10:00
Video 3: 10:15
Upload with Scheduled Publishing
const scheduledTime = calculateScheduledTime();
const result = await youtubeClient.uploadVideo({
videoPath: 'video.mp4',
title: 'My Video',
description: 'Video description',
scheduledPublishTime: scheduledTime, // Sets publishAt
});
Important Notes
Privacy Status
Scheduled videos must be private initially:
const status = {
privacyStatus: scheduledPublishTime ? 'private' : 'private',
publishAt: scheduledPublishTime?.toISOString(), // ISO 8601 UTC
};
UTC Timestamps
YouTube API expects UTC timestamps:
const utcTime = scheduledTime.toISOString(); // "2026-01-14T00:45:00.000Z"
Timezone Display
For user display, convert to JST:
function formatJST(date: Date): string {
return date.toLocaleString('ja-JP', {
timeZone: 'Asia/Tokyo',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
// Example: "2026/01/14 09:45"
Compliance Audit Note
For projects created after July 28, 2020:
- Scheduled publishing requires YouTube API compliance audit
- Until verified, videos remain
privatedespitepublishAtsetting - Apply for audit: YouTube API Compliance Form
- Audit takes several days to weeks
Complete Example
class SchedulingService {
calculateScheduledTime(baseTime?: Date): Date {
const now = baseTime || new Date();
const currentMinutes = now.getMinutes();
const remainder = currentMinutes % 15;
const minutesToAdd = remainder === 0 ? 15 : 15 - remainder;
const scheduledTime = new Date(now);
scheduledTime.setMinutes(now.getMinutes() + minutesToAdd);
scheduledTime.setSeconds(0);
scheduledTime.setMilliseconds(0);
return scheduledTime;
}
formatForDisplay(date: Date): string {
return date.toLocaleString('ja-JP', {
timeZone: 'Asia/Tokyo',
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
// Usage
const scheduled = this.calculateScheduledTime();
console.log(`Scheduled: ${this.formatForDisplay(scheduled)} JST`);
// Output: "Scheduled: 2026/01/14 09:45 JST"
}