Skip to main content
POST
/
v0
/
boards
/
{boardId}
/
charts
curl --request POST \
  --url https://api.formo.so/v0/boards/{boardId}/charts \
  --header 'Authorization: Bearer <token>' \
  --header 'Content-Type: application/json' \
  --data '
{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Onboarding Funnel",
  "steps": [
    {
      "type": "event",
      "event": "page"
    },
    {
      "type": "event",
      "event": "connect"
    },
    {
      "type": "event",
      "event": "transaction"
    }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": {
      "value": 7,
      "unit": "day"
    }
  }
}
'
{
  "isSuccess": true,
  "data": "<string>"
}

Overview

Creates a new chart and attaches it to a board. Returns the new chart’s ID on success. The chart_type field controls which other request body fields are required or validated. Funnel charts are special — their SQL is auto-generated from the steps array, so pass "SELECT 1" as the query placeholder.

Request Fields

FieldTypeRequiredDescription
projectIdstringYesProject the chart belongs to
querystringConditionalSQL query. Required for all types except retention. For funnel, pass "SELECT 1"
chart_typestringYestable · number · bar · line · pie · stacked · funnel · user_paths · retention
titlestringYesDisplay name (minimum 1 character)
descriptionstringNoOptional description
x_axisstringConditionalColumn for the X axis. Required for bar, line, stacked
y_axisstring[]ConditionalColumn(s) for Y axis metrics. See per-type rules below
group_bystringConditionalColumn to group/stack series by. Required for stacked
stepsFunnelStep[]ConditionalOrdered funnel steps. Required for funnel (minimum 2)
settingsChartSettingsNoType-specific configuration object

Chart Types

table

Renders query results in a paginated table. No axis fields needed.
{
  "projectId": "proj_abc",
  "query": "SELECT * FROM events ORDER BY timestamp DESC LIMIT 10",
  "chart_type": "table",
  "title": "Recent Events"
}

number

Renders a single scalar value (KPI card). The query must return exactly 1 row × 1 column.
{
  "projectId": "proj_abc",
  "query": "SELECT COUNT(DISTINCT address) FROM events WHERE type = 'connect'",
  "chart_type": "number",
  "title": "Total Connected Wallets"
}

bar and line

Both require x_axis and at least 1 column in y_axis.
{
  "projectId": "proj_abc",
  "query": "SELECT toDate(timestamp) AS date, countDistinct(address) AS users FROM events GROUP BY date ORDER BY date DESC LIMIT 30",
  "chart_type": "line",
  "title": "Daily Active Users",
  "x_axis": "date",
  "y_axis": ["users"]
}

pie

Requires exactly 1 column in y_axis. x_axis identifies the label column.
{
  "projectId": "proj_abc",
  "query": "SELECT device, COUNT(*) AS session_count FROM sessions GROUP BY device ORDER BY session_count DESC LIMIT 10",
  "chart_type": "pie",
  "title": "Sessions by Device",
  "x_axis": "device",
  "y_axis": ["session_count"]
}

stacked

Requires x_axis, exactly 1 column in y_axis, and group_by.
{
  "projectId": "proj_abc",
  "query": "SELECT device, browser, COUNT(*) AS session_count FROM sessions GROUP BY device, browser ORDER BY session_count DESC",
  "chart_type": "stacked",
  "title": "Sessions by Device and Browser",
  "x_axis": "device",
  "y_axis": ["session_count"],
  "group_by": "browser"
}

Funnel Charts

Funnel charts measure step-by-step user conversion. The SQL is auto-generated from steps, so query must be the placeholder "SELECT 1". At least 2 steps are required.

The FunnelStep Object

Each step in the steps array has the following shape:
{
  "type": "event | track | decoded_log",
  "event": "<event name>",
  "<propertyKey>": { "op": "<operator>", "value": "<value>" }
}
FieldTypeRequiredDescription
typestringYesevent — built-in events (page, connect, transaction); track — custom tracked events; decoded_log — decoded smart-contract events
eventstringYesEvent name (e.g. page, connect, transaction)
<propertyKey>StepFilterConditionNoOne or more property filters. Any extra key on the step object is treated as a filter. Standard columns (device, browser, os, location, referrer, ref, utm_*) are filtered directly; all other keys are extracted from properties via JSONExtractString

Filter Operators (StepFilterCondition)

