Skip to content

Google Sheet setup

Set up your Google Sheet in 10 minutes.

One Sheet and one small script connect your AI to FlowVid. Copy the script below, follow the steps, and test it yourself.

Go to the script
1

Step 1. Create a new Google Sheet

Open sheets.new in your browser. Name it FlowVid.

Sign in with the same Google account you use for Google Flow.

Open sheets.new
Screenshot goes here
2

Step 2. Open the script editor

In the Sheet menu bar click Extensions, then Apps Script. A new tab opens with a code editor.

Screenshot goes here
3

Step 3. Remove the existing code

Click inside the code box, press Ctrl+A (Cmd+A on Mac) to select everything, then press Delete. The editor should now be empty.

Screenshot goes here
4

Step 4. Paste the FlowVid script

Use the copy block below (Copy script button), click inside the empty editor and press Ctrl+V (Cmd+V on Mac).

Screenshot goes here
FlowVid Google Sheet script (v3)
// FlowVid Google Sheet script (v3): job inbox + result log + addJob for AI tools.
// "FlowVid Jobs" tab: an AI (or you) adds a job here, and FlowVid starts it.
// "FlowVid Results" tab: FlowVid logs every finished batch here.
var JOBS = 'FlowVid Jobs';
var RESULTS = 'FlowVid Results';
var JOB_HEADERS = ['Job', 'Type', 'Name', 'Text', 'Status', 'Result', 'Updated'];
var RESULT_HEADERS = ['Time', 'Batch', 'No.', 'Name', 'Prompt', 'Status', 'File', 'Quality'];
// Run this once from the editor (or just deploy: the tabs are made on first use).
function setup() {
tab_(JOBS, JOB_HEADERS);
tab_(RESULTS, RESULT_HEADERS);
}
function tab_(name, headers) {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sh = ss.getSheetByName(name) || ss.insertSheet(name);
if (sh.getLastRow() === 0) {
sh.appendRow(headers);
sh.setFrozenRows(1);
sh.getRange(1, 1, 1, headers.length).setFontWeight('bold');
}
return sh;
}
function json_(o) {
return ContentService.createTextOutput(JSON.stringify(o)).setMimeType(ContentService.MimeType.JSON);
}
// GET ?action=next — hand the oldest ready job to FlowVid.
function doGet(e) {
try {
var action = e && e.parameter ? e.parameter.action : '';
if (action === 'next') return json_(nextJob_());
return json_({ ok: true, app: 'FlowVid', version: 3, job: null });
} catch (err) {
return json_({ ok: false, error: String(err) });
}
}
function nextJob_() {
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var sh = tab_(JOBS, JOB_HEADERS);
var last = sh.getLastRow();
if (last < 2) return { ok: true, job: null };
var values = sh.getRange(2, 1, last - 1, 7).getValues();
// Group the rows marked Ready by job name, oldest job first.
var order = [], groups = {};
for (var i = 0; i < values.length; i++) {
var job = String(values[i][0]).trim();
var status = String(values[i][4]).trim().toLowerCase();
if (!job || status !== 'ready') continue;
if (!groups[job]) { groups[job] = []; order.push(job); }
groups[job].push({ row: i + 2, type: String(values[i][1]).trim().toLowerCase(), name: String(values[i][2]).trim(), text: String(values[i][3]) });
}
if (!order.length) return { ok: true, job: null };
var name = order[0];
var rows = groups[name];
// Only hand the job over once it has stopped growing, so FlowVid never
// starts a job the AI is still writing: same row count on two checks in a row.
var props = PropertiesService.getScriptProperties();
var key = 'seen:' + name;
if (props.getProperty(key) !== String(rows.length)) {
props.setProperty(key, String(rows.length));
return { ok: true, job: null, waiting: name };
}
props.deleteProperty(key);
var out = { id: name, name: name, project: [], character: [], script: [], prompts: [] };
var instructionRows = [];
for (var j = 0; j < rows.length; j++) {
var r = rows[j];
if (r.type.indexOf('character') >= 0) { out.character.push(r.text); instructionRows.push(r.row); }
else if (r.type.indexOf('script') >= 0) { out.script.push(r.text); instructionRows.push(r.row); }
else if (r.type.indexOf('project') >= 0 || r.type.indexOf('instruction') >= 0) { out.project.push(r.text); instructionRows.push(r.row); }
else if (r.text.trim()) out.prompts.push({ row: r.row, name: r.name, text: r.text });
}
var now = new Date();
if (!out.prompts.length) {
for (var k = 0; k < rows.length; k++) sh.getRange(rows[k].row, 5, 1, 3).setValues([['Error', 'This job has no Prompt rows.', now]]);
return { ok: true, job: null };
}
for (var m = 0; m < instructionRows.length; m++) sh.getRange(instructionRows[m], 5, 1, 3).setValues([['Used', '', now]]);
for (var n = 0; n < out.prompts.length; n++) sh.getRange(out.prompts[n].row, 5, 1, 3).setValues([['Picked up', '', now]]);
return { ok: true, job: out };
} finally {
lock.releaseLock();
}
}
// POST — FlowVid writes progress ({action:'status'}) or logs a finished batch.
function doPost(e) {
try {
var body = e && e.postData && e.postData.contents ? JSON.parse(e.postData.contents) : {};
if (body.action === 'status') return json_(writeStatus_(body));
if (body.action === 'addJob') return json_(addJob_(body));
var sh = tab_(RESULTS, RESULT_HEADERS);
var rows = Array.isArray(body.rows) ? body.rows : [];
var now = new Date();
for (var i = 0; i < rows.length; i++) {
var r = rows[i];
sh.appendRow([now, body.batchName || 'Batch', r.number || (i + 1), r.name || '', r.finalPrompt || r.prompt || '', r.status || '', r.file || '', r.quality || '']);
}
return json_({ ok: true, added: rows.length });
} catch (err) {
return json_({ ok: false, error: String(err) });
}
}
function writeStatus_(body) {
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var sh = tab_(JOBS, JOB_HEADERS);
var updates = Array.isArray(body.updates) ? body.updates : [];
var now = new Date();
var written = 0;
for (var i = 0; i < updates.length; i++) {
var u = updates[i];
var row = Number(u.row);
if (!row || row < 2 || row > sh.getLastRow()) continue;
// Skip a row that no longer belongs to this job (someone moved rows).
if (String(sh.getRange(row, 1).getValue()).trim() !== String(body.job || '').trim()) continue;
sh.getRange(row, 5, 1, 3).setValues([[u.status || '', u.result || '', now]]);
written++;
}
return { ok: true, written: written };
} finally {
lock.releaseLock();
}
}
// POST {action:'addJob', key, job, project, character, script, prompts:[{name,text}] | ["text"]}
// Lets a custom GPT Action or any AI tool queue a whole video in one call.
// Optional safety: set a Script Property FLOWVID_KEY and send the same key.
function addJob_(body) {
var need = PropertiesService.getScriptProperties().getProperty('FLOWVID_KEY');
if (need && String(body.key || '') !== need) return { ok: false, error: 'Wrong key' };
var prompts = Array.isArray(body.prompts) ? body.prompts : [];
if (!prompts.length) return { ok: false, error: 'No prompts' };
var job = String(body.job || ('FlowVid job ' + Utilities.formatDate(new Date(), Session.getScriptTimeZone(), 'yyyy-MM-dd HH:mm')));
var rows = [];
if (body.project) rows.push([job, 'Project instructions', 'Project instructions', String(body.project)]);
if (body.character) rows.push([job, 'Character consistency', 'Character consistency', String(body.character)]);
if (body.script) rows.push([job, 'Script instructions', 'Script instructions', String(body.script)]);
for (var i = 0; i < prompts.length; i++) {
var p = prompts[i];
var name = typeof p === 'string' ? '' : String(p.name || '');
var text = typeof p === 'string' ? p : String(p.text || '');
if (!name) name = 'Shot ' + (i < 9 ? '0' : '') + (i + 1);
rows.push([job, 'Prompt', name, text]);
}
var lock = LockService.getScriptLock();
lock.waitLock(20000);
try {
var sh = tab_(JOBS, JOB_HEADERS);
var start = sh.getLastRow() + 1;
// Write the rows first, then Ready on all of them, so FlowVid never sees half a job.
sh.getRange(start, 1, rows.length, 4).setValues(rows);
SpreadsheetApp.flush();
var ready = rows.map(function () { return ['Ready']; });
sh.getRange(start, 5, rows.length, 1).setValues(ready);
return { ok: true, job: job, rows: rows.length, firstRow: start };
} finally {
lock.releaseLock();
}
}

