Skip to content

JsWorkflows provides a platform-managed Google OAuth app. You do not need to create your own Google Cloud project or manage Google client credentials.

Google OAuth in JsWorkflows is built around one idea:

  • create a Google connection once
  • give it a handle such as my-google
  • use that handle later in workflow code, templates, or the editor

You can then use that same handle to:

  • get an access token in workflow code with api.getOAuthToken(handle)
  • pick Google Sheets, Docs, or Drive folders from the app UI
  • save those selected file references into template settings or workflow variables

JsWorkflows currently documents the Google picker-based file flow around this scope:

ResourceUse in JsWorkflowsScope
Google Drive, Google Sheets, Google DocsSelect existing files and folders, create files, upload files, and work with selected Sheets and Docsdrive.file

drive.file is the important scope for Google Sheets, Google Docs, and Google Drive file picker flows. It allows you to select the specific files you want to share with JsWorkflows and lets workflows create new files as needed.

You can open the OAuth section in either of these places:

  1. From Settings → OAuth2 Tokens
  2. From the workflow code editor:
    • open More actions
    • select Manage OAuth2 tokens

Then:

  1. Click Connect to Service
  2. Choose Google
  3. Complete Google’s consent screen
  4. Save the connection with a handle such as my-google

That handle is the stable name your workflows use later. Use lowercase letters, numbers, and hyphens only.

You do not pick files during the OAuth approval step.

Instead, the normal flow is:

  1. create the Google OAuth connection
  2. save the handle
  3. later, enter that handle in either:
    • the Manage Environment Variables page in the editor
    • a template settings field that uses the Google picker
  4. select the Google Sheet, Doc, or Drive folder you want the workflow to use

This keeps the Google connection separate from the file selection.

In Manage Environment Variables, the Google picker is an optional helper. It does not replace normal secret variables.

Typical flow:

  1. open Manage Environment Variables
  2. in Optional: Add Google File Reference, enter your Google OAuth handle
  3. choose the resource type
  4. click the picker button
  5. select the file or folder
  6. save the selected file reference as a workflow variable

The variable key you choose becomes the env key used in workflow code.

For example:

  • if you save the picker value under the variable key GOOGLE_SHEET
  • the workflow reads it as env.GOOGLE_SHEET

The saved value is a JSON reference, for example:

{
"id": "1abc123...",
"name": "Inventory Sheet",
"mimeType": "application/vnd.google-apps.spreadsheet",
"resourceKey": "",
"url": "https://docs.google.com/spreadsheets/d/1abc123..."
}

This is workflow-scoped. It is not a store-global file setting.

So a common pattern is:

  1. save the selected spreadsheet reference under a key such as GOOGLE_SHEET
  2. read that value later in code with env.GOOGLE_SHEET

Templates can also use Google picker fields.

In a picker-enabled template:

  1. enter the Google OAuth handle in the template settings
  2. use the file picker field for the spreadsheet, document, or folder
  3. save the workflow

When the workflow is saved, JsWorkflows generates the runtime config.js for that workflow using the selected template values.

The original template file is not modified. The saved file references belong to the workflow created from the template.

The safest pattern is to support either:

  • a raw Google file ID
  • or a JSON reference saved by the picker

Use a small helper:

function parseGoogleReference(input) {
const raw = String(input || '').trim();
if (!raw) return { id: '', resourceKey: '' };
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
return {
id: String(parsed.id || '').trim(),
resourceKey: String(parsed.resourceKey || '').trim(),
};
}
} catch {}
return { id: raw, resourceKey: '' };
}

This example appends one row to a spreadsheet selected with the picker.

function parseGoogleReference(input) {
const raw = String(input || '').trim();
if (!raw) return { id: '', resourceKey: '' };
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
return {
id: String(parsed.id || '').trim(),
resourceKey: String(parsed.resourceKey || '').trim(),
};
}
} catch {}
return { id: raw, resourceKey: '' };
}
export class Workflow {
async start(payload, headers, api) {
const { token, error } = await api.getOAuthToken('my-google');
if (error || !token) throw new Error(error || 'Missing Google token');
const { id: spreadsheetId, resourceKey } = parseGoogleReference(env.SHEET_REF);
const range = encodeURIComponent('Sheet1!A1');
const requestHeaders = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
if (resourceKey) {
requestHeaders['X-Goog-Drive-Resource-Keys'] = `${spreadsheetId}/${resourceKey}`;
}
const response = await fetch(
`https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}/values/${range}:append?valueInputOption=USER_ENTERED`,
{
method: 'POST',
headers: requestHeaders,
body: JSON.stringify({
values: [[
'JsWorkflows',
'Google Sheets test',
new Date().toISOString(),
]],
}),
}
);
if (!response.ok) {
throw new Error(`Google Sheets append failed: ${response.status} ${await response.text()}`);
}
}
}

If your sheet name contains spaces or special characters, always URL-encode the full range string before placing it in the Sheets API URL.

This example appends text to a Google Doc selected with the picker.

