Skip to content

Annotations

An annotation is your statement about the data, drawn on a chart: a target line, a highlighted period, a labeled peak, a fitted trend. It is anchored in data coordinates, so it stays attached to the data when the query changes.

bar, line, area, and scatter charts accept an annotations: list. Each entry has an explicit type (rule, band, note, or trend), an anchor on the x or y axis, optional text, and an optional style: that works like a layer's.


Minimum Required for an Annotation

An annotation needs a type and what that type draws against.

dbt Charts field Allowed values Notes
type rule, band, note, trend Selects the annotation.
x and/or y Anchor Required for rule and band. A note takes both or neither.
fit Fit function Required for trend, in place of an anchor.

Minimum Example

queries:
  monthly_revenue:
    columns: [month, revenue]
    values:
      - ["2026-01-01", 18.4]
      - ["2026-02-01", 20.1]
      - ["2026-03-01", 19.2]
      - ["2026-04-01", 23.6]
      - ["2026-05-01", 24.8]
      - ["2026-06-01", 27.3]

charts:
  revenue_vs_goal:
    query: monthly_revenue
    type: line
    x: month
    y: revenue
    annotations:
      - type: rule
        y: 25
        text: FY goal
rows:
  - revenue_vs_goal
FY goalJan2026FebMarAprMayJun2025 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Common Fields

Every annotation type accepts these.

dbt Charts field Allowed values Notes
type rule, band, note, trend Unknown types are an error.
text One line of text Optional. A template: {{ value \| format }} prints the anchor's value in the axis format, {{ series }} the series name.
plane background, foreground See Plane.
series each, total, or a list of series names See Series.
y_column A plotted y column See Which y Column.
style marks: patch See Styling.

List order is draw order within a plane. annotations: is not accepted on pie, donut, map, table, or KPI charts.


Rule

A rule is a line across the plot at one value, on either axis. A range on the other axis limits it to a segment.

dbt Charts field Allowed values Notes
x Anchor Vertical rule.
y Anchor Horizontal rule.
style.marks.rule opacity, stroke The line.
style.marks.text Text style The label.

Rule Examples

A fixed target, a dated event, a segment, and a computed average.

queries:
  monthly_revenue:
    columns: [month, revenue]
    values:
      - ["2026-01-01", 18.4]
      - ["2026-02-01", 20.1]
      - ["2026-03-01", 19.2]
      - ["2026-04-01", 23.6]
      - ["2026-05-01", 24.8]
      - ["2026-06-01", 27.3]
      - ["2026-07-01", 26.1]
      - ["2026-08-01", 28.9]

charts:
  revenue_rules:
    query: monthly_revenue
    type: line
    x: month
    y: revenue
    annotations:
      - type: rule
        y: 25
        text: FY goal
      - type: rule
        x: 2026-04-01
        text: Pricing change
      - type: rule
        y: 22
        x:
          from: 2026-05-01
          to: max()
        text: Q2 floor
      - type: rule
        y: avg()
        text: "Avg {{ value | format }}"
rows:
  - revenue_rules
FY goalPricing changeQ2 floorAvg 24Jan2026FebMarAprMayJunJulAug2025 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Band

A band is a shaded region over a range, or over one category on a discrete axis. Ranges on both axes make a box.

dbt Charts field Allowed values Notes
x Anchor Range, or a category on a discrete axis.
y Anchor Range.
style.marks.rect opacity, fill, stroke The shaded region.
style.marks.text Text style The label.

A missing from or to extends to the axis edge. Ranges are half-open, [from, to): from is included and to is excluded. On a date axis drawn as buckets (bars), a range covers the buckets that start in [from, to).

Band Examples

A period on a time axis, and an open-ended threshold on the value axis.

queries:
  monthly_orders:
    columns: [month, orders]
    values:
      - ["2026-01-01", 120]
      - ["2026-02-01", 132]
      - ["2026-03-01", 128]
      - ["2026-04-01", 150]
      - ["2026-05-01", 171]
      - ["2026-06-01", 165]
      - ["2026-07-01", 182]
      - ["2026-08-01", 190]

charts:
  orders_bands:
    query: monthly_orders
    type: line
    x: month
    y: orders
    annotations:
      - type: band
        x:
          from: 2026-04-01
          to: 2026-07-01
        text: Summer promo
      - type: band
        y:
          from: 180
        text: Capacity
        style:
          marks:
            text:
              position:
                edge: top
                align: start
rows:
  - orders_bands
Summer promoCapacityJan2026FebMarAprMayJunJulAug120140160180 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Category Band

On a discrete axis, a single value fills that category's slot.

queries:
  regions:
    columns: [region, bookings]
    values:
      - [East, 14.2]
      - [West, 17.8]
      - [North, 9.6]
      - [South, 12.4]

