Skip to main content

Report JSON

How a Folio report is written down as JSON, key by key, for options the Editor does not set and for reports generated by code.

Overview​

Every report is stored as one JSON document in the Config JSON field of its Folio Definition record. The Editor writes it, and most reports never need anything else. Edit it by hand when you want:

  • Presentation the Editor does not offer yet: coloring cells by rule, coloring column headers, fixed column widths, limiting which columns readers can sort, and PDF page sizing. See Display.
  • Reports built by code, including reports whose data comes from somewhere other than a live read. See Supplying the data yourself.

To edit it, click Go to Definition on the report, then Edit on the record, and change Config JSON. The Editor keeps keys it does not show, so options you add by hand survive later edits in the Editor.

Nothing in the JSON is trusted. Every object and field name is checked against your org when the report runs, values are never pasted into a query, and the report reads data as the person viewing it. A field that does not exist, or that the reader cannot see, stops the report with an error naming it rather than showing anything it should not.

A report looks like this:

{
"schemaVersion": 1,
"format": "summary",
"primaryObject": "Opportunity",
"columns": [
{ "id": "c_name", "field": "Name", "label": "Opportunity", "dataType": "string" },
{ "id": "c_amt", "field": "Amount", "label": "Amount", "dataType": "currency", "aggregate": "SUM", "format": { "decimals": 0 } }
],
"groupRows": [
{ "id": "g_industry", "field": "Account.Industry", "label": "Industry", "dataType": "picklist", "direction": "ASC" }
],
"filters": {
"conditions": [ { "id": "1", "field": "IsWon", "operator": "equals", "value": true } ]
},
"chart": { "type": "bar", "measureRef": "c_amt", "title": "Won Revenue by Industry" }
}

Top level​

KeyRequired
schemaVersionYesCurrently 1.
formatYestabular, summary, matrix or scorecard.
primaryObjectYesAPI name of the object the report is on, such as Opportunity.
columnsYesThe fields shown. See Columns.
groupRowsSummary, matrix, scorecardWhat to group by. See Groupings.
groupColumnsMatrixWhat runs across the top of a matrix.
filtersNoWhich records to include. See Filters.
sortByNoDefault order. See Sort.
rowLimitNoHow many records a summary lists under its groups. Defaults to, and cannot exceed, 2,000. Subtotals and totals count every record regardless, and a tabular report ignores it and pages through everything.
chartNoSummary and matrix. See Chart.
displayNoPresentation. See Display.
toolsNoWhich reader tools are allowed. See Tools.

A key Folio does not know is ignored.


Columns​

{ "id": "c_amt", "field": "Amount", "label": "Amount", "dataType": "currency", "aggregate": "SUM", "format": { "decimals": 0, "prefix": "$" } }
Key
idYour own identifier, unique within the report. Sort, chart and display settings refer to columns by it.
fieldThe field's API name. A lookup path reaches a related record: Account.Name, Account.Owner.Name.
labelThe column heading.
dataTypeHow to format values: string, textarea, int, double, currency, percent, boolean, date, datetime, time, picklist, multipicklist, reference, id, email, phone or url. The real type is looked up in your org for anything that matters; this only affects display.
aggregateOptional. SUM, AVG, MIN, MAX, COUNT or COUNT_DISTINCT. A column with one is a measure: it fills subtotals, totals, matrix cells and charts.
formatOptional number display: decimals, currencyCode (such as "USD"), prefix, suffix.

Counting records is a measure on Id: { "id": "m_count", "field": "Id", "label": "Records", "aggregate": "COUNT", "dataType": "int" }.

On a matrix, only the first measure is used. It fills every cell, and columns without an aggregate are ignored.

Scorecard measures​

On a scorecard, every column is a measure with a source: a related object whose records are gathered up for each row.

{
"id": "m_won", "label": "Won Revenue", "aggregate": "SUM", "dataType": "currency",
"source": {
"object": "Opportunity",
"joinPath": "AccountId",
"field": "Amount",
"filter": { "conditions": [ { "id": "1", "field": "IsWon", "operator": "equals", "value": true } ] }
}
}
Key
source.objectThe related object.
source.joinPathThe lookup on that object pointing at the report's object.
source.fieldThe field to summarize, or Id to count records.
source.filterOptional. conditions and logic as in Filters, on the related object's fields.

joinPath has to point at the report's own object. A scorecard cannot have groupColumns.


Groupings​

