Google OAuth
JsWorkflows provides a platform-managed Google OAuth app. You do not need to create your own Google Cloud project or manage Google client credentials.
How Google OAuth works in JsWorkflows
Section titled “How Google OAuth works in JsWorkflows”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
Current Google scope
Section titled “Current Google scope”JsWorkflows currently documents the Google picker-based file flow around this scope:
| Resource | Use in JsWorkflows | Scope |
|---|---|---|
| Google Drive, Google Sheets, Google Docs | Select existing files and folders, create files, upload files, and work with selected Sheets and Docs | drive.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.
Step 1: Create a Google connection
Section titled “Step 1: Create a Google connection”You can open the OAuth section in either of these places:
- From Settings → OAuth2 Tokens
- From the workflow code editor:
- open More actions
- select Manage OAuth2 tokens
Then:
- Click Connect to Service
- Choose Google
- Complete Google’s consent screen
- 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.
Step 2: Use the handle with a file picker
Section titled “Step 2: Use the handle with a file picker”You do not pick files during the OAuth approval step.
Instead, the normal flow is:
- create the Google OAuth connection
- save the handle
- later, enter that handle in either:
- the Manage Environment Variables page in the editor
- a template settings field that uses the Google picker
- select the Google Sheet, Doc, or Drive folder you want the workflow to use
This keeps the Google connection separate from the file selection.
Using the picker in the editor
Section titled “Using the picker in the editor”In Manage Environment Variables, the Google picker is an optional helper. It does not replace normal secret variables.
Typical flow:
- open Manage Environment Variables
- in Optional: Add Google File Reference, enter your Google OAuth handle
- choose the resource type
- click the picker button
- select the file or folder
- 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:
- save the selected spreadsheet reference under a key such as
GOOGLE_SHEET - read that value later in code with
env.GOOGLE_SHEET
Using the picker in templates
Section titled “Using the picker in templates”Templates can also use Google picker fields.
In a picker-enabled template:
- enter the Google OAuth handle in the template settings
- use the file picker field for the spreadsheet, document, or folder
- 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.
Using the saved file reference in code
Section titled “Using the saved file reference in code”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: '' };}Example: Google Sheets
Section titled “Example: Google Sheets”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.
Example: Google Docs
Section titled “Example: Google Docs”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()}`); } }}Example: Google Drive folder
Section titled “Example: Google Drive folder”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()}`); } }}Picker values vs pasted file IDs
Section titled “Picker values vs pasted file IDs”In JsWorkflows, a Google file reference can come from two places:
- the built-in picker
- a manually pasted Google file ID
Use the picker when
Section titled “Use the picker when”- 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.
Use a pasted file ID only when
Section titled “Use a pasted file ID only when”- 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 IDKnowing 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.
Summary
Section titled “Summary”The normal Google OAuth pattern in JsWorkflows is:
- create one Google OAuth connection
- save a handle
- use that handle in workflow code with
api.getOAuthToken(handle) - optionally use the picker in:
- template settings
- Manage Environment Variables
- save the selected file reference and use it in code