Use {{expr:...}} in step params to transform data between steps — extract JSON fields, format timestamps, manipulate strings.
# .factorly/factorly.yaml
tools:
daily.summary:
type: workflow
description: Fetch today's calendar events and post a summary to Slack
parameters:
- name: channel
default: "#general"
steps:
- tool: google-calendar.list_events
params:
calendarId: primary
timeMin: "{{expr:now()}}"
timeMax: "{{expr:now('24h')}}"
maxResults: "10"
store: events
- tool: slack.post_message
if: "jsonpath(events, '$.items') != ''"
params:
channel: "{{channel}}"
text: "{{expr:concat('You have ', len(events), ' events today')}}"factorly call daily.summary
factorly call daily.summary --channel '#engineering'steps:
- tool: api.get_user
params:
id: "{{user_id}}"
store: user
- tool: notification.send
params:
# Extract nested JSON field
email: "{{expr:jsonpath(user, '$.contact.email')}}"
# Uppercase the name
greeting: "{{expr:concat('Hello ', upper(jsonpath(user, '$.name')))}}"
# Default if missing
role: "{{expr:default(jsonpath(user, '$.role'), 'member')}}"steps:
- tool: analytics.query
params:
start_date: "{{expr:today('-7')}}"
end_date: "{{expr:today()}}"
store: metrics
- tool: report.generate
params:
title: "{{expr:concat('Weekly Report: ', today('-7'), ' to ', today())}}"
data: "{{metrics}}"steps:
- tool: git.log
store: log_output
- tool: slack.post
params:
# First 500 chars of output
text: "{{expr:left(log_output, 500)}}"
# Extract filename from path
filename: "{{expr:cut('/home/user/docs/report.pdf', '/', '-1')}}"
# Clean up whitespace
summary: "{{expr:trim(log_output)}}"steps:
- tool: api.health_check
store: health
- tool: pagerduty.alert
# Only alert if status is not healthy
if: "jsonpath(health, '$.status') != 'healthy'"
params:
severity: "critical"
message: "{{expr:concat('Service down: ', jsonpath(health, '$.message'))}}"See the full expressions reference for all 20 functions: concat, cut, default, find, join, jsonpath, left, len, lower, now, replace, right, substr, today, trim, upper, and more.
{{expr:...}}is a resolver backend — same system as{{vault:KEY}}and{{env:VAR}}- In workflows, expressions have access to stored step outputs and input params
- In any tool call, stateless functions like
now(),today(),upper()work - Expressions that fail return empty string — no workflow crashes