malloy-reports
Build or modify a Malloy Publisher report: a .malloy file with an ## artifact tag, written at the package root unless asked otherwise, laid out as a grid (a…
Skill by malloydata
Chalk Labs
Need help deploying malloy-reports?
Chalk Labs sets up agents like this inside real teams: connected to your tools, tested on your data, with a human in the loop where it matters.
Install
Works with Claude Code, Claude apps, Codex, Cursor, Gemini CLI, GitHub Copilot, VS Code, OpenCode, Goose, Windsurf. Read the repository before you give any tool access to your data.
npx agentmarket install skill/malloydata-malloy-reports
Asks which of your tools to add it to (Claude Code, Codex, Cursor, VS Code, Windsurf, Gemini CLI, Claude Desktop) and shows the change first. See https://agentmart.dev/cli.
Get the weekly diff
The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.
About
Use cases: documents, data
Reviews
Write a reviewNo reviews yet. Installed it? Reviews come from verified installers only. How reviews work
Permissions
What this skill can do and what it asks for, from its SKILL.md. Checked Oct 25, 2026.
- Tools
- No tools found
- Secrets
- None asked for
- Install scripts
- None found
Network hosts it talks to 1
codeload.github.com· found in install command
Files it touches
~/.claude/skills/malloy-reports
What's inside
- Skill name
malloy-reports- Description
- Build or modify a Malloy Publisher report: a .malloy file with an ## artifact tag, written at the package root unless asked otherwise, laid out as a grid (a dashboard, scanned at a glance) or linear (a notebook or data story, read top to bottom), with filter controls rendered from givens and # drill click-through. Use when the user asks for a dashboard, a notebook, a report, a data story, a filterable view, or drill-through between views, and no code is wanted. Legacy .malloynb files are read, not authored.
- License
- MIT
- Instructions
- 299 lines in SKILL.md · view on GitHub
Files (3)
- SKILL.md
- grid.md · reference/
- linear.md · reference/
SKILL.md (first lines)
<!--
Copyright (c) Credible Data Inc.
SPDX-License-Identifier: MIT
-->
# Publisher Reports
> A `.malloy` file with an `## artifact { layout=… tiles=[…] }` tag **is** a report. It imports the model, names the views to show, binds the filtering on those views, and says how to lay them out. Publisher discovers it at package load, renders the filter controls from the givens its tiles reference, offers `# drill` click-through, and serves it at `/<env>/<pkg>/<path>`, its package path without `.malloy`. No code, no build step.
What other tools call a dashboard and a notebook are both reports here. The **layout** is the only difference:
- **`layout=grid`**: tiles across a grid behind one filter row, scanned at a glance (a dashboard). Read `reference/grid.md` before you lay one out.
- **`layout=linear`**: tiles and prose top to bottom, read in order (a notebook, a data story). Read `reference/linear.md` before you write one.
Everything else (filters, text tiles, drill, the builder, the lint, the write path) is the same for both, and switching layout later is a one-property edit.
## Where the file goes
- **Write a new report at the package root**, as `<slug>.malloy`, unless the author asks for another location. No directory is required, and none is a convention: a report can live anywhere in the package except `public/`, and the root `index.malloy` is never a report. Do not create a `dashboards/` or `notebooks/` directory for it.
- **Always write `layout=grid` or `layout=linear`** in the tag. A tag without one is read by a fallback rule, and a linear report that lists tiles without `layout=linear` is read as a grid. `kind=` is no longer read: a tag that sets it makes no report (`report-kind-removed`), so replace it with `layout=`.
- **The package path is the report's identity.** `sales/q3.malloy` is the report `sales/q3`: its URL, its `GET …/reports/{path}` address, and its `# drill` target. Two reports may share a basename in different directories.
- **Imports are relative to the report's own location.** At the root: `import { order_items } from "storefront.malloy"`. In a subdirectory: `"../storefront.malloy"`.
- **An untagged `.malloy` file is an ordinary model**, wherever it lives. Shared definitions several reports import go in one.
## When this is the right tool
| The user wants | Use |
| ---------------------------------------------------- | ------------------------------------------------------------ |
| A recurring, at-a-glance view behind shared filters | this skill, `layout=grid` |
| A narrative, with prose between the numbers | this skill, `layout=linear` |
| A quick write-up of queries you already ran | `skill:malloy-analysis-report`, which writes a linear report |
| Custom design, branding, or interactions beyond tags | an HTML data app |
| The model itself: sources, measures, joins | `malloy-model` |
Interactivity is not the axis: both layouts get filter controls, URL-addressable state, Apply batching, and `# drill`. Pick on the shape of the document: scanned at a glance is grid, read top to bottom is linear. When in doubt, linear.
## Build sequence
Tool names below are bare (`get_context`); match each against the tools you actually have, since a host may prefix or rename them. A host names its compile and reload tools its own way. Where you have none, use the REST endpoint named beside the step.
1. **READ THE MODEL FIRST.** Get the real source, view, dimension, and given names from the package: `get_context` if you have it, otherwise the REST model endpoint for `index.malloy` (it answers 404 for a file off the surface) or the `.malloy` files. Never guess a name. A guessed field in a query fails the whole package load, not just that one report; a guessed tile or suggest source is quieter, and only shows up in the package warnings. If the package root holds an `index.malloy` (every scaffolded package does), a tile reads only the sources that file exports.
2. **PICK THE LAYOUT AND THE TILES.** Which views, in what order, and on a grid how wide each sits. A query tile is a `view:` you declare in the report, named as `"source -> view"`.
3. **DECLARE THE GIVENS** the report will filter by, in the report file itself, with their control tags (see "Filter controls"). A report holds its own filters: the builder edits the report file and nothing else, so a filter it can add, change or remove is a declaration in that file. A given the model already declares and reads is imported by name, never declared again; if it is only a reader's filter, move it into the report instead (see "Givens that stay in the model").
4. **COMPOSE THE FILE** at its path (the package root unless asked otherwise), following the template below, but do not save it yet. Name the sources you need in the import. Each tile binds its controls with a refinement, `view: t is v + { where: category ~ $CATEGORY }`, one clause per given it answers to; a `filter<...>` binds with `~`, a plain `date` or `number` given is a value and binds with `>=`, `<=` or `=`. Only the givens some tile references become controls. Then import every source or query any referenced given names in a `suggest`; **a `suggest` naming a `query=` needs that query's own source imported as well**, because an import is not transitive.
5. **COMPILE IT** with your compile tool (`POST …/models/<path>/compile` over REST) at the path the file will have, with **`"scope": "file"`**: a report opens with an `import`, which the default `append` scope refuses, and an existing file compiled at `append` collides with itself. **`"scope": "file"` does not check tiles, drills or suggest queries.** To see those before you save, compile the same text at `"scope": "package"` with `modelPath` set to the report's path: it runs the lint step 6 reads and returns its findings with code `dashboard-lint` (or `render-tag`). Editing an untagged model several reports import also wants `"scope": "package"`, which recompiles every file as saved.
6. **SAVE IT, RELOAD, AND READ THE MANIFEST AND THE WARNINGS.** Save by writing the file, or with `PUT …/models/<path>` over REST (omit `expectedHash` to create; it creates at any new path and replaces only a file the package already serves as a report). Then your reload tool, or `GET …/packages/<pkg>?reload=true`. A 424 means the package did not load and your edit is not live. **The `warnings` key is absent when there are none.** Then read `GET …/packages/<pkg>/reports/<path>`: its `givens` are exactly the controls that will render, and its `layout` is the layout Publisher read. See "Read the lint".
7. **OPEN IT AND LOOK.** Not optional; see "What done means".
## The form
```malloy
##! experimental.givens
##" One line saying what this report is for.
##| artifact { layout=grid title="Storefront overview"
tiles=[
intro { kind=text colspan=12 },
"overview -> kpis",
"overview -> revenue_trend",
"overview -> revenue_by_state"
]
} dashboard { columns=12 }
|##
import { order_items, products } from "storefront.malloy"
# label="Category" control=select suggest { source=products dimension=category }
given: CATEGORY :: filter<string> is f''
# label="Ordered since"
given: SINCE :: date is @2023-01-01
##|(markdown) intro
Revenue and margin for the selected category.
|##
source: overview is order_items extend {
# colspan=12
# label="Key figures"
# big_value
view: kpis is key_figures + { where: category ~ $CATEGORY, where: created_at >= $SINCE }
# colspan=8
# break
# label="Revenue by month"
view: revenue_trend is sales_by_month + { where: category ~ $CATEGORY, where: created_at >= $SINCE }
# colspan=4
# label="Revenue by state"
view: revenue_by_state is sales_by_state + { where: category ~ $CATEGORY, where: created_at >= $SINCE }
}
```
The same file with `layout=linear` and no `dashboard { … }` or `colspan` is a linear report.
- **The tag is model-level**, one line (`## artifact { … }`) or a `##|` … `|##` block with the same text inside, the form to use once the list is long: one tile per line, `}` on its own line, `|##` closing at the opener's column. A one-line `##` tag must stay on one line: wrapping it is a compile error, or worse, compiles clean and quietly stops being a report.
- **The header** above the tag holds only `##!` flags, `//` comments and the unnamed `"` notes that are the report's description. Keep statements and `#` tags below `## artifact`: nothing refuses one above it, but the header is where readers and the builder look.
- **Quote every query entry.** `"overview -> kpis"` is a string; unquoted, the tag does not parse (`notebook-artifact-unparsed`). A bare quoted name (`"top_products"`) names a model-level `query:` in the file.
- **A query tile is a thin `view:` on an extension the report declares** (`source: overview is order_items extend { … }`): the modelled view keeps its chart tag, the wrapper carries the tile's `# label` and its binding. On a grid its width, row break and height can go on its entry instead, `"overview -> kpis" { colspan=12 rowspan=2 }`, which wins over the view's tags; `rowspan=N` is rows of 80px, and `row=N col=N` place the tile at a grid cell (a report with any `row` is placed: fixed rows, tiles where they say, empty cells allowed).
- **A text tile is a `##|(markdown) name` block** listed as `name { kind=text }`. The name is one bare word on the opener line, the body starts on the next line (a heading goes inside the block), and `|##` closes it at the opener's column. A bare `intro` without `{ kind=text }` is the query-tile form for a `query:`, so the block is not shown. On a grid the entry also takes `colspan`, `break` and `rowspan`, and nothing else.
- **File order:** header, imports and givens, the prose blocks in tile order, then the extension that defines the tile views. That is the order the builder writes.
- **A `run:` in a report that lists `tiles` is never shown** (`notebook-layout-run`). Define a view, list it, delete the `run:`.
- **A tile expression is a string, so the compiler never checks it.** Rename a view and the file still compiles; the tile fails at package load, where the lint names it.
- **Chart tags go on the view** (`# line_chart`, `# bar_chart`, `# big_value`, `# label="…"`). `skill:malloy-charts` covers choosing them.
## Filter controls
Controls come from the `given:` declarations the tiles reference. The tags on the declaration are the control contract:
```malloy
##! experimental.givens
# label="Category" control=select suggest { source=products dimension=category }
given: CATEGORY :: filter<string> is f''
# label="Brand" control=multiselect suggest { query=brand_suggest dimension=brand }
given: BRAND :: filter<string> is f''
# label="Minimum line total" range_min=0 range_max=250
given: MIN_SALE :: filter<number> is f''
# label="Ordered since"From malloydata/publisher, MIT.
More from malloydata/publisher
- 0.784score
eval-import
malloydata
Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email…
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
malloy-powerbi-review
malloydata
Move a Power BI semantic model (TMDL, PBIP, or .pbix) onto Malloy - read their model, transpile its DAX, and prove the numbers match.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-improve
malloydata
Make the smallest safe edit that closes a diagnosed gap the model repo owns: a Malloy model edit, or a package skill under skills/.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
fix-scan-finding
malloydata
Fix a CRITICAL Trivy finding that is failing CI in this repo (a vulnerability, misconfiguration, or secret from security-scan.yml or image-scan.yml), or add…
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-diagnose
malloydata
Diagnose why a scored answer failed and who owns the fix, then cluster the failures by shared root cause.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-answer
malloydata
Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer.
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
malloy-document
malloydata
Add documentation with #(doc) tags to Malloy models so fields and sources are described in plain language.
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
eval-judge
malloydata
Decide whether ONE answer matches its golden, and say whether you believe the golden.
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
malloy-getting-started
malloydata
First steps for using a Malloy Publisher deployment through its MCP tools.
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
malloy-review
malloydata
Malloy semantic-model code review.
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
eval-report
malloydata
Write the summary a person reads after an evaluation run: what ran, what it scored, which failures were the MODEL and which were the EVAL itself (out of…
SkillAFreshness: A★ 118Claude Code +9 - 0.779score
malloy-analysis-report
malloydata
Combine validated Malloy queries into a report (a linear report, read top to bottom).
SkillAFreshness: A★ 118Claude Code +9
Signals
- Stars
- 118
- Forks
- 31
- Open issues
- 106
- Last commit (this folder)
- Oct 18, 2026 (13 days ago)
- Last release
- Oct 18, 2026 (13 days ago)
From GitHub (fetched Oct 24, 2026).
Details
- License
- MIT
- Repository
- github.com/malloydata/publisher
- First seen
- Oct 24, 2026
- Last checked
- Oct 24, 2026
- Source
- GitHubgithub-graphql
More from malloydata
- 0.784score
eval-import
malloydata
Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email…
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-answer
malloydata
Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-diagnose
malloydata
Diagnose why a scored answer failed and who owns the fix, then cluster the failures by shared root cause.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
fix-scan-finding
malloydata
Fix a CRITICAL Trivy finding that is failing CI in this repo (a vulnerability, misconfiguration, or secret from security-scan.yml or image-scan.yml), or add…
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
eval-improve
malloydata
Make the smallest safe edit that closes a diagnosed gap the model repo owns: a Malloy model edit, or a package skill under skills/.
SkillAFreshness: A★ 118Claude Code +9 - 0.784score
malloy-powerbi-review
malloydata
Move a Power BI semantic model (TMDL, PBIP, or .pbix) onto Malloy - read their model, transpile its DAX, and prove the numbers match.
SkillAFreshness: A★ 118Claude Code +9
Done for you
Rather not set it up yourself?
Chalk Labs scopes, builds and runs agents for your team: the task audit, the eval harness, the integrations and the weekly ops report. Fixed scope, priced before we start.
See how we build AI agents →Read the case studies
Chalk Labs is a two-founder AI and growth studio. We build AI agents, CRMs and MVPs for Web3 and AI companies, and have shipped 50+ product and token launches.
- launches shipped
- 50+
- launches shipped
- media placements
- 500+
- media placements
- founders on every account
- 2
- founders on every account
Press we've placed: Forbes · CoinDesk · Decrypt · Cointelegraph · The Block
Rahil Jain, Co-founder, Chalk Labs. Ex-Goldman Sachs engineer. You talk to the person who builds it.
Get malloy-reports running in your business
Tell us what you want to automate. A founder replies within one working day with a plan, not a pitch deck.
Cite this page
malloy-reports skill. Agent Mart. https://agentmart.dev/skills/malloydata-malloy-reports (last updated 2026-10-24)