The future of BI: Exploring the impact of BI-as-code tools with DuckDB
2023/12/18The "BI as Code" movement applies software engineering practices like version control and automated testing to analytics, solving the rigidity of traditional UI-driven BI tools. This guide explores how MotherDuck and DuckDB provide an ideal data backend for BI as Code and offers a hands-on comparison of three popular BI as Code tools: Evidence, Rill, and Streamlit.
Developing and maintaining analytics dashboards can be painful. Your data lifecycle (ingestion, transformation, and storage) might be running smoothly, but the final step often introduces human error and technical debt through traditional, UI-driven business intelligence (BI) tools.
What if we treated dashboards like any other software asset? That's what "BI as Code" does.
BI as Code defines every component of the analytics process in source code, from data transformation to visualization. Unlike traditional BI tools that rely on proprietary drag-and-drop interfaces, this code-first approach enables versioning, collaboration through Git, and automated testing.
In this guide, we'll explore why DuckDB and MotherDuck power this movement effectively. Then we'll get hands-on, comparing three popular BI as Code tools (Evidence, Rill, and Streamlit) by building the same dashboard with each.
The Pain of Point-and-Click Analytics
Data engineering has seen significant advancements, yet the analytics chain hasn't always kept pace. Business analysts typically extract data from a warehouse and build dashboards using UI-driven tools like Tableau or Power BI.
While these tools lower the technical barrier to entry, they weren't designed with a developer's workflow in mind:
- Versioning and Rollbacks: Rolling back a change made in a complex UI isn't straightforward. Viewing the history of a chart's definition requires workarounds.
- Testing and Environments: A dashboard presenting key business KPIs is a critical software asset. Testing and promoting this asset through staging and production environments is cumbersome and error-prone in a point-and-click world.
- Collaboration and Code Review: Git-based workflows are the cornerstone of modern software development. Traditional BI tools don't integrate naturally with pull requests, code reviews, and collaborative branching strategies.
The BI as Code movement addresses these issues by treating dashboards as code, enabling versioning, automated testing, and integration into CI/CD pipelines.
Why DuckDB and MotherDuck are the Ideal Backend for BI as Code
BI as Code requires a data backend that's as flexible and developer-friendly as the tools themselves. DuckDB and MotherDuck excel here.
1. The Local-First Advantage with DuckDB
DuckDB is an embedded, in-process OLAP database perfect for local analytics. It's incredibly easy to install (pip install duckdb) and requires no external dependencies. This approach dramatically shortens the development loop. You can do everything locally, from building the dashboard to testing queries, without relying on a cloud data warehouse.
2. The Hybrid Power of MotherDuck
MotherDuck provides a serverless, shareable analytics platform built on DuckDB's architecture. You can develop and test your entire dashboard locally against a DuckDB file, then with a one-line change, point it to your MotherDuck instance to run on scalable cloud infrastructure.
This combines the speed of local development with the power and collaboration of the cloud.
Comparing BI as Code Tools: Evidence vs. Rill vs. Streamlit
We'll compare three open-source tools that embody the BI as Code philosophy. Each has a different approach but all offer excellent support for DuckDB and MotherDuck.
- Evidence (SQL + Markdown): A lightweight JavaScript framework for building data apps. You write SQL queries inside Markdown files and use pre-built components to render charts and text. The final output is a static website that can be hosted anywhere.
- Rill (SQL + YAML): An opinionated framework from Rill Data for creating dashboards from metrics defined in YAML. It comes with a convenient CLI for local development. Fun fact: Rill itself is built using DuckDB.
- Streamlit (Python): A popular Python framework for building data apps. It allows you to stay entirely within the Python ecosystem, using familiar libraries like Pandas to create interactive applications.
Hands-On Lab: Building a PyPI Stats Dashboard, Three Ways
We'll build a simple dashboard to visualize PyPI download statistics for the duckdb package. We'll use a local duckdb_stats.db file for development. You can find all the code and data for these examples in this GitHub repository.
For each tool, we'll walk through setup, data connection, visualization, and deployment.
1. Evidence: The SQL and Markdown Approach
Evidence is perfect for those who think in SQL and want to quickly create beautiful, static reports.
A. Setup & Project Structure
You can bootstrap an Evidence project with a single command. The Makefile in our demo repo simplifies this process to:
Copy code
# Install dependencies
make evidence-install
# Run the local development server
make evidence
The project structure is simple:
pages/: Contains your Markdown files, where each file becomes a page in your app.evidence.plugins.yaml: Manages plugins and component configurations.
B. Connecting to DuckDB & MotherDuck
Once the local server runs, you can configure your data source in the settings UI at http://localhost:3000/settings.
- For Local DuckDB: Provide the path to your
.dbfile. - For MotherDuck: Select DuckDB, and in the "Filename / DSN" field, enter your MotherDuck connection string:
md:?motherduck_token=YOUR_TOKEN_HERE.- Note: For production applications, manage credentials like your MotherDuck token using environment variables or a secrets manager rather than hardcoding them in your connection string.
C. Creating a Visualization
In Evidence, you embed SQL queries directly into your Markdown files using fenced code blocks. You then pass the query result to a component.
Here's how to display the total download count and a line chart in pages/index.md:
Copy code
---
title: DuckDB pypi stats | Evidence
---
## How many people downloaded DuckDB?
<BigValue
data={total_downloads}
value='download_count'
fmt='#,##0.00,,"M"'
/>
### Daily Downloads
<LineChart
data={daily_downloads}
y=daily_download
x=timestamp_day
/>
```sql total_downloads
SELECT SUM(daily_download_count) AS download_count
FROM daily_stats
Copy code
SELECT
SUM(daily_download_count) AS daily_download,
timestamp_day
FROM
daily_stats
GROUP BY
timestamp_day
ORDER BY
timestamp_day DESC;
The <BigValue> and <LineChart> components automatically render the data returned by the total_downloads and daily_downloads SQL queries.

2. Rill: The Opinionated Metrics Framework
Rill provides a CLI and an opinionated structure for turning SQL queries into fast, interactive dashboards.
A. Setup & Project Structure
Rill uses a single Go binary for its CLI. The demo repo's Makefile handles the installation.
Copy code
# Install the Rill CLI
make rill-install
# Start the local development server
make rill
This command starts the Rill UI and points it to our local DuckDB database. The project structure is organized into:
sources/: YAML files defining your data sources.models/: SQL files that transform your source data.dashboards/: YAML files that define the metrics and dimensions for your dashboard.
B. Connecting to DuckDB & MotherDuck
- For Local DuckDB: Start Rill using the command
rill start rill/rill-project. Then, in the web interface, add theduckdb_stats.dbfile location as a source. - For MotherDuck: Set an environment variable with your token before running
rill start:export MOTHERDUCK_TOKEN=<YOUR_TOKEN>. Rill will automatically detect and use this token.
C. Creating a Visualization
First, we create a "model" to expose our table to Rill. This model is a simple SQL file, models/model.sql:
Copy code
select * from duckdb_stats.main.daily_stats
Next, we define the dashboard in dashboards/model_dashboard.yaml. Rill automatically generates a dashboard based on the measures (aggregations) and dimensions (group-by columns) you specify.
Copy code
# Visit https://docs.rilldata.com/reference/project-files to learn more
title: Pypi Download Stats
model: daily_stats
timeseries: timestamp_day
measures:
- label: Total Downloads
expression: sum(daily_download_count)
name: total_records
description: Total number of records present
format_preset: humanize
dimensions:
- name: python_version
label: Python_version
column: python_version
- name: system_name
label: System_name
column: system_name
Rill uses this definition to create a full-featured dashboard with a time-series line chart, leaderboards for each dimension, and filtering capabilities, all without you needing to configure individual components.

3. Streamlit: The Python Data Workflow
For those who live in the Python ecosystem, Streamlit allows you to build data apps using pure Python scripts.
A. Setup & Project Structure
Getting started is as simple as installing the required Python packages. Our Makefile uses Poetry:
Copy code
# Install Python dependencies
make streamlit-install
# Run the Streamlit app
make streamlit
For a simple app, your entire project can be a single app.py file.
B. Connecting to DuckDB & MotherDuck
Connecting to your data is done with standard Python code inside your script.
Copy code
import streamlit as st
import duckdb
import pandas as pd
# For Local DuckDB:
con = duckdb.connect(database='duckdb_stats.db', read_only=True)
# For MotherDuck:
# con = duckdb.connect(database='md:?motherduck_token=YOUR_TOKEN_HERE')
# Note: For production applications, manage credentials
# using Streamlit's secrets management or environment variables.
C. Creating a Visualization
The workflow is straightforward: query data into a Pandas DataFrame, then pass that DataFrame to one of Streamlit's many charting or display components.
Here's the code from streamlit-demo/app.py to show the total downloads metric and a monthly line chart:
Copy code
import streamlit as st
import duckdb
import pandas as pd
# Connect to your DuckDB database
con = duckdb.connect(database='duckdb_stats.db', read_only=True)
st.title("DuckDB pypi stats | Streamlit")
# Query for data (simplified)
query = "SELECT timestamp_day, daily_download_count FROM duckdb_stats.main.daily_stats"
df = con.execute(query).df()
# Total Downloads Metric
total_downloads = df['daily_download_count'].sum()
st.metric("Total Downloads", f"{total_downloads / 1_000_000:.2f}M")
# Line Graph of Downloads Over Time
st.subheader("Monthly Downloads Over Time")
# Resample data by month for the chart
df['timestamp_day'] = pd.to_datetime(df['timestamp_day'])
df_monthly = df.set_index('timestamp_day').resample('M')['daily_download_count'].sum().reset_index()
st.line_chart(df_monthly, x='timestamp_day', y='daily_download_count')
This approach gives you the full power of Python to manipulate data before visualization.


Conclusion: The Future of Analytics is Code-First
We've explored three powerful tools that each offer a unique, developer-centric approach to BI as code. By embracing these workflows, your analytics dashboards become first-class software assets. You can use Git for version control and collaboration, implement CI/CD for automated testing and deployment, and manage your dashboards with the same rigor as your application code.
BI no longer has to be a tedious series of clicks in an expensive UI. With the local-first speed of DuckDB and the hybrid power of MotherDuck, the BI as Code movement is changing how developers build and share data insights, making analytics a fully integrated part of the modern software development lifecycle.
Frequently Asked Questions
What is BI as Code?
BI as Code manages and defines business intelligence assets (like dashboards, data models, and reports) using code, rather than manual, UI-based tools. This practice allows teams to use software development best practices such as version control (Git), automated testing, and CI/CD for their analytics workflows.
Why use DuckDB and MotherDuck for BI as Code?
DuckDB offers a fast, embedded database ideal for local development, allowing you to build and test analytics dashboards without relying on a cloud warehouse. MotherDuck extends DuckDB by providing a serverless, shareable cloud platform, creating a hybrid workflow from local development to cloud deployment.
What is the main difference between Evidence, Rill, and Streamlit?
The main difference lies in their core workflow. Evidence is best for those who prefer SQL and Markdown to create static data websites. Rill is an opinionated framework that uses SQL and YAML to quickly generate interactive dashboards from defined metrics. Streamlit is a Python-native framework for building interactive data apps, ideal for those who want to work within the Python data science ecosystem.
Related Videos
2026-01-13
The MCP Sessions Vol. 1: Sports Analytics
Watch us dive into NFL playoff odds and PGA Tour stats using using MotherDuck's MCP server with Claude. See how to analyze data, build visualizations, and iterate on insights in real-time using natural language queries and DuckDB.
AI, ML and LLMs
SQL
MotherDuck Features
Tutorial
BI & Visualization
Ecosystem

2025-12-26
Building a Serverless Lakehouse with DuckLake
In this hands-on workshop, Jacob Matson walks through building a serverless lakehouse from scratch using DuckLake.
Webinar
Tutorial

2025-12-10
Watch Me Deploy a DuckLake to Production with MotherDuck!
In this video, Hoyt Emerson will show you the fastest way to get DuckLake into production using MotherDuck's beta implementation. If you've been following his DuckLake series, this is the next step you've been waiting for!
YouTube
Data Pipelines
Tutorial
MotherDuck Features
SQL
Ecosystem

