Command-line interface for the Formo API. Manage wallet profiles, alerts, dashboards, charts, contracts, segments, and run analytics SQL — directly from your terminal or via AI agents.
npm install -g @formo/cli
# or use without installing:
npx @formo/cliSave your API key locally:
formo login <apiKey>Or set the FORMO_API_KEY environment variable — it takes precedence over the saved config:
export FORMO_API_KEY=formo_abc123Get your API key from Settings → API Keys in the Formo dashboard.
Save your API key to ~/.config/formo/config.json. Validates the key against the API and stores the workspace context.
formo login formo_abc123Remove the saved API key and clear authentication state.
formo logoutShow current authentication state, workspace, and project ID.
formo statusWallet profile commands.
Fetch a single wallet profile by address or ENS name.
| Option | Description |
|---|---|
--expand |
Comma-separated fields: apps, chains, tokens, labels |
formo profiles get 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045
formo profiles get vitalik.eth --expand labels,chainsSearch wallet profiles with filters, sorting, and pagination. Returns a PaginatedResponse<Profile>.
| Option | Description |
|---|---|
--address |
Filter by wallet address |
--page |
Page number (1-indexed, default 1) |
--size |
Page size (default 100, max 1000) |
--order-by |
last_onchain, first_onchain, net_worth_usd, updated_at, tx_count, first_seen, last_seen, num_sessions, revenue, volume, points |
--order-dir |
asc or desc |
--expand |
Comma-separated fields to expand |
--conditions |
JSON array of FilterCondition objects (see below) |
--logic |
Combine conditions with and (default) or or |
formo profiles search --size 10
formo profiles search --order-by net_worth_usd --order-dir desc --size 5
formo profiles search --page 2 --size 20
formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000}]' --size 20
formo profiles search --conditions '[{"field":"users.net_worth_usd","op":"gt","value":10000},{"field":"users.volume","op":"gt","value":1000}]' --logic or --size 20
formo profiles search --conditions '[{"field":"chains.1.balance","op":"gt","value":1000}]' --size 20Merge-update identity properties on a wallet profile.
| Option | Description |
|---|---|
--properties |
JSON object of properties to merge |
Allowed property keys: user_id, display_name, email, farcaster, discord, twitter, telegram, instagram, website, github, linkedin, facebook, tiktok, youtube, reddit, avatar, description, location, ens, lens, basenames, linea. Unknown keys are rejected server-side.
formo profiles update 0xd8dA... --properties '{"display_name":"Vitalik","twitter":"VitalikButerin"}'
formo profiles update vitalik.eth --properties '{"email":"alice@example.com"}'Requires
profiles:writescope.
Upsert one or more labels on a wallet profile. Provide either a single label via --tag-id or a batch via --labels.
| Option | Description |
|---|---|
--tag-id |
Label identifier (e.g. vip, airdrop_eligible) |
--value |
Optional label value (e.g. tier name, country code) |
--chain-id |
Optional chain identifier the label applies to |
--labels |
JSON array of UserLabelInput objects for batch upsert |
formo profiles labels create 0xd8dA... --tag-id vip
formo profiles labels create 0xd8dA... --tag-id tier --value gold --chain-id 1
formo profiles labels create 0xd8dA... --labels '[{"tag_id":"vip"},{"tag_id":"airdrop_eligible","chain_id":"1"}]'Delete a label from a wallet profile.
| Option | Description |
|---|---|
--tag-id |
Label identifier to delete (required) |
--chain-id |
Optional chain identifier to scope the deletion |
formo profiles labels delete 0xd8dA... --tag-id vip
formo profiles labels delete 0xd8dA... --tag-id tier --chain-id 1Requires
profiles:writescope.
Project alert commands. Requires alerts:read (list/get) or alerts:write (create/update/delete/toggle).
List all alerts for the project.
Get a single alert by ID.
| Option | Description |
|---|---|
--name |
Alert name |
--trigger-type |
Trigger type (e.g. event, threshold) |
--trigger-filters |
JSON array of trigger filter objects |
--recipient |
JSON array of recipient objects |
--secret |
Webhook secret |
formo alerts create --name "High value tx" --trigger-type event \
--trigger-filters '[{"name":"event","operator":"equals","value":"transaction"}]' \
--recipient '[{"type":"email","value":["alerts@myapp.com"]}]'Same options as create. Replaces the alert configuration.
Delete an alert.
Toggle an alert between active and paused.
formo alerts toggle alert_abc123 --status pausedDashboard board commands. Requires boards:read / boards:write.
List all boards for the project.
Get a single board by ID.
| Option | Description |
|---|---|
--name |
Board name |
--description |
Optional board description |
formo boards create --name "Revenue Metrics" --description "Weekly revenue tracking"| Option | Description |
|---|---|
--name |
New board name |
--description |
New board description |
Delete a board.
Chart commands. Charts live inside a board. Requires charts:read / charts:write.
List all charts in a board.
Get a single chart by ID.
Create a chart from a JSON config string.
Update a chart.
Delete a chart.
Smart contract commands. Requires contracts:read / contracts:write.
List all tracked contracts. Returns { data: Contract[], deploy: { last_deployed_at, diff }, total, page, size, has_more }.
| Option | Description |
|---|---|
--address |
Contract address (0x…) |
--chain |
Chain ID (e.g. 1, 137) |
--name |
Human-readable contract name |
--abi |
Contract ABI as a JSON string |
--events |
Events configuration as a JSON string |
formo contracts create --address 0x1f9840a85d5af5bf1d1762f925bdaddc4201f984 --chain 1 \
--name "UNI Token" --abi '[{"type":"event","name":"Transfer","inputs":[]}]' \
--events '{"Transfer":true}'| Option | Description |
|---|---|
--name |
Updated contract name |
--abi |
Updated ABI |
--events |
Updated events config |
Remove a tracked contract.
User segment commands. Requires segments:read / segments:write.
List all user segments.
| Option | Description |
|---|---|
--title |
Segment title |
--filter-sets |
JSON array of filter set strings defining the segment |
Delete a user segment.
Run a SQL query against your Formo analytics data. Returns { data, total, limit, offset, has_more }.
formo query run "SELECT count(*) FROM events"
formo query run "SELECT address, net_worth_usd FROM wallet_profiles ORDER BY net_worth_usd DESC LIMIT 10"Requires
query:readscope.
Pre-built analytics pipes — the same data that powers the Formo dashboard — without writing SQL. Each pipe is a subcommand: formo analytics <pipe>.
Pipes: kpis, event_timeseries, funnel, flow, frequency, lifecycle, retention, revenue_overview, revenue_by_metric, revenue_timeseries, volume_by_metric, top_chains, top_events, top_locations, top_pages, top_sources, top_wallets
| Option | Description |
|---|---|
--date-from |
Inclusive start date YYYY-MM-DD (default: 7 days before --date-to) |
--date-to |
Inclusive end date YYYY-MM-DD (default: today) |
--filters |
JSON array of [{field,op,value}]. Use in/notIn with a pipe-delimited value (e.g. "chrome|firefox") |
--params |
JSON object of pipe-specific params merged into the query (e.g. {"limit":10,"group_by":"device"}) |
formo analytics kpis
formo analytics kpis --date-from 2026-04-01 --date-to 2026-04-30 --params '{"group_by":"device"}'
formo analytics funnel --date-from 2026-04-01 --date-to 2026-04-30 --params '{"steps":[{"type":"event","event":"page","name":"page::0","filters":[]},{"type":"track","event":"connect","name":"connect::1","filters":[]}],"window_seconds":86400}'
formo analytics top_wallets --date-from 2026-04-01 --date-to 2026-04-30 --params '{"limit":10}'
formo analytics retention --filters '[{"field":"location","op":"equals","value":"US"}]'Requires
query:readscope. Runformo analytics <pipe> --helpfor the pipe-specific params accepted via--params.
Bulk-import wallet addresses into the project via the events API.
| Option | Description |
|---|---|
--addresses |
JSON array of wallet address strings |
--write-key |
Project write SDK key |
formo import wallets --addresses '["0xabc...","0xdef..."]' --write-key write_key_xyzprofiles search --conditions accepts a JSON array of filter condition objects:
[
{ "field": "users.net_worth_usd", "op": "gt", "value": 10000 },
{ "field": "chains.1.balance", "op": "gte", "value": 1000 }
]The
fieldmust be a typed path. A bare name likenet_worth_usdis silently ignored by the API (no error, no filtering — the search returns everything). Always prefix the field with its type.
| Field | Type | Description |
|---|---|---|
field |
string |
Typed path (see prefixes below) |
op |
string |
eq, neq, gt, gte, lt, lte, in, nin |
value |
any |
Value to compare against |
scope |
string |
(token filters only) any or protocol |
appId |
string |
(token filters with scope: protocol) e.g. aave-v3 |
| Prefix | Examples |
|---|---|
users. |
users.net_worth_usd, users.volume, users.revenue, users.points, users.device, users.location, users.lifecycle, users.ens, users.farcaster |
chains. |
chains.balance (any chain), chains.1.balance (Ethereum) |
apps. |
apps.uniswap-v3.balance |
tokens. |
tokens.0xA0b8…48.balance |
labels. |
labels.coinbase.verified_account |
Combine multiple conditions with --logic and (default) or --logic or.
Every command supports the standard incur output flags:
| Flag | Description |
|---|---|
--format <toon|json|yaml|md|jsonl> |
Output format (default: toon) |
--json |
Shorthand for --format json |
--verbose |
Include the full envelope (ok, data, meta) |
--filter-output <keys> |
Filter output by key paths (e.g. data,meta.duration) |
Every list endpoint returns a PaginatedResponse<T> envelope: { data: [...], total, page, size, has_more }. Every error follows: { error: { code, message, doc_url, param?, details? } } — branch on error.code, not message.
# Install dependencies
pnpm install
# Run in development mode
pnpm dev
# Build TypeScript
pnpm build
# Lint
pnpm lint
# Run tests (requires TEST_TOKEN in .env)
pnpm test