Support
Log In

Dashboards and Notebooks

Build dashboards and notebooks as Malloy documents, and add filter controls with givens

A dashboard or notebook in Credible is one .malloy document. It names the views to show, lays them out, and declares the filters a viewer can change. Every viewer runs it as themselves, so a source with access rules shows each person only their own rows.

The two differ only in shape:

  • A dashboard is read at a glance: tiles on a grid.
  • A notebook is read top to bottom: text between the results.

Both get the same filter controls, shareable URLs, and click-through.

Ask the agent

You rarely write one by hand. Ask in a chat, for example "build me a dashboard of revenue by brand and by month, with a filter on product category".

  • In a draft package (a modeling chat), the agent writes the document as a file in the package, dashboards/<name>.malloy or notebooks/<name>.malloy. It ships with the package when you publish.
  • In an analysis chat, the agent saves the document to your workspace. It reads a published model and doesn't change the package.

To change one, ask again in a chat: "add a filter on brand", "add a tile with order count by month".

What the document looks like

A dashboard is an ## artifact tag listing its tiles, followed by the views those tiles name:

##! experimental.givens
## artifact { title="Storefront overview" tiles=["overview -> revenue_trend", "overview -> revenue_by_brand"] } 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''

source: overview is order_items extend {
  # colspan=8
  # label="Revenue by month"
  view: revenue_trend is sales_by_month + { where: category ~ $CATEGORY }

  # colspan=4
  # label="Revenue by brand"
  view: revenue_by_brand is sales_by_brand + { where: category ~ $CATEGORY }
}
  • tiles=[…] names each tile as source -> view, in order.
  • dashboard { columns=12 } sets the grid width. # colspan=N on a view sets how wide its tile is, and # break starts a new row.
  • A view's tags go on the lines above view:, never inside its braces.

A notebook uses kind=notebook and adds text entries between the query tiles.

Filter controls are givens

A filter control is a given: a named parameter declared with given: and read with $NAME.

  1. Declare the given with its control tags. control=select renders a dropdown, and suggest { … } fills it with a dimension's values. The default f'' matches every row.
  2. Bind it on each tile that should respond to it, with a refinement: + { where: category ~ $CATEGORY }. A filter<…> given binds with ~, and a plain date or number given binds with >=, <= or =. A date or number given always filters from its default value (for example SINCE :: date is @2023-01-01), because unlike f'' it has no default that matches every row.

Only givens that some tile reads become controls. A given the model already declares is imported by name rather than declared again.

Use givens, not the legacy #(filter) annotation, for new filters. Three cases keep #(filter) for now, because migrating them fails without an error: #(filter, required) (it partitions the value index), implicit filters (row security, which moves to #(access_filter)), and a date or number range that must default to no filter. See Fine-grained access control.

Dashboards saved from an analysis chat

A document saved to your workspace is restricted: it may not declare a given:, import a file, or set ##! flags. Its filters come from givens the model already declares. When the model declares none, the dashboard uses a fixed filter, and a modeler can add a given to the model to turn it into a control.

On this page