
CTO & Co-founder of Visivo
From YAML to Dashboard: A Day in the Life of BI-as-Code
A concrete walkthrough of the BI-as-code loop: define a model and metric in YAML, preview locally, open a PR, and ship a reviewed dashboard.

The BI-as-code workflow is a tight loop borrowed straight from software development: define your data and metrics in YAML, preview locally with visivo serve, open a pull request so a teammate reviews the diff, and deploy the reviewed result. This post walks that loop end to end with real, valid Visivo 2.0 configuration, so you can see exactly what a day in the life of analytics-as-code looks like.
If you have read the conceptual pieces on this blog and wondered what the actual keystrokes are, this is for you. No abstractions, no "imagine if." Just the literal steps from an empty file to a dashboard your team has reviewed and shipped.
The loop in one diagram
The whole workflow is four steps, and they form a cycle you run many times a day:
- Define a model, a metric, and a dimension in YAML.
- Preview it locally with
visivo serveand iterate until it looks right. - Review it: commit, open a pull request, and let a teammate read the diff.
- Ship it: merge, then deploy the reviewed project to a stage.
That is the same edit-run-review-ship loop a software engineer runs, and that is the entire point. BI-as-code is not a new discipline you have to learn from scratch. It is the discipline your engineering org already practices, applied to dashboards. Every property that makes software development reliable (local iteration, code review, version history, CI) comes along for free because your analytics are now text in a repository. We made the broader case in what BI-as-code is; here is the loop in motion.
Define a model and a metric
Everything starts in a YAML file. You describe your data once in the semantic layer: a model that points at a table, a metric that defines a number, and a dimension that defines how you can slice it.
# project.visivo.yml
name: orders-overview
sources:
- name: warehouse
type: postgresql
host: "{{ env_var('PG_HOST') }}"
database: analytics
models:
- name: orders
source: ${ref(warehouse)}
sql: "SELECT * FROM orders"
metrics:
- name: net_revenue
expression: "SUM(amount - refunds)"
- name: order_count
expression: "COUNT(*)"
dimensions:
- name: order_month
expression: "DATE_TRUNC('month', created_at)"
- name: region
expression: "region"
Read that and you know the shape of the analytics. net_revenue is the sum of amount minus refunds, full stop. There is exactly one place that lives, which means there is exactly one answer to "what is revenue" and exactly one place to change it. order_month and region are the axes you are allowed to slice by. This is the semantic layer, and it is the foundation everything else references rather than re-derives.
Build an Insight from the semantic layer
With the model defined, a chart is a small, declarative thing. An Insight references the metrics and dimensions you already defined and says what to show. It does not re-derive any SQL.
insights:
- name: monthly-revenue
props:
type: bar
x: ?{ ${ref(orders).order_month} }
y: ?{ ${ref(orders).net_revenue} }
interactions:
- split: ?{ ${ref(orders).region} }
- sort: ?{ ${ref(orders).order_month} ASC }
A few things are worth noticing. The Insight names a bar chart with month on the x axis and net revenue on the y axis, and both axes reference the semantic-layer definitions through ?{ ${ref(...)} }. The interactions block adds a split by region (so each month breaks out by region) and a sort by month ascending. Because the metric is referenced and not re-typed, if finance later changes how net_revenue is computed, this chart and every other chart that uses it updates on the next run. No hunting through dashboards, no drift.
To put it on a page, you arrange Insights into a dashboard:
dashboards:
- name: Revenue Overview
rows:
- height: medium
items:
- insight: ${ref(monthly-revenue)}
That is the full definition of a real dashboard: a source, a model with metrics and dimensions, an Insight, and a layout. All of it text, all of it in one file you can read top to bottom.
Preview locally with visivo serve
Before anyone else sees it, you see it. From your project directory:
visivo serve
This compiles the project, runs the queries against your source, and starts a local server with the dashboard rendered live. You open it in your browser and look. Is the bar chart right? Does the region split make sense? Is the sort order what you wanted? You edit the YAML, save, and the preview updates. This is the inner loop, and it is fast on purpose, because the faster the feedback, the better the result. We have written about why tight feedback cycles change how analysts work.
The key thing here is that you are iterating in private, against real data, with zero blast radius. Nothing is published. No stakeholder is looking at a half-finished chart. You polish until it is right, and only then do you involve anyone else. That separation (private iteration, then public review) is exactly how good software gets written, and now it is how dashboards get built.
Open a PR and review the diff
When the dashboard looks right locally, you commit it and open a pull request, the same as any code change.
git checkout -b revenue-overview-dashboard
git add project.visivo.yml
git commit -m "Add monthly revenue overview dashboard"
git push origin revenue-overview-dashboard
Now a teammate opens the PR and reads the diff. This is where BI-as-code earns its keep. The reviewer is not squinting at a screenshot trying to reverse-engineer what a chart measures. They are reading the literal definition. They can see that net_revenue nets out refunds and ask, in a line comment, "should this exclude internal test orders too?" They can catch that the split is by region when the team agreed to slice by segment. They can confirm the dimension is the finance-approved one before a single stakeholder sees the number.
Catching a wrong definition in review costs a comment. Catching it after it has shipped to an executive deck costs your credibility. The diff makes the definition reviewable, and reviewable definitions are how you stop bad numbers before they spread. This is the same argument we make for tracking analytics changes through pull requests, applied to a brand-new dashboard rather than a change to an existing one. CI can run your data tests on the branch too, so a broken metric fails the check before anyone approves it.
Deploy the reviewed dashboard
Once the PR is approved and merged, you deploy the reviewed project to a stage:
visivo deploy -s production
That publishes the exact configuration your team reviewed. Not a slightly different version someone tweaked in a UI after the fact. The thing in the deck is the thing in the PR is the thing in your Git history. If someone asks in three months why Q1 revenue looks the way it does, the answer is a git blame away, and the review conversation that approved the definition is attached to the PR forever.
Then the loop starts again. Next change (a new metric, a different split, an added dimension) runs the same path: define, preview, review, ship. The cycle is the product. You can manage staging and production as separate stages, which we cover in managing staging and production environments, so you can promote a dashboard through environments the same way you promote application code.
That is a day in the life of BI-as-code. No clicking through a builder, no untracked changes, no screenshot-driven review. Just a tight loop of text you define, preview, review, and ship, with every property of good software engineering inherited for free. The docs cover each command in depth, the examples gallery is built entirely on this loop, and /get-started will have you running visivo serve against your own data in a few minutes.
Previously in Visivo
This is the hands-on companion to AI-ready analytics starts with a semantic layer, which argued for the governed definitions this workflow produces. If you want the conceptual foundation first, start with what BI-as-code is and then come back to run the loop.