> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowmaticai.in/llms.txt
> Use this file to discover all available pages before exploring further.

# Build a Rule-Based Filter Workflow Without AI in Flowmatic

> Create a Flowmatic workflow that uses the FILTER node to select rows with a rule expression and send emails — no AI or LLM key required.

Not every workflow needs a language model. When you want to select rows based on a specific column value — for example, all customers whose rating is greater than 4, or all contacts whose subscription status is active — the `FILTER` node gives you a fast, deterministic, and LLM-free way to do it. This guide walks you through building a complete filter-based workflow that reads a CSV upload, applies an expression to select matching rows, and sends each matched recipient a personalized email.

## FILTER vs AI: choosing the right node

Both the `FILTER` node and the `AI` node can narrow down a list of rows, but they work very differently:

|                      | FILTER node                                          | AI node                                                       |
| -------------------- | ---------------------------------------------------- | ------------------------------------------------------------- |
| **Mechanism**        | Rule-based expression engine                         | Large language model (Groq)                                   |
| **Requires LLM key** | No                                                   | Yes                                                           |
| **Deterministic**    | Yes — same input always produces the same output     | No — output may vary between runs                             |
| **Best for**         | Numeric comparisons, equality checks, flag filtering | Fuzzy matching, natural language criteria, content generation |
| **Output**           | `items` array of matching rows                       | Named fields you define in `output`                           |

Use `FILTER` when your selection criteria can be expressed as a simple rule. Use `AI` when you need the model to interpret, summarize, or generate content alongside filtering.

<Tip>
  The `FILTER` node is the best choice when you're testing a new workflow end-to-end. It runs instantly without any external API calls, so you can validate your graph structure and email templates before introducing an AI node.
</Tip>

## FILTER node configuration

The `FILTER` node has two required fields inside its `data` object:

* **`source`** — A template expression that resolves to the array of rows you want to filter. Typically this is `{{ds.rows}}` where `ds` is the id of an upstream `DATA_SOURCE` node.
* **`expr`** — A filter expression written as a comparison against column values. The expression is evaluated once per row; rows for which it evaluates to `true` are included in the output.

### Expression syntax