{ "op": "<operator>", "value": "<value>" }
opSQL equivalentNotes
equals= 'value'Exact match
notEquals!= 'value'Inverse match
inIN (...)Pipe-delimited value: "metamask|rainbow|coinbase"
notInNOT IN (...)Pipe-delimited value
gt> valueNumeric comparison
gte>= valueNumeric comparison
lt< valueNumeric comparison
lte<= valueNumeric comparison
startsWithstartsWith(col, 'v')String prefix
endsWithendsWith(col, 'v')String suffix
includeslike '%v%'Substring match
For in and notIn, join multiple values with a pipe |. Escape a literal pipe with \| and a literal backslash with \\.

settings for Funnel Charts

FieldTypeDefaultDescription
funnelType"closed" | "open""closed"closed — strict ordering, no events between steps; open — ordered but other events may occur between steps
conversionWindowConversionWindow1 dayMax time from Step 1 for a user to complete all steps
breakdownstringSplit each funnel bar by this dimension

ConversionWindow

{ "value": 7, "unit": "day" }
unit must be one of: minute · hour · day · week (7 days) · month (30 days).

breakdown values

device · browser · os · referrer · ref · utm_source · utm_medium · utm_campaign · utm_term · utm_content The top categories are shown individually. The rest collapse into “Others”.

Funnel Examples

Basic 3-step closed funnel

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Onboarding Funnel",
  "steps": [
    { "type": "event", "event": "page" },
    { "type": "event", "event": "connect" },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 7, "unit": "day" }
  }
}

With per-step property filters

Filter step 2 to MetaMask wallet connections on Ethereum mainnet:
{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "MetaMask Conversion Funnel",
  "steps": [
    { "type": "event", "event": "page" },
    {
      "type": "event",
      "event": "connect",
      "rdns": { "op": "equals", "value": "io.metamask" },
      "chain_id": { "op": "equals", "value": "1" }
    },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 7, "unit": "day" }
  }
}

With breakdown by device

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Onboarding Funnel by Device",
  "steps": [
    { "type": "event", "event": "page" },
    { "type": "event", "event": "connect" },
    { "type": "event", "event": "signature" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 30, "unit": "day" },
    "breakdown": "device"
  }
}

Open funnel with multi-value in filter

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Mobile Onboarding Funnel",
  "steps": [
    {
      "type": "event",
      "event": "page",
      "device": { "op": "equals", "value": "mobile" }
    },
    {
      "type": "event",
      "event": "connect",
      "provider_name": { "op": "in", "value": "metamask|rainbow|coinbase" }
    },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "open",
    "conversionWindow": { "value": 30, "unit": "day" },
    "breakdown": "device"
  }
}

User Paths Charts

Visualize how users navigate your app after a starting event. settings.startStep is required.

settings for User Paths Charts

FieldTypeDefaultDescription
startStepFunnelStepRequired. Event where the flow begins
endStepFunnelStep | nullnullOptional ending event. null = open-ended
maxStepsinteger (2–10)3Max steps to show in the flow. Values above 10 are clamped to 10
nodesPerStepinteger (2–10)5Max unique event nodes per step. Values above 10 are clamped to 10
conversionWindowConversionWindowTime window to group the flow
filtersstringJSON-encoded string of additional path filters
{
  "projectId": "proj_abc",
  "query": "SELECT anonymous_id, type AS event, timestamp FROM events WHERE timestamp >= today() - INTERVAL 30 DAY ORDER BY anonymous_id, timestamp",
  "chart_type": "user_paths",
  "title": "Post-Connect User Flow",
  "settings": {
    "startStep": { "type": "event", "event": "connect" },
    "endStep": { "type": "event", "event": "transaction" },
    "maxSteps": 5,
    "conversionWindow": { "value": 2, "unit": "week" }
  }
}

Retention Charts

Measures how often users return over time. The query field is not used — pass "" or omit it.

settings for Retention Charts

FieldTypeDescription
retentionFilterFunnelStep | nullEvent that qualifies a returning visit as “retained”. null = any event counts
retentionUserFiltersRetentionUserFilter[]User-segment filters that narrow the retention cohort
Each RetentionUserFilter:
FieldTypeDescription
operandstringUser property (e.g. device, browser, os, utm_source, location)
operatorstringComparison operator (same set as StepFilterCondition.op)
valuestring | numberValue to compare against
{
  "projectId": "proj_abc",
  "query": "",
  "chart_type": "retention",
  "title": "Weekly Retention — Desktop Users",
  "settings": {
    "retentionFilter": { "type": "event", "event": "transaction" },
    "retentionUserFilters": [
      { "operand": "device", "operator": "equals", "value": "desktop" },
      { "operand": "utm_source", "operator": "notEquals", "value": "direct" }
    ]
  }
}
For all-user retention with no filters, omit settings:
{
  "projectId": "proj_abc",
  "query": "",
  "chart_type": "retention",
  "title": "Overall Retention"
}

