Language reference

This is the practical language reference for Org2 v0.

For the normative draft spec, see spec/v0/SPEC.org.

Spec map

Use this mapping when deciding where behavior is defined:

Headings

  • Syntax: one or more stars, a space, then title text.

  • Example:

* Project
** Subproject
*** Task
  • Heading level is the number of stars.

  • Level skips are accepted in v0.

TODO keywords

Headline TODO states parsed into the canonical AST:

  • TODO

  • IN_PROGRESS

  • DONE

  • CANCELLED / CANCELED

Workflow tools may accept aliases such as PROG or WAITING and normalize them to one of the canonical TODO keywords above.

Example:

* TODO Ship docs
* IN_PROGRESS Implement parser tests
* DONE Publish changelog

Priority cookies

  • Syntax: [#A] style cookie after TODO keyword (or at headline start when no TODO keyword).

* TODO [#A] Critical task

Tags

  • Tags are suffixes at headline end: :tag: or :tag1:tag2:

* TODO Draft docs :docs:website:

Drawers

Property drawers

  • Drawer delimiters: :PROPERTIES: ... :END:

  • Consistently space-indented property drawers are accepted for legacy files. org2 fmt canonicalizes their delimiter and property lines to column zero.

  • Common keys used by Org2 workflows:

    • :ID:

    • :CUSTOM_ID:

    • :EFFORT:

    • :ROAM_ALIASES:

:PROPERTIES:
:ID: 123e4567-e89b-12d3-a456-426614174000
:EFFORT: 45
:END:

Generic drawers

  • Generic :NAME: ... :END: drawers are preserved.

Planning lines

Recognized planning keywords:

  • SCHEDULED:

  • DEADLINE:

  • CLOSED:

SCHEDULED: <2026-02-21 Sat>
DEADLINE: <2026-02-25 Wed>
CLOSED: [2026-02-20 Fri]

Timestamps

Supported timestamp forms:

  • Active: <2026-02-21 Sat>

  • Inactive: [2026-02-21 Sat]

  • With time: <2026-02-21 Sat 09:30>

  • Ranges: <2026-02-21 Sat>--<2026-02-23 Mon>

  • Repeaters (agenda projection): +1w, ++1m, .+2d

Lists

  • Unordered markers: -, +, *

  • Ordered markers: 1., 1)

  • Checkboxes: [ ], [X], [x], [-]

  • Checkbox progress cookies: [n/m] and [p%] may appear in headings or list items; compile output exposes computed checkbox totals and lint reports stale cookies.

  • Nested lists via indentation.

Links

Supported link forms:

  • Bracket links: [[https://example.com][label]]

  • Plain URLs: https://example.com

  • File links: [[file:path/to/file.org]]

  • ID links: [[id:UUID][label]] (roam-style link)

  • Wiki links: [[Node Title]] (roam-style link)

Both ID links and Wiki links are first-class in Org2 roam workflows.

Link abbreviations are supported with built-ins, config, and #+LINK directives.

Built-in abbreviations:

  • gh:https://github.com/%s

  • gl:https://gitlab.com/%s

  • yt:https://www.youtube.com/watch?v%s=

  • wiki:https://en.wikipedia.org/wiki/%s

You can add/override abbreviations in org2.json:

{
  "links": {
    "linearTeam": "acme",
    "abbreviations": {
      "linear": "https://linear.app/acme/issue/%s",
      "gh": "https://github.example.com/%s"
    }
  }
}

And define file-local abbreviations with Org-compatible syntax:

  • Define: #+LINK: linear https://linear.app/acme/issue/%s

  • Use: [[linear:APP-3718][APP-3718]] or [[linear:APP-3718]]

Precedence (highest to lowest):

  1. file-local #+LINK

  2. org2.json links.abbreviations

  3. built-ins

Org2 expands abbreviation targets when rendering/exporting and for LSP document-link targets.

Corpus identity

The root org2.json may identify the corpus independently of its local path:

{
  "corpus": {
    "schema": "org2:corpus:v1",
    "id": "team-operations",
    "name": "Team Operations",
    "kind": "shared"
  }
}

id is a stable lowercase slug, name is the human-facing label, and kind is personal, shared, or project. This record is portable. Local mount paths, credentials, and membership stay outside the corpus. See Shared corpora and collaboration and spec/v0/corpus.schema.json.

Inline emphasis

  • Bold: *bold*

  • Italic: /italic/

  • Underline: _underline_

  • Strike: +strike+

  • Verbatim: =verbatim=

  • Code: ~code~, with Markdown-style `code` accepted as shorthand

Blocks

Recognized block forms include:

  • Source blocks: #+begin_src ... #+end_src

  • Example blocks: #+begin_example ... #+end_example

  • Quote blocks: #+begin_quote ... #+end_quote

  • Verse blocks: #+begin_verse ... #+end_verse

  • Center blocks: #+begin_center ... #+end_center

  • Comment blocks: #+begin_comment ... #+end_comment

Syntactic sugar / aliases

Org2 supports a friendlier fenced-code alias for source blocks:

```ts
const x = 1
```

This is treated as syntactic sugar for:

#+begin_src ts
const x = 1
#+end_src

Prefer the fenced form for new hand-written examples and docs. The verbose #+begin_src form remains fully supported for compatibility and explicit Org-style interchange.

Fenced source blocks use exactly three backticks for the opener and closer. Four-or-more backticks remain ordinary text in v0, which keeps Markdown examples that quote org2 fences from accidentally becoming executable org2 source, chart, dataset, or SQL blocks.

Tables

  • Pipe table rows: | a | b |

Org2 defines the custom link type color: as explicit, non-navigating presentation markup. Use [[color:red][urgent]] for foreground color, [[color:bg=yellow][review]] for background color, or [[color:fg=white;bg#b42318][blocked]]= for both. Keys may be abbreviated as fg=bg= or written as =foreground=background; text is also accepted for foreground. Values may be supported names or three/six-digit hex literals.

The portable named palette is black, blue, brown, gray/grey, green, indigo, mint, orange, pink, purple, red, teal, white, and yellow. Hex literals provide exact custom colors without allowing arbitrary CSS.

The binding works anywhere links are parsed, including prose, headings, and table cells. In ordinary inline content, background color applies to the labeled span. When one color binding occupies an entire table cell, rendered clients apply its foreground and background to the whole cell. It remains a normal bracket link in the canonical AST and round-trips byte-for-byte, so standard Org tools preserve the label even when they do not interpret the color: type. Invalid color declarations remain ordinary links rather than accepting arbitrary CSS.

  • Horizontal separator rows: |---+---|

  • Table content is parsed and preserved.

Tables, source blocks, and common blocks can carry adjacent affiliated keyword metadata:

#+name: quarterly_revenue
#+chart: line x=quarter y=revenue
#+dataset: finance.revenue
| quarter | revenue |
|---------+---------|
| 2026-Q1 | 1200    |

Recognized affiliated keys include NAME, CAPTION, PLOT, CHART, DATASET, VIEW, RESULTS, HEADER/HEADERS, and ATTR_* keys. The parser preserves them on the following table or block as affiliatedKeywords for chart renderers, dataset indexes, SQL views, and exporters.

For new chart examples, prefer an adjacent fenced chart block when it is easier to read:

#+name: fetch_buckets
| bucket | fetches |
|--------+---------|
| 0-10   | 14      |
| 11-50  | 32      |

```chart histogram
x: bucket
y: fetches
sort: y-desc
source: previous-table
```

This is ergonomic syntax sugar for chart metadata over the preceding table. Fenced chart blocks may also point at a named table or materialized result elsewhere in the same note:

#+name: package_fetches_result
#+results: query-fetches-by-company
| day        | fetches |
|------------+---------|
| 2026-06-10 | 84      |

#+name: package_fetches_chart
```chart line
x: day
y: fetches
source: package_fetches_result
size: compact
height: 300
interactive: true
```

The current renderer supports bar, line, and bucketed histogram charts over the previous table or a named table source. Add sort: x-asc, sort: x-desc, sort: y-asc, or sort: y-desc when the visual should order marks independently of the table's source row order. size accepts compact, medium, or wide. height accepts 220–720 pixels and defaults according to the size preset. interactive defaults to true; in the Mac app it enables nearest-point hover, exact-value tooltips, a crosshair, and keyboard navigation between marks. Static SVG remains the canonical CLI and export fallback.

The Mac app treats chart interaction as app-owned presentation behavior. Notes cannot provide arbitrary JavaScript. Chart cards can also be resized horizontally in the rendered view; use size and height when the desired dimensions should remain deterministic in source.

Chart SVG uses CSS custom properties with opinionated defaults: --org2-chart-mark, --org2-chart-surface, --org2-chart-axis, --org2-chart-grid, --org2-chart-label, and --org2-chart-title. Set them on .org2-chart in a published stylesheet or the Mac app's .org2/app.css to customize the visual theme without changing the data declaration.

The CLI can render chart-affiliated tables directly:

org2 render-chart --file report.org2 --block-id quarterly_revenue --format svg

Local datasets and SQL views

Org2 can describe local/ad hoc data workflows with fenced dataset and sql blocks. This is intended for notebook-like reports over explicit local files, read-only URLs, and named org2 tables, with DuckDB as the first execution bridge:

```dataset fetches
type: csv
path: ./data/package-fetches.csv
engine: duckdb
```

```sql results=fetches_by_state artifact=views/fetches_by_state.org freshness=24h
SELECT state, count(*) AS fetches
FROM fetches
GROUP BY state
ORDER BY fetches DESC
```

```chart bar
source: fetches_by_state
x: state
y: fetches
```

org2 query-data reads these blocks, creates DuckDB views for local files, URL-backed sources, named org2 tables, or configured remote analytics datasets, creates declared SQL views, runs a selected SQL result block through the local DuckDB CLI, and materializes the returned rows as a named org table or JSON envelope. Materialized org tables include a #+query-data: provenance line with the result id, row count, optional freshness token, selected SQL hash, full DuckDB script hash, and ran_at timestamp:

org2 query-data --file report.org2 --results fetches_by_state --out report.fetches.org
org2 query-data --file report.org2 --results fetches_by_state --apply
org2 query-data --file report.org2 --line 42 --format json
org2 query-data --file report.org2 --inspect --include-script
cat report.org2 | org2 query-data --stdin --results fetches_by_state --format json

Use --stdin when an editor, agent, or notebook-style client wants to run the current buffer without saving it first. Use --line N to select the SQL result block containing or after the cursor line. JSON output includes resultBlocks with each available SQL result id and source range. Use --inspect to return parsed datasets, SQL views, result metadata, diagnostics, and selected-query provenance without running DuckDB; --include-script also returns generated DuckDB SQL. Relative file paths are resolved from the working directory for stdin input. SQL result ids must be unique, and dataset ids and SQL view ids must be distinct because they become DuckDB relation names. Local URL/table datasets may carry metadata-only credential/config references such as env:SCARF_API_TOKEN or profile:product-analytics; remote analytics datasets use their named profile operationally but never insert credentials into generated SQL. Inline bearer tokens, passwords, and other literal secrets are rejected. SQL result blocks can include artifact=PATH and freshness=24h / ttl=24h / max-age=24h metadata. Executed results stamp ranAt so clients can evaluate freshness.

Use sql view=NAME when a report needs a reusable local projection before the final result table:

```sql view=california_fetches
SELECT state, fetches
FROM fetches
WHERE state = 'CA'
```

```sql results=fetches_by_state
SELECT state, sum(fetches) AS fetches
FROM california_fetches
GROUP BY state
```

Use url: instead of path: when DuckDB should read a declared HTTP(S) or file: URL directly:

```dataset remote_fetches
type: csv
url: https://data.example.test/package-fetches.csv
engine: duckdb
credential: env:SCARF_API_TOKEN
config: profile:product-analytics
```

```sql results=remote_fetches_by_state
SELECT state, count(*) AS fetches
FROM remote_fetches
GROUP BY state
```

Remote analytics datasets

Remote datasets use the same named-dataset and DuckDB result pipeline as local CSV files. A note names a profile, but never contains a warehouse URL, API key, username, or password. Profiles live in the nearest org2.json and refer to secrets through environment-variable names:

{
  "dataSources": {
    "scarf-clickhouse": {
      "type": "clickhouse",
      "url": "https://clickhouse.example.com/",
      "database": "analytics",
      "userEnv": "SCARF_CLICKHOUSE_USER",
      "passwordEnv": "SCARF_CLICKHOUSE_PASSWORD",
      "timeoutMs": 300000,
      "maxRows": 10000
    },
    "scarf-metabase": {
      "type": "metabase",
      "url": "https://metabase.example.com/",
      "apiKeyEnv": "SCARF_METABASE_API_KEY",
      "databaseIdEnv": "SCARF_METABASE_DATABASE_ID",
      "timeoutMs": 300000,
      "maxRows": 10000
    }
  }
}

A ClickHouse dataset carries read-only SQL:

```dataset package_fetches
type: clickhouse
profile: scarf-clickhouse
query: |
  SELECT toDate(timestamp) AS day, count() AS fetches
  FROM package_downloads
  WHERE timestamp >= now() - INTERVAL 30 DAY
  GROUP BY day
  ORDER BY day
```

A Metabase dataset executes a saved question by numeric question/card ID. Optional parameters: | is JSON using Metabase's parameter payload format:

```dataset revenue
type: metabase
profile: scarf-metabase
question: 123
parameters: |
  []
```

For a reproducible notebook, put the native SQL directly in the dataset block instead of relying on a mutable saved question. The profile must provide either a positive databaseId or a databaseIdEnv naming an environment variable that contains it:

```dataset recent_organizations
type: metabase
profile: scarf-metabase
query: |
  SELECT name, billing_email, created_at
  FROM organizations
  ORDER BY created_at DESC
  LIMIT 25
```

Use either dataset in ordinary DuckDB SQL, materialize the result as an org table with org2 query-data, then attach a ```chart block to that named result. ClickHouse requests set server-side read-only mode, row limits, execution timeouts, and JSON output. Metabase requests use API-key authentication and saved-question JSON export. Both adapters enforce response-size and row limits locally as well.

For notebooks with multiple remote sources, declare each result's dependency edge explicitly with sources=DATASET_ID[,DATASET_ID...]. Org2 validates the named sources, loads only those datasets for that result, and records them in materialization provenance:

```sql results=recent_organizations sources=recent_organizations_source freshness=24h
SELECT * FROM recent_organizations_source
ORDER BY created_at DESC
```

Refreshing data is always explicit. Use org2 query-data --apply to insert the selected result after its SQL block or replace the existing provenance-stamped result in place. HTML rendering and opening a note in the Mac app only consume tables already present in the document; they never contact Metabase or ClickHouse. Valid chart declarations over those tables are rendered automatically as responsive inline SVG in the Mac app and static HTML exports.

Use type: table with source: NAME when the source data already lives in a named org2 table:

#+name: raw_fetches
| state | fetches |
|-------+---------|
| CA    | 42      |
| NY    | 24      |

```dataset fetches
type: table
source: raw_fetches
engine: duckdb
```

```sql results=fetches_total
SELECT sum(fetches) AS fetches
FROM fetches
```

This is deliberately scoped to explicit local/ad hoc data sources. Org2 stores the source path, read-only URL, table/view name, optional column and primary-key metadata, credential/config reference, query, and materialized result/provenance; secrets, bearer tokens, and remote warehouse credentials belong in external tooling, not in notes.

Presentations

Org2 can compile an Org-shaped outline into a backend-neutral presentation model and render that model as Beamer LaTeX or PDF:

org2 export beamer --file talk.org2
org2 export beamer --file talk.org2 --out talk.tex --apply
org2 export beamer --file talk.org2 --out talk.pdf --pdf --apply

Export previews by default. --pdf invokes pdflatex twice; use --latex-engine COMMAND to select another installed LaTeX engine.

For pdfLaTeX compatibility, common Unicode arrows, comparison operators, checkmarks, bullets, dashes, and related symbols are translated to equivalent LaTeX commands in prose, headings, metadata, and literal/source blocks. This includes symbols such as , , , and that pdfLaTeX's default UTF-8 layer does not define directly.

The frame level defaults to 2 and follows the familiar Org convention in #+OPTIONS: H:2: level-one headlines become sections and level-two headlines become slides. #+ORG2_SLIDE_LEVEL:, #+SLIDE_LEVEL:, and legacy #+BEAMER_FRAME_LEVEL: can set the level explicitly. toc:t adds an outline frame.

A minimal talk is ordinary Org-shaped text:

#+TITLE: Shipping a local-first compiler
#+AUTHOR: Example Speaker
#+OPTIONS: H:2 toc:t
#+BEAMER_THEME: Madrid

* Why
** Plain text is the interface
- Reviewable in Git
- Editable anywhere
- One source → slides, sites, and agent context

* How
** Shared semantics
The compiler parses once and renders each target from the same structure.

Document metadata and Beamer compatibility keywords include:

  • #+TITLE:, #+AUTHOR:, #+DATE:

  • #+LATEX_CLASS_OPTIONS:

  • #+BEAMER_THEME:, #+BEAMER_COLOR_THEME:, #+BEAMER_FONT_THEME:

  • #+BEAMER_INNER_THEME:, #+BEAMER_OUTER_THEME:

  • repeatable #+LATEX_HEADER: and #+BEAMER_HEADER:

Headlines below a slide become presentation groups. Legacy properties remain accepted so existing Org Beamer talks can move over incrementally:

Org2 propertyLegacy aliasMeaning
SLIDE_ENVBEAMER_ENVblock, note, ignoreheading, or plain
SLIDE_COLUMNBEAMER_COLcolumn width such as 0.45 or 45%
SLIDE_REVEALBEAMER_ACToverlay such as <2-> or 3-
SLIDE_ROLE: noteBEAMER_ENV: notespeaker note content

The :B_note: tag is also accepted for legacy notes. Source blocks make their frame fragile. Lists, nested blocks, quotes, tables, links, inline emphasis, images, and #+ATTR_LATEX image sizing render through the shared syntax tree. A #+RESULTS: image immediately following a source block is omitted from Beamer output, matching Org's default code-export behavior.

Backend-specific escape hatches remain explicit and reviewable:

#+LATEX: \pause

#+BEGIN_EXPORT beamer
\vfill
#+END_EXPORT

Relative image paths resolve from the source file's directory during PDF compilation. Unknown slide environments produce warnings rather than being silently discarded.

The macOS Workspace exposes the same pipeline as an embedded View → Slides PDF preview, including live compilation from an unsaved source-editor draft, and through File → Export Slides as PDF… and File → Export Slides as LaTeX…. See macOS Workspace → Slide preview and export for the application workflow and troubleshooting.

Keyword/directive lines

Org2 parses keyword lines in #+KEY: VALUE form.

Commonly used in export/publish:

  • #+TITLE:

  • #+SUBTITLE:

  • #+AUTHOR:

  • #+DATE:

  • #+DESCRIPTION:

  • #+KEYWORDS:

  • #+LANGUAGE:

  • #+HTML_HEAD:

  • #+LATEX_CLASS_OPTIONS:

  • #+LATEX_HEADER:

  • #+BEAMER_THEME:

  • #+ID:

Unknown directives are preserved for round-tripping.

Comments

  • Line comments: lines beginning with #.

  • Comment blocks are also supported (see Blocks).

Losslessness goal

Org2 aims for practical round-tripping of supported constructs. When behavior is ambiguous, defer to the current implementation and spec fixtures.