Skip to main content
Formo’s data warehouse gives you direct SQL access to events, users, sessions, revenue, and wallet profiles across chains.
  • Events, Users, Sessions, Revenue, and Sources are first-party data scoped to your project.
  • Wallet Profiles are based on public onchain data (net worth, apps, tokens, chains, social profiles, etc.) and are globally accessible across all projects.
Data catalog
Key details:
  • SQL dialect: ClickHouse SQL (not standard SQL)
  • Default row limit: 100 rows (add your own LIMIT clause to override)
  • Project scoping: All queries are automatically filtered to your project - you never need to add WHERE project_id = ...
  • Aggregate tables: Several tables use ClickHouse AggregateFunction types that require -Merge suffix functions (see Working with Aggregate Tables)
  • Functions and variables: Formo supports dynamic date variables and other built-in functions. Query data with the Query API and Profiles API, explore with the BI integration, export with Data sync, or write SQL directly in the Explorer.

Data flow

Tables

events

The core event log. Contains all user interaction events - page views, custom events, wallet connects, and onchain contract events (decoded_log). Use cases: Event-level analysis, user journey tracking, custom event queries, debugging Example:

users

Aggregated wallet user profiles with attribution, engagement, and revenue metrics. Each row represents one wallet address. Use cases: User segmentation, attribution analysis, lifecycle tracking, wallet analytics
This is an aggregate table. Most columns require -Merge functions. Always GROUP BY address. Never use SELECT *. See Working with Aggregate Tables.
Lifecycle definitions (based on activity_dates, computed against a reference date that defaults to today):
  • New: first_seen within the last 30 days and still active
  • Power: first_seen more than 30 days ago and 5+ unique active days in the last 30 days
  • Resurrected: first_seen more than 30 days ago and re-engaged within the last 30 days after a 30+ day gap
  • At Risk: first_seen more than 30 days ago, still active, but last_seen 14+ days ago with fewer than 5 active days in the last 30 days, no 30+ day gap, and 1+ active day in the prior window (days 30 to 60 ago)
  • Churned: last_seen more than 30 days ago
  • Returning: default (first seen more than 30 days ago, active recently, but not Power, Resurrected, or At Risk)
Example:

anonymous_users

Profiles for users who haven’t connected a wallet yet. Same structure as users but keyed by anonymous_id instead of address. Use cases: Pre-wallet user analysis, conversion funnel tracking, anonymous visitor segmentation
Total users = users (wallet-connected) + anonymous_users (pre-wallet)
All other columns are identical to the users table, with anonymous_id replacing address as the primary key, except activity_dates. In anonymous_users, activity_dates is AggregateFunction(groupUniqArray, Date), read with groupUniqArrayMerge(activity_dates), not groupUniqArrayIfMerge() as in users.

sessions

Aggregated session data with engagement metrics and attribution. Each row represents one session on a given date. Use cases: Session analysis, bounce rate calculations, session duration, user attribution
device, browser, os, location, and the attribution columns (referrer, referrer_url, ref, utm_*, channel_type) are argMin states that resolve to the session’s first-touch (entry) value. Read them with argMinMerge(...), not by selecting the column directly. paid_source (acquiring ad network) and click_id (raw click ID) resolve independently at the first non-empty paid touch, so they can come from different events within the same session; don’t assume click_id belongs to the paid_source network.
Example:

sources

Daily aggregated traffic source metrics and attribution data. Each row represents one day of activity from a specific referrer/UTM combination. Use cases: Marketing attribution, referrer performance, traffic source analysis, campaign ROI

revenue

Financial metrics with full attribution context. Tracks revenue, transaction volume, and points. Use cases: Revenue attribution, ROI analysis, financial performance by source/campaign Example:

identities

Identity graph linking anonymous sessions to wallet addresses. Each row represents a connection between an anonymous user and a wallet. Use cases: Conversion tracking, user journey analysis, linking pre/post-wallet activity Example:

user_profiles_mv

Project-scoped, first-party user profile traits captured from identify events. One row per address, holding the latest value of each trait. This is the identity-data counterpart to wallet_profiles_mv (which holds public onchain profile data); user_profiles_mv holds the traits you send. Use cases: User enrichment, first-party identity resolution, personalization, outreach
This is an aggregate table (AggregatingMergeTree). Each trait is stored as an aggregate state, so read it with -Merge functions (e.g. argMaxIfMerge(email)) and always GROUP BY address. Never use SELECT *. See Working with Aggregate Tables.
Example:

wallet_profiles_mv

Global wallet profile data aggregated across all chains. One row per wallet address with social profiles, contact info, and net worth. Use cases: Wallet intelligence, social identity resolution, user enrichment, outreach

wallet_profiles_chains_mv