groupRows and groupColumns are lists of:

{ "id": "g_month", "field": "CloseDate", "label": "Month", "dataType": "date", "bucket": "month", "direction": "ASC" }
Key
id, field, label, dataTypeAs for columns.
directionASC or DESC: the order of the groups.
bucketOptional, date and date/time fields only: day, month, quarter or year, labeled 2026-06-19, 2026-06, 2026-Q2 and 2026. Calendar periods only, evaluated in GMT.
displayFieldScorecard only. The field that labels each row.

Only the first grouping in each list is used. Further entries are kept but have no effect.

A scorecard's row grouping is always on Id, one row per record, with displayField naming the label:

"groupRows": [ { "id": "g_account", "field": "Id", "displayField": "Name", "label": "Account", "dataType": "id", "direction": "ASC" } ]

Filters​

"filters": {
"conditions": [
{ "id": "1", "field": "StageName", "operator": "in", "value": ["Prospecting", "Qualification"] },
{ "id": "2", "field": "Amount", "operator": "greaterThan", "value": 10000 },
{ "id": "3", "field": "Account.Industry", "operator": "equals", "value": "Education" }
],
"logic": "1 AND (2 OR 3)",
"dateFilter": { "field": "CloseDate", "range": "THIS_FISCAL_YEAR" }
}
operatorvalue
equals, notEqualsA single value. For a checkbox, true or false.
greaterThan, lessThan, greaterOrEqual, lessOrEqualA single value. Dates as "2026-06-30".
contains, notContains, startsWithText.
in, notInA list of values.
includes, excludesA list of values, for multi-select picklists.
isNull, isNotNullNone.

logic combines conditions by their position in the list, counting from 1, with AND, OR, NOT and parentheses. NOT binds tightest, then AND, then OR. Leave it out and every condition must match. An expression that refers to a condition that is not there, or uses any other word, stops the report with an error.

dateFilter is applied on top of the conditions and outside logic. Its range is one of:

  • TODAY, YESTERDAY, TOMORROW
  • THIS_WEEK, LAST_WEEK, NEXT_WEEK
  • THIS_MONTH, LAST_MONTH, NEXT_MONTH
  • THIS_QUARTER, LAST_QUARTER, NEXT_QUARTER
  • THIS_YEAR, LAST_YEAR, NEXT_YEAR
  • THIS_FISCAL_QUARTER, LAST_FISCAL_QUARTER, NEXT_FISCAL_QUARTER
  • THIS_FISCAL_YEAR, LAST_FISCAL_YEAR, NEXT_FISCAL_YEAR
  • LAST_N_DAYS:30 or NEXT_N_DAYS:30, with any whole number
  • CUSTOM, with startDate and endDate as "2026-01-01"

The Editor offers a shorter list, and keeps any of these you set by hand.


Sort​

"sortBy": [ { "ref": "c_close", "direction": "DESC" }, { "ref": "c_name", "direction": "ASC" } ]

ref is a column or grouping id. Up to three are used, in order.

A matrix ignores sortBy; its order comes from its groupings' direction. A scorecard uses only the first entry, and only when it names a measure: rows are then ordered by that measure, with empty values last. Otherwise a scorecard follows its row grouping's direction.


Chart​

"chart": { "type": "pie", "measureRef": "c_amt", "title": "Won Revenue by Industry" }
Key
typeSummary: bar, horizontalBar, line, pie, doughnut. Matrix: bar (grouped), stackedBar, line.
measureRefThe id of a column with an aggregate. Without one, there is no chart.
titleOptional. Without it, the chart is named after its measure.

A summary charts its grouping and a matrix charts its row grouping, with the column grouping as the series. The Editor also writes a dimensionRef naming the grouping; it is not read.

An unknown type, or one the format does not offer, draws as bar. So do pie, doughnut and stackedBar on a measure other than SUM or COUNT, since only those add up to a whole.


Display​

Everything in display is optional, and anything left out keeps its default.

