Browse documentation
Guide Updated 5 September 2026

Run a report and download it

Ask one question of the records you have kept — the records themselves or a breakdown of them — run it on demand, and download the answer as CSV, Excel, JSON or PDF.

View as Markdown

A report is a saved question about the records in your warehouse. A dashboard widget draws a picture that keeps itself current; a report answers one question when you press Run, shows the answer as a table, and hands it to you as a file.

Dashboards → Reports in the sidebar lists the ones your workspace has, with what each asks and which data model it reads.

Outcome

A saved report you can re-run whenever you want, and a file of its answer in the format whoever asked for it can open.

Before you start

  • A data model that already holds records. A report against an empty model answers No records matched, which is hard to tell from a mistake.
  • Nothing else. Reports read what is already there; they change nothing.

1. Start a report

New report opens an unsaved draft called Untitled report.

Nothing is written until you press Save. Every control changes the draft in front of you, so you can open a saved report, push it around to see something, and leave without having changed it.

Checkpoint: the page shows Untitled report and Nothing run yet.

2. Choose the question

Three controls sit at the top of every report.

Control What it decides
Data model Which records the report reads.
Question Records or Breakdown — the two shapes.
Time range Which slice of history it reads.

Records gives you the records themselves, one row each. Breakdown counts, sums or averages those same records into buckets — one number, a number per category, or a number per period.

The Time range picker offers Last hour, Last 24 hours, Last 7 days, Last 30 days, All time, and Custom range, which adds From and To dates. A new report starts on Last 30 days. A custom range is anchored to your own days rather than to UTC, and both dates are required — a half-filled range is refused rather than quietly widened to everything.

This is a smaller set than the dashboard header's, and there is one range per report rather than one per anything.

Checkpoint: a data model is selected and the controls below have changed to match your Question.

3. Say what you want back

Records

Control Options
Rows Current per record — one row per entity, as it stands now. Every change — every version, so a record that changed five times contributes five rows.
Time filtered on Observed at, when the thing happened, or Inserted at, when Hodoflow stored it. The time range applies to whichever you pick.
Sorted by Observed at, Inserted at or Entity id.
Order Newest first or Oldest first.

Columns is a tick list of everything the report can return: your data model's own fields, plus entity_id — the record's primary key value — observed_at and inserted_at. Tick none and you get every column.

Alongside reads a second data model next to the first, matched on the record's own id, and adds its columns under model-slug.field names. Records with no match decides what happens to a record the second model knows nothing about: Keep them, with the columns empty, or Leave them out. Adding a second model switches Rows to Current per record, which is the only way it can be read.

Breakdown

Control What it does
Measure Count, Distinct count, Sum, Average, Min, Max, Median, Percentile, Std deviation, Variance, First or Last.
Of field Which field the measure reads. Count needs none.
Grouped by A field to break the answer down by, or Nothing — one number.
Or over time Minute, Hour, Day or Week to get a series instead, or Not a series.
Most buckets How many groups or points come back at most.

Percentile asks which percentile you want. Choosing a time bucket adds Bucketed on, which is the timestamp the buckets are cut on.

Checkpoint: the controls describe the question you would say out loud.

4. Narrow it

Filters are conditions on the records themselves. Add filter gives you a field, a comparison — is, is not, contains, greater than, at least, less than, at most — and a value. A half-typed row changes nothing until all three are filled in.

With a second data model in play, one list holds the conditions on both; a condition on the second model carries its model-slug.field name. Those conditions decide which record matches, so under Keep them, with the columns empty they can empty a row's extra columns, and under Leave them out they can remove the row.

5. Run it

Run executes what is on screen, not what was last saved — that is what makes the builder answer the question you are asking rather than the one you wrote down last week.

A Records answer arrives 50 rows at a time, with the total in the card heading. A Breakdown answer arrives whole; its Most buckets is its size.

Checkpoint: the table below shows rows, or says No records matched.

6. Save it

Save writes the report. Until then the page shows Unsaved changes under the name, and Download is disabled — Save your changes to download them. The file and the screen should be answers to the same question, so downloading is offered only once the two agree.

Delete removes the saved question. The records it reads are untouched.

Checkpoint: Unsaved changes is gone and Download is available.

Writing the query as text

The Builder / Text toggle is two editors over one draft, not two drafts. Switching to Text writes the current report out as a query, and every keystroke parses it back, so the controls behind it are already right when you switch away. Text that does not parse leaves the report exactly as it was and says which word stopped it.

What can I write? under the box lists the clauses, the measures and the buckets it accepts, read straight off the parser.

The time range stays a control in both editors. It belongs to whoever is reading the report, not to the query.

Where your organization has a language model linked, a Describe it box sits above the query. Type what you want in a sentence, press Draft, and the query it writes lands in the editor for you to read and change. Nothing is run or saved until you say so.

Opening a widget as a report