function parseGoogleReference(input) {
const raw = String(input || '').trim();
if (!raw) return { id: '', resourceKey: '' };
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
return {
id: String(parsed.id || '').trim(),
resourceKey: String(parsed.resourceKey || '').trim(),
};
}
} catch {}
return { id: raw, resourceKey: '' };
}
export class Workflow {
async start(payload, headers, api) {
const { token, error } = await api.getOAuthToken('my-google');
if (error || !token) throw new Error(error || 'Missing Google token');
const { id: documentId, resourceKey } = parseGoogleReference(env.DOC_REF);
const requestHeaders = { Authorization: `Bearer ${token}` };
if (resourceKey) {
requestHeaders['X-Goog-Drive-Resource-Keys'] = `${documentId}/${resourceKey}`;
}
const docRes = await fetch(`https://docs.googleapis.com/v1/documents/${documentId}`, {
headers: requestHeaders,
});
if (!docRes.ok) {
throw new Error(`Google Docs read failed: ${docRes.status} ${await docRes.text()}`);
}
const doc = await docRes.json();
const endIndex = Math.max(1, (doc.body?.content?.[doc.body.content.length - 1]?.endIndex ?? 1) - 1);
const updateHeaders = {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
};
if (resourceKey) {
updateHeaders['X-Goog-Drive-Resource-Keys'] = `${documentId}/${resourceKey}`;
}
const updateRes = await fetch(`https://docs.googleapis.com/v1/documents/${documentId}:batchUpdate`, {
method: 'POST',
headers: updateHeaders,
body: JSON.stringify({
requests: [
{
insertText: {
location: { index: endIndex },
text: `Updated by JsWorkflows at ${new Date().toISOString()}\n`,
},
},
],
}),
});
if (!updateRes.ok) {
throw new Error(`Google Docs update failed: ${updateRes.status} ${await updateRes.text()}`);
}
}
}

This example uploads a text file to a Drive folder selected with the picker.

function parseGoogleReference(input) {
const raw = String(input || '').trim();
if (!raw) return { id: '', resourceKey: '' };
try {
const parsed = JSON.parse(raw);
if (parsed && typeof parsed === 'object') {
return {
id: String(parsed.id || '').trim(),
resourceKey: String(parsed.resourceKey || '').trim(),
};
}
} catch {}
return { id: raw, resourceKey: '' };
}
export class Workflow {
async start(payload, headers, api) {
const { token, error } = await api.getOAuthToken('my-google');
if (error || !token) throw new Error(error || 'Missing Google token');
const { id: folderId, resourceKey } = parseGoogleReference(env.DRIVE_FOLDER_REF);
const boundary = `jsw-${Date.now()}`;
const metadata = {
name: `jsworkflows-demo-${Date.now()}.txt`,
mimeType: 'text/plain',
parents: folderId ? [folderId] : undefined,
};
const body = [
`--${boundary}`,
'Content-Type: application/json; charset=UTF-8',
'',
JSON.stringify(metadata),
`--${boundary}`,
'Content-Type: text/plain; charset=UTF-8',
'',
'Created by JsWorkflows',
`--${boundary}--`,
'',
].join('\r\n');
const requestHeaders = {
Authorization: `Bearer ${token}`,
'Content-Type': `multipart/related; boundary=${boundary}`,
};
if (folderId && resourceKey) {
requestHeaders['X-Goog-Drive-Resource-Keys'] = `${folderId}/${resourceKey}`;
}
const response = await fetch('https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart', {
method: 'POST',
headers: requestHeaders,
body,
});
if (!response.ok) {
throw new Error(`Google Drive upload failed: ${response.status} ${await response.text()}`);
}
}
}

In JsWorkflows, a Google file reference can come from two places:

  • the built-in picker
  • a manually pasted Google file ID
  • you want to choose an existing Google Sheet, Doc, or Drive folder from the app UI
  • you want the workflow to use the file you selected through your Google connection
  • you want the safest setup for drive.file

This is the recommended option for most merchants.

  • you already know the exact Google file ID
  • you copied it from a Google URL
  • the file is already accessible to the same Google OAuth connection
  • you are updating an existing workflow that already has access to that file

A Google file ID is the long identifier inside a Google URL. For example:

https://docs.google.com/spreadsheets/d/1abc123XYZ456/edit
^^^^^^^^^^^^^
this part is the file ID

Knowing a Google file ID does not automatically give the workflow access to that file.

With drive.file, a workflow can only use files that are already accessible to the same Google OAuth connection. In practice, that usually means:

  • the file was selected with the picker
  • the file was created by the workflow
  • or the file was already made accessible to that same Google OAuth app and connection

If you are unsure, use the picker. That is the clearest and safest way to make an existing Google Sheet, Doc, or Drive folder available to the workflow.

For most merchants, the picker should be treated as the normal way to connect an existing Google file to a workflow.

Manual file IDs are an advanced option. They do not grant access by themselves.

The normal Google OAuth pattern in JsWorkflows is:

  1. create one Google OAuth connection
  2. save a handle
  3. use that handle in workflow code with api.getOAuthToken(handle)
  4. optionally use the picker in:
    • template settings
    • Manage Environment Variables
  5. save the selected file reference and use it in code