Open source · Reporting as Code

Reporting as Code. Strictly IBCS®.

Write a report as YAML and SQL. bino renders it as an IBCS PDF. The files live in Git, so every change is reviewed and every report can be built again.

The tutorial runs in your browser. It takes you from your first manifest to a PDF, with no install.

bino-bi/bino-cli6$ brew install bino-bi/tap/bino-cli
Follow this report
  • YAML and SQL
  • DuckDB inside
  • PDF from headless Chrome
  • IBCS notation
  • Reviewed in Git
  • Built in CI
  • MCP server for agents
  • Language server
  • AGPL-3.0

One report, start to finish

From a CSV file to a PDF in four steps

Kestrel Outdoor GmbH is a made-up company with a real report. Scroll through the files that build it. Next to each file you see the data, or what bino built at that step.

Step 1 of 4

Point bino at the data

A DataSource names a file, a database or a URL. bino loads it into DuckDB under its name. Here it is one CSV file with 240 rows: month, region, product line, and revenue and cost of goods as actual, plan and last year.

datasources/kestrel.yamlyaml
apiVersion: bino.bi/v1alpha1
kind: DataSource
metadata:
  name: kestrel
  description: Fictional monthly revenue and cost of goods 2025 (AC, PY, PL) of Kestrel Outdoor GmbH
spec:
  type: csv
  path: ../data/kestrel_2025.csv
Open datasources/kestrel.yaml
data/kestrel_2025.csv240 rows · loaded as kestrel
First rows of data/kestrel_2025.csv
monthregionproductrevenue_acrevenue_pyrevenue_plcogs_accogs_pycogs_pl
2025-01-01NorthTents472004350046900274002550027200
2025-01-01NorthBackpacks364003380037300193001780019400
2025-01-01NorthApparel304003080033100142001460015600
2025-01-01NorthFootwear210001860020700118001030011400
2025-01-01SouthTents626005590061900338003110034100
2025-01-01SouthBackpacks516004550049200252002270024300
2025-01-01SouthApparel431003970043700196001790019500
2025-01-01SouthFootwear285002590027300151001370014300

Step 2 of 4

Shape it with SQL

A DataSet is a SQL query. This one adds up the months. Months after REPORT_MONTH become a forecast: plan times the ratio of actual to plan so far.

datasets/monthly.yamlyaml
apiVersion: bino.bi/v1alpha1
kind: DataSet
metadata:
  name: monthly
  description: >-
    Revenue per month in kEUR. Months up to REPORT_MONTH are actual (ac1), later months are a
    run-rate forecast (fc1): plan times the year-to-date ratio of actual to plan.
spec:
  dependencies: [kestrel]
  query: |
    WITH m AS (
      SELECT month, SUM(revenue_ac) AS ac, SUM(revenue_pl) AS pl, SUM(revenue_py) AS py
      FROM kestrel GROUP BY month
    ),
    rate AS (
      SELECT SUM(ac) / SUM(pl) AS r FROM m WHERE month(month) <= ${REPORT_MONTH:12}
    )
    SELECT
      strftime(month, '%Y-%m-%d') AS date,
      CASE WHEN month(month) <= ${REPORT_MONTH:12} THEN ROUND(ac / 1000) END AS ac1,
      CASE WHEN month(month) > ${REPORT_MONTH:12} THEN ROUND(pl * rate.r / 1000) END AS fc1,
      ROUND(pl / 1000) AS pl1,
      ROUND(py / 1000) AS pp1
    FROM m, rate
    ORDER BY month
Open datasets/monthly.yaml
The ChartTime component built from this DataSet: revenue per month against plan, with the relative variance on top.
components/monthly_chart.yaml, rendered by a bino ScreenshotArtefact with REPORT_MONTH=8

Step 3 of 4

Describe the page

A LayoutPage places a chart, a table and a text, and writes the IBCS title: who, what, when. Each dot on the page explains one part of it.

pages/monthly.yamlyaml
apiVersion: bino.bi/v1alpha1
kind: LayoutPage
metadata:
  name: monthly
  description: Revenue by month and region, year to date
