Portal Community

Example 1: Append New Order Row to Orders Sheet with Column Mapping

Scenario: An e-commerce workflow receives a new order webhook. After processing, it appends a row to the Orders tracking sheet with all relevant fields mapped to the correct columns.

{
  "operation": "sheet/append",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ORDERS_SPREADSHEET_ID }}",
  "SheetName": "Orders",
  "DataMode": "DefineBelow",
  "ColumnMappings": [
    { "Column": "Order ID",     "Value": "{{ vars.order_id }}" },
    { "Column": "Customer",     "Value": "{{ vars.customer_name }}" },
    { "Column": "Email",        "Value": "{{ vars.customer_email }}" },
    { "Column": "Amount",       "Value": "{{ vars.order_total }}" },
    { "Column": "Currency",     "Value": "{{ vars.currency }}" },
    { "Column": "Status",       "Value": "New" },
    { "Column": "Created Date", "Value": "{{ vars.order_date }}" }
  ],
  "CellFormat": "UserEntered",
  "HandlingExtraData": "IgnoreIt",
  "UseAppend": true
}
Expected outcome: A new row is appended after the last existing data row in the Orders sheet. Each column is written with the value from the corresponding workflow variable. CellFormat: UserEntered ensures dates and numbers are parsed into native Sheets types. The output updatedRange confirms which row was written (e.g. Orders!A47:G47).

Example 2: Update Existing Customer Record Using Email as Lookup Filter

Scenario: A CRM sync workflow receives an updated customer record. It uses the customer's email address to find the existing row in a Customers sheet and updates the address, phone, and last-modified date — or appends a new row if the customer is not yet in the sheet.

{
  "operation": "sheet/appendOrUpdate",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.CRM_SPREADSHEET_ID }}",
  "SheetName": "Customers",
  "DataMode": "DefineBelow",
  "ColumnMappings": [
    { "Column": "Email",         "Value": "{{ vars.email }}" },
    { "Column": "First Name",    "Value": "{{ vars.first_name }}" },
    { "Column": "Last Name",     "Value": "{{ vars.last_name }}" },
    { "Column": "Phone",         "Value": "{{ vars.phone }}" },
    { "Column": "Address",       "Value": "{{ vars.address }}" },
    { "Column": "Last Updated",  "Value": "{{ vars.sync_timestamp }}" }
  ],
  "LookupFilters": [
    { "Column": "Email", "Value": "{{ vars.email }}" }
  ],
  "CombineFilters": "And",
  "CellFormat": "UserEntered",
  "HeaderRow": 1,
  "FirstDataRow": 2,
  "HandlingExtraData": "IgnoreIt"
}
Expected outcome: If a row with the matching email is found, its columns are updated in place. If not found, a new row is appended. The output action field is either "updated" or "appended", which can be used in a downstream Switch node to trigger different notifications (e.g. "Customer updated" vs. "New customer added to sheet").

Example 3: Get All Unprocessed Rows (Filter by Status = "Pending")

Scenario: A scheduled workflow runs every hour to pick up unprocessed order rows from the Orders sheet. It filters rows where the Status column equals "Pending", then passes the array to a Loop node for individual row processing.

{
  "operation": "sheet/get",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ORDERS_SPREADSHEET_ID }}",
  "SheetName": "Orders",
  "Filters": [
    { "Column": "Status", "Value": "Pending" }
  ],
  "CombineFilters": "And",
  "HeaderRow": 1,
  "FirstDataRow": 2,
  "ValueRender": "FORMATTED_VALUE",
  "DateTimeRender": "FORMATTED_STRING",
  "ReturnFirstMatch": false
}
Expected outcome: Returns a rows array containing all rows where Status is exactly "Pending". The array is passed to a Loop node. Each loop iteration processes one order row — for example, calling a payment API, updating the Status to "Processing", and sending a confirmation email. After processing, a sheet/update node updates the Status column to "Done".
Accessing row fields in the Loop: Within the loop, access the current row's Order ID with {{ loop.current["Order ID"] }} and the customer email with {{ loop.current["Email"] }}.

Example 4: Create a New Sheet Tab for Each Month

Scenario: A monthly reporting workflow runs on the 1st of each month. It creates a new sheet tab in the annual reporting spreadsheet named for the current month, with a color-coded tab for easy navigation.

{
  "operation": "sheet/create",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ANNUAL_REPORT_SPREADSHEET_ID }}",
  "Title": "{{ vars.current_month_name }} {{ vars.current_year }}",
  "Hidden": false,
  "TabColor": "#4CAF50"
}
Expected outcome: A new green-tabbed sheet named (e.g.) "May 2026" is created in the spreadsheet. Subsequent nodes in the workflow use the returned sheetId or the Title to populate the new tab with the month's data. Use a ScheduledTrigger node set to run on the 1st of each month at 00:05 to automate this workflow.
Set vars.current_month_name and vars.current_year with a VariableAssignment node using a date expression upstream, such as {{ date.format(date.now(), "MMMM") }} and {{ date.format(date.now(), "yyyy") }}.

Example 5: Delete Processed Rows and Archive to Another Sheet

Scenario: After a batch processing workflow completes, all rows with Status "Done" are moved to an Archive sheet and then deleted from the active Orders sheet to keep it lean.

// Step 1: Get all "Done" rows for archiving
{
  "operation": "sheet/get",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ORDERS_SPREADSHEET_ID }}",
  "SheetName": "Orders",
  "Filters": [{ "Column": "Status", "Value": "Done" }],
  "ReturnFirstMatch": false
}

// Step 2: Loop — append each "Done" row to the Archive sheet
// (Inside the loop, for each row in nodes.getDoneRows.output.rows)
{
  "operation": "sheet/append",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ORDERS_SPREADSHEET_ID }}",
  "SheetName": "Archive",
  "DataMode": "AutoMap",
  "CellFormat": "UserEntered",
  "HandlingExtraData": "IgnoreIt"
}

// Step 3: Delete rows from Orders where Status = "Done"
// (Use sheet/update to mark them, then deleteRowsOrColumns for the range)
{
  "operation": "sheet/deleteRowsOrColumns",
  "CredentialId": "{{ env.GOOGLE_CRED_ID }}",
  "SpreadsheetId": "{{ env.ORDERS_SPREADSHEET_ID }}",
  "SheetName": "Orders",
  "Dimension": "Rows",
  "StartIndex": "{{ vars.first_done_row_index }}",
  "Amount": "{{ vars.done_row_count }}"
}
Expected outcome: All "Done" rows are written to the Archive sheet preserving all column values, and then deleted from the Orders sheet. The Orders sheet retains only active rows. The archived rows are available in Archive for historical reporting and audit purposes.
Row index calculation: StartIndex in deleteRowsOrColumns is 0-based. Row 1 (the header) is index 0; the first data row is index 1. Calculate vars.first_done_row_index from the row numbers returned by sheet/get. When deleting multiple non-contiguous rows, delete from the bottom up to avoid index shifting that would cause incorrect deletions.