How to Build a Dashboard with Observable Framework
Last updated Apr 25, 2026

Observable Framework is a free, open-source static site generator for building data dashboards. You process your data in Python, SQL, or R at build time, and the framework compiles the results into a fast static site that loads instantly with no server required. This guide walks through creating a project, writing a data loader, building charts with Observable Plot, and deploying to GitHub Pages with automatic daily refreshes.
What Observable Framework Is
Observable Framework is a static site generator built specifically for data work. The team behind D3.js and the Observable notebook platform released it as an open-source project in 2023, and it has gained steady adoption since then among data engineers and analysts who want shareable dashboards without a BI subscription.
The core idea: your data processing stays in whatever language you already know (Python, SQL, R, or Node.js), and Observable handles the front end. You write a data loader script, Framework runs it at build time, and the result is a static site that anyone can open in a browser.
This differs from traditional BI tools in one important way: there is no server running queries when a user loads the dashboard. Everything is precomputed. A dashboard built with Observable Framework loads as fast as a regular website, with no Tableau Server license, no Metabase instance to maintain, and no backend costs.
The tradeoff is data freshness. Dashboards reflect the state of your data at the last build time. For daily or weekly reporting, the static-first model is highly practical. For sub-minute data, a different architecture is needed.
Prerequisites
You need Node.js version 18 or later installed on your machine. Check your current version:
node --version
If Node.js is not installed, download it from nodejs.org or use a version manager like nvm.
If you plan to write data loaders in Python, you also need Python 3.9 or later, plus any database drivers your analysis requires (for example, psycopg2 for PostgreSQL or snowflake-connector-python for Snowflake).
Step 1: Create a New Project
Observable Framework includes a project scaffolding tool. Run this in your terminal:
npm create @observablehq
The tool asks three questions: your project name, whether you want TypeScript support, and which starter template to use. For a first project, choose the default template and skip TypeScript.
When scaffolding completes:
cd your-project-name
npm install
npm run dev
Your browser opens at http://localhost:3000 with a live preview. The development server reloads automatically as you save files.
Step 2: Understand the File Structure
Two directories matter for authoring.
src/ holds your Markdown pages, JavaScript modules, and data loaders. Each .md file in src/ becomes a page. The file at src/index.md is your home page.
dist/ holds the compiled static site, generated by npm run build. This is what you deploy.
Open src/index.md in a text editor. You will see Markdown mixed with code blocks labeled js. Those blocks execute as JavaScript in the browser. That combination is the core authoring model: write prose, embed charts.
Step 3: Write a Data Loader
Data loaders are scripts that run at build time and output data your pages reference. They can be written in Python, SQL, R, or JavaScript. Observable Framework detects the file extension and runs the script with the correct interpreter.
Create a file at src/data/sales.csv.py:
import csv
import sys
rows = [
{"month": "2026-01", "revenue": 42000, "segment": "enterprise"},
{"month": "2026-02", "revenue": 51000, "segment": "enterprise"},
{"month": "2026-03", "revenue": 48000, "segment": "smb"},
{"month": "2026-04", "revenue": 63000, "segment": "enterprise"},
]
writer = csv.DictWriter(sys.stdout, fieldnames=["month", "revenue", "segment"])
writer.writeheader()
writer.writerows(rows)
The filename convention sales.csv.py tells Framework: run this Python script and treat the stdout as a CSV. When your page references data/sales.csv, it receives the precomputed result.
For a production deployment, replace the hardcoded rows with a real database query:
import psycopg2, csv, sys, os
conn = psycopg2.connect(os.environ["DATABASE_URL"])
cur = conn.cursor()
cur.execute(
"SELECT date_trunc('month', created_at)::date, SUM(amount), tier "
"FROM orders GROUP BY 1, 3 ORDER BY 1"
)
writer = csv.writer(sys.stdout)
writer.writerow(["month", "revenue", "segment"])
for row in cur.fetchall():
writer.writerow(row)
Your database credentials are read from environment variables, run at build time on your machine or CI server, and never included in the deployed static files.
Step 4: Build a Chart with Observable Plot
Observable Plot is the charting library included by default in every Framework project. It covers the most common chart types with a concise, composable API.
In src/index.md, add:
const sales = await FileAttachment("data/sales.csv").csv({typed: true});
Plot.plot({
marks: [
Plot.barY(sales, {x: "month", y: "revenue", fill: "segment"}),
Plot.ruleY([0])
],
color: {legend: true}
})
FileAttachment loads the CSV your loader produced. Plot.barY draws a grouped bar chart with color encoding for segment. Plot.ruleY([0]) adds a zero baseline so bars do not mislead. The color: {legend: true} option renders a legend automatically.
Save the file. The development server updates the preview in under a second.
Step 5: Add a Grid Layout
Observable Framework ships with built-in CSS grid classes for arranging dashboard panels. Update your src/index.md:
<div class="grid grid-cols-2">
<div class="card">
<h2>Monthly Revenue</h2>
${Plot.plot({
marks: [Plot.barY(sales, {x: "month", y: "revenue", fill: "segment"})]
})}
</div>
<div class="card">
<h2>Total Revenue</h2>
<span class="big-number">${d3.format("$,")(d3.sum(sales, d => d.revenue))}</span>
</div>
</div>
grid-cols-2 creates a two-column layout that collapses to a single column on narrow screens. The card class applies a bordered, padded panel. Observable Framework's default styles produce a clean, professional dashboard without any custom CSS.
Step 6: Deploy with Automatic Data Refresh
Run the build to generate the static site:
npm run build
This outputs everything to the dist/ directory. Deploy that folder to GitHub Pages, Netlify, Vercel, or any static hosting provider.
For dashboards that need to stay current, add a GitHub Actions workflow that rebuilds on a cron schedule. Create .github/workflows/deploy.yml:
name: Build and Deploy
on:
push:
branches: [main]
schedule:
- cron: "0 6 * * *"
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install psycopg2-binary
- run: npm install
- run: npm run build
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
- uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: dist
The schedule cron triggers a nightly rebuild at 6 AM UTC. Your data loaders re-execute against your live database, and GitHub Pages serves the updated files. A daily reporting dashboard with no server costs and no ongoing maintenance.
When Observable Framework Is the Right Choice
Observable Framework fits well when your team needs dashboards that non-technical stakeholders can open in a browser without a BI tool account, when your data processing is already written in Python or SQL, and when you want version-controlled dashboards that live in your git repository alongside the rest of your codebase.
It is a poor fit for dashboards that require sub-hourly data freshness, or for teams with no command-line experience, since the data loader pattern and CI workflow require some comfort with a terminal.
According to Observable's published benchmarks, dashboards built with Framework typically score above 95 in Google Lighthouse performance, because the static-first model removes the round trips that slow down server-rendered analytics tools.
For teams that want to skip setup entirely and go directly from a file upload to a shareable analysis, VSLZ handles data ingestion and visualization from a single upload with no configuration needed.
Summary
Observable Framework gives analysts a practical path from raw data to a deployed, shareable dashboard: write data processing in Python or SQL, build charts with Observable Plot, deploy as a static site, and automate daily rebuilds with GitHub Actions. The full workflow, from project creation to a live dashboard that refreshes every morning, takes under an hour to set up for the first time.
FAQ
What is Observable Framework used for?
Observable Framework is used for building and deploying static data dashboards and reports. It lets you write data processing code in Python, SQL, R, or JavaScript and combines it with front-end visualizations built in Observable Plot or D3. The output is a fast static website that can be hosted on GitHub Pages, Vercel, or Netlify.
Is Observable Framework free to use?
Yes. Observable Framework is open source and released under the ISC license. You can use it for commercial projects at no cost. Observable also offers a paid hosted platform (observablehq.com) with collaboration features, but the framework itself can be self-hosted for free.
What languages can I use in Observable Framework data loaders?
Observable Framework data loaders support any language installed on your machine or CI server. Commonly used languages include Python, R, SQL (via DuckDB or other CLI tools), JavaScript (Node.js), and TypeScript. The loader is identified by file extension: a file named sales.csv.py runs as Python, sales.csv.sql runs as SQL, and so on.
How do I update data in an Observable Framework dashboard?
Data updates by rebuilding the site. You run npm run build locally, or automate it with a scheduled GitHub Actions workflow. A common pattern is to trigger a rebuild every morning using a cron schedule (0 6 * * *), which runs your data loaders, fetches fresh data, and redeploys the static site. This gives you a daily-refreshed dashboard with no server infrastructure.
Do I need to know JavaScript to use Observable Framework?
Basic JavaScript knowledge helps, but you do not need to be a JavaScript developer. Observable Plot, the charting library bundled with Framework, is concise and well-documented with many copy-paste examples. Data processing can be done entirely in Python or SQL using data loaders, so the JavaScript you write is limited to loading data and calling Plot functions to render charts.


