Google Sheets Adapter
Load and execute ffai workflows from Google Sheets spreadsheets, with write-back of results.
Setup
1. Install with Google Sheets support
pip install ffai-workflow-adapters[google_sheets]
This installs gspread, the Python library for the Google Sheets API.
2. Enable API access
Regardless of authentication method, you need a Google Cloud project with the Sheets and Drive APIs enabled:
Go to the Google Cloud Console.
Create a project (or select an existing one).
Search for Google Sheets API and enable it.
Search for Google Drive API and enable it.
3. Choose an authentication method
The adapter supports three authentication methods. Choose based on your use case:
Method |
Best for |
Access scope |
|---|---|---|
Service account |
Bots, automation, server-side |
Any spreadsheet shared with the service account |
OAuth Client ID |
End-user apps, interactive use |
The authenticated user’s spreadsheets |
API key |
Public spreadsheets only |
Only publicly shared spreadsheets |
Authentication
Service Account (default)
A service account is a bot account that accesses spreadsheets on its own behalf. You must share each spreadsheet with the service account’s email address.
Setup:
Go to IAM & Admin → Service Accounts and create a service account.
Create a key (JSON format) and download it.
Share your spreadsheet with the service account’s
client_email.
Environment variables:
GOOGLE_SHEETS_CREDENTIALS=/path/to/service-account.json
Usage (default — no extra parameters needed):
spec = load_workflow_google_sheets(
"SPREADSHEET_ID",
credentials_file="/path/to/service-account.json", # or set GOOGLE_SHEETS_CREDENTIALS
)
Or pass an explicit path via credentials_file:
spec = load_workflow_google_sheets(
"SPREADSHEET_ID",
credentials_file="/path/to/sa.json",
)
OAuth Client ID
For applications that access spreadsheets on behalf of an end user. The first run opens a browser for the user to grant access. Subsequent runs reuse the stored credentials.
Setup:
Go to APIs & Services → OAuth Consent Screen and configure it.
Go to APIs & Services → Credentials and create an OAuth client ID (Desktop app).
Download the credentials JSON file.
Environment variables:
GOOGLE_SHEETS_CREDENTIALS=/path/to/credentials.json
GOOGLE_SHEETS_AUTHORIZED_USER=/path/to/authorized_user.json
The authorized_user.json is created automatically after the first browser login. Set GOOGLE_SHEETS_AUTHORIZED_USER to persist it across runs.
Usage:
spec = load_workflow_google_sheets(
"SPREADSHEET_ID",
auth_method="oauth",
credentials_file="/path/to/credentials.json",
authorized_user_file="/path/to/authorized_user.json", # optional on first run
)
Or configure in config/adapters.yaml to make OAuth the default:
adapters:
google_sheets:
auth_method: oauth
credentials_env: GOOGLE_SHEETS_CREDENTIALS
authorized_user_env: GOOGLE_SHEETS_AUTHORIZED_USER
API Key (public spreadsheets only)
For read-only access to publicly shared spreadsheets. No OAuth flow, no service account — just a simple API key.
Setup:
Go to APIs & Services → Credentials and create an API key.
Make sure your spreadsheet is shared publicly (Anyone with the link → Viewer).
Environment variables:
GOOGLE_SHEETS_API_KEY=AIzaSyD...
Usage:
spec = load_workflow_google_sheets(
"SPREADSHEET_ID",
auth_method="api_key",
api_key="AIzaSyD...",
)
Limitations: API key auth only works with open_by_key (which the adapter uses internally). The gc.open() method (searching by name) is not supported. Writing results requires a service account or OAuth — API key auth is read-only for most practical purposes.
Choosing auth method at call time
You can override the configured auth method per call:
# Config says service_account, but this call uses API key
spec = load_workflow_google_sheets(
"SPREADSHEET_ID",
auth_method="api_key",
api_key="AIzaSyD...",
)
The auth_method parameter takes precedence over the config setting.
Loading Workflows
load_workflow_google_sheets(spreadsheet_id, *, ...)
from ffai_workflow_adapters import load_workflow_google_sheets
spec = load_workflow_google_sheets(
"1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms", # spreadsheet ID from URL
worksheet="Steps", # optional: worksheet name or index (default: first worksheet)
adapter="marketing", # optional: named adapter from config
auth_method="service_account", # optional: "service_account" (default), "oauth", or "api_key"
credentials_file="/path/to/sa.json", # optional: overrides env var
credentials_env="MY_CREDS_VAR", # optional: overrides config env var name
name="my_workflow",
defaults={"temperature": 0.5},
clients={"reviewer": {"type": "litellm", "model": "gpt-4o"}},
)
The spreadsheet_id is the long string in the Google Sheets URL:
https://docs.google.com/spreadsheets/d/SPREADSHEET_ID/edit
Worksheet Format
The first row of the selected worksheet must be a header with column names. Each subsequent row is a workflow step.
Required Columns
Column |
Description |
|---|---|
|
Unique step identifier |
|
Prompt text (supports |
Optional Columns
Column |
Description |
Default |
|---|---|---|
|
Client name from |
System default |
|
Direct model string override |
Client default |
|
Comma-separated step names for context |
— |
|
Sampling temperature (0-2) |
Client default |
|
Maximum tokens to generate |
Client default |
|
Per-step system prompt |
— |
|
Skip step unless expression is true |
— |
|
Abort workflow if expression is true |
— |
See Airtable adapter docs for the full list of column name aliases.
Example Google Sheets Workflow
name |
prompt |
client |
history |
temperature |
|---|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
Worksheet Selection
The worksheet parameter accepts a sheet name (string) or index (integer):
# By name
spec = load_workflow_google_sheets(spreadsheet_id, worksheet="Workflow")
# By index (0-based)
spec = load_workflow_google_sheets(spreadsheet_id, worksheet=1)
# Default: first worksheet
spec = load_workflow_google_sheets(spreadsheet_id)
Writing Results
write_workflow_results_google_sheets(spreadsheet_id, result, *, ...)
from ffai_workflow_adapters import write_workflow_results_google_sheets
result = await ffai.execute_workflow(spec)
# Write to the default "Results" worksheet
write_workflow_results_google_sheets(spreadsheet_id, result)
# Specify a custom worksheet
write_workflow_results_google_sheets(spreadsheet_id, result, worksheet="Run 2")
# With passthrough columns — pass the spec returned by load_workflow_google_sheets
write_workflow_results_google_sheets(spreadsheet_id, result, spec=spec)
The worksheet parameter defaults to output_worksheet from the adapter config, or "Results" if not configured. If the worksheet doesn’t exist, it is created with a header row followed by data rows. If it does exist, data rows are appended after existing data.
Output Columns
Column |
Description |
|---|---|
|
Workflow name |
|
Step name |
|
Execution status |
|
AI response text |
|
Model used |
|
Prompt tokens |
|
Completion tokens |
|
Estimated cost |
|
Execution time (ms) |
|
ISO 8601 timestamp |
Column names are remapped via output_field_map if configured.
Field Mapping
Same system as other adapters. Configure in config/adapters.yaml:
adapters:
google_sheets:
input_field_map:
Task: name
Instructions: prompt
"AI Model": client
output_field_map:
step: Task
response: Output
See Airtable adapter - Field Mapping for full details on mapping and inheritance rules.
Passthrough Columns
Carry extra source columns (Comments, Priority, etc.) into the output. Configure in config/adapters.yaml:
adapters:
google_sheets:
passthrough_columns:
- Comments
- Priority
- Category
At load time, values are captured from the source worksheet and stored as metadata on the spec. At write time, pass the spec to include them:
spec = load_workflow_google_sheets(spreadsheet_id, worksheet="Steps")
result = await ffai.execute_workflow(spec)
write_workflow_results_google_sheets(spreadsheet_id, result, spec=spec)
Passthrough columns appear in the output after the standard columns, with their original names (or remapped via output_field_map if configured). If a step has no value for a passthrough column, None is written.
Named adapters inherit passthrough_columns from the base config and can override.
Extra Output Columns
Add derived columns to every output row. Configure in config/adapters.yaml:
adapters:
google_sheets:
extra_output_columns:
run_id: "{{run_id}}"
run_date: "{{date}}"
batch_id: "2026-Q2-run-01"
run_time: "{{now:%H:%M:%S}}"
Template Expressions
Template |
Resolves to |
|---|---|
|
Timestamp-based run ID (e.g., |
|
Current UTC date (e.g., |
|
Current UTC ISO timestamp |
|
|
Any other string |
Literal value |
All templates resolve once per write call — every row in the same run gets the same run_id, date, etc. Pass run_id="batch-42" to write_workflow_results_google_sheets() to use a custom ID instead of the auto-generated one.
Extra columns appear in the output after passthrough columns. Named adapters inherit and can override.
Schema Validation
At load time, the adapter validates your worksheet before making any API calls:
Required columns —
nameandpromptmust be present (after field mapping). RaisesTabularLoadErrorwith the list of missing columns and suggestions.Type checking —
temperaturemust be numeric,max_tokensmust be numeric. Raises per-row errors.Unrecognized columns — columns that don’t match any canonical field, field map entry, or passthrough column trigger a warning log message. They’re passed through to ffai (which ignores them).
Canonical Field Names
Field |
Type |
Required |
|---|---|---|
|
string |
Yes |
|
string |
Yes |
|
string |
No |
|
string |
No |
|
string |
No |
|
float |
No |
|
int |
No |
|
string |
No |
|
string |
No |
|
string |
No |
|
string |
No |
|
string |
No |
|
bool |
No |
|
string |
No |
|
string |
No |
If your worksheet uses different column names, configure input_field_map to map them.
Column Order in Output
Standard columns → passthrough columns → extra output columns.
Resilience
All Google Sheets API calls are automatically protected by:
Rate limiting — A token-bucket limiter caps request throughput (default: 5 req/s, burst 10).
Circuit breaker — After repeated failures (default: 5), calls are blocked until a recovery timeout (default: 30s), then a limited number of probe requests are allowed before the circuit fully re-opens.
Retry with backoff — Failed requests with retryable status codes (429, 502, 503, 504) are retried with exponential backoff and jitter (default: 3 attempts).
These are internal — the public API is unchanged. Configure via config/main.yaml. See Airtable adapter - Resilience for full configuration details.
Observability
Adapter operations emit OpenTelemetry spans when FFAI’s observability is enabled. Spans cover Google Sheets load and write operations, including record counts, timing, and error details.
Enabling
Same as Airtable adapter — see Airtable adapter - Observability.
Emitted Spans
Span Name |
Operation |
Key Attributes |
|---|---|---|
|
Load workflow from spreadsheet |
|
|
Write results to spreadsheet |
|
|
External API call (Google Sheets) |
|
|
Retry attempt |
|
Config Reference
adapters:
google_sheets:
# Authentication method: "service_account" (default), "oauth", or "api_key"
auth_method: service_account
# Service account / OAuth credentials env var (holds path to JSON file)
credentials_env: GOOGLE_SHEETS_CREDENTIALS
# OAuth authorized user env var (holds path to authorized_user.json)
authorized_user_env: GOOGLE_SHEETS_AUTHORIZED_USER
# API key env var (for api_key auth method)
api_key_env: GOOGLE_SHEETS_API_KEY
# Output worksheet name (used when worksheet= is omitted)
output_worksheet: "Results"
# Carry source columns to output
passthrough_columns:
- Comments
- Priority
# Add derived columns
extra_output_columns:
run_id: "{{run_id}}"
run_date: "{{date}}"
batch: "demo-run"
# Remap source column names to canonical fields
input_field_map:
Name: name
Prompt: prompt
# Remap output column names
output_field_map:
step: Step
response: Response
Full Example
import asyncio
from ffai import FFAI
from ffai.Clients.AsyncFFLiteLLMClient import AsyncFFLiteLLMClient
from ffai_workflow_adapters import (
load_workflow_google_sheets,
write_workflow_results_google_sheets,
)
SPREADSHEET_ID = "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms"
async def main():
client = AsyncFFLiteLLMClient(model_string="mistral/mistral-small-latest", api_key="...")
ffai = FFAI(client)
spec = load_workflow_google_sheets(
SPREADSHEET_ID,
worksheet="Steps",
name="sheets_workflow",
)
result = await ffai.execute_workflow(spec)
# Appends to "Results" worksheet with passthrough + extra columns
write_workflow_results_google_sheets(SPREADSHEET_ID, result, spec=spec)
asyncio.run(main())