Skip to main content

List Change Trigger Deep Dive — Reactive Automation, DSL Filtering & Workflow Orchestration

The List Change Trigger brings reactive automation to ApudFlow. Instead of running on a timer or waiting for an external webhook, your workflow fires automatically the moment a User List changes — whether the change happened through the UI, through another workflow, or through the API.

This guide covers everything you need to know to use it effectively.

How the List Change Trigger Works

The List Change Trigger is a reactive node — it sits in your workflow and waits for change events from a Redis Stream. When you click Run Test, it:

  1. Saves a subscription document in MongoDB (list_trigger_subscriptions) with the workflow ID, list ID, trigger operations, optional DSL filter, and active: true
  2. From that moment on, any mutation to the watched list (add, update, delete) that passes the filter criteria creates a new workflow run with list data injected as context variables

Event Pipeline

List Mutation → Service Layer → Redis Stream (XADD)
→ Background Listener (XREAD BLOCK)
→ MongoDB Subscription Lookup
→ Operation + Filter Evaluation
→ Run Creation → Workflow Queue → Executor

Latency: Typically under 5 seconds between mutation and run creation. The listener uses XREAD BLOCK 5000 — it checks for new events every 5 seconds when idle, or instantly when events arrive.

Persistence

  • Redis Stream apudflow:stream:list-change-events — holds up to ~1000 events with a 3600s TTL
  • Last processed stream ID is persisted in Redis key listener:list-change:last-id
  • Subscriptions are stored in MongoDB collection list_trigger_subscriptions
  • If the listener restarts, it resumes from the last processed stream ID — no events are missed (except those published during the restart window, which is handled by the stream's TTL)

Configuration Deep Dive

List Selection (Autocomplete)

The listId parameter uses an autocomplete field that searches your User Lists by name. Start typing and matching lists appear. Pick one — only that list is watched.

Important: You can only watch lists you own. Shared lists or lists owned by other users are not visible in the autocomplete.

Trigger Operations (Multi-Select)

Choose one or more operations that fire the trigger:

OperationWhen It FiresUser Action
add_rowA new row is insertedUI "Add Row", add_row() worker, POST API
update_rowsExisting rows are modifiedUI inline edit, update_rows() worker, PUT API
delete_rowA row is removedUI "Delete Row", delete_row() worker, DELETE API

Operation filtering is strict — if you select only update_rows, an add_row event is silently ignored. No run is created. This lets you build precise, focused workflows that only react to exactly what matters.

DSL Filter

The optional DSL filter is evaluated against the current rows of the list after the change. If the filter matches at least one row, the workflow fires. If no rows match, the event is silently ignored.

This is particularly useful when you share a list across multiple workflows. For example:

  • Workflow A watches the same list with status eq pending_approval — it fires when approval items appear
  • Workflow B watches with status eq active AND price gt 100 — it fires when high-value active items change
  • Workflow C watches with no filter — it fires on every change (full audit trail)

DSL Syntax Reference

OperatorAlso Written AsMeaningExample
eq==Equal (exact match)status eq active
neq!=Not equalstatus != archived
gtGreater thanprice gt 100
gte>=Greater than or equalage >= 18
ltLess thanprice lt 50
lte<=Less than or equalvolume <= 10000
containslikeSubstring matchname contains apple
startswithPrefix matchsymbol startswith BTC
endswithSuffix matchemail endswith .com
inMember of a liststatus in (active, pending)
existsField exists (any truthy value)notes exists

Combinators: AND, OR (case-insensitive)

Grouping: Parentheses ( ) for complex logic

Examples:

# Simple condition
status eq active

# Range check
price gt 100 AND price lte 500

# OR logic
status eq urgent OR priority gte 8

# Mixed with grouping
(status eq active OR status eq pending) AND price gt 50

# Text matching
name contains trade AND notes exists

# List membership
symbol in (AAPL, MSFT, GOOGL)

How matchedRows Works

When a DSL filter is set and the trigger fires, the context variable trigger.matchedRows contains only the rows that matched the filter — not all rows. This is useful when you have a large list but only care about a subset:

  • trigger.listData — all rows (if passListData is on)
  • trigger.matchedRows — only rows that satisfied the DSL filter

passListData Toggle

When enabled (default: on), the trigger passes the full list rows to the triggered workflow as trigger.listData. When disabled, only metadata (listId, listName, operation) is passed — you would need to use a User Lists (Read) worker inside the triggered workflow to fetch the data.

When to disable: If your list has thousands of rows and passing them all as context variables would bloat the run document. In that case, read the list inside the workflow instead.

Context Variables Reference

Available in all downstream nodes when the trigger fires:

{
"trigger": {
"listId": "6a3813519ddc3a467466c561",
"listName": "Trading Journal",
"operation": "add_row",
"listData": [
{"_id": 1, "symbol": "AAPL", "price": 190, "status": "active"},
{"_id": 2, "symbol": "MSFT", "price": 420, "status": "active"}
],
"matchedRows": [
{"_id": 1, "symbol": "AAPL", "price": 190, "status": "active"}
]
}
}

Use these in downstream workers with template syntax like {{trigger.listName}}, {{trigger.operation}}, or iterate over {{trigger.listData}} in a Parameter Loop or other processing worker.

Workflow Orchestration Patterns

Pattern 1: Reactive Dashboard Update

When a journal entry is updated, recalculate and refresh all related dashboard widgets.

[List Change Trigger: update_rows]
→ [Aggregate Metrics (User Lists Read)]
→ [Update Dashboard Widgets]
→ [Log Update Timestamp]

Pattern 2: Conditional Alerting with DSL

Only notify you when high-priority items change. The DSL filter does the heavy lifting — no separate condition node needed.

[List Change Trigger: all ops, filter: "priority gte 8 OR status eq urgent"]
→ [Format Alert Message]
→ [Send Telegram / Email]

Pattern 3: Multi-Stage Data Pipeline with Chained Lists

A data collection workflow writes to a "Raw" list. The trigger fires on new rows, an AI worker enriches them, and results go into a "Processed" list.

[Raw Data List Change: add_row]
→ [AI Enrichment (classify, summarize)]
→ [Add Row to Processed List]
→ [Processed List Change Trigger → Next Pipeline Stage]

Pattern 4: Parallel Processing with Multiple Triggers

One list, three triggers — each handling a different concern:

[Shared Team List]
├── Trigger A: add_row only → [New Assignment Notification]
├── Trigger B: update_rows, filter: "status eq completed" → [Completion Handler]
└── Trigger C: delete_row only → [Cleanup & Archive]

Pattern 5: Parameter Loop + List Change Optimization

The Parameter Loop worker generates parameter combinations and stores them in a config list. Each new row triggers a backtest run.

[Parameter Loop Worker]
→ [Add Row to Config List]
→ [List Change Trigger: add_row]
→ [Backtest with Config]
→ [Store Results in Results List]

Each backtest runs independently and in parallel, since each add_row event creates a separate workflow run.

Pattern 6: Data Sync / Replication

When data changes in a source list, replicate it to an external system (PostgreSQL, Google Sheets, MongoDB Atlas).

[Source List Change: add_row OR update_rows]
→ [Transform Data Format]
→ [Send to External API / DB Connector]
→ [Log Sync Status]

Integration with Other Workers

User Lists Worker

The List Change Trigger and the User Lists worker are designed to work together. The User Lists worker performs CRUD operations from within a workflow — and any change it makes can trigger a List Change Trigger in a separate workflow.

This enables chained automation:

  • Workflow A: fetch data → process → store in List → Workflow B (triggered by the change) → further processing

AI Workers

Combine with AI Classifier, AI Summarizer, or AI Data Analyzer to react to data changes with intelligent processing:

[List Change] → [AI Classifier: categorize new rows]
→ [Route to Different Workflows Based on Category]

Signal Generator

When a watchlist price changes, run a technical analysis and decide if there's a trading signal:

[Price Watchlist Update: update_rows]
→ [Fetch Full Market Data]
→ [Signal Generator: Technical Analysis]
→ [If Signal: Send Alert + Place Order]

Testing and Debugging

How to Verify a Trigger Works

  1. Save the trigger — click "Run Test" on the List Change Trigger node. You should see status: active and the list name confirmed
  2. Make a change — go to Profile → Lists, find your watched list, and add or edit a row
  3. Check runs — navigate to the workflow's run history. Within a few seconds, a new run should appear with trigger: list_change
  4. Inspect context — open the run and check the initialPayload.contextVars.trigger section to verify listData, operation, and listName are correct

Common Issues

SymptomLikely CauseFix
Trigger doesn't fireOperation mismatchCheck triggerOn includes the operation you performed
Trigger doesn't fireDSL filter too strictTry removing the filter temporarily to isolate
Trigger fired but no datapassListData is offEnable it in the trigger config, or use a User Lists Read worker
Multiple runs for one changeMultiple triggers on same (workflow, list) pairOnly one trigger per pair is allowed; check for duplicates
Old trigger still firingSubscription still activeDeactivate or delete the old trigger node

Best Practices

  • Start with a broad filter, then narrow — set triggerOn to all operations first, verify the workflow fires, then restrict operations and add filters
  • Use matchedRows for efficient processing — if your DSL filter narrows down to a few rows, use trigger.matchedRows instead of iterating over trigger.listData
  • Avoid infinite loops — if Workflow A triggers on a list change and also writes to that same list, you get a feedback loop. Either use different lists for trigger and output, or add a guard variable
  • One purpose per trigger — each workflow should do one thing well. Create separate workflows for notification, analysis, archiving, etc., each watching the same list with different filters
  • Monitor listener health — the List Change Trigger depends on the background listener process. If the listener is down, changes are still published to Redis but not processed until the listener restarts

This is not financial advice. Trading involves significant risk of loss. Use reactive automation workflows at your own discretion.