charts:
  bookings_by_region:
    query: regions
    type: bar
    x: region
    y: bookings
    style:
      orientation: vertical
    annotations:
      - type: band
        x: East
        text: Pilot
rows:
  - bookings_by_region
PilotEastWestNorthSouth051015 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Note

A note is text at a point, or pinned to the plot frame when it has no anchor. A connector joins the text to its anchor; an enclosure circles or boxes the anchor.

dbt Charts field Allowed values Notes
x, y A single value, min(), or max() Both, or neither. A range is an error. With min() or max() on one axis and the other omitted, the note sits at the row holding that extreme.
connector line, arrow Draws from the anchor to text moved with dx/dy. Needs an anchor.
enclose circle, rect Shape around the anchor. Needs an anchor.
style.marks.text Text style plus max_width max_width wraps a note with no anchor: pixels, or auto. A note at a point does not wrap.
style.marks.line opacity, curve, length, stroke The connector. length is its default distance from the anchor.
style.marks.circle opacity, radius, stroke The circle enclosure.
style.marks.rect opacity, fill, padding, stroke The rect enclosure.

A note boxed with enclose: rect is centered on its anchor, so style.marks.text.position does not apply to it unless there is a connector.

Note Examples

A callout with a connector, a circled point derived from the data, and a frame note.

queries:
  monthly_revenue:
    columns: [month, revenue]
    values:
      - ["2026-01-01", 18.4]
      - ["2026-02-01", 20.1]
      - ["2026-03-01", 19.2]
      - ["2026-04-01", 23.6]
      - ["2026-05-01", 24.8]
      - ["2026-06-01", 27.3]
      - ["2026-07-01", 26.1]
      - ["2026-08-01", 28.9]

charts:
  revenue_notes:
    query: monthly_revenue
    type: line
    x: month
    y: revenue
    annotations:
      - type: note
        x: 2026-03-01
        y: 19.2
        text: Slow month
        connector: arrow
        style:
          marks:
            text:
              dx: 50
              dy: 60
      - type: note
        y: max()
        text: Record high
        enclose: circle
      - type: note
        text: Revenue in $M
        style:
          marks:
            text:
              position:
                edge: top
                align: start
                overlay: true
rows:
  - revenue_notes
Slow monthRecord highRevenue in $MJan2026FebMarAprMayJunJulAug2025 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Trend

A trend is a parametric function fitted to the plotted rows and drawn across the x range, with an optional interval. A rolling average, loess, or cumulative series is a new series: derive it in the query and plot it as a layer.

dbt Charts field Allowed values Notes
fit linear(), polynomial(<order>), exponential(), log(), power() Required. polynomial takes an order of 2 or more. A categorical x cannot be fitted.
interval confidence(<pct>), prediction(<pct>) Band around the fit. pct is between 0 and 100, exclusive.
x Range Limits or extends the drawn span; defaults to the data extent.
style.marks.line opacity, curve, stroke The fitted curve.
style.marks.area opacity, fill The interval.
style.marks.text Text style plus max_width The label, wrapped to max_width.

On date bars a trend spans whole bars: from the outer edge of the first to the outer edge of the last.

A fit answers "what is the pattern", so it belongs on a line or scatter mark. The mark that draws the trend's column decides:

Mark Result
Line, scatter Fits.
Bar, or the series: total of a stacked bar Fits, with WARN-TREND-ON-BARS: a bar reads as length from zero, so a line chart is the better home. To keep the bars, thin them with style.marks.bar.band_width so more of the trend shows.
Area, stacked or not Error. Use layers: to draw the column as a line or scatter, then point y_column at it.

The chart's type: does not decide: an area chart with a scatter layer takes a trend on the scatter's column.

Trend Examples

A linear fit with a 95% confidence band, and a curved fit.

queries:
  accounts:
    columns: [seats, arr]
    values:
      - [10, 6.2]
      - [30, 9.8]
      - [50, 14.9]
      - [70, 22.4]
      - [90, 30.1]
      - [110, 43.5]
      - [130, 58.0]
      - [150, 75.2]
      - [170, 96.3]
      - [190, 121.7]

charts:
  arr_by_seats:
    query: accounts
    type: scatter
    x: seats
    y: arr
    annotations:
      - type: trend
        fit: linear()
        interval: confidence(95)
        text: Linear fit
      - type: trend
        fit: polynomial(2)
        text: Quadratic fit
rows:
  - arr_by_seats
Linear fitQuadratic fit050100150200Seats050100ARR Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Anchors

An anchor is where an annotation sits on an axis. Literals are read in the axis column's type: a number, a date, or a category.

