Lint rules
This appendix lists all lint rules that bino lint and bino build apply to your report manifests.
Lint rules check the content of your documents (semantic/business rules), while schema validation checks the structure.
The Severity column below is the severity a rule reports: it picks the colour of the squiggle in the editor.
No lint finding blocks a build, and none of them changes the exit code of bino lint on its own - only a severity you set yourself in bino.toml does that, and bino lint prints that one in front of the finding; see Disabling and re-grading rules.
The last five rules only run on a predef project - a project whose bino.toml carries a [package] table (see Predef packages). A project without that table never sees them.
| ID | Name | Severity | Description |
|---|---|---|---|
report-artefact-required |
Report Artefact Required | Warning | At least one ReportArtefact document must be defined. |
artefact-layoutpage-required |
Artefact LayoutPage Required | Warning | Each ReportArtefact must have at least one LayoutPage that matches its constraints and format. |
text-content-required |
Text Content Required | Warning | Text components must have a non-empty spec.value field. |
dataset-required |
Dataset Required | Warning | Table, ChartStructure, ChartTime, ChartScatter, ChartBubble, and ChartBullet components must have a spec.dataset binding. |
table-sum-title-unused |
Table Sum Title Unused | Warning | spec.sumTitle only labels the grand-total row of type: sum / type: opt tables. |
chart-time-field-no-effect |
ChartTime Field Without Effect | Warning | level, order, orderDirection and limit have no effect on a ChartTime. |
inherited-prop-unresolved |
Inherited Prop Unresolved | Warning | inherited-closest / inherited-page need an enclosing LayoutCard or LayoutPage that sets the matching field. |
missing-required-reference |
Missing Required Reference | Error | Required child references (ref) must point to existing documents. |
page-layout-slots-used |
Page Layout Slots Used | Warning | LayoutPage children count must match the expected slots for its pageLayout. |
card-layout-slots-used |
Card Layout Slots Used | Warning | LayoutCard children count must match the expected slots for its cardLayout. |
asset-reference-undefined |
Asset Reference Undefined | Warning | Image references must name a declared Asset document or a reachable URL. |
asset-source-missing |
Asset Source Missing | Warning | An Asset’s spec.source.localPath must point at an existing file. |
asset-prefix-deprecated |
Asset Prefix Deprecated | Info | The asset: prefix in image fields is deprecated; write the plain asset name. |
style-fillpattern-deprecated |
Fill Pattern ‘fc’ Deprecated | Info | The fill pattern fc in a ComponentStyle is the deprecated name of hatched. |
inline-ref-bounds |
Inline Reference Bounds | Warning | @inline(N) references must have valid indices within the dependencies array. |
dataset-source-exclusive |
DataSet Source Exclusive | Warning | The source field is mutually exclusive with query and prql in DataSet specs. |
inline-naming-conflict |
Inline Naming Conflict | Warning | Document names must not start with _inline_ as this prefix is reserved. |
ref-params |
Ref Params | Warning | metadata.params declarations and the parameter values passed to referenced documents must match. |
dataset-derive-conflict |
DataSet Derive Conflict | Warning | A previous-period slot may be declared in derive or assert, not both. |
reserved-name-prefix |
Reserved Name Prefix | Warning | DataSource and DataSet names must not start with bino_ or _bino_. |
dataset-dependency-undeclared |
DataSet Dependency Undeclared | Warning | A DataSource that a DataSet query reads must be listed in spec.dependencies. |
dataset-dependency-unused |
DataSet Dependency Unused | Info | A DataSource listed in spec.dependencies should be read by the DataSet query. |
dataset-constants-spec |
DataSet Constants Spec | Warning | constants.spec.<kind>.<field> must name a known kind token and a field of that kind’s spec. |
duplicate-name |
Duplicate Name | Error | Documents of one kind must have unique names within each ReportArtefact after constraints are applied. |
i18n-code-unused |
Internationalization Code Unused | Warning | An Internationalization spec.code must exactly match an artefact spec.language. |
i18n-namespace-unreferenced |
Internationalization Namespace Unreferenced | Warning | A named spec.namespace is only read by components whose i18nNamespace points at it. |
i18n-title-namespace-deprecated |
titleNamespace Deprecated | Info | titleNamespace is deprecated; use i18nNamespace, which also inherits to children. |
i18n-no-data-markers |
No-Data Label Markers | Warning | A *.no-data label set to ==No Data== or ==Keine Daten== prints the == markers. |
package-config-invalid |
Package Config Invalid | Error | The [package] table in bino.toml must satisfy the constraints a registry publish enforces. |
predef-name-namespace |
Predef Name Namespace | Error | Documents inside the package include set must be named after the package. |
predef-forbidden-kind |
Predef Forbidden Kind | Error | Artefact kinds and credential kinds must not be inside the package include set. |
predef-asset-absolute-path |
Predef Asset Absolute Path | Error | A packaged Asset’s spec.source.localPath must be relative to its manifest. |
predef-external-ref |
Predef External Reference | Error | Structural and presentational references must resolve inside the package or a declared dependency. |
Rule details
Section titled “Rule details”report-artefact-required
Section titled “report-artefact-required”Every bino report project needs at least one ReportArtefact document to define what PDF(s) to generate.
Example fix: Add a ReportArtefact manifest to your project:
artefact-layoutpage-required
Section titled “artefact-layoutpage-required”Each ReportArtefact must have at least one LayoutPage that:
- Passes constraints - The page’s
metadata.constraintsmust match the artefact’s context (labels, spec, or mode). The linter checks bothbuildandpreviewmodes; a page is valid if it matches in either mode. - Matches format - The page’s
spec.pageFormatmust match the artefact’sspec.format. Both default toxgaif not specified.
Example problem:
Fix: Set spec.pageFormat: a4 on the LayoutPage.
text-content-required
Section titled “text-content-required”Text components are meant to display content. An empty spec.value produces blank output.
Example problem:
Fix: Provide meaningful content in spec.value.
dataset-required
Section titled “dataset-required”Data visualization components (Table, ChartStructure, ChartTime, ChartScatter, ChartBubble, ChartBullet) need a dataset to display. Without spec.dataset, there’s nothing to render.
Example problem:
Fix: Add a dataset reference:
Or multiple datasets for charts:
table-sum-title-unused
Section titled “table-sum-title-unused”spec.sumTitle labels the grand-total row at the bottom of a table. That row only exists for type: sum and type: opt. On type: list (the default), type: sumnototal and type: optnototal no total row is generated, so the label is silently dropped.
Example problem:
Fix: render a total row, or drop the label.
The rule also covers tables defined inline in LayoutPage, LayoutCard and Grid children, and in Tree nodes. A child that inherits its type from a referenced Table via ref is checked against the effective type, so overriding only sumTitle on a type: sum component does not warn.
chart-time-field-no-effect
Section titled “chart-time-field-no-effect”A time chart always shows one value per period, in date order. level, order, orderDirection and limit are still accepted on a ChartTime, so that existing reports keep building, but they change nothing and are not passed to the template engine.
Example problem:
Fix: Remove the fields. The chart does not change.
With a template engine before v1.0.0-next.29 the time chart reports the notices AUTO_level and AUTO_order when these attributes are missing, and before v1.0.0-next.27 also the warnings ERR_level and ERR_orderDirection. bino no longer writes the attributes, so on such an engine they appear whether the fields are set or not.
The rule also covers a ChartTime defined inline in children or nodes. On Table and ChartStructure these fields work and are not reported.
See also: ChartTime
inherited-prop-unresolved
Section titled “inherited-prop-unresolved”Table and ChartStructure can take scenarios, variances, order and orderDirection from the layout around them. ChartTime can take scenarios and variances: it is drawn in date order and has no order to inherit. These three and ChartScatter, ChartBubble and ChartBullet can take ruleset the same way. The prop is set to inherited-closest or inherited-page, and the value comes from a field of an enclosing LayoutCard or of the LayoutPage:
| Prop | Field on the card or page |
|---|---|
scenarios |
titleScenarios |
variances |
titleVariances |
order |
titleOrder |
orderDirection |
titleOrderDirection |
ruleset |
ruleset |
When that field is not set, there is nothing to inherit and the component renders without the value you expected. The rule looks for the field like this:
inherited-page: theLayoutPageonly. Cards are skipped.inherited-closest: every enclosingLayoutCardfrom the inside out, then theLayoutPage. The first one that sets the field wins. A card without the field is skipped. The keyword is reported only when no card and not the page set it.
A LayoutCard can set its own titleScenarios, titleVariances, titleOrder, titleOrderDirection or ruleset to inherited-page. When the page does not set that field, the card is reported too, and it has no value to pass on to its children.
Example problem:
Fix: set the field where the keyword looks for it, or give the prop an explicit value.
Only the plain string is a keyword. Grid and Tree are passed through: a component inside them inherits from the next card or from the page. A component used through ref is checked at every place it is used, with the inline spec merged over the referenced document, and the finding points at the key that holds the keyword. A standalone document that no page uses is not reported. A child whose ref names several documents is not checked, because the rule does not evaluate constraints.
missing-required-reference
Section titled “missing-required-reference”Child references (ref) in LayoutPage and LayoutCard must point to documents that exist. This rule catches typos, missing files, and broken references early.
Severity: Error
Example problem:
Error message:
Fixes:
-
Correct the typo - Fix the reference name to match the target document’s
metadata.name. -
Create the missing document - Add the referenced manifest to your project.
-
Mark as optional - If the reference is legitimately optional (e.g., debug-only component), add
optional: true:
Note: References that exist but are filtered out by constraints (e.g., mode == "preview") do not trigger this error. Only genuinely missing references cause failures.
See also: Optional references
page-layout-slots-used
Section titled “page-layout-slots-used”Each LayoutPage must have exactly the number of children that match its pageLayout slot count. This rule is evaluated per artefact, counting only children that:
- Pass constraints - Children with
metadata.constraintsmust match the artefact’s context. - Have valid refs - Children with
refmust point to an existing document (or be markedoptional: true). Missing required refs trigger themissing-required-referenceerror. Optional refs that are missing don’t count as slots. - Are renderable - Children must have either a
refor inlinespec.
Slot counts for predefined layouts:
| Layout | Slots |
|---|---|
full |
1 |
split-horizontal |
2 |
split-vertical |
2 |
2x2 |
4 |
3x3 |
9 |
4x4 |
16 |
1-over-2 |
3 |
1-over-3 |
4 |
2-over-1 |
3 |
3-over-1 |
4 |
For custom-template, the slot count is the number of distinct named area tokens in pageCustomTemplate:
Example problem:
Fix: Add children to fill all slots, or change pageLayout to match actual children count.
card-layout-slots-used
Section titled “card-layout-slots-used”Same as page-layout-slots-used, but for LayoutCard components. Each card’s cardLayout defines how many children it expects.
Example problem:
Fix: Add a second child or change cardLayout: full.
asset-reference-undefined
Section titled “asset-reference-undefined”Images are referenced by the name of an Asset document, not by file path. This rule checks every image reference and warns when it cannot resolve, which otherwise fails silently: the name is passed through to the browser as if it were a URL, and you get a broken image with no error.
It covers messageImage (LayoutPage), titleImage (LayoutCard), source (Image) - including components defined inline in children or nodes - and asset: image references inside Markdown (messageText, titleBusinessUnit, and a Text component’s value).
Example problem:
Fix: declare the Asset and reference it by its metadata.name.
A reference resolves when it is one of:
- the
metadata.nameof anAssetdocument (messageImage: logo). The form with theasset:prefix (messageImage: asset:logo) still resolves, but is deprecated, seeasset-prefix-deprecated; - an absolute URL or data URI the browser can fetch (
https://…,data:image/png;base64,…).
A value that looks like a file path (images/logo.png) is checked against the filesystem, relative to the manifest, and warns when the file is absent. Note that bino serves only declared Assets - a bare path is not served even when the file exists, so prefer declaring an Asset.
Font Assets do not count: they are not available to image references.
See also: Asset
asset-source-missing
Section titled “asset-source-missing”An Asset whose spec.source.localPath points at a file that is not on disk. The path is resolved relative to the manifest that declares it, the same way the renderer resolves it.
This is a build error at render time; the rule surfaces it while you are still editing, instead of at the next build.
Example problem:
Fix: restore the file, or correct localPath. Assets sourced from remoteURL or inlineBase64 are not checked.
See also: Asset
asset-prefix-deprecated
Section titled “asset-prefix-deprecated”messageImage (LayoutPage), titleImage (LayoutCard) and source (Image) take the name of an Asset document. The older form with the asset: prefix still works with template engine 1.x, but is deprecated there.
Example problem:
Fix: Write the plain asset name.
Markdown image references such as  are not affected. bino resolves them itself, and there the prefix is required.
See also: Asset
style-fillpattern-deprecated
Section titled “style-fillpattern-deprecated”A ComponentStyle selects the fill of a bar with fillpattern: solid, hatched or outlined. fc is the old name of hatched. It still works with template engine 1.x, but is deprecated there.
Example problem:
Fix: Write fillpattern: "hatched". This needs template engine v1.0.0-next.28 or later. An older engine knows only fc in time and structure charts; in a project pinned to such an engine keep fc and disable the rule.
See also: ComponentStyle
inline-ref-bounds
Section titled “inline-ref-bounds”When using inline DataSource definitions in a DataSet’s dependencies, the @inline(N) references in queries must point to valid indices.
Example problem:
Error message:
Fix: Use @inline(0) to reference the first (and only) inline dependency.
See also: Inline DataSet definitions
dataset-source-exclusive
Section titled “dataset-source-exclusive”The source field in DataSet provides direct pass-through to a DataSource without transformation. It cannot be used together with query or prql.
Example problem:
Fix: Choose one approach:
See also: Direct source pass-through
inline-naming-conflict
Section titled “inline-naming-conflict”Document names starting with _inline_ are reserved for generated inline definitions. User-defined documents must not use this prefix.
Example problem:
Fix: Choose a different name that doesn’t start with _inline_:
ref-params
Section titled “ref-params”Validates metadata.params declarations on documents that accept parameters (LayoutPage, ReportArtefact and the other param-capable kinds) and the values passed to them where they are referenced: a layout child, a grid child or a tree node with ref and params. A parameter that is passed but not declared, a required parameter that is missing, or a value that does not fit the declared type is reported.
Example problem:
Fix: Pass exactly the declared parameters:
dataset-derive-conflict
Section titled “dataset-derive-conflict”A DataSet declares what a previous-period slot means either with derive (the CLI produces the column) or with assert (the query supplies the column and the CLI checks it). The same slot cannot be in both maps: the executor rejects the dataset before running it.
Example problem:
Fix: Keep the slot in one map. Use assert when the query already returns the column, derive when it does not:
reserved-name-prefix
Section titled “reserved-name-prefix”Names starting with bino_ are reserved for the functions and macros the CLI registers in every DuckDB session (op, iop, bino_shift), and names starting with _bino_ for the views it creates while executing a dataset with derive or assert. A DataSource or DataSet with such a name can shadow one of them. The executor also refuses to run a project with a _bino_ name.
Example problem:
Fix: Choose a name without the prefix:
dataset-dependency-undeclared
Section titled “dataset-dependency-undeclared”Every DataSource is registered as a view for every DataSet query, so a query that reads a DataSource missing from spec.dependencies still runs. The dataset cache, however, only watches the files of the listed dependencies. When the unlisted source changes, the build keeps serving the old result and prints no warning. An entry must be spelled exactly like the DataSource’s metadata.name; the query itself may use any case.
Example problem:
Fix: List every DataSource the query reads:
The rule only sees what it can read from the query:
- PRQL queries,
source:DataSets and inline datasets inside a component’sdataset:are not checked. - A query DuckDB cannot parse at lint time is not checked, for example a
PIVOTor an unquoted${param}placeholder. - A CTE named like a DataSource hides unqualified reads of that DataSource.
- Only names of DataSource documents count; tables in attached databases are ignored.
- Table functions are not seen, except
bino_shiftandquery_tablewith a literal table name.
dataset-dependency-unused
Section titled “dataset-dependency-unused”An entry in spec.dependencies names a DataSource the query never reads. Nothing breaks, but the dataset is run again whenever that source changes, and the dependency graph shows a link that is not there.
Example problem:
Fix: Remove the entry, but only after checking that the query really does not read the source. The rule has the same limits as dataset-dependency-undeclared, and a read it cannot see, for example through a CTE named like the source, looks like no read at all.
The Info severity does not exempt the finding from bino lint --fail-on-warnings; only a severity you set in bino.toml does (see Disabling and re-grading rules):
With that setting validate_project still reports the finding.
dataset-constants-spec
Section titled “dataset-constants-spec”The keys under a DataSet’s constants.spec become the reserved _spec_<kind>_<field> columns. The kind token must be one of text, table, chartstructure, charttime, chartscatter, chartbubble, chartbullet or any, and the field must exist in that kind’s spec (for any, in at least one of them). A typo would produce a column nothing ever reads.
Example problem:
Fix: Use the lowercase manifest kind and the field name as written in the component’s spec:
duplicate-name
Section titled “duplicate-name”A name only has to be unique per kind within one ReportArtefact, after its metadata.constraints are applied. The rule checks every ReportArtefact in build and preview mode and reports the later of two documents that are both included. Documents of different kinds may share a name.
This is a build error at render time: bino build fails, and bino preview and bino serve show a duplicate ... name error instead of the artefact. The rule surfaces it while you are still editing.
Example problem:
Fix: Rename one of them, or add constraints so that no artefact includes both. This is allowed:
See also: Name scoping
i18n-code-unused
Section titled “i18n-code-unused”Locale lookup is an exact string match against the artefact’s spec.language. An Internationalization document whose spec.code matches no artefact language is loaded into the browser store and then never read - most often because the code carries a region subtag.
Example problem:
Fix: Use the plain language code, exactly as written on the artefact. There is no BCP 47 normalization.
See also: Internationalization
i18n-namespace-unreferenced
Section titled “i18n-namespace-unreferenced”spec.namespace files a translation set under a named bundle instead of _system. A named bundle is only consulted by components whose i18nNamespace (or a page/card titleNamespace) points at it, so a namespace nothing references is dead weight.
Example problem:
Fix: Point a component, card, page or the artefact at it - or drop spec.namespace so the content merges into _system, the bundle every component reads.
See also: Scoped overrides with i18nNamespace
i18n-title-namespace-deprecated
Section titled “i18n-title-namespace-deprecated”titleNamespace on LayoutPage / LayoutCard only ever applied to that page or card’s own title; it never reached the children. i18nNamespace supersedes it, takes precedence when both are set, and inherits to every descendant.
Example problem:
Fix: Rename it to i18nNamespace.
See also: Scoped overrides with i18nNamespace
i18n-no-data-markers
Section titled “i18n-no-data-markers”Up to v0.97.0, bino add i18n --defaults wrote the no-data labels as ==No Data== and ==Keine Daten==. The == was an old placeholder, not markup, and the engine no longer uses it. Content in an Internationalization document overrides the engine’s labels, so a file scaffolded back then keeps printing the markers on every empty Table and chart.
Example problem:
Fix: Set the current default, or delete the key so the engine’s label applies.
See also: Internationalization
package-config-invalid
Section titled “package-config-invalid”The [package] table is not checked when bino.toml is loaded - an unrelated command must not fail over a field only bino publish reads. bino lint checks it instead and reports the first problem it finds as one finding on bino.toml.
Example problem:
Fix: Use a scoped name, one of the two visibilities, and a valid semver range.
When this rule fires, the four content rules below do not run: the include set and the name prefix both derive from the table, so their findings would be meaningless.
predef-name-namespace
Section titled “predef-name-namespace”Every document a package publishes carries the package name, so a consumer who installs two kits never gets a name collision. One document - the package’s main definition - may be named exactly like the package; every other one needs a <package>/<definition> name.
Example problem:
Fix: Prefix it with the package name, or move the manifest out of the include set.
A DataSource is the exception: its name becomes a DuckDB view name and is limited to two segments (@scope/name), so it cannot carry a package segment at all. Either name it exactly like the package (as the main definition) or keep it out of the include set - mocks/ is the conventional place.
predef-forbidden-kind
Section titled “predef-forbidden-kind”Two groups of kinds must never be published.
- Artefacts (
ReportArtefact,LiveReportArtefact,ScreenshotArtefact,DocumentArtefact) render a report. They are the harness that previews your package, not package content. Keep them inreports/ormocks/, both of which are excluded from every package. - Credentials (
ConnectionSecret,SigningProfile) must never leave your machine.secrets/andsigning/are part of the default include set, on purpose: if they were excluded the rule could never fire on the one layout where credentials actually live.
Fix: Move the manifest out of the include set, or write an explicit include list in [package] that leaves its folder out.
predef-asset-absolute-path
Section titled “predef-asset-absolute-path”A published package is unpacked on someone else’s machine, so an Asset inside it must find its file relative to its own manifest. An absolute path - /home/you/logo.svg, or a C:\ drive path - resolves to nothing there.
Example problem:
Fix: Put the file next to the manifest and reference it relatively.
Values containing ${ are skipped (missing-env-var owns those), as are empty ones (inlineBase64 / remoteURL forms).
predef-external-ref
Section titled “predef-external-ref”A packaged document may only point at documents that travel with it: something else inside the include set, or a package listed under [dependencies] in bino.toml. Anything else breaks the moment a consumer installs the package.
The rule inspects structural and presentational references only:
{kind, ref}children anywhere in the documentspec.selectedStyle(aComponentStyle)spec.ruleset(aRuleSet)
Data bindings are deliberately exempt. spec.dataset is the seam a predef exists for: a packaged Table binds to a dataset the consumer supplies, so it points outside the package by construction. Flagging it would make the primary use case unlintable. The same reasoning covers a DataSet’s spec.dependencies and spec.source, and spec.i18nNamespace names a namespace rather than a document. Raw SQL in spec.query is never parsed.
Example problem:
Fix: Move the style into the package and namespace it, or - when it belongs to someone else’s package - declare that package as a dependency.
References containing ${ (a parameter or environment placeholder), glob characters, or the reserved _inline_ prefix are skipped. A name that matches no document at all is left to missing-required-reference, which words it better.
Adding custom rules
Section titled “Adding custom rules”The bino linter is designed for easy rule addition. Each rule has:
- ID: A unique identifier (e.g.,
naming-lowercase,reference-undefined-dataset). - Name: A short human-readable name.
- Description: An explanation of what the rule checks.
- Check function: The logic that inspects documents and returns findings.
Rules are registered in internal/report/lint/rules.go.
Disabling and re-grading rules
Section titled “Disabling and re-grading rules”The [lint] table in bino.toml switches rules off and changes the severity they report:
disable hides a rule’s findings everywhere - CLI output, lint log and editor.
severity re-grades them everywhere: bino lint prints the finding under its
new severity and the editor squiggles it in the matching colour.
Only a severity you set in bino.toml affects an exit code: a rule you raise to
"error" fails bino lint on its own, a rule you lower to "info" no longer
counts for --fail-on-warnings. The Severity column above changes no exit code
by itself, and no lint finding fails bino build - [lint] changes neither.
Besides the rule IDs above you can name four more IDs, which findings carry although no rule of that name declares them:
| ID | Covers |
|---|---|
schema-validation |
A manifest that failed schema validation. |
manifest-load |
A manifest that could not be loaded at all. |
engine-version-incompatible |
The engine-version pin check in bino.toml. |
missing-required-reference |
The dangling-reference finding above, which page-layout-slots-used emits while it walks the children. |
Disabling schema-validation or manifest-load hides the lines only - bino lint
still exits non-zero on a bundle it could not load, because the load really
failed. Disabling engine-version-incompatible, in contrast, drops the finding
and the exit code it forced together.
Those three accept disable only: their weight follows the state of the bundle,
so bino lint rejects a [lint.severity] entry on them with a warning rather
than honouring nothing.
There is still no per-file or per-line suppression: [lint] works on rule IDs
for the whole project.
See Lint rules in bino.toml for the full semantics, plugin rule IDs and how unknown entries are reported.
See also
Section titled “See also”- bino lint - CLI command for running lint.
- bino build - build command (runs lint by default).
- Predef packages - the
[package]table the last five rules switch on.