sheet/get FormID SH07
Read rows from a Google Sheets tab with optional column-level filter conditions. Returns an array of row objects keyed by header names, with support for combined And/Or filter logic and configurable value rendering.
When to Use
- Finance — budget vs. actuals pull: A month-end reporting workflow reads the "Budget 2026" tab to retrieve all rows where
Departmentequals "Engineering", then joins those figures with actuals from the ERP system to compute variance and route to a Slack notification node. - HR — employee roster lookup: An onboarding trigger workflow reads the "Employees" sheet filtered by
Status = "Active"andStartDate >= todayto generate welcome emails and provision system accounts for all employees starting this week. - Operations — inventory audit: A nightly reconciliation workflow reads the "Inventory" tab filtered by
StockQty < ReorderLevelto identify items below threshold and create purchase orders automatically. - Marketing — campaign tracking: A weekly analytics workflow reads the "Campaigns" tab filtered by
Status = "Live"to pull active campaign IDs and then calls the ad platform API for each, appending performance metrics back to the sheet viasheet/appendOrUpdate. - Reporting — single record lookup: A workflow triggered by a form submission reads the "Products" sheet with
ReturnFirstMatch: trueand a SKU filter to retrieve product details before generating a quote PDF. This avoids fetching all rows when only one record is needed.
Configuration
| Field | Required | Description |
|---|---|---|
CredentialId | Required | Integer ID referencing a stored Google credential (Service Account or OAuth2) in the BizFirst Credentials Manager. |
SpreadsheetId | Required | The Google Sheets spreadsheet ID. Found in the spreadsheet URL: https://docs.google.com/spreadsheets/d/{SpreadsheetId}/edit. |
SheetName | Required | The exact name of the tab/sheet within the spreadsheet. Case-sensitive. Spaces are preserved — "Employee Roster" is different from "EmployeeRoster". |
Filters | Optional | List of SheetFilterInfo objects. Each filter has: Column (header name), Operator (Equals, NotEquals, Contains, NotContains, GreaterThan, LessThan, GreaterOrEqual, LessOrEqual, IsEmpty, IsNotEmpty), Value (comparison value as string). When omitted, all rows are returned. |
CombineFilters | Optional | Enum: And | Or. Default And. When And, a row must satisfy all filters to be included. When Or, a row that satisfies any single filter is included. |
HeaderRow | Optional | Integer. Default 1. The row number (1-based) that contains column headers. Used to map values to named keys in the output objects. |
FirstDataRow | Optional | Integer. Default 2. The first row (1-based) containing actual data. Rows between HeaderRow and FirstDataRow are skipped (useful when there are merged title rows). |
ValueRender | Optional | String. Default "UNFORMATTED_VALUE". Controls how the Sheets API renders cell values. Options: UNFORMATTED_VALUE (raw underlying value, no formatting), FORMATTED_VALUE (display string as shown in the spreadsheet including number/date formatting), FORMULA (cell formula text). Use UNFORMATTED_VALUE for numeric comparisons and FORMATTED_VALUE when the display string is what downstream nodes expect. |
DateTimeRender | Optional | String. Default "SERIAL_NUMBER". How dates and times are returned. Options: SERIAL_NUMBER (Excel-style numeric serial, e.g. 46000), FORMATTED_STRING (formatted as displayed in the sheet, e.g. "2026-01-15"). |
ReturnFirstMatch | Optional | Boolean. Default false. When true, the node returns only the first row that satisfies all filter conditions and stops scanning. Use when you expect exactly one record (e.g. lookup by unique key) to avoid unnecessary processing. |
Validation Errors
| Error Code | Cause |
|---|---|
VAL_MISSING_SPREADSHEET_ID | SpreadsheetId is empty or whitespace. |
VAL_MISSING_SHEET_NAME | SheetName is empty or whitespace. |
VAL_INVALID_HEADER_ROW | HeaderRow is less than 1 or greater than FirstDataRow. |
VAL_INVALID_FILTER_OPERATOR | A filter Operator value is not one of the supported enum members. |
VAL_INVALID_CREDENTIAL | CredentialId does not resolve to a valid Google credential in Credentials Manager. |
SHEET_NOT_FOUND | The specified SheetName does not exist in the spreadsheet. Check for typos and case sensitivity. |
Output Fields
| Field | Type | Description |
|---|---|---|
rows | array | Array of row objects. Each object has one key per column header, with the cell value as the property value. Empty cells are included as empty strings. |
rowCount | integer | Total number of rows returned after applying filters. |
spreadsheetId | string | The spreadsheet ID from the request, echoed back for chaining. |
sheetName | string | The sheet/tab name from the request, echoed back for chaining. |
status | string | "success" on the success port. |
resource | string | Always "sheet". |
operation | string | Always "get". |
Sample Configuration
{
"resource": "sheet",
"operation": "get",
"CredentialId": 12,
"SpreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"SheetName": "Employees",
"Filters": [
{ "Column": "Department", "Operator": "Equals", "Value": "Engineering" },
{ "Column": "Status", "Operator": "Equals", "Value": "Active" },
{ "Column": "Salary", "Operator": "GreaterOrEqual","Value": "60000" }
],
"CombineFilters": "And",
"HeaderRow": 1,
"FirstDataRow": 2,
"ValueRender": "UNFORMATTED_VALUE",
"DateTimeRender": "FORMATTED_STRING",
"ReturnFirstMatch": false
}
Sample Output
{
"rows": [
{
"EmployeeId": "E-1042",
"FirstName": "Sarah",
"LastName": "Mitchell",
"Department": "Engineering",
"Title": "Senior Software Engineer",
"Salary": 95000,
"Status": "Active",
"StartDate": "2023-03-14",
"ManagerEmail": "james.carter@acme.com"
},
{
"EmployeeId": "E-1087",
"FirstName": "David",
"LastName": "Okonkwo",
"Department": "Engineering",
"Title": "DevOps Engineer",
"Salary": 88000,
"Status": "Active",
"StartDate": "2024-07-01",
"ManagerEmail": "james.carter@acme.com"
},
{
"EmployeeId": "E-1103",
"FirstName": "Priya",
"LastName": "Sharma",
"Department": "Engineering",
"Title": "Staff Engineer",
"Salary": 120000,
"Status": "Active",
"StartDate": "2021-11-08",
"ManagerEmail": "james.carter@acme.com"
}
],
"rowCount": 3,
"spreadsheetId": "1BxiMVs0XRA5nFMdKvBdBZjgmUUqptlbs74OgVE2upms",
"sheetName": "Employees",
"status": "success",
"resource": "sheet",
"operation": "get"
}
Expression Reference
Assuming this node is named getEmployees in the workflow:
| Expression | Returns | Description |
|---|---|---|
{{ $output.getEmployees.rows }} | array | All returned row objects as an array. |
{{ $output.getEmployees.rows[0] }} | object | First row object in the result set. |
{{ $output.getEmployees.rows[0].Email }} | string | Email field of the first row (column header must be "Email"). |
{{ $output.getEmployees.rows[0].EmployeeId }} | string | EmployeeId of the first row. |
{{ $output.getEmployees.rowCount }} | integer | Total number of rows matching the filters. |
{{ $output.getEmployees.spreadsheetId }} | string | Spreadsheet ID (useful for passing to subsequent write nodes). |
{{ $output.getEmployees.sheetName }} | string | Sheet/tab name, echoed from the request. |
{{ $output.getEmployees.rows | length }} | integer | Alternative row count via filter expression. |
{{ $output.getEmployees.rows | map('Salary') | sum }} | number | Sum all Salary values across matching rows. |
Node Policies & GuardRails
- Credential storage: The OAuth token or service account key is resolved from the Credentials Manager at runtime via
CredentialId. Tokens are never exposed in workflow logs or output ports. - Quota awareness: The Sheets API allows 300 read requests per minute per project. If a loop node calls
sheet/getin rapid succession, insert a Delay node (600 ms minimum) to avoid429errors. - Large datasets: Sheets with more than 1,000 rows should use narrow filter conditions to reduce result size. For full exports of very large sheets, prefer the Google Drive CSV export endpoint via an
HttpRequestnode. - Sheet name case sensitivity:
SheetNameis matched exactly as typed. A sheet named "Employee Data" will not be found if you specify "employee data". - ValueRender selection: Use
UNFORMATTED_VALUEwhen downstream nodes perform numeric comparisons or arithmetic. UseFORMATTED_VALUEwhen the visual display string is the required output (e.g. currency formatted as "$1,200.00"). - Empty result handling: When no rows match the filters,
rowsis an empty array[]androwCountis0. The node routes to the success port — it does not route to error. Add an IF node downstream to branch onrowCount == 0if your workflow requires different handling for empty results. - ReturnFirstMatch performance: When looking up a single record by a unique key (e.g. EmployeeId), always set
ReturnFirstMatch: true. This stops row scanning at the first match and avoids reading the rest of the sheet.
Workflow Examples
Example 1 — Weekly Payroll Summary Email
Trigger: Scheduled (every Friday 9 AM). Read all active employees from the Payroll sheet, compute total payroll cost, and send a summary email to Finance.
// Node 1 — sheet/get (getActiveEmployees)
{
"resource": "sheet", "operation": "get",
"CredentialId": 12,
"SpreadsheetId": "{{ $env.PAYROLL_SHEET_ID }}",
"SheetName": "Employees",
"Filters": [{ "Column": "Status", "Operator": "Equals", "Value": "Active" }],
"ValueRender": "UNFORMATTED_VALUE"
}
// Node 2 — Code node (computeTotals)
// $output.getActiveEmployees.rows → sum Salary field
// Outputs: totalPayroll, headcount
// Node 3 — Email node
{
"To": "finance@acme.com",
"Subject": "Weekly Payroll Summary — {{ $now | date('YYYY-MM-DD') }}",
"Body": "Headcount: {{ $output.computeTotals.headcount }}\nTotal Payroll: ${{ $output.computeTotals.totalPayroll }}"
}
Example 2 — Inventory Reorder Alert
Trigger: Nightly at 2 AM. Read all inventory rows where stock is below the reorder threshold, then create purchase orders via the ERP HTTP API for each item.
// Node 1 — sheet/get (getLowStock)
{
"resource": "sheet", "operation": "get",
"CredentialId": 15,
"SpreadsheetId": "{{ $env.INVENTORY_SHEET_ID }}",
"SheetName": "Stock",
"Filters": [
{ "Column": "CurrentQty", "Operator": "LessThan", "Value": "{{ $json.ReorderLevel }}" }
],
"ValueRender": "UNFORMATTED_VALUE"
}
// Node 2 — Loop node over $output.getLowStock.rows
// Node 3 — HttpRequest: POST /erp/purchase-orders for each item
Example 3 — Single Product Lookup Before Quote
Trigger: Form submission with a SKU. Retrieve the product details and use them to populate a quote PDF template.
// Node 1 — sheet/get (getProduct)
{
"resource": "sheet", "operation": "get",
"CredentialId": 12,
"SpreadsheetId": "{{ $env.CATALOG_SHEET_ID }}",
"SheetName": "Products",
"Filters": [{ "Column": "SKU", "Operator": "Equals", "Value": "{{ $json.sku }}" }],
"ReturnFirstMatch": true
}
// Node 2 — PDF generation node
{
"Template": "quote-template",
"ProductName": "{{ $output.getProduct.rows[0].ProductName }}",
"UnitPrice": "{{ $output.getProduct.rows[0].UnitPrice }}",
"Description": "{{ $output.getProduct.rows[0].Description }}"
}