Form Example Meaning
Literal y: 25 A value in the axis column's type.
Range x: with from: and to: Either bound may be omitted: it extends to the axis edge. A rule or trend range limits the segment it draws.
Category x: East On a discrete axis: a band fills the category's slot; a rule or note sits at its center. A category the column does not hold is an error that lists the categories.
Aggregate y: avg() Computed over the plotted rows.
Clock x: now(), x: current(month) Resolved at render time.

Range bounds take any literal, aggregate, or clock form. Anchor values and range bounds widen the axis domain as data does; pin axis_y.scale.domain to clip.

Functions

A closed set, parsed when the board compiles. No nesting, no arithmetic, no column arguments.

Where Functions
Anchors and range bounds min(), max(), avg(), median(), percentile(<0-100>)
Clock anchors and bounds now(), current(day \| week \| month \| quarter \| year)
fit linear(), polynomial(<order>), exponential(), log(), power()
interval confidence(<pct>), prediction(<pct>)
  • Aggregates run over the plotted rows of the axis column: avg() on monthly revenue is the average month, not the average transaction.
  • Aggregates are an error on a categorical axis. On a time axis only min() and max() are valid.
  • now() and current() need a date or timestamp x. current(<unit>) is a period: use it on a band or as a range bound; it expands to that period's [from, to).

Series

On a chart with a color channel, a computed annotation (an aggregate or a trend) must say which series it is computed for.

series Meaning Color
each One annotation per series. The series color.
List of names Like each, for only the named series. An unknown name is an error. The series color.
total Once, over all rows pooled; on a stacked chart, over the per-x sum. Theme colors.

series is an error on an annotation whose anchors are all literal. On a stacked chart only total is valid. On a single-series chart it may be omitted. With small multiples, computed annotations are computed per panel.

queries:
  by_segment:
    columns: [month, segment, revenue]
    values:
      - ["2026-01-01", SMB, 6.1]
      - ["2026-02-01", SMB, 5.4]
      - ["2026-03-01", SMB, 6.6]
      - ["2026-04-01", SMB, 7.2]
      - ["2026-01-01", Enterprise, 10.2]
      - ["2026-02-01", Enterprise, 11.0]
      - ["2026-03-01", Enterprise, 12.4]
      - ["2026-04-01", Enterprise, 13.1]

charts:
  segment_averages:
    query: by_segment
    type: line
    x: month
    y: revenue
    color: segment
    annotations:
      - type: rule
        y: avg()
        series: each
        text: "{{ series }} avg"
        style:
          marks:
            text:
              position:
                edge: bottom
                align: end
rows:
  - segment_averages
Enterprise avgSMB avgJan2026FebMarApr46810121413 Enterprise7 SMB Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Which y Column

y_column names the y column an annotation refers to. It picks the y axis that plots the column, and the rows a computed anchor or trend reads.

It is optional with a literal y anchor (the base axis when omitted) and with a computed anchor on a chart that has one y column and no layers:. It is required for a computed y anchor, a trend, or a note derived with min()/max() on a chart with several y columns or with layers:. It is an error on an annotation with no y anchor, and when the column is plotted twice from different queries or axes.

queries:
  subscriptions:
    columns: [month, revenue, churn_rate]
    values:
      - ["2026-01-01", 42, 5.1]
      - ["2026-02-01", 46, 4.7]
      - ["2026-03-01", 49, 4.4]
      - ["2026-04-01", 53, 4.9]
      - ["2026-05-01", 58, 4.1]

charts:
  revenue_and_churn:
    query: subscriptions
    type: bar
    x: month
    y: revenue
    layers:
      - type: line
        y: churn_rate
        axis_y:
          position: right
    annotations:
      - type: rule
        y: 5
        y_column: churn_rate
        text: Churn limit
      - type: rule
        y: avg()
        y_column: revenue
        text: Avg revenue
        style:
          marks:
            text:
              position:
                edge: top
                align: start
rows:
  - revenue_and_churn
RevenueChurn rateChurn limitAvg revenueJan2026FebMarAprMay0204060Revenue012345Churn rate Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Plane

plane sets whether the annotation draws behind or in front of the data.

Value Draws
background Above the gridlines, below all data marks and layers.
foreground Above all data marks and layers.

Defaults: bands are background; rules, trends, and notes are foreground on every chart type, so a bar that reaches a rule never covers it.


Positioning

style.marks.text.position places an annotation's text against the geometry its type draws.

Key Values Meaning
edge top, bottom, left, right; auto on a rule, band or note Which side of the geometry.
align start, center, end; auto on a rule, band or note Where along that side: left to right on top and bottom, top to bottom on left and right.
overlay true, false true: on the line, inside the rect or frame. false: outside, which can be the chart margin.
dx, dy Pixels A nudge after placement; on a note with a connector, the connector's length.
Type Geometry Default
rule The line. auto, auto, outside: above the right end.
band The region. auto: top and center inside an x band; right and center inside a y band.
note The point, or the enclosure. auto, auto, outside: past the value and centered, above the point on a vertical chart and right of it on a horizontal one.
note with no anchor The plot frame. bottom, end, overlay.
trend The end of the line. right, center, outside.