Validation Reference

Chart TypeValidation Rule
tablequery must execute successfully
numberquery must return exactly 1 row × 1 column
barx_axis required; y_axis requires ≥ 1 element
linex_axis required; y_axis requires ≥ 1 element
piey_axis requires exactly 1 element
stackedx_axis required; y_axis requires exactly 1 element; group_by required
funnelsteps requires ≥ 2 FunnelStep objects; query must be "SELECT 1" or any valid SQL
user_pathssettings.startStep required; query must execute and return valid path data
retentionNo query validation — query is ignored

Response

201 Created
{
  "isSuccess": true,
  "data": "chart_1a2b3c4d"
}
data is the new chart’s ID. Use it with the Get Chart, Update Chart, and Delete Chart endpoints. 400 Bad Request — returned when validation fails (missing required fields, invalid SQL, wrong step count, etc.)
{
  "isSuccess": false,
  "message": "Funnel chart must have at least 2 steps"
}

Overview

Creates a new chart and attaches it to a board. Returns the new chart’s ID on success. The chart_type field controls which other request body fields are required or validated. Funnel charts are special — their SQL is auto-generated from the steps array, so pass "SELECT 1" as the query placeholder.

Request Fields

FieldTypeRequiredDescription
projectIdstringYesProject the chart belongs to
querystringConditionalSQL query. Required for all types except retention. For funnel, pass "SELECT 1"
chart_typestringYestable · number · bar · line · pie · stacked · funnel · user_paths · retention
titlestringYesDisplay name (minimum 1 character)
descriptionstringNoOptional description
x_axisstringConditionalColumn for the X axis. Required for bar, line, stacked
y_axisstring[]ConditionalColumn(s) for Y axis metrics. See per-type rules below
group_bystringConditionalColumn to group/stack series by. Required for stacked
stepsFunnelStep[]ConditionalOrdered funnel steps. Required for funnel (minimum 2)
settingsChartSettingsNoType-specific configuration object

Chart Types

table

Renders query results in a paginated table. No axis fields needed.
{
  "projectId": "proj_abc",
  "query": "SELECT * FROM events ORDER BY timestamp DESC LIMIT 10",
  "chart_type": "table",
  "title": "Recent Events"
}

number

Renders a single scalar value (KPI card). The query must return exactly 1 row × 1 column.
{
  "projectId": "proj_abc",
  "query": "SELECT COUNT(DISTINCT address) FROM events WHERE type = 'connect'",
  "chart_type": "number",
  "title": "Total Connected Wallets"
}

bar and line

Both require x_axis and at least 1 column in y_axis.
{
  "projectId": "proj_abc",
  "query": "SELECT toDate(timestamp) AS date, countDistinct(address) AS users FROM events GROUP BY date ORDER BY date DESC LIMIT 30",
  "chart_type": "line",
  "title": "Daily Active Users",
  "x_axis": "date",
  "y_axis": ["users"]
}

pie

Requires exactly 1 column in y_axis. x_axis identifies the label column.
{
  "projectId": "proj_abc",
  "query": "SELECT device, COUNT(*) AS session_count FROM sessions GROUP BY device ORDER BY session_count DESC LIMIT 10",
  "chart_type": "pie",
  "title": "Sessions by Device",
  "x_axis": "device",
  "y_axis": ["session_count"]
}

stacked

Requires x_axis, exactly 1 column in y_axis, and group_by.
{
  "projectId": "proj_abc",
  "query": "SELECT device, browser, COUNT(*) AS session_count FROM sessions GROUP BY device, browser ORDER BY session_count DESC",
  "chart_type": "stacked",
  "title": "Sessions by Device and Browser",
  "x_axis": "device",
  "y_axis": ["session_count"],
  "group_by": "browser"
}

Funnel Charts

Funnel charts measure step-by-step user conversion. The SQL is auto-generated from steps, so query must be the placeholder "SELECT 1". At least 2 steps are required.

The FunnelStep Object

