Google Sheets to Video: Automate Video Creation from a Sheet
Turn spreadsheet rows into finished videos. Connect a Google Sheet to the Renderly API with Apps Script, map columns to variables, and render every row.

A spreadsheet is already the fastest place to write a video brief: one row per video, one column per thing that changes. This guide connects that sheet to the Renderly API with a short Apps Script, so every row renders itself. Setup takes about 30 minutes. After that, a new row produces a new video.
The finished video
This guide renders the
Content Recap template - a
34-second vertical recap of one written piece, built for Reels and Shorts. Nine
fields change per row: headline, hero_image, point_1 to point_4,
publication_name, article_url and qr_image. Watch the output on the
template page before you build anything.
What you need
- A Google Sheet with one row per video you want.
- A Renderly API key. Create one in the dashboard under Settings → API keys.
- A template with dynamic overlays. Any template on the templates page works.
Step 1 - Prepare the Google Sheet
Name every column after a template variable. Row 1 is the header row, and the script reads it, so column order does not matter - only the spelling does.
Add two more columns: status, holding pending, rendering or done, and
video_url for the result.
| headline | hero_image | point_1 | status | video_url |
|---|---|---|---|---|
| The state of video in 2026 | https://cdn.example.com/hero-1.jpg | 63% of marketers used AI tools this year | pending | |
| Why templates beat prompts | https://cdn.example.com/hero-2.jpg | A template render costs twenty cents | pending |
Every URL you put in a cell must serve the file directly. This is where most Sheets pipelines break, because a Google Drive share link is not a file URL.
Step 2 - Get a Renderly API key and template ID
List the templates available to you and copy the id of the one you want:
curl https://renderly.video/api/v1/templates \
-H "Authorization: Bearer $RENDERLY_API_KEY"The response also lists the variables for each template. Those names are your
column headers.
Store the key with PropertiesService, not in a cell:
PropertiesService.getScriptProperties()
.setProperty("RENDERLY_API_KEY", "rnd_...");Anyone with view access to the sheet can read a cell. Script properties are not part of the sheet, so a shared or published sheet does not leak the key.
Step 3 - Map columns to template variables
Renderly templates mark replaceable elements with isDynamic: true, and the
name of each dynamic overlay is the key you send in the replacements object.
Match those names to your headers one to one. Text and shape overlays take
content; image, video and sound overlays take src.
One Sheets-specific detail: a line break inside a cell (Alt+Enter) survives into the video. Templates with fixed-size text boxes, Content Recap among them, use those breaks to control where a headline wraps.
Step 4 - Send one render request per row
One row becomes one POST. Paste this into Extensions → Apps Script:
const ENDPOINT = "https://renderly.video/api/v1/renders";
const TEMPLATE_ID = "content-recap";
const SKIP = ["status", "video_url"];
function renderPendingRows() {
const sheet = SpreadsheetApp.getActiveSheet();
const rows = sheet.getDataRange().getValues();
const header = rows.shift();
const statusCol = header.indexOf("status") + 1;
const key = PropertiesService.getScriptProperties()
.getProperty("RENDERLY_API_KEY");
rows.forEach((row, i) => {
if (row[statusCol - 1] !== "pending") return;
const replacements = {};
header.forEach((name, c) => {
if (!SKIP.includes(name) && row[c] !== "") {
replacements[name] = String(row[c]);
}
});
// Claim the row first, so a mid-run failure cannot double-render it.
sheet.getRange(i + 2, statusCol).setValue("rendering");
SpreadsheetApp.flush();
const response = UrlFetchApp.fetch(ENDPOINT, {
method: "post",
contentType: "application/json",
headers: { Authorization: "Bearer " + key },
payload: JSON.stringify({
templateId: TEMPLATE_ID,
replacements: replacements,
webhookUrl: "https://your-app.com/hooks/renderly",
}),
});
const job = JSON.parse(response.getContentText()).data;
Logger.log("%s -> %s", replacements.headline, job.jobId);
});
}The request returns a job ID immediately. Rendering happens asynchronously, so nothing blocks and a 500-row sheet does not hold the script open.
Run it from the editor once. Then attach a time-driven trigger, or an
onEdit trigger if you want a row to render the moment someone marks it
pending.
If you would rather write no code, the same three fields go into an HTTP request module in n8n, Make.com or Zapier.
Step 5 - Receive the finished video with a webhook
Because webhookUrl was included, Renderly posts back when the job finishes:
{
"event": "render.completed",
"data": {
"jobId": "cm9x2k4p10001qw8r7a3b2c1d",
"status": "COMPLETED",
"outputUrl": "https://cdn.renderly.video/renders/cm9x2k4p10001qw8r7a3b2c1d.mp4",
"creditsUsed": 1,
"durationInFrames": 1020,
"fps": 30
},
"timestamp": "2026-08-13T09:12:44.110Z"
}Write outputUrl into the row's video_url cell and set status to done.
The sheet is now the single record of what has rendered and what has not, which
is the reason to keep the pipeline in the sheet at all.
Payloads are signed with HMAC-SHA256. Verify the signature before you trust the body - the webhooks guide covers the check.
What this costs at scale
Renderly bills 1 credit per minute of 1080p output, rounded up to the nearest half credit. Content Recap runs 34 seconds, so each row costs 1 credit.
| Rows per month | Credits | Plan | Cost |
|---|---|---|---|
| 100 | 100 | Creator, $29/mo | included |
| 1,000 | 1,000 | Business, $99/mo | included |
| 5,000 | 5,000 | Business + 3,500 extra | $99 + $280 |
Extra credits on the Business plan are $0.08 each, so a 5,000-video month runs about $379, or 7.6 cents per finished video.
Watch the rounding if volume is high. Anything from 31 to 60 seconds bills 1 credit, and anything up to 30 seconds bills half. Trimming a 34-second cut to 30 seconds halves the bill on every row.
Where to go next
Only Step 1 changes for a different source. The replacements object and the
render call are identical, which is why the
Airtable version of this guide shares Steps 2
through 5 with this one.
For the pattern behind the whole approach - one design, many payloads - read dynamic video templates.