Paste this exactly. Do not edit it.

5

Step 5. Save it

Click the disk icon in the toolbar (or press Ctrl+S). Optionally rename the project to FlowVid at the top left.

Screenshot goes here
6

Step 6. Start a deployment

Click the blue Deploy button at the top right, then New deployment.

Screenshot goes here
7

Step 7. Choose Web app

In the dialog click the gear icon next to Select type and choose Web app.

Screenshot goes here
8

Step 8. Set the options

Description: FlowVid. Execute as: Me. Who has access: Anyone. Then click Deploy.

Screenshot goes here
9

Step 9. Allow access (first time only)

Click Authorize access and pick your Google account.

Google may show Google hasn't verified this app. This is expected because the script is yours. Click Advanced, then Go to FlowVid (unsafe), then Allow.

Note. The script only touches this one Sheet.

Screenshot goes here
10

Step 10. Copy the Web app URL

Copy the address that ends in /exec and click Done.

Note. Keep it private. Anyone who has this address can use your Sheet through FlowVid.

Screenshot goes here
11

Step 11. Test it

Paste the /exec address into a new browser tab. You should see a short line of text that includes "ok":true and "app":"FlowVid".

If you see a Google sign-in page or an error, go back to step 8 and check that Who has access is Anyone.

Screenshot goes here
12

