How to Post to TikTok from Google Sheets: Automation Guide
Key Takeaways:
- Transform Google Sheets into a TikTok content calendar with automated posting
- Choose from 3 methods: n8n (no-code), Google Apps Script, or Python
- Handle timezone conversions and approval workflows automatically
- Scale from single posts to bulk scheduling with error handling
- Integrate with Google Drive for video storage and team collaboration
Google Sheets is the perfect place to manage your TikTok content calendar. You can plan posts, track status, and collaborate with your team in a familiar interface. This guide shows you how to connect google sheets tiktok workflows that automatically post your scheduled content using TikTok API integrations.
You will learn multiple approaches for sheets to tiktok automation. From simple no-code tools to advanced scripting, you will find a method that fits your technical comfort level.
Related: For no-code automation using visual workflows, see our n8n TikTok guide. For programmatic control, check our Python TikTok scheduler.
What You Will Build
Complete Google Sheets to TikTok automation:
- Content calendar that posts automatically
- Approval workflows with status tracking
- Bulk upload from spreadsheet rows
- Scheduled posting with timezone support
- Error handling and retry logic
Prerequisites
Before connecting sheets to tiktok, gather these items:
- Google account with Sheets access
- Postqued account with API key
- TikTok account connected to Postqued
- Sample video files stored in Google Drive or accessible URLs
- Basic understanding of spreadsheet formulas (helpful but not required)
Setting Up Your Content Calendar Sheet
Create a Google Sheet with this structure:
| Column | Header | Description |
|---|---|---|
| A | Date | When to post (YYYY-MM-DD format) |
| B | Time | Post time (HH:MM format, 24-hour) |
| C | Timezone | Timezone name (e.g., America/New_York) |
| D | Video URL | Link to video file (Google Drive, Dropbox, or direct) |
| E | Caption | Post caption with hashtags |
| F | Status | Ready, Scheduled, Posted, Failed |
| G | Publish ID | Auto-filled after posting |
| H | Posted At | Auto-filled timestamp |
Add data validation to the Status column with these options: Ready, Scheduled, Posted, Failed.
Method 1: n8n No-Code Automation
The easiest way to automate google sheets tiktok posting uses n8n visual workflows.
Step 1: Set Up n8n
Sign up for n8n Cloud or run self-hosted. The free cloud tier works for testing.
Create a new workflow named "TikTok Sheets Poster".
Step 2: Add Google Sheets Trigger
- Add "Google Sheets" trigger node
- Operation: Row Added or Modified
- Spreadsheet: Select your content calendar
- Sheet: Sheet1
- Filter: Status column equals "Ready"
This triggers whenever you mark a row as Ready.
Step 3: Add Postqued Credentials
- Click the key icon, create new credential
- Choose "Header Auth"
- Name: Postqued API
- Header Name: Authorization
- Value: Bearer pq_your_api_key_here
Step 4: Upload Video
Add HTTP Request node:
- Method: POST
- URL:
https://api.postqued.com/v1/content/upload - Authentication: Postqued API
- Body (JSON):
{
"filename": "={{ $json.videoUrl.split('/').pop() }}",
"contentType": "video/mp4",
"fileSize": 5242880
}
Note: You may need to fetch the file first to determine actual size.
Step 5: Fetch Video File
If your video URL is external:
- Add HTTP Request node before upload
- Method: GET
- URL:
={{ $json.videoUrl }} - Response Format: File
Step 6: Publish to TikTok
Add another HTTP Request node:
- Method: POST
- URL:
https://api.postqued.com/v1/content/publish - Authentication: Postqued API
- Headers: Idempotency-Key:
={{ $now.format('x') }} - Body (JSON):
{
"contentIds": ["={{ $json.contentId }}"],
"targets": [{
"platform": "tiktok",
"accountId": "your-tiktok-account-id",
"intent": "draft",
"caption": "={{ $('Google Sheets Trigger').item.json.caption }}",
"dispatchAt": "={{ $('Google Sheets Trigger').item.json.date }}T{{ $('Google Sheets Trigger').item.json.time }}:00",
"options": {
"privacyLevel": "PUBLIC_TO_EVERYONE"
}
}]
}
Step 7: Update Sheet Status
Add Google Sheets node:
- Operation: Update Row
- Spreadsheet: Your content calendar
- Row Number:
={{ $('Google Sheets Trigger').item.json.row_number }} - Column: Status
- Value: Scheduled
Step 8: Save and Activate
Click "Save" then toggle the workflow to "Active". Now when you type "Ready" in the Status column, the workflow triggers and schedules your post.
Method 2: Google Apps Script
For a fully integrated sheets to tiktok solution inside Google Sheets, use Apps Script.
Step 1: Open Apps Script
In your Google Sheet, click Extensions -> Apps Script.
Step 2: Add the Script
Paste this code:
const CONFIG = {
POSTQUED_API_KEY: 'pq_your_api_key_here',
TIKTOK_ACCOUNT_ID: 'your-account-id',
API_BASE_URL: 'https://api.postqued.com'
};
/**
* Main function to process ready posts
*/
function processTikTokPosts() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = sheet.getDataRange().getValues();
const headers = data[0];
// Find column indices
const colIndex = {
date: headers.indexOf('Date'),
time: headers.indexOf('Time'),
timezone: headers.indexOf('Timezone'),
videoUrl: headers.indexOf('Video URL'),
caption: headers.indexOf('Caption'),
status: headers.indexOf('Status'),
publishId: headers.indexOf('Publish ID'),
postedAt: headers.indexOf('Posted At')
};
// Process each row
for (let i = 1; i < data.length; i++) {
const row = data[i];
const status = row[colIndex.status];
if (status === 'Ready') {
try {
const result = schedulePost(row, colIndex);
// Update status to Scheduled
sheet.getRange(i + 1, colIndex.status + 1).setValue('Scheduled');
sheet.getRange(i + 1, colIndex.publishId + 1).setValue(result.id);
sheet.getRange(i + 1, colIndex.postedAt + 1).setValue(new Date());
Logger.log(`Scheduled row ${i + 1}: ${result.id}`);
// Rate limiting - wait 6 seconds between posts
Utilities.sleep(6000);
} catch (error) {
Logger.log(`Error on row ${i + 1}: ${error}`);
sheet.getRange(i + 1, colIndex.status + 1).setValue('Failed');
}
}
}
}
/**
* Schedule a single post
*/
function schedulePost(row, colIndex) {
const videoUrl = row[colIndex.videoUrl];
const caption = row[colIndex.caption];
const date = row[colIndex.date];
const time = row[colIndex.time];
const timezone = row[colIndex.timezone] || 'UTC';
// Build schedule datetime
const scheduleDate = new Date(date);
const [hours, minutes] = time.split(':').map(Number);
scheduleDate.setHours(hours, minutes, 0, 0);
// Convert to UTC ISO string
const dispatchAt = scheduleDate.toISOString();
// Upload video first (simplified - assumes direct URL)
const contentId = uploadVideo(videoUrl);
// Schedule the post
const publishRequest = {
contentIds: [contentId],
targets: [{
platform: 'tiktok',
accountId: CONFIG.TIKTOK_ACCOUNT_ID,
intent: 'draft',
caption: caption,
dispatchAt: dispatchAt,
options: {
privacyLevel: 'PUBLIC_TO_EVERYONE',
disableDuet: false,
disableStitch: false,
disableComment: false
}
}]
};
const options = {
method: 'POST',
headers: {
'Authorization': `Bearer ${CONFIG.POSTQUED_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': Utilities.getUuid()
},
payload: JSON.stringify(publishRequest)
};
const response = UrlFetchApp.fetch(
`${CONFIG.API_BASE_URL}/v1/content/publish`,
options
);
if (response.getResponseCode() !== 200) {
throw new Error(`Publish failed: ${response.getContentText()}`);
}
return JSON.parse(response.getContentText());
}
/**
* Upload video and return content ID
*/
function uploadVideo(videoUrl) {
// For Google Drive files, you need to handle authentication
// For direct URLs, we need to download and upload
// Simplified example - assumes video is already accessible
// In production, implement actual file upload logic
const fileName = videoUrl.split('/').pop() || 'video.mp4';
const uploadRequest = {
filename: fileName,
contentType: 'video/mp4',
fileSize: 5242880 // You should get actual file size
};
const options = {
method: 'POST',
headers: {
'Authorization': `Bearer ${CONFIG.POSTQUED_API_KEY}`,
'Content-Type': 'application/json'
},
payload: JSON.stringify(uploadRequest)
};
const response = UrlFetchApp.fetch(
`${CONFIG.API_BASE_URL}/v1/content/upload`,
options
);
const uploadData = JSON.parse(response.getContentText());
// Download and upload file to presigned URL
const videoBlob = UrlFetchApp.fetch(videoUrl).getBlob();
UrlFetchApp.fetch(uploadData.upload.url, {
method: 'PUT',
headers: uploadData.upload.headers || {},
payload: videoBlob
});
// Confirm upload
UrlFetchApp.fetch(
`${CONFIG.API_BASE_URL}/v1/content/upload/complete`,
{
method: 'POST',
headers: {
'Authorization': `Bearer ${CONFIG.POSTQUED_API_KEY}`,
'Content-Type': 'application/json'
},
payload: JSON.stringify({
contentId: uploadData.contentId,
key: uploadData.key || '',
filename: fileName,
contentType: 'video/mp4',
size: videoBlob.getBytes().length,
width: 1080,
height: 1920,
durationMs: 15000
})
}
);
return uploadData.contentId;
}
/**
* Add custom menu to sheet
*/
function onOpen() {
const ui = SpreadsheetApp.getUi();
ui.createMenu('TikTok Automation')
.addItem('Process Ready Posts', 'processTikTokPosts')
.addItem('Check Post Status', 'checkAllStatuses')
.addToUi();
}
/**
* Check status of all scheduled posts
*/
function checkAllStatuses() {
const sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
const data = sheet.getDataRange().getValues();
const headers = data[0];
const statusCol = headers.indexOf('Status');
const publishIdCol = headers.indexOf('Publish ID');
for (let i = 1; i < data.length; i++) {
const status = data[i][statusCol];
const publishId = data[i][publishIdCol];
if (status === 'Scheduled' && publishId) {
try {
const currentStatus = checkPublishStatus(publishId);
if (currentStatus === 'sent' || currentStatus === 'published') {
sheet.getRange(i + 1, statusCol + 1).setValue('Posted');
} else if (currentStatus === 'failed') {
sheet.getRange(i + 1, statusCol + 1).setValue('Failed');
}
} catch (error) {
Logger.log(`Error checking status for ${publishId}: ${error}`);
}
}
}
}
/**
* Check individual publish status
*/
function checkPublishStatus(publishId) {
const response = UrlFetchApp.fetch(
`${CONFIG.API_BASE_URL}/v1/content/publish/${publishId}`,
{
headers: {
'Authorization': `Bearer ${CONFIG.POSTQUED_API_KEY}`
}
}
);
const data = JSON.parse(response.getContentText());
return data.status;
}
Step 3: Configure the Script
Replace these values in the CONFIG section:
- POSTQUED_API_KEY: Your actual API key
- TIKTOK_ACCOUNT_ID: Your TikTok account ID from Postqued
Step 4: Authorize and Run
- Click the "Save" button (disk icon)
- Select function "processTikTokPosts"
- Click "Run"
- Grant permissions when prompted
The script will process all rows marked "Ready" and schedule them for posting.
Step 5: Add Custom Menu
The onOpen function automatically adds a menu to your sheet. Reload the spreadsheet to see the "TikTok Automation" menu with options to process posts and check statuses.
Method 3: Python with gspread
For advanced users who prefer Python, use the gspread library to read from Sheets and schedule posts.
Step 1: Install Dependencies
pip install gspread google-auth requests pytz
Step 2: Set Up Google Sheets API
- Go to Google Cloud Console
- Create a new project
- Enable Google Sheets API
- Create service account credentials
- Download the JSON key file
- Share your spreadsheet with the service account email
Step 3: Create the Python Script
import os
import json
import uuid
import requests
import pytz
import gspread
from datetime import datetime
from google.oauth2.service_account import Credentials
# Configuration
API_KEY = os.environ.get("POSTQUED_API_KEY")
TIKTOK_ACCOUNT_ID = os.environ.get("TIKTOK_ACCOUNT_ID")
API_BASE_URL = "https://api.postqued.com"
# Google Sheets setup
SCOPES = [
'https://www.googleapis.com/auth/spreadsheets',
'https://www.googleapis.com/auth/drive'
]
def get_sheet_data(spreadsheet_id, sheet_name="Sheet1"):
"""Read data from Google Sheets."""
creds = Credentials.from_service_account_file(
'credentials.json', scopes=SCOPES
)
client = gspread.authorize(creds)
spreadsheet = client.open_by_key(spreadsheet_id)
worksheet = spreadsheet.worksheet(sheet_name)
return worksheet.get_all_records()
def upload_video(file_path_or_url: str) -> str:
"""Upload video and return content ID."""
# Handle URL vs local file
if file_path_or_url.startswith('http'):
import urllib.request
temp_path = "/tmp/temp_video.mp4"
urllib.request.urlretrieve(file_path_or_url, temp_path)
file_path = temp_path
else:
file_path = file_path_or_url
import os as os_module
file_size = os_module.path.getsize(file_path)
file_name = os_module.path.basename(file_path)
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
# Get upload URL
upload_res = requests.post(
f"{API_BASE_URL}/v1/content/upload",
headers=headers,
json={
"filename": file_name,
"contentType": "video/mp4",
"fileSize": file_size
}
)
upload_res.raise_for_status()
upload_data = upload_res.json()
content_id = upload_data["contentId"]
# Upload file
with open(file_path, "rb") as f:
requests.put(
upload_data["upload"]["url"],
data=f,
headers=upload_data["upload"].get("headers", {})
)
# Confirm upload
requests.post(
f"{API_BASE_URL}/v1/content/upload/complete",
headers=headers,
json={
"contentId": content_id,
"key": upload_data.get("key", ""),
"filename": file_name,
"contentType": "video/mp4",
"size": file_size,
"width": 1080,
"height": 1920,
"durationMs": 15000
}
).raise_for_status()
return content_id
def schedule_from_sheet(spreadsheet_id: str):
"""Process ready posts from Google Sheet."""
records = get_sheet_data(spreadsheet_id)
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json"
}
for i, row in enumerate(records, start=2): # start=2 for row number (header is row 1)
if row.get("Status") != "Ready":
continue
try:
print(f"Processing row {i}: {row.get('Caption', '')[:50]}...")
# Upload video
content_id = upload_video(row["Video URL"])
# Parse schedule time
date_str = row["Date"]
time_str = row["Time"]
timezone_str = row.get("Timezone", "UTC")
# Combine date and time
if isinstance(date_str, str):
schedule_dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
else:
schedule_dt = datetime.combine(date_str, datetime.strptime(time_str, "%H:%M").time())
# Localize and convert to UTC
tz = pytz.timezone(timezone_str)
schedule_dt = tz.localize(schedule_dt)
utc_time = schedule_dt.astimezone(pytz.UTC)
# Schedule post
idempotency_key = str(uuid.uuid4())
publish_request = {
"contentIds": [content_id],
"targets": [{
"platform": "tiktok",
"accountId": TIKTOK_ACCOUNT_ID,
"intent": "draft",
"caption": row["Caption"],
"dispatchAt": utc_time.isoformat().replace("+00:00", "Z"),
"options": {
"privacyLevel": "PUBLIC_TO_EVERYONE",
"disableDuet": False,
"disableStitch": False,
"disableComment": False
}
}]
}
response = requests.post(
f"{API_BASE_URL}/v1/content/publish",
headers={**headers, "Idempotency-Key": idempotency_key},
json=publish_request
)
response.raise_for_status()
result = response.json()
print(f" Scheduled! ID: {result['id']}")
# Update sheet status (requires additional gspread code)
# worksheet.update_cell(i, 6, "Scheduled")
# worksheet.update_cell(i, 7, result['id'])
except Exception as e:
print(f" Error: {e}")
# worksheet.update_cell(i, 6, "Failed")
# Usage
if __name__ == "__main__":
SPREADSHEET_ID = "your-spreadsheet-id-here"
schedule_from_sheet(SPREADSHEET_ID)
Step 4: Run the Script
export POSTQUED_API_KEY="your-key"
export TIKTOK_ACCOUNT_ID="your-account-id"
python schedule_from_sheets.py
Handling Google Drive Video Files
If your videos are in Google Drive, you need to make them accessible:
Option 1: Public Links
- Right-click video in Google Drive
- Click "Share"
- Change to "Anyone with the link"
- Copy the link
- Use the direct download URL format:
https://drive.google.com/uc?export=download&id=YOUR_FILE_ID
Option 2: Service Account Access
For private files, use Google Drive API with your service account:
from googleapiclient.discovery import build
from google.oauth2.service_account import Credentials
def get_drive_file(file_id):
"""Download file from Google Drive using service account."""
creds = Credentials.from_service_account_file(
'credentials.json',
scopes=['https://www.googleapis.com/auth/drive.readonly']
)
service = build('drive', 'v3', credentials=creds)
request = service.files().get_media(fileId=file_id)
file_content = request.execute()
return file_content
Building an Approval Workflow
Add approval steps to your schedule tiktok from sheets workflow:
- Add an "Approval" column to your sheet
- Change your automation to only process rows where Status = "Ready" AND Approval = "Approved"
- Use Google Sheets data validation for the Approval column (Pending, Approved, Rejected)
- Add conditional formatting to highlight rows awaiting approval
For n8n workflows, add an "If" node that checks the Approval column before proceeding to publish.
Best Practices for Google Sheets TikTok Integration
Use consistent date formats. Stick to ISO format (YYYY-MM-DD) to avoid parsing errors across different locales.
Include timezone information. Always specify the timezone column. This prevents posts from going out at the wrong time.
Validate video URLs. Add a formula column that checks if the URL returns a 200 status before marking as Ready.
Implement rate limiting. Process no more than 10 posts per minute to avoid API limits. Add delays between requests.
Log everything. Track Publish IDs, timestamps, and error messages in your sheet for debugging.
Use data validation. Restrict the Status column to specific values. This prevents typos that break your automation.
Set up monitoring. Create a dashboard view that shows upcoming posts, failed posts, and posts needing approval.
Troubleshooting Sheets to TikTok Automation
"Ready" rows not processing
Check your trigger configuration. For n8n, verify the filter matches exactly. For Apps Script, check the Logs (View -> Logs).
Video upload fails
Ensure the video URL is publicly accessible or your service account has access. Check file size is under 500MB.
Posts scheduled for wrong time
Verify your timezone column uses correct names like "America/New_York" not "EST". Check daylight saving time handling.
Authentication errors
For Apps Script, re-run authorization. For Python, verify your credentials.json file path. For n8n, check your credential is selected.
Quota exceeded errors
Google Sheets API has quotas (500 requests per 100 seconds). Add delays between requests or batch operations.
Videos not appearing on TikTok
Check your account ID is correct. Ensure you are using "intent": "draft" (requires manual approval in TikTok). Posts using "publish" require special TikTok approval.
Example Content Calendar Templates
Template 1: Weekly Posting Schedule
| Date | Time | Timezone | Video URL | Caption | Status |
|---|---|---|---|---|---|
| 2026-03-17 | 09:00 | America/New_York | [drive link] | Monday motivation! #monday | Ready |
| 2026-03-18 | 12:00 | America/New_York | [drive link] | Tutorial Tuesday #tutorial | Ready |
| 2026-03-19 | 15:00 | America/New_York | [drive link] | Behind the scenes #bts | Ready |
Template 2: Campaign Tracker
Add columns for Campaign, Objective, Target Audience, and Performance metrics.
Template 3: Team Collaboration
Add columns for Assigned To, Reviewed By, and Notes for team coordination.
Next Steps for Your Google Sheets TikTok Automation
You now have three methods to automate google sheets tiktok posting:
- n8n for visual, no-code workflows and automation workflows
- Google Apps Script for native Sheets integration with TikTok API
- Python with gspread for advanced customization and TikTok automation
Choose the method that fits your technical skills and workflow needs. Start with a simple schedule, then add approval workflows, error handling, and performance tracking.
For more no-code tools and automation workflows, see our guides on:
- n8n TikTok workflows - Visual workflow builder
- Zapier TikTok integration - 5,000+ app connections
- Notion to TikTok - Content management system
- Python TikTok scheduling - Developer-friendly automation
Keywords covered: google sheets tiktok, sheets to tiktok, schedule tiktok from sheets, google sheets social media automation, spreadsheet to tiktok, TikTok API, automation workflows, no-code tools
Ready to automate your TikTok posting from Google Sheets? Get your Postqued API key and start building your content calendar today.
Need help with your integration? Check our API documentation or contact support.