Expressions support standard comparison operators. The left-hand side must be a column name from your CSV header row (no curly-brace syntax needed here — you're inside the expression language, not the template engine).

| Operator | Example              | Meaning                                    |
| -------- | -------------------- | ------------------------------------------ |
| `>`      | `rating > 4`         | Greater than                               |
| `>=`     | `age >= 18`          | Greater than or equal                      |
| `<`      | `score < 50`         | Less than                                  |
| `<=`     | `priority <= 2`      | Less than or equal                         |
| `==`     | `status == 'active'` | Strict equality (strings in single quotes) |
| `!=`     | `tier != 'free'`     | Not equal                                  |

### Output

After the `FILTER` node runs, the matched rows are available as `{{nodeId.items}}`. If your filter node has `id: "f"`, you reference the result as `{{f.items}}` in downstream nodes.

## Building the workflow

<Steps>
  <Step title="Authenticate">
    Obtain an access token by logging in with your Flowmatic credentials:

    ```bash theme={null}
    curl -X POST https://api.flowmatic.io/api/auth/login \
      -H "Content-Type: application/json" \
      -d '{ "email": "you@example.com", "password": "yourpassword" }'
    ```

    ```json theme={null}
    {
      "accessToken": "eyJ...",
      "refreshToken": "eyJ..."
    }
    ```

    Export the token for use in subsequent requests:

    ```bash theme={null}
    export FM_TOKEN="eyJ..."
    ```
  </Step>

  <Step title="Upload your CSV">
    If you haven't already, upload the CSV file you want to filter. The file must include a header row whose column names match the field names you'll use in the `expr`.

    ```bash theme={null}
    curl -X POST https://api.flowmatic.io/api/uploads \
      -H "Authorization: Bearer $FM_TOKEN" \
      -F "file=@customers.csv"
    ```

    ```json theme={null}
    { "uploadId": "upload_abc123xyz" }
    ```

    For this guide, the CSV is expected to have at least `name`, `email`, and `rating` columns:

    ```csv theme={null}
    name,email,rating
    Alice Johnson,alice@example.com,5
    Bob Smith,bob@example.com,3
    Carol White,carol@example.com,5
    ```
  </Step>

  <Step title="Create the workflow">
    Submit the workflow definition. Replace `<your-upload-id>` with the `uploadId` from the previous step.

    ```bash theme={null}
    curl -X POST https://api.flowmatic.io/api/workflows \
      -H "Authorization: Bearer $FM_TOKEN" \
      -H "Content-Type: application/json" \
      -d '{
        "name": "Email 5-star raters",
        "graph": {
          "nodes": [
            { "id": "t",  "type": "TRIGGER",     "data": {} },
            { "id": "ds", "type": "DATA_SOURCE", "data": { "uploadId": "<your-upload-id>" } },
            { "id": "f",  "type": "FILTER",      "data": { "source": "{{ds.rows}}", "expr": "rating > 4" } },
            { "id": "out", "type": "OUTPUT", "data": {
                "forEach": "{{f.items}}",
                "to": "{{item.email}}",
                "subject": "Thanks {{item.name}}",
                "body": "Hi {{item.name}}, thanks for the 5-star review!"
            }}
          ],
          "edges": [
            { "source": "t",  "target": "ds" },
            { "source": "ds", "target": "f" },
            { "source": "f",  "target": "out" }
          ]
        }
      }'
    ```

    On success you'll receive a `201` response with the created workflow:

    ```json theme={null}
    {
      "id": "wf_def456",
      "name": "Email 5-star raters",
      "graph": { "nodes": [...], "edges": [...] },
      "createdAt": "2024-06-01T10:00:00Z"
    }
    ```

    Note the workflow `id` — you'll use it to trigger runs.
  </Step>

  <Step title="Run the workflow">
    Trigger an execution against the workflow you just created:

    ```bash theme={null}
    curl -X POST https://api.flowmatic.io/api/workflows/wf_def456/run \
      -H "Authorization: Bearer $FM_TOKEN"
    ```

    The API acknowledges the request with a `202` and returns a run identifier:

    ```json theme={null}
    { "runId": "run_abc111", "status": "PENDING" }
    ```

    The run will move through the `PENDING → RUNNING → SUCCESS` (or `FAILED`) lifecycle. See the [Run & Monitor](/guides/run-and-monitor) guide for details on polling for status and inspecting per-node output.
  </Step>
</Steps>

## Full workflow JSON reference

Here is the complete workflow definition you used above, with a breakdown of each node's role:

```json theme={null}
{
  "name": "Email 5-star raters",
  "graph": {
    "nodes": [
      { "id": "t",  "type": "TRIGGER",     "data": {} },
      { "id": "ds", "type": "DATA_SOURCE", "data": { "uploadId": "<your-upload-id>" } },
      { "id": "f",  "type": "FILTER",      "data": { "source": "{{ds.rows}}", "expr": "rating > 4" } },
      { "id": "out", "type": "OUTPUT", "data": {
          "forEach": "{{f.items}}",
          "to": "{{item.email}}",
          "subject": "Thanks {{item.name}}",
          "body": "Hi {{item.name}}, thanks for the 5-star review!"
      }}
    ],
    "edges": [
      { "source": "t",  "target": "ds" },
      { "source": "ds", "target": "f" },
      { "source": "f",  "target": "out" }
    ]
  }
}
```

**Node breakdown:**

* **`t` (TRIGGER)** — The mandatory pipeline entry point. It carries no configuration and simply initiates the execution chain when a run is enqueued.
* **`ds` (DATA\_SOURCE)** — Fetches and parses the uploaded CSV, making the full array of rows available as `{{ds.rows}}`. Each row is an object whose keys are the CSV column headers.
* **`f` (FILTER)** — Iterates over `{{ds.rows}}` and evaluates `rating > 4` for each row. Rows where the expression is `true` are collected into `{{f.items}}`. In this example, Alice (rating 5) and Carol (rating 5) pass; Bob (rating 3) does not.
* **`out` (OUTPUT)** — Loops over `{{f.items}}` and dispatches one email per matched row. The `{{item.email}}`, `{{item.name}}` placeholders resolve to the current row's values on each iteration.

## Adapting the expression

You can swap out the `expr` value to filter on any column in your CSV. Here are a few common patterns:

```json theme={null}
"expr": "status == 'active'"
```

```json theme={null}
"expr": "age >= 18"
```

```json theme={null}
"expr": "score != 0"
```

```json theme={null}
"expr": "tier == 'premium'"
```

<Warning>
  String values in the `expr` must be wrapped in single quotes (e.g., `status == 'active'`). Numeric values should be written without quotes (e.g., `rating > 4`). Using double quotes inside the expression string will cause a parse error because the outer JSON string already uses double quotes.
</Warning>