spec:
  selectedStyle: kestrel
  pageFormat: a4
  pageOrientation: landscape
  pageLayout: custom-template
  pageCustomTemplate: '"a a a a" "a a a a" "b b b c"'
  pageGridGap: "12"
  titleBusinessUnit: Kestrel Outdoor GmbH
  titleMeasures:
    - name: Revenue
      unit: kEUR
  titleScenarios: [ac1, fc1, pl1, pp1]
  titleVariances: [dac1_pl1_pos, drac1_pl1_pos]
  titleDateStart: "2025-01-01"
  titleDateEnd: "2025-12-31"
  titleDateFormat: month
  titleDateLink: interval
  footerText: "Fictional data: data/kestrel_2025.csv"
  children:
    - kind: ChartTime
      ref: monthly_chart
    - kind: Table
      ref: region_table
    - kind: Text
      ref: commentary
Open pages/monthly.yaml
The page built for August: actual months are solid, September to December are a hatched forecast.
pages/monthly.yaml, built with REPORT_MONTH=8

Step 4 of 4

Build the PDF

bino build validates every file, runs the queries and prints the pages with headless Chrome. The same command runs on your laptop and in CI, so the PDF can always be built again.

reports/kestrel.yamlyaml
apiVersion: bino.bi/v1alpha1
kind: ReportArtefact
metadata:
  name: kestrel
  description: The full report, four pages
spec:
  filename: kestrel-2025.pdf
  title: Kestrel Outdoor GmbH - Revenue and gross profit 2025
  language: en
  format: a4
  orientation: landscape
  layoutPages: [monthly, structure, profit, portfolio]
Open reports/kestrel.yaml
BINO 0.100.0
 
ℹ INFO build: Using template engine v1.0.0-next.29
→ Loading manifests...
✓ Validated 29 document(s) (26ms)
 
ℹ Manifest summary:
 
→ Building 1 artefact(s)...
→ Rendering kestrel...
 
Generated 1 artefact(s):
  • kestrel → dist/kestrel-2025.pdf (+graph)
 
✓ Build complete in 1.31s

Monthly close

Same files.
Twelve months.

REPORT_MONTH is an environment variable. Every frame here is a real bino build of the same page, from January to December. Actual months turn solid, the forecast stays hatched, and a SQL query writes the comment.

$ REPORT_MONTH=5 bino build --artefact monthly✓ Build complete in 1.03s

Pick a month, or use the arrow keys.

The monthly report as bino built it with REPORT_MONTH=5.

Agentic reporting

An agent writes the report. bino checks it.

A bino report is plain YAML and SQL, so an AI agent can write it. bino mcp gives the agent tools to read your data, check every draft and render the result. The IBCS rules stay the same. The agent is one more way to write the report.

This video shows a real run: an agent built an IBCS report from a CSV file with only the bino tools, and a person reviewed the PDF twice.See the prompt, the reviews, every tool call and the PDFs →
  1. 1validate_draft

    Check the draft

    The agent writes a manifest. bino validates it against the schema in memory, before anything is saved.

  2. 2write_manifest

    Write it

    bino validates the manifest again and only then writes it to disk.

  3. 3get_rows

    Check the numbers

    The agent queries its own DataSet and compares the rows with what the page should say.

  4. 4build

    Render the PDF

    The same bino build as on your machine. The PDF is what you review.

Projects

One workflow, three projects

bino cli is open source under AGPL-3.0. The sandbox and the registry are free hosted services around it.

bino cliOpen source

IBCS reports from YAML + SQL

$ brew install bino-bi/tap/bino-cli

Report-as-code runtime in the browser

Share report definitions with the community

$ bino registry add @acme/base-layout

Plugins · early access

Extend bino with plugins

A plugin adds data sources, visual components, lint rules, pipeline hooks or commands to bino. You write it in Go with the plugin SDK (Apache-2.0). The SDK is at v0.0.x, so the API can still change.

Your turn

Your first report is three commands away.

bino init standard writes a full starter report: a CSV data source, a dataset, an IBCS table and chart, a style and translations. bino preview shows it in your browser and reloads on every save.

Need help running bino in your team? See services →
Questions about your reports, a bug, or working together? Write to us →

Terminalbash
$ brew install bino-bi/tap/bino-cli
$ bino init standard --directory my-report
$ cd my-report && bino preview