ACTION_ID: lookup_another_floqer_workflow_row NAME: Lookup Row from a Floqer Table CATEGORY: Data Operations CREDITS: 0 Look up a row in another Floqer table by matching a column value. Returns the matched row(s) plus a count of matches. INDEX: 1. Inputs 2. Outputs 3. How to configure 4. Key notes 5. Where it fits in a workflow 6. Accessing record fields 7. When to use 8. When not to use ================================================================================ 1. INPUTS ================================================================================ table_to_search (string, required) — Target Table The destination sheet to search. Pass a `sheet_id` (UUID) for any sheet the caller's org owns — typically the master sheet that already holds the rows you want to find (e.g. "All Companies", "All Contacts"). Discovering the value: 1. GET /api/v1/workflows -> pick the workflow. 2. GET /api/v1/workflows/{workflow_id} (Get Workflow Overview) -> pick the sheet from `data.sheets[].sheet_id`. For the main sheet, `sheet_id` equals the workflow's own ID, so step 2 is optional — the workflow_id from step 1 works as a sheet_id directly. Example: "table_to_search": "7dca51c6-1308-4501-81a0-8d655c3bfd2a" target_column (string, required) — Target Column Identifier of the column on `table_to_search` to match on. This is a UUID — get it via Get Action Field Options: POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/ {action_instance_id}/options/target_column Body: { "context": { "target_table": "" } } The response is `{ options: [{ value, label }, ...] }` where each `value` is the column's id and `label` is the column's display name (e.g. "Email", "Domain"). Pass the chosen `value` straight into Configure Action — there's no separate lookup; the column id is what storage and the engine use. Note on naming: the options-call context key is `target_table` (NOT `table_to_search`). This is the only place where the two names diverge — everywhere else in the Configure Action body the field is `table_to_search`. Scope: input columns only. Action-output cells cannot be match keys. Get Action Field Options for `target_column` returns input columns only. If the match key lives in an action-output cell on the target sheet, either: (a) push the value into an input column on a destination sheet via `push_data_to_sheet` first, then look up against that sheet, or (b) match on any input column that identifies the same row (e.g. `linkedin_url`, `email`), and read the action output out of `record` downstream (see §2 and §6). operator (string, required) — Operator Match operator. Allowed values: "eq" exact match. "contains" substring match. Slower; prone to false positives. Use only when an exact key isn't available. value (string, required) — Target Value The value to search for. Can be a variable reference (e.g. `{{input.email}}`) or a static string. References resolve per-row at run time. find_all (boolean, optional) — Find All Rows Match one vs. match all. Default `false`. false returns the first match as a single object. true matches every row that satisfies the operator, returned as an array of objects. This flag also changes the shape of the `record` output — see section 2. ================================================================================ 2. OUTPUTS ================================================================================ record (object | array) — the matched row(s). Carries every cell on the matched row: input columns AND every action output. Asymmetric with `target_column` — matching is input-only; the returned `record` is the full row. Shape: - `find_all: false` (default): single object, or `{}` if no match. - `find_all: true`: array of objects, or `[]` if no match. Key naming inside each object: - Input columns → input's snake_case name (e.g. `email`, `domain`, `company_name`). - Action-output cells → action's display name VERBATIM, including spaces, em-dashes, parentheses, `??`, etc. (e.g. "Email 1 — LLM-generated opener (Claude Sonnet 4.6)"). No slug-ification, no rename — display name as shown in the UI. Multi-output actions: the value under the display-name key is the action's primary output (`formatted_data` / `generated_content` / the documented single value). Structured-array outputs are not exploded into per-field keys. Verified 2026-06-08. FILTER-ACTION CELLS ARE A TRAP. A filter action appears under its display name like any other, but its value is the filter's STATUS, not the value you filtered on: empty string `""` when the row passed, `conditionNotMet` when it was blocked. So `record["Filter Name"] === "high"` never matches — the cell never holds `"high"`. Verified 2026-06-08. See §6 for downstream access patterns. total (number) — total number of records that matched. `0` means no match. Always populated regardless of `find_all`. ================================================================================ 3. HOW TO CONFIGURE ================================================================================ Discovery flow (resolves the target sheet and column id before Configure Action): 1. Add the action. POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/actions/add Body: { "action_id": "lookup_another_floqer_workflow_row" } Returns the new `action_instance_id`. 2. Pick the target sheet via List Workflows → Get Workflow Overview (`data.sheets[].sheet_id`). GET /api/v1/workflows GET /api/v1/workflows/{target_workflow_id} Take the chosen sheet's `sheet_id`. For the workflow's main sheet, `sheet_id === workflow_id` so step 2 is optional. 3. Resolve `target_column` via Get Action Field Options. POST /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/ actions/{action_instance_id}/options/target_column Body: { "context": { "target_table": "" } } Pick the chosen option's `value` (a UUID) for the column you want to match on. The options call works without first PATCHing anything on the action — pass `target_table` inline in the context. 4. Configure the action with all five fields in a single PATCH. PATCH /api/v1/workflows/{workflow_id}/sheets/{sheet_id}/ actions/{action_instance_id} Body: { "inputs": { "table_to_search": "", "target_column": "", "operator": "eq", "value": "{{input.email}}", "find_all": false } } Field-by-field: - table_to_search `sheet_id` (UUID) of the destination sheet. - target_column id (UUID) of the column to match on (from Get Action Field Options). - operator `"eq"` or `"contains"`. - value String literal or `{{...}}` reference. - find_all Boolean. `false` (default) returns one match; `true` returns all matches as an array. ================================================================================ 4. KEY NOTES ================================================================================ - Always branch on `total` before reading fields off `record` — `total === 0` means the lookup found nothing. - `record` shape depends on `find_all`: object when `false`, array of objects when `true`. Downstream actions that read individual fields work for the single-match case; for multi-match, use `format_data_using_js_expression` to iterate. - `target_column` is the column's id (UUID), NOT the column's snake_case name. Always pass the `value` returned by Get Action Field Options — typing a column name directly (e.g. `"email"`) will fail at run time. - RECORD KEYS ARE ACTION DISPLAY NAMES. Each cell on the matched row comes back keyed by the action's UI display name verbatim (spaces and caps included); the value is that action's PRIMARY output. To filter or extract an LLM sub-field that has no dedicated column of its own (it lives nested inside the action's output), add a `format_data_using_js_expression` column on the SOURCE sheet that surfaces it as its own cell — the lookup `record` then carries it by that formatter's display name. See §2 and §6. Verified 2026-06-08. - FILTER CELLS HOLD STATUS, NOT THE FILTERED VALUE. A filter action's cell on `record` is keyed by its display name but its value is the filter status (`""` passed / `conditionNotMet` blocked), so `record["Filter Name"] === ""` never matches. To carry the underlying value, surface it with a source-sheet formatter column (above). Verified 2026-06-08. - Get Options for `target_column` requires the target sheet id in the context, passed under the key `target_table` (NOT `table_to_search`). This is the one place the legacy and new names diverge: PATCH uses `table_to_search`; the options-call context uses `target_table`. Either pass it inline in the `context` body (recommended), or PATCH `table_to_search` first and then call options. - Free to run (0 credits). ================================================================================ 5. WHERE IT FITS IN A WORKFLOW ================================================================================ UPSTREAM — what feeds the lookup The current row carries the key (email, domain, etc.) you want to match against the destination table. The key reaches this action via {{input.}} — populated by an inbound trigger, webhook, list ingest, or an upstream enrichment step. THIS ACTION Searches `table_to_search` for rows where `target_column` matches `value` under `operator`. Returns `record` + `total`. DOWNSTREAM — typical chains Branch on found: this -> filter (total > 0) -> reuse data from `record` (enrichment, outreach, update). Branch on not found: this -> filter (total == 0) -> enrichment / create-row path. Cross-table consolidation: this -> push_data_to_sheet or google_sheet_upsert_row to merge the looked-up record into the current sheet. ================================================================================ 6. ACCESSING RECORD FIELDS ================================================================================ Matching is input-only (§1); `record` returns the full row as a json/object output (§2). Dotted nested reference syntax does NOT dive into `record` — `{{.record.}}` resolves to an empty string. The substitution layer only reaches top-level keys on an action's output schema; nested access requires bracket form. Canonical pattern — bracket access on `{{.record}}`: {{.record}}?.[""] This single form works for any key on the record, including: - Input-column keys (snake_case): `email`, `domain`, `company_name`, `full_name`, etc. - Action-output cells (display-name keys verbatim, including spaces, em-dashes, parentheses, `??`): e.g. `"Email 1 — LLM-generated opener (Claude Sonnet 4.6)"`. Examples: {{lookup_another_floqer_workflow_row_01.record}}?.["email"] {{lookup_another_floqer_workflow_row_01.record}}?.["full_name"] {{lookup_another_floqer_workflow_row_01.record}}?.["Email 1 — LLM-generated opener (Claude Sonnet 4.6)"] This expression can be passed directly as a `data_formatter` value (pure-template form — no JS, no `return`, no IIFE), or used inside any downstream action input that accepts variable references. See https://floqer.com/docs/action-detail/format_data_using_js_expression.txt §1 (PURE FLOQER-EXPRESSION TEMPLATE). Anti-patterns: 1. Dotted nested access — `{{.record.}}`. Resolves to empty. Use bracket form instead. 2. JSON.parse the record blob: JSON.parse(`{{.record}}`)[""] The substituted blob is JSON-shaped but string-valued cells (multi-line dossiers / emails) keep their raw newlines. Strict `JSON.parse` rejects unescaped control characters inside string values; downstream `|| ""` masks the failure as empty. Use the canonical pattern. Extracting one value per downstream variable (the standard pattern): When a downstream action needs ONE field as ONE variable (e.g. a `full_name` input on an email finder), the standard pattern is: upstream chain └─ lookup_another_floqer_workflow_row (returns `record`) └─ format_data_using_js_expression — extract one field data_formatter: {{.record}}?.["full_name"] (pure template — no return, no const, no IIFE) └─ format_data_using_js_expression — extract another field data_formatter: {{.record}}?.["company_website"] └─ inputs: full_name: {{.formatted_data}} company_domain: {{.formatted_data}} One formatter per value you need as a separate variable downstream. Each formatter's `formatted_data` is the clean single-value output the consuming action sees in its input. This avoids wrapping everything in one combined JSON blob that downstream actions then have to parse. Gating on no match: Always branch on `total` before reading from `record`. Pattern: next_action.run_if: {{.total}} greater than 0 ================================================================================ 7. WHEN TO USE ================================================================================ Use lookup_another_floqer_workflow_row to reuse data that already lives in another Floqer table. Common patterns: - You run multiple use cases against the same list. Centralize the list in one master sheet, then look it up from each downstream workflow so enrichments aren't re-run. - You want to skip work that's already been done. Look the row up before paying for an enrichment or outreach step. - You need to read context from a master table onto a row in a campaign sheet. ================================================================================ 8. WHEN NOT TO USE ================================================================================ Need to send rows to another sheet or Floq, not read from one -> push_data_to_sheet (https://floqer.com/docs/action-detail/push_data_to_sheet.txt) -> push_data_to_another_floqer_workflow (https://floqer.com/docs/action-detail/push_data_to_another_floqer_workflow.txt) ================================================================================ This file is maintained manually. Last updated: 2026-06-08. Full interactive reference: https://floqer.com/docs/reference Action catalog: https://floqer.com/docs/action-catalog.txt