Step 12. Paste it into FlowVid

Right-click the FlowVid icon in Chrome, click Options, paste the address into the Google Sheet web app box and click Save, then Test connection.

Under Start jobs from your Google Sheet tick Watch my Sheet for new jobs, then click Check Sheet now once. Two tabs, FlowVid Jobs and FlowVid Results, appear in your Sheet.

Screenshot goes here
13

Step 13. Connect your AI

Now let Gemini, Claude or ChatGPT write jobs into your Sheet.

Connect Gemini, Claude or ChatGPT

Tip. If you update the script later: open Deploy > Manage deployments, click the pencil icon, set Version to New version and click Deploy. The /exec address stays the same.

Job format

Each row is one line of a job. Rows with the same Job name belong together. Type is one of: Project instructions, Character consistency, Script instructions, Prompt.

FlowVid JobsExample
JobTypeNameTextStatus
Promo 24 OctProject instructionsBright, upbeat product promo. Vertical 9:16.Ready
Promo 24 OctCharacter consistency@me as the presenter, friendly and calm.Ready
Promo 24 OctPromptScene 1@me waves and introduces the product at a desk.Ready
Promo 24 OctPromptScene 2A creator copies prompts one by one, looking tired.Ready
Promo 24 OctPromptScene 3@me presses Start batch and videos start appearing.Ready
Promo 24 OctPromptScene 4@me points to the Add to Chrome button.Ready

Statuses

  1. Ready
  2. Picked up
  3. Queued in FlowVid
  4. Sending
  5. Generating
  6. Done
  • If something goes wrong the status is Failed, with the reason in the Result column.
  • Instruction rows become Used once the job starts.
  • Only rows with Status Ready are taken, oldest job first, one job at a time.
  • FlowVid waits one check after a job appears, so it never starts a job your AI is still writing.
  • Set a row back to Ready to run it again.
  • FlowVid checks your Sheet every 1 to 60 minutes. You choose how often.
  • Chrome must be open, with you signed in to Google Flow.

Your /exec address is private to you. Keep it to yourself.

Troubleshooting

Need help?

We are here to help. Write to us any way you like.