Each step in the steps array has the following shape:
{
  "type": "event | track | decoded_log",
  "event": "<event name>",
  "<propertyKey>": { "op": "<operator>", "value": "<value>" }
}
FieldTypeRequiredDescription
typestringYesevent — built-in events (page, connect, transaction); track — custom tracked events; decoded_log — decoded smart-contract events
eventstringYesEvent name (e.g. page, connect, transaction)
<propertyKey>StepFilterConditionNoOne or more property filters. Any extra key on the step object is treated as a filter. Standard columns (device, browser, os, location, referrer, ref, utm_*) are filtered directly; all other keys are extracted from properties via JSONExtractString

Filter Operators (StepFilterCondition)

{ "op": "<operator>", "value": "<value>" }
opSQL equivalentNotes
equals= 'value'Exact match
notEquals!= 'value'Inverse match
inIN (...)Pipe-delimited value: "metamask|rainbow|coinbase"
notInNOT IN (...)Pipe-delimited value
gt> valueNumeric comparison
gte>= valueNumeric comparison
lt< valueNumeric comparison
lte<= valueNumeric comparison
startsWithstartsWith(col, 'v')String prefix
endsWithendsWith(col, 'v')String suffix
includeslike '%v%'Substring match
For in and notIn, join multiple values with a pipe |. Escape a literal pipe with \| and a literal backslash with \\.

settings for Funnel Charts

FieldTypeDefaultDescription
funnelType"closed" | "open""closed"closed — strict ordering, no events between steps; open — ordered but other events may occur between steps
conversionWindowConversionWindow1 dayMax time from Step 1 for a user to complete all steps
breakdownstringSplit each funnel bar by this dimension

ConversionWindow

{ "value": 7, "unit": "day" }
unit must be one of: minute · hour · day · week (7 days) · month (30 days).

breakdown values

device · browser · os · referrer · ref · utm_source · utm_medium · utm_campaign · utm_term · utm_content The top categories are shown individually. The rest collapse into “Others”.

Funnel Examples

Basic 3-step closed funnel

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Onboarding Funnel",
  "steps": [
    { "type": "event", "event": "page" },
    { "type": "event", "event": "connect" },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 7, "unit": "day" }
  }
}

With per-step property filters

Filter step 2 to MetaMask wallet connections on Ethereum mainnet:
{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "MetaMask Conversion Funnel",
  "steps": [
    { "type": "event", "event": "page" },
    {
      "type": "event",
      "event": "connect",
      "rdns": { "op": "equals", "value": "io.metamask" },
      "chain_id": { "op": "equals", "value": "1" }
    },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 7, "unit": "day" }
  }
}

With breakdown by device

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Onboarding Funnel by Device",
  "steps": [
    { "type": "event", "event": "page" },
    { "type": "event", "event": "connect" },
    { "type": "event", "event": "signature" }
  ],
  "settings": {
    "funnelType": "closed",
    "conversionWindow": { "value": 30, "unit": "day" },
    "breakdown": "device"
  }
}

Open funnel with multi-value in filter

{
  "projectId": "proj_abc",
  "query": "SELECT 1",
  "chart_type": "funnel",
  "title": "Mobile Onboarding Funnel",
  "steps": [
    {
      "type": "event",
      "event": "page",
      "device": { "op": "equals", "value": "mobile" }
    },
    {
      "type": "event",
      "event": "connect",
      "provider_name": { "op": "in", "value": "metamask|rainbow|coinbase" }
    },
    { "type": "event", "event": "transaction" }
  ],
  "settings": {
    "funnelType": "open",
    "conversionWindow": { "value": 30, "unit": "day" },
    "breakdown": "device"
  }
}

User Paths Charts

Visualize how users navigate your app after a starting event. settings.startStep is required.

settings for User Paths Charts

FieldTypeDefaultDescription
startStepFunnelStepRequired. Event where the flow begins
endStepFunnelStep | nullnullOptional ending event. null = open-ended
maxStepsinteger (2–10)3Max steps to show in the flow. Values above 10 are clamped to 10
nodesPerStepinteger (2–10)5Max unique event nodes per step. Values above 10 are clamped to 10
conversionWindowConversionWindow2 weeksTime window to group the flow
filtersstringJSON-encoded string of additional path filters
{
  "projectId": "proj_abc",
  "query": "SELECT anonymous_id, type AS event, timestamp FROM events WHERE timestamp >= today() - INTERVAL 30 DAY ORDER BY anonymous_id, timestamp",
  "chart_type": "user_paths",
  "title": "Post-Connect User Flow",
  "settings": {
    "startStep": { "type": "event", "event": "connect" },
    "endStep": { "type": "event", "event": "transaction" },
    "maxSteps": 5,
    "conversionWindow": { "value": 2, "unit": "week" }
  }
}