A widget is a rendering and a report is a query, so "what are the rows behind this chart?" is a question the report surface answers rather than the chart.

In Dashboards → Widget Library, every widget carries Open as report. It opens as an unsaved draft asking the question the widget already asks, which you can then change, save and download like any other. A text widget queries nothing, so it has none.

Two widget types measure something a report has no shape for, and the draft says so in a note rather than quietly answering something else: a heatmap crosses hour with day of week, and the report buckets by hour; a pivot table crosses two fields, and the report groups by the row field.

Download the answer

Download on a saved report offers four formats. Each reads the whole report rather than the fifty rows on screen — the PDF stops at 2,000 rows, and says so on itself.

Format Reach for it when
CSV Anything opens it. Every value is text.
Excel Numbers, dates and true/false stay what they are.
JSON Another system is going to read it.
PDF You are sending someone a document.

Files are named after the report and the day — late-orders-2026-09-05.csv — so the four formats of one report sort together in a downloads folder.

CSV

One type, so everything is written as text: timestamps as ISO 8601, a nested value as the JSON it already is, and a missing value as an empty cell.

A value that starts with =, +, - or @ comes back with an apostrophe in front of it. That is deliberate and the file is not broken. A spreadsheet reads a cell starting with one of those characters as a formula to run, and the values in your warehouse arrived from outside — a webhook or a connector, whose contents somebody else chose. The apostrophe is what makes the cell data rather than code. Numbers, timestamps, true/false and nested values are untouched, and CSV is the only format that does this.

Excel

Types survive the trip: a number stays a number, a date stays a date, true/false stays a checkbox value. Sorting, filtering and formulas work on the file without re-typing a single column, and the sheet is named after the report.

One thing is lost. A spreadsheet date carries whole seconds, so timestamps come out rounded to the second. If you need the instant exactly as it was recorded, take JSON.

JSON

Every value exactly as the report read it, which makes this the format to feed another system. Alongside the rows the file states the report's name, its columns in order, the window it read, when it was generated, and how many rows the whole answer has.

PDF

A document to send someone: landscape, page-numbered, and headed with the report's name, the time range it read, its filters, how many rows it holds and when it was generated.

A PDF carries at most 2,000 rows. The Download menu says so next to the format, and the document itself says which of the two it is — First 2,000 of 7,412 rows, or 412 rows — the whole report. It is built in your browser, so give it a moment on a large report.

How many rows a download carries

Two ceilings, depending on what the report asks:

Report Ceiling
Records, Current per record 250,000 rows
Records, Every change 10,000 rows
Breakdown Most buckets — always well below either

A report above its ceiling is refused before the download starts, and the message names the numbers: This report has 340,112 rows, more than the 250,000 a download can stream. Narrow its time range or filters and try again. That is the fix — a shorter time range, or a filter that removes what you were not going to read anyway.

The two numbers differ because the two questions are reached differently. One row per record can be walked from the first to the last at a steady cost; every version of every record cannot, and gets slower the further in it goes. A quarter of a million rows is already more than a spreadsheet is pleasant with, which is where the larger number is set.

What a download is a snapshot of

A download is a picture of the moment you clicked it. Records that land while the file is being written are not in it, even under a relative range like Last 24 hours, so the file holds together instead of repeating or skipping rows. One consequence worth knowing: a record whose observed time is in the future is on screen and not in the file.

Who can download

Anyone who can open the report can download it. There is no separate export permission to grant, and none to withhold — a member downloading the file is reading the numbers they are already looking at. The place to withhold data is the report or the workspace.

Verify the result

  • Run fills the table, and the heading counts what came back.
  • Unsaved changes is gone and Download is enabled.
  • The downloaded file opens, and its row count matches the heading.

Common problems

Download is greyed out. Either the report has never been saved — Save this report to download it. — or the screen has changed since it was — Save your changes to download them.

The CSV has an apostrophe in front of some values. Expected, and only on values starting =, +, - or @. See CSV above. Excel and JSON give the string back untouched.

The download was refused for having too many rows. Narrow the time range or add a filter. Every change reaches 10,000 rows and Current per record reaches 250,000, so switching Rows to Current per record is often the whole fix.

The PDF stops short of the report. It carries 2,000 rows and says so on the first page. Take Excel or CSV for the whole thing.

The PDF would not build. The message says whether the report was out of range or too many downloads were running at once. Trying the same report as CSV shows the reason in full.

Hodoflow says too many downloads are running. One organization can hold a few at a time. Wait for one to finish and try again.

The answer is empty. The time range excludes your records, or a filter is narrower than you meant. Try All time first, then remove filters one at a time.

A breakdown returns fewer groups than expected. Most buckets is capping it.

The report will not run at all. Its data model may have been deleted — the report survives so you can point it at another one, but it cannot answer anything until you do.

Next step

Build a dashboard if the answer is something you want to watch rather than something you want to send.

Related documentation