"display": {
"theme": { "--folio-brand": "#1F5C99" },
"header": { "fields": [ { "label": "Owner", "value": "Development Team" }, { "label": "Period", "value": "FY 2026" } ] },
"grid": { "cellPadding": "4px 10px", "cellBorderColor": "#D0D7E2", "columnWidths": { "c_notes": "220px" } },
"headerStyles": { "c_amt": { "background": "#E6F4EA", "color": "#1B5E20" } },
"conditionalFormats": [
{ "columns": ["c_status"], "equals": "At Risk", "style": { "background": "#FBE9C8", "color": "#8A5A00" } },
{ "columns": ["c_amt"], "when": { "op": "greaterThan", "value": 50000 }, "style": { "background": "#D6F0DE" } }
],
"sort": { "columns": ["c_name", "c_amt"] },
"hideMeta": true,
"pdf": { "fit": "width" }
}
KeyEffectIn the Editor
theme--folio-brand sets the report's brand color.Brand color
header.fieldsLabel and value pairs under the title. An empty value shows as a dash.Header Strip
grid.cellPaddingCell padding, as CSS. The default is 8px 12px; the Editor's Compact is 4px 10px.Density
grid.cellBorderColorDraws cell borders in this color.Cell border color
grid.freezeFirstColumntrue keeps the first column in view on a wide summary or scorecard.Freeze the first column
grid.columnWidthsColumn id to a fixed width, such as "220px". Long text in those columns wraps instead of widening them. Summary and scorecard only.Not yet
headerStylesColumn id to a background and color for that header.Not yet
conditionalFormatsColors data cells by rule. See below.Not yet
sortTabular only. { "enabled": false } turns header sorting off; { "columns": [...] } allows it on those column ids only.Not yet
hideMetatrue hides the line under the title.Hide the record-count / format subline
pdfPDF page size. See below.Not yet

grid.cellPadding and grid.cellBorderColor apply to every format except matrix.

Any other grid.cellPadding becomes 4px 10px the next time the report is saved in the Editor, since those two are all it offers.

Conditional formats​

A list of rules, checked in order. The first rule that matches a cell colors it.

Key
columnsColumn ids the rule applies to. Leave it out for every column.
equalsMatches a cell whose value is exactly this.
containsMatches a cell whose value contains this text.
whenA number comparison: op is greaterThan, greaterOrEqual, lessThan, lessOrEqual, equals or notEquals, against value. Dollar signs, commas and percent signs are ignored, so it works on formatted text too.
stylebackground and color.

Rules color the cells of records, not subtotal or total rows. Header styles and conditional formats both carry into the Excel (Formatted) and PDF exports.

PDF page size​

Key
fitauto, the default: standard Letter when the table fits, a wider page when it does not. width: always a page as wide as the table. letter: always Letter, squeezing columns to fit.
pageWidth, pageHeightExact page size in points, overriding fit. pageHeight can be "auto" for one continuous page.

Tools​

Which reader tools the report allows. Leave tools out to allow everything.

"tools": {
"export": { "enabled": true, "modes": ["pdf", "csv"] },
"filter": { "enabled": false },
"chart": { "enabled": true }
}

Once tools is present, it is a list of what is allowed: a tool shows only if its key is there and not false. modes narrows the export formats to xlsx, xlsxFormatted, pdf, csv and json; leave it out for all of them.

Where the report is placed can narrow this further but never widen it, apart from the admin views. See The Utility Bar setting.


Supplying the data yourself​

A report does not have to read Salesforce. Put rows in the definition's Data JSON field and the report shows them instead, with the same formatting, grouping, subtotals, totals and charts. This suits figures your own code computes, or data held outside Salesforce.

{ "rows": [
{ "g_region": "North", "c_program": "After School", "c_served": 412 },
{ "g_region": "North", "c_program": "Summer Camp", "c_served": 188 },
{ "g_region": "South", "c_program": "After School", "c_served": 305 }
] }

Each row is keyed by column and grouping id:

FormatOne row per
TabularRecord, with a value for each column.
SummaryRecord, with its group's value under the grouping's id.
MatrixCell: the row group's value, the column group's value, and the measure.
ScorecardReport row: the label under the grouping's id, and a value for each measure.

Folio builds the groups, subtotals, totals and matrix itself; supply only the rows. A missing value shows as a dash.

Because nothing is read from Salesforce:

  • Give every column a label and dataType, since there is no field to look them up from. field and primaryObject are not used.
  • filters do not apply. Put only the rows you want in the data.
  • Readers get no Filter tool, since there is nothing to rerun.
  • The whole field holds 131,072 characters, which suits summaries and scorecards better than long lists.

Code that creates these records needs create and edit access to Folio Definitions, which Flourish Folio: Admin grants. Set Name, Unique Name, Type (Report), Status (Published), Config JSON and Data JSON. Version, the version key and the live version are filled in for you.