Malloy Notebooks (.malloynb files) combine queries, results, and documentation in a single interactive document. They're the best way to explore data, build analyses, and share findings.
What Is a Malloy Notebook?
A notebook is a file with alternating cells of:
Markdown - Text, headings, explanations
Malloy code - Model definitions, queries
When you run a notebook, each code cell executes and displays results inline. The document becomes a living report.

This entire documentation website is built from .malloynb files—interweaving markdown with actual running Malloy code blocks.
Creating a Notebook
1. Create the File
In VS Code, create a new file with the .malloynb extension:
File → New File → sales_analysis.malloynb2. Add Cells
Click + Code or + Markdown to add new cells to your notebook.

Under the hood, .malloynb files are plain text with cell markers (>>>markdown or >>>malloy), so they're easy to diff and version control.
3. Run the Notebook
Click Run All at the top of the VS Code editor, or run individual cells by clicking the play button next to each one.
Use Cases
Static Dashboards
Notebooks render code blocks with descriptions. They can render any Malloy visualization—dashboards, charts, tables. Readers can inspect the code used to generate them.
Data Stories
From EDA and high-level statistics to detailed drill-down, notebooks show the flow of analysis. Use them to document your analytical journey and share insights with others.
When published via Publisher, notebooks become read-only—great for dashboards and reports.
Example Notebooks
See these notebooks for inspiration:
Names Analysis - Exploring baby name trends
Carrier Analysis - Flight carrier performance
Auto Recalls - Example of starting with an import
Tip: Press . on any GitHub page to open it in github.dev (VS Code in the browser).
Importing Models
A typical notebook has two cells: an import cell and a query cell.
Cell 1 — Import your model:
Cell 2 — Query using sources from that model:
run: flights -> { aggregate: flight_count nest: by_carrier is { group_by: carrier aggregate: flight_count limit: 5 } }
[ { "flight_count": 344827, "by_carrier": [ { "carrier": "WN", "flight_count": 88751 }, { "carrier": "US", "flight_count": 37683 }, { "carrier": "AA", "flight_count": 34577 }, { "carrier": "NW", "flight_count": 33580 }, { "carrier": "UA", "flight_count": 32757 } ] } ]
WITH __stage0 AS ( SELECT group_set, CASE WHEN group_set=0 THEN COUNT(1) END as "flight_count__0", CASE WHEN group_set=1 THEN base."carrier" END as "carrier__1", CASE WHEN group_set=1 THEN COUNT(1) END as "flight_count__1" FROM '../../documentation/data/flights.parquet' as base CROSS JOIN (SELECT UNNEST(GENERATE_SERIES(0,1,1)) as group_set ) as group_set GROUP BY 1,3 ) SELECT MAX(CASE WHEN group_set=0 THEN "flight_count__0" END) as "flight_count", COALESCE(LIST({ "carrier": "carrier__1", "flight_count": "flight_count__1"} ORDER BY "flight_count__1" desc NULLS LAST) FILTER (WHERE group_set=1)[1:5],[]) as "by_carrier" FROM __stage0
This keeps your semantic model separate from analysis notebooks.
Publishing Notebooks
Notebooks can be deployed via Publisher alongside your semantic models. Published notebooks:
Render as browsable HTML reports
Include live query results
Can be shared via URL
To publish, include .malloynb files in your package alongside .malloy models:
my-analytics/ ├── publisher.json ├── models/ │ └── orders.malloy └── notebooks/ └── monthly_report.malloynb
Interactive Filters
Notebooks can include interactive dropdown filters that allow viewers to filter query results dynamically. When a user selects filter values, all queries in the notebook automatically re-execute with those filters applied.
Note: Interactive filters are rendered when viewing notebooks via Publisher or the Publisher SDK. They are not currently displayed in VS Code—that capability is coming soon.
How It Works
Annotate dimensions in your Malloy source files to mark them as filterable
Add a
##(filters)annotation in your notebook to specify which dimensions appear as filter controlsWhen published, the notebook displays filter dropdowns that users can interact with
Step 1: Annotate Filterable Dimensions
In your Malloy source file, add #(filter) annotations to dimensions you want to expose as filters:
source: flights is duckdb.table('data/flights.parquet') extend { dimension: // Multi-select dropdown for string values #(filter) {"type": "Star"} origin_code is origin // Date range picker #(filter) {"type": "DateMinMax"} flight_departure is dep_time join_one: carriers with carrier } source: carriers is duckdb.table('data/carriers.parquet') extend { dimension: #(filter) {"type": "Star"} nickname is nickname_old }
Filter Types
| Type | UI Component | Use Case |
|---|---|---|
Star |
Multi-select dropdown | String fields with discrete values |
MinMax |
Numeric input | Numeric fields |
DateMinMax |
Date range picker | Date/timestamp fields |
Boolean |
Toggle selector | Boolean fields |
Step 2: Add Notebook Filter Annotation
In your notebook, add a ##(filters) annotation in the code cell that imports your model. The annotation lists which dimensions should appear as filters using source.dimension format:
##(filters) ["flights.origin_code", "carriers.nickname", "flights.flight_departure"] import {flights, carriers} from 'flights.malloy'
The filter type for each dimension is determined by the #(filter) annotation on that dimension in the source file.
Custom Labels
By default, filters display the dimension field name. You can customize the label using the # label="..." annotation in your source file:
source: recalls is duckdb.table('data/recalls.csv') extend { dimension: #(filter) {"type": "Star"} # label="Vehicle Manufacturer" Manufacturer is Manufacturer_old }
Match Types
Each filter type supports different match types that users can select:
| Filter Type | Available Match Types |
|---|---|
| Star | Equals, Contains |
| MinMax | Equals, Less Than, Greater Than, Between |
| DateMinMax | Equals, Before, After, Between |
| Boolean | Equals (True/False) |
Example
See the Carrier Analysis notebook and Auto Recalls notebook for working examples of interactive filters.
Tips
Start simple - Begin with a basic source, run a query, then add complexity.
Use markdown liberally - Document your analysis as you go. Future you will thank present you.
Keep models separate - For reusable logic, put it in a .malloy file and import it into notebooks.
Run cells individually - When debugging, run one cell at a time to isolate issues.
Next Steps
Building Semantic Models - Deep dive into Malloy modeling
Publishing Models - Deploy your work
Malloy in Jupyter - Use Malloy in Jupyter notebooks