When to Use
- HR — status change workflow: When an HR system fires a "termination" event, a workflow updates only the
Status and TerminationDate columns in the employee's row in the "Employees" sheet — leaving all other columns (name, department, salary history) unchanged.
- Finance — invoice payment marking: When a payment webhook arrives from the payment gateway, a workflow updates the
PaidDate, PaymentMethod, and AmountReceived columns for the matching invoice row in the "Invoices" sheet without touching the original invoice amount or line items.
- Operations — shipment status update: A carrier webhook calls the workflow each time a parcel status changes. The workflow updates
Status, LastUpdated, and CurrentLocation columns in the shipment row, leaving the destination, weight, and sender information intact.
- Marketing — approval workflow: After a manager approves a campaign budget request in an approval node, a workflow updates the
Status column from "Pending" to "Approved" and writes the ApprovedBy and ApprovedAt columns in the "Campaign Requests" sheet.
- Reporting — last-run timestamp: After each automated report generation, a workflow updates a "Last Generated" column in a "Report Registry" sheet so dashboard users can see when each report was last refreshed.
Row must exist: sheet/update only updates matching rows. If no row matches the
LookupFilters, the operation completes with
updatedRows: 0 — it does not create a new row. If you need insert-or-update behavior, use
sheet/appendOrUpdate instead.
Configuration
| Field | Required | Description |
CredentialId | Required | Integer ID referencing a stored Google credential in the BizFirst Credentials Manager. |
SpreadsheetId | Required | The Google Sheets spreadsheet ID from the URL. |
SheetName | Required | Exact name of the target tab. Case-sensitive. |
LookupFilters | Required | List of SheetFilterInfo objects that identify the row(s) to update. Same structure as sheet/get filters: Column, Operator, Value. All rows that match the filters will be updated. |
CombineFilters | Optional | Enum: And | Or. Default And. How multiple LookupFilters are combined. |
DataMode | Optional | Enum: AutoMap | DefineBelow | Nothing. Default AutoMap. Controls how input data is mapped to sheet columns for the write. |
ColumnMappings | Optional | Required when DataMode = DefineBelow. List of ColumnMapping objects: Column (sheet header) and Value (expression or literal). Only the columns listed here are written — all other columns are untouched. |
CellFormat | Optional | Enum: UserEntered | Raw. Default UserEntered. |
HeaderRow | Optional | Integer. Default 1. Row number containing column headers. |
FirstDataRow | Optional | Integer. Default 2. First row containing data. |
Validation Errors
| Error Code | Cause |
VAL_MISSING_SPREADSHEET_ID | SpreadsheetId is empty or whitespace. |
VAL_MISSING_SHEET_NAME | SheetName is empty or whitespace. |
VAL_MISSING_LOOKUP_FILTERS | LookupFilters is null or empty. Without filters, the node cannot identify which rows to update. |
VAL_MISSING_COLUMN_MAPPINGS | DataMode = DefineBelow but ColumnMappings is null or empty. |
SHEET_NOT_FOUND | The specified SheetName does not exist in the spreadsheet. |
Output Fields
| Field | Type | Description |
updatedRange | string | A1 notation of the cells that were written. When multiple rows are updated, this is the full bounding range (e.g. "Employees!A10:H12"). |
updatedRows | integer | Number of rows that matched the filters and were updated. 0 if no match was found. |
spreadsheetId | string | Echoed spreadsheet ID. |
sheetName | string | Echoed sheet/tab name. |
status | string | "success". |
resource | string | Always "sheet". |
operation | string | Always "update". |
Sample Configuration
{
"resource": "sheet",
"operation": "update",
"CredentialId": 12,
"SpreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"SheetName": "Invoices",
"LookupFilters": [
{ "Column": "InvoiceNumber", "Operator": "Equals", "Value": "{{ $json.invoiceNumber }}" }
],
"CombineFilters": "And",
"DataMode": "DefineBelow",
"ColumnMappings": [
{ "Column": "Status", "Value": "Paid" },
{ "Column": "PaidDate", "Value": "{{ $json.paymentDate }}" },
{ "Column": "PaymentMethod", "Value": "{{ $json.method }}" },
{ "Column": "AmountReceived", "Value": "{{ $json.amountReceived }}" },
{ "Column": "TransactionRef", "Value": "{{ $json.transactionId }}" }
],
"CellFormat": "UserEntered",
"HeaderRow": 1,
"FirstDataRow": 2
}
Sample Output
{
"updatedRange": "Invoices!A34:M34",
"updatedRows": 1,
"spreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"sheetName": "Invoices",
"status": "success",
"resource": "sheet",
"operation": "update"
}
Expression Reference
Assuming this node is named markPaid in the workflow:
| Expression | Returns | Description |
{{ $output.markPaid.updatedRows }} | integer | Number of rows updated. Check for 0 to detect no-match scenarios. |
{{ $output.markPaid.updatedRange }} | string | A1 range of cells written. |
{{ $output.markPaid.status }} | string | "success" on the success port. |
{{ $output.markPaid.updatedRows == 0 }} | boolean | True if no matching row was found — use in IF node to handle the no-match case. |
Node Policies & GuardRails
- Multi-row update: If the
LookupFilters match more than one row, all matching rows are updated. This is intentional for bulk status changes. To update exactly one row, ensure your lookup key is unique (e.g. an ID column with no duplicates).
- Zero-match behavior: A no-match condition is not an error — the node routes to the success port with
updatedRows: 0. Add an IF node checking updatedRows == 0 if your workflow must handle the case where the expected record does not exist.
- Partial column write: Only columns listed in
ColumnMappings are written. This makes sheet/update ideal for targeted field updates — no risk of accidentally blanking other columns.
- Formula injection: Sanitize all user-supplied values before passing them as
ColumnMappings values when CellFormat = UserEntered. Values starting with =, +, -, or @ are interpreted as formulas.
- Concurrent updates: The read-then-write pattern is not atomic. Avoid two parallel workflow branches calling
sheet/update on the same sheet with overlapping row ranges simultaneously.
- Quota: Each update call is one write request toward the 100 writes/minute per project quota.
Workflow Examples
Example 1 — Invoice Payment Webhook Handler
// Trigger: Webhook from payment gateway (POST /webhooks/payment-received)
// Node 1 — sheet/update (markInvoicePaid)
{
"resource": "sheet", "operation": "update",
"CredentialId": 12,
"SpreadsheetId": "{{ $env.FINANCE_SHEET_ID }}",
"SheetName": "Invoices",
"LookupFilters": [
{ "Column": "InvoiceNumber", "Operator": "Equals", "Value": "{{ $json.invoiceRef }}" }
],
"DataMode": "DefineBelow",
"ColumnMappings": [
{ "Column": "Status", "Value": "Paid" },
{ "Column": "PaidDate", "Value": "{{ $json.paidAt }}" },
{ "Column": "PaidAmount","Value": "{{ $json.amount }}" }
]
}
// Node 2 — IF: updatedRows == 0
// True: Slack alert to finance@acme.com "Unknown invoice payment received: {{ $json.invoiceRef }}"
// False: Continue to confirmation email
Example 2 — Approval Workflow — Mark Campaign Approved
// Trigger: Approval node output (manager approved campaign budget request)
// Node 1 — sheet/update (approveCampaign)
{
"resource": "sheet", "operation": "update",
"CredentialId": 15,
"SpreadsheetId": "{{ $env.MARKETING_SHEET_ID }}",
"SheetName": "Campaign Requests",
"LookupFilters": [
{ "Column": "RequestId", "Operator": "Equals", "Value": "{{ $json.requestId }}" }
],
"DataMode": "DefineBelow",
"ColumnMappings": [
{ "Column": "Status", "Value": "Approved" },
{ "Column": "ApprovedBy", "Value": "{{ $json.approverName }}" },
{ "Column": "ApprovedAt", "Value": "{{ $now | date('YYYY-MM-DD HH:mm') }}" },
{ "Column": "BudgetFinal","Value": "{{ $json.approvedBudget }}" }
]
}
Example 3 — Bulk Status Reset at Month End
// Update all rows where Status = "Pending Review" to "Archived" at month end
// Uses CombineFilters: And with a date filter
{
"resource": "sheet", "operation": "update",
"CredentialId": 12,
"SpreadsheetId": "{{ $env.OPS_SHEET_ID }}",
"SheetName": "Tasks",
"LookupFilters": [
{ "Column": "Status", "Operator": "Equals", "Value": "Pending Review" },
{ "Column": "DueDate", "Operator": "LessThan", "Value": "{{ $now | date('YYYY-MM-DD') }}" }
],
"CombineFilters": "And",
"DataMode": "DefineBelow",
"ColumnMappings": [
{ "Column": "Status", "Value": "Archived" },
{ "Column": "ArchivedAt", "Value": "{{ $now | date('YYYY-MM-DD') }}" }
]
}