Skip to main content

Documentation index: llms.txt. This page is also available as markdown: append .md to this URL or send Accept: text/markdown.

Annotation Declarations Reference

A SQL Node Type declares its annotations under the annotations key of its definition. Declarations make annotations discoverable: each one appears in the Annotations panel on Nodes of the type, with its description and a copyable snippet. This page is the reference for the declaration format.

For annotation syntax in Node SQL and how values reach templates, see the SQL Annotations Reference. For the workflow around building a type, see Defining a SQL Node Type.

Schema at a Glance

annotations: # optional; the only V2-specific key in the Node Metadata Spec
node: # optional list: annotations written above WITH or SELECT
- name: string # required; the key as written in SQL, without the @
description: string # optional; shown in the Annotations panel
isRequired: boolean # optional; documentation only, shows a "required" tag
allowsMultiple: boolean # optional; the one field with runtime effect: values collect into an array
parameters: # optional list; positional order is the argument order in SQL
- name: string # required; display name and snippet placeholder, never written in SQL
type: string | boolean | number # required
description: string # optional
isRequired: boolean # optional; optional parameters show a trailing ? in the signature
default: string | number | boolean # optional; shown in the panel, not applied at runtime
options: [string, ...] # optional; string parameters only
example: string | number | boolean # optional; overrides the snippet placeholder only
column: # optional list: annotations written after AS <alias>; same declaration shape
- ...

Fields that are not listed are errors. In particular, type, default, options, and example belong on a parameter, never on the annotation itself.

Structure

annotations:
node:
- name: disableTests
description: Skip all node-level and column-level tests.
- name: preSQL
description: SQL statement to run before the load. Repeat to run several, in order.
allowsMultiple: true
parameters:
- name: querySQL
type: string
isRequired: true
column:
- name: not_null
description: Column-level test. Fails on rows where the column is NULL.

annotations accepts exactly two sections, node and column, each an optional list of declarations. Any other section name is an error.

Declarations are strictly validated

Unknown or misspelled fields anywhere in the annotations block make the whole Node Metadata Spec fail validation when Coalesce plans a deploy, runs a Job, or creates a Node of the type. The Node Type Editor does not currently flag these errors, so the Annotations panel can look correct while runs behave as if the block were absent: in particular, repeatable annotations collapse to their last occurrence. Check the block carefully. Fields that commonly trip this: allowMultiple (missing the s), type or default placed on the annotation instead of on a parameter, non-string options, and a parameters key nested inside a parameter.

Annotation names follow SQL identifier rules: letters, digits, and underscores, not starting with a digit, and not a SQL keyword such as unique, as, or primary. A keyword name fails to parse in Node SQL.

Declaration Fields

FieldTypeRequiredDefaultDescription
namestringYes-The annotation name as written in SQL, without the @. Must not collide with a reserved annotation name.
descriptionstringNo-Shown in the Annotations panel. Treat it as the annotation's documentation.
parameterslistNononeThe annotation's parameters, in the positional order they are written in SQL. Omit for a bare flag annotation.
allowsMultiplebooleanNoomitted, behaves as falseMarks the annotation repeatable. Each occurrence in the SQL is kept, and the hydrated value is an ordered array with one entry per occurrence, even when there is only one. Without it, repeating the annotation keeps only the last occurrence.
isRequiredbooleanNoomitted, behaves as falseDocuments that the annotation should be present on every Node or column of the type. Shown as a required tag in the panel.

type, default, options, and example belong on parameters, not on the annotation. A flag annotation with no value, such as @disableTests, is declared with no parameters at all and hydrates to true.

allowsMultiple matches the declared name exactly, including case. Write the annotation in SQL with the same casing you declared; @PreSQL does not collect into the preSQL list.

Parameter Fields

FieldTypeRequiredDefaultDescription
namestringYes-The parameter's display name, used in the panel and as the placeholder in copy snippets. It is never written in the SQL itself.
typestringYes-string, number, or boolean. Annotation arguments are always literal values of these types.
descriptionstringNo-Shown in the panel.
isRequiredbooleanNoomitted, behaves as falseNon-required parameters render with a trailing ? in the annotation's signature.
defaultmatches typeNo-The value your templates should assume when the argument is omitted. Shown in the panel and used to pre-fill number and boolean snippets. Nothing applies it at runtime; see Enforcement below.
optionslist of stringsNo-For string parameters, the fixed set of accepted values, shown in the panel and in the copy snippet. Must be strings; options: [true, false] on a boolean parameter is a parse error, and options on number or boolean parameters are ignored in snippets.
examplematches typeNo-Overrides the placeholder in the copy snippet with a concrete value. It is not shown as its own field, so put explanatory prose in description. A value that does not match type is ignored.

Repeatable Annotations

Use allowsMultiple when the same annotation can appear several times. Coalesce's Work Node Type uses it for @preSQL and @postSQL (one statement per occurrence), for @tests, and for the column-level @inHash, which marks a column as an input to a generated hash key:

column:
- name: inHash
description: Marks a column as an input to a hash key, grouped by hashName and ordered by hashOrder.
allowsMultiple: true
parameters:
- name: hashName
type: string
isRequired: true
- name: hashOrder
type: number
isRequired: true
SELECT
"N_NATIONKEY" AS "N_NATIONKEY" @inHash("GH_COL", 1),
"N_NAME" AS "N_NAME" @inHash("GH_COL", 2),
...

The template receives column.inHash as an ordered array, one { parameters: [...] } entry per occurrence, even when a column carries only one:

{% for col in columns %}
{% for entry in col.inHash | default([]) %}
{# entry.parameters[0] is the hash name, entry.parameters[1] the position #}
{% endfor %}
{% endfor %}

Enforcement

Declarations drive discovery, not validation. Only the reserved annotations are validated. Coalesce does not reject a Node whose SQL:

  • uses an annotation the type never declared, or misspells a declared one (the value hydrates under the name as written; your templates decide whether anything reads it),
  • omits an annotation declared isRequired,
  • passes a value outside a parameter's options, or the wrong number of arguments,
  • writes a bare @name for an annotation that declares parameters (it hydrates to true, not to { parameters: [...] }).

Write templates defensively: guard reads with is defined, apply your declared default in template logic, and treat options as documentation of what your template supports. Where a wrong value would produce bad SQL, prefer failing loudly in the template over silently generating an unexpected statement.

What's Next