Wallet profile data broken down by blockchain network. Each row represents a wallet’s activity on a specific chain. Use cases: Chain-specific analysis, cross-chain behavior tracking, multi-chain user segmentation Example:

wallet_profiles_chains_tokens_mv

Token holdings data per wallet per chain. Each row represents one token held by a wallet on a specific chain. Use cases: Token portfolio analysis, token-based segmentation, whale identification

wallet_profiles_chains_apps_mv

DeFi app usage data per wallet per chain. Each row represents a wallet’s interaction with a specific DeFi protocol. Use cases: DeFi app adoption analysis, protocol usage tracking, user portfolio analysis

wallet_profiles_events

Raw timestamped wallet profile data from blockchain analysis. Contains the unprocessed profile updates that feed into the wallet_profiles_* materialized views. Use cases: Debugging wallet profile data, historical profile snapshots, raw data access

wallet_profiles_labels

Wallet labels and tags for categorizing and filtering wallet addresses. Each row is a label assigned to an address, optionally scoped to a chain. Use cases: Wallet categorization, risk scoring, user segmentation, compliance filtering

user_labels

Project-scoped labels and tags you assign to wallet addresses (e.g. your own segments, scores, or categories). The latest value per (tag_id, address, chain_id) is kept automatically. This is the project-specific counterpart to wallet_profiles_labels, which holds global labels supplied by Formo’s wallet profiler. Use cases: Custom segmentation, scoring, audience building, tagging

Table relationships

The identities table is the bridge between anonymous and identified users. It links anonymous_id and session_id to address, enabling you to track user behavior before and after wallet connection.

Variables

Template variables are replaced with actual values at query time. Use them to build charts and dashboards that respond to a shared date picker instead of hardcoded ranges.

Date range variables

{{date_from}}

Start date of the selected date range. Replaced with a YYYY-MM-DD string at query time. Example:

{{date_to}}

End date of the selected date range. Replaced with a YYYY-MM-DD string at query time. Example:
Combined example:

Functions

Custom SQL functions that resolve at query time. Functions use the {{ function_name(...) }} syntax - the macro is expanded into a numeric literal before your query runs, so results can be used anywhere a number would be (SELECT, WHERE, arithmetic, etc.). Rules:
  • Maximum 10 function calls per query
  • Results are cached for 15 minutes

Price oracle functions

Fetch live token prices directly inside your SQL queries. Use these to convert token amounts to USD values without joining external price data manually.

alchemy.token_price

Get token price by contract address and chain ID from Alchemy. Signature: {{ alchemy.token_price('ADDRESS', CHAIN_ID, 'CURRENCY') }} Parameters:
  • ADDRESS - EVM contract address (0x-prefixed, 42 characters)
  • CHAIN_ID - Numeric chain ID (e.g., 1 for Ethereum, 8453 for Base, 42161 for Arbitrum)
  • CURRENCY - One of usd, eur, eth, btc
Example:
See the Alchemy Prices API - Get Token Prices by Address reference.

alchemy.token_price_by_symbol

Get token price by symbol from Alchemy. Signature: {{ alchemy.token_price_by_symbol('SYMBOL', 'CURRENCY') }} Parameters:
  • SYMBOL - Token symbol (e.g., ETH, BTC, SOL)
  • CURRENCY - One of usd, eur, eth, btc
Example:
See the Alchemy Prices API - Get Token Prices by Symbol reference.

pyth.token_price

Get token price by pair name from Pyth. Signature: {{ pyth.token_price('BASE/QUOTE') }} Parameters:
  • BASE/QUOTE - Price pair (e.g., ETH/USD, BTC/USD, SOL/USD)
Example:
See the Pyth Hermes API - Latest Price Updates reference.

Working with aggregate tables

Several tables (users, anonymous_users, sessions, sources, identities, user_profiles_mv) use ClickHouse AggregateFunction types. These store intermediate aggregation states, not final values.

Rules

  1. Always GROUP BY the primary key when querying aggregate tables
  2. Use -Merge suffix for AggregateFunction columns (e.g., argMaxMerge(last_utm_source))
  3. Use standard functions for SimpleAggregateFunction columns (e.g., min(first_seen), max(last_seen))
  4. Never SELECT * on aggregate tables - it returns binary aggregate states, not readable values

Quick reference

Example queries

Most active wallets

Daily active users

Average session duration

Events by type

Revenue by UTM campaign

Top countries by session count

Sessions by device and browser

Enrich users with social profiles

Top DeFi apps of US users

High net worth DeFi positions

Track user journey across anonymous and wallet sessions

Next steps

SQL Explorer Guide

Step-by-step guide to writing queries

Ask AI

Generate SQL with natural language

BI integration

Connect BI tools such as Metabase, Grafana, and PowerBI

Query API

Query data programmatically