Retention Charts

Measures how often users return over time. The query field is not used — pass "" or omit it.

settings for Retention Charts

FieldTypeDescription
retentionFilterFunnelStep | nullEvent that qualifies a returning visit as “retained”. null = any event counts
retentionUserFiltersRetentionUserFilter[]User-segment filters that narrow the retention cohort
Each RetentionUserFilter:
FieldTypeDescription
operandstringUser property (e.g. device, browser, os, utm_source, location)
operatorstringComparison operator (same set as StepFilterCondition.op)
valuestring | numberValue to compare against
{
  "projectId": "proj_abc",
  "query": "",
  "chart_type": "retention",
  "title": "Weekly Retention — Desktop Users",
  "settings": {
    "retentionFilter": { "type": "event", "event": "transaction" },
    "retentionUserFilters": [
      { "operand": "device", "operator": "equals", "value": "desktop" },
      { "operand": "utm_source", "operator": "notEquals", "value": "direct" }
    ]
  }
}
For all-user retention with no filters, omit settings:
{
  "projectId": "proj_abc",
  "query": "",
  "chart_type": "retention",
  "title": "Overall Retention"
}

Validation Reference

Chart TypeValidation Rule
tablequery must execute successfully
numberquery must return exactly 1 row × 1 column
barx_axis required; y_axis requires ≥ 1 element
linex_axis required; y_axis requires ≥ 1 element
piey_axis requires exactly 1 element
stackedx_axis required; y_axis requires exactly 1 element; group_by required
funnelsteps requires ≥ 2 FunnelStep objects; query must be "SELECT 1" or any valid SQL
user_pathssettings.startStep required; query must execute and return data in the expected user-path format (columns: user identifier, event name, timestamp)
retentionNo query validation — query is ignored

Response

201 Created
{
  "isSuccess": true,
  "data": "chart_1a2b3c4d"
}
data is the new chart’s ID. Use it with the Get Chart, Update Chart, and Delete Chart endpoints. 400 Bad Request — returned when validation fails (missing required fields, invalid SQL, wrong step count, etc.)
{
  "isSuccess": false,
  "message": "Funnel chart must have at least 2 steps"
}

Authorizations

Authorization
string
header
required

Workspace API key (e.g. formo_xxx). Create one in the Formo dashboard under Team Settings > API Keys.

Path Parameters

boardId
string
required

Body

application/json

Request body for creating a chart.

projectId
string
required

Project the chart belongs to.

chart_type
enum<string>
required

Visualization type. Determines which other fields are required:

chart_typeExtra required fields
tablequery
numberquery (must return 1 row × 1 column)
barquery, x_axis, y_axis (≥ 1)
linequery, x_axis, y_axis (≥ 1)
piequery, y_axis (exactly 1)
stackedquery, x_axis, y_axis (exactly 1), group_by
funnelsteps (≥ 2), query placeholder "SELECT 1"
user_pathsquery, settings.startStep
retentionnone (query ignored)
Available options:
table,
number,
funnel,
bar,
line,
pie,
stacked,
user_paths,
retention
title
string
required

Display name shown on the chart and board.

Minimum string length: 1
query
string

SQL query that powers the chart.

  • funnel — pass "SELECT 1"; the actual query is auto-generated from steps.
  • retention — can be omitted or pass ""; data is fetched from the retention pipe directly.
  • All other types — required; must be a valid SQL string.
Minimum string length: 1
description
string

Optional description.

x_axis
string

Column name for the X axis. Required for bar, line, and stacked.

y_axis
string[]

Column name(s) used as Y axis metrics.

  • bar / line — at least 1 element required.
  • pie / stacked — exactly 1 element required.
group_by
string

Column to group / stack series by. Required for stacked.

steps
object[]

Ordered list of funnel steps. Required for funnel (minimum 2 steps).

Each element is a FunnelStep — add property filters as extra keys on the step object (e.g. "rdns": { "op": "equals", "value": "io.metamask" }).

Minimum array length: 2
settings
object

Chart-type-specific configuration. The fields that apply depend on chart_type:

  • funnel: funnelType, conversionWindow, breakdown
  • user_paths: startStep, endStep, maxSteps, nodesPerStep, conversionWindow, filters
  • retention: retentionFilter, retentionUserFilters

All fields are optional at the schema level; see per-type validation rules for which are functionally required.

Response

201 - application/json

Chart created

isSuccess
boolean
data
string

Chart ID