Text moves off the chart's bars, points, lines, value labels and other annotation text. An auto edge or align is where the text starts, and placement may change it to another side or spot along the side; an explicit one stays fixed. Either way the text may slide along its edge, clear of what it moves off. A note that leaves its side without a connector warns. Rule, band and trend lines never move.

Text that sits in the margin reserves room for itself. A chart whose layout cannot grow its margins fails with ERR-ANNOTATION-MARGIN-UNSUPPORTED; set overlay: true or move the text inside the plot.


Styling

style.marks.<mark> on an annotation opens the mark style for each mark its type draws, as on a layer. A mark style the annotation does not draw is accepted and has no effect.

Type style.marks keys
rule rule, text
band rect, text
note text, line, circle, rect
trend line, area, text

The look of every annotation of a type comes from the theme slot style.annotations.<type>.marks, a sibling of style.charts. Set it on the board to restyle every chart, or in a chart's own style.annotations.<type> for that chart. An annotation's own style: wins over both. Leaves left unset fall back to style.charts.marks.

For series: each or a list, the series color fills every color the annotation does not set. A color you set always wins.

A rule's line and its label share one color. The light themes draw it in rung 7 of the gray scaffold; paper and stark keep their own rule ink. Neon sets the stroke color to auto: each rule takes the quietest gray that holds 3:1 against the canvas and the bars or fills it crosses, kept clear of the gridlines. Setting style.marks.rule.stroke.color on a rule, or scoping it to a series, replaces the pick, and the label follows.

queries:
  monthly_revenue:
    columns: [month, revenue]
    values:
      - ["2026-01-01", 18.4]
      - ["2026-02-01", 20.1]
      - ["2026-03-01", 19.2]
      - ["2026-04-01", 23.6]
      - ["2026-05-01", 24.8]
      - ["2026-06-01", 27.3]

style:
  annotations:
    rule:
      marks:
        rule:
          stroke:
            color: category[3]
            width: 2
            dasharray: "6 3"
        text:
          font:
            color: category[3]
            weight: bold

charts:
  themed_goal:
    query: monthly_revenue
    type: line
    x: month
    y: revenue
    annotations:
      - type: rule
        y: 25
        text: FY goal
rows:
  - themed_goal
FY goalJan2026FebMarAprMayJun2025 Data as of 14:49 UTC on 29 Oct 2026 made withdbt Charts
▶

Text Style

style.marks.text is a text mark style plus these fields.

Field Allowed values Notes
font color, size, weight, style, line_height, family The label's type. On a rule, color: line (the default) is the rule's line color.
halo Pixels or auto Canvas-colored outline behind the text; 0 draws none. auto (the default) draws 3px only on text over the chart's own bars, points or area fill; a layers: mark doesn't count.
halo_opacity 0 to 1 Opacity of the halo as a whole.
box visible, fill, opacity, padding, radius, stroke A box behind the text; visible: true draws it.
position edge, align, overlay See Positioning.
dx, dy Pixels Nudge after placement.
max_width Pixels or auto Notes with no anchor and trends only.

A stroke's dasharray is auto (sized to the width) or dash and gap lengths such as "6 3".


Errors and Warnings

An annotation that cannot be placed on its chart fails the chart rather than drawing something wrong.

Code When
ERR-ANNOTATION-INVALID The annotation does not fit the chart: a trend that reads an area mark, a value that does not parse in the axis column's type, an aggregate or fit the axis cannot take, a missing or unknown series, from past to, an unknown category, a missing or unknown y_column, or text that does not render.
ERR-ANNOTATION-UNSUPPORTED The chart's layout cannot draw it yet: a bar chart on a continuous time axis, or a number anchor on a bar chart's category axis.
ERR-ANNOTATION-MARGIN-UNSUPPORTED Text falls outside the plot on a chart with fixed margins.
WARN-ANNOTATION-NOTE-MOVED A note's auto text moved to another side of its point to clear the chart, and it has no connector.
WARN-TREND-ON-BARS A trend reads a bar mark. It still draws.
WARN-ANNOTATION-NOTHING-DRAWN The annotation resolves but has nothing to draw: it falls outside the plot, names no slot on the axis, has no plotted rows in its scope, or a trend has too few rows. The rest of the chart renders.

Also an error: a key the type does not take, a note with only one of x and y, connector or enclose on a note with no anchor, an unknown function, nesting, or arithmetic, and now() or current() on a non-date x.