Skip to content

Automatic links

When you or an agent write a note’s text, the engine links the notes it mentions by name. A review that says Cary Grant links the note Cary Grant, even when the agent wrote plain text.

The engine links what it’s sure of and asks about the rest. The write’s answer lists the links in links_added. It lists the doubtful mentions in ambiguous_mentions, each with the notes it may name.

The engine knows every note by its file name and its aliases property, rows and plain notes alike. It looks for these names in the text you write:

  • insert_row: body
  • update_row: body, body_sections, body_prepend and body_append, and only the text you pass
  • update_rows: body
  • batch: the same operations inside it

A mention becomes a link when all of these hold:

  • Whole words on one line: case doesn’t matter for a name of two words or more, and the link keeps your spelling, as in [[Cary Grant|cary grant]].
  • A capital for one word: a one-word name links only when you write it with a capital, such as House for the note House or house.
  • Inside a sentence: a one-word name at the start of a sentence, a line, a list item or a label isn’t linked. There every word has a capital, so the capital says nothing.
  • Standing alone: a name glued to a path, a domain, an address or a snake_case word is part of it, as in Documents/House/bill.pdf, greenmart.com or House_new.
  • The first mention: the note gets one link per body, and none when the body links it already.
  • Another note: the engine never links the note you write.
  • A link target: a row in a table’s archive folder is never one. Neither is a row of a table that isn’t a target, as the next section explains.
  • The longest name: Nora Wells links the note Nora Wells, not the note Nora.

The engine leaves these parts of the text alone:

  • The front matter, code blocks and inline code
  • Links, web addresses, email addresses, tags, HyperText Markup Language (HTML), comments and math
  • Headings: a heading’s text is the anchor that body_sections and [[note#heading]] links look for
  • Dataview inline fields: a key:: value line, [key:: value] and (key:: value)
  • Templater blocks: <% ... %>

It also skips names that would link ordinary words:

  • Short names: fewer than three characters, such as Al
  • Numbers, dates and ids: names without a letter, or named after a date, such as 2026-09-27
  • Common words: names such as The, Index or Welcome
  • Lowercase words: a one-word mention in lowercase, such as will for the note Will

In a Markdown table, a link with an alias escapes its bar, as in [[Cary Grant\|cary grant]]. The engine treats a line as a table row when it has a bar outside links and code.

Each link has the form new links take in your vault, as seen from where the note ends up. A write that renames the note links from its new path. links_added lists each link. The links are part of the write. dry_run shows them before you write, and undo_migration takes them back.

Plain notes are always link targets. A table decides for its rows with the option autolink_target:

  • Absent: the rows are targets, unless the table names them by a date or a time. A table created with time_column names its rows that way, as do tables of meals, readings or events.
  • true: the rows are targets, whatever their file names.
  • false: the rows are never targets. Set it on a table of tasks, so an old task Call Oscar never takes the words call Oscar from the person Oscar.

Pass it to create_table or alter_table. alter_table with "default" goes back to the default.

A sync program writes text it copied from elsewhere, such as tasks, calendar events or receipts. That text should stay as it came. Give the program’s token "autolink": false, and its writes link nothing unless a write asks with autolink: true. Tokens and permissions shows how.

The engine never guesses. It leaves a mention as text and lists it in ambiguous_mentions for one of these reasons:

  • same_name: several notes have the name.
  • adjacent: names of two notes stand side by side and may form one name. With the notes Nora and Wells, the text Nora Wells may name neither of them.
  • overlap: names of two notes overlap, such as New York and York City in New York City.
  • part_of_name: a capitalized word next to the name suggests a longer name, such as Philadelphia in The Philadelphia Story.
  • sentence_start: a one-word name starts a sentence, a line, a list item or a label, such as House in House: clean the bathroom.

Each entry gives the mention as text and the notes it may name as candidates. It adds the line around it as context, and a hint. The other mentions of that name in the same text stay as text too. A sentence_start mention is the exception: a later mention of the name inside a sentence is linked. To resolve one:

  1. Read the candidates with read_note.
  2. Decide from the text which note the mention names, if any.
  3. Write the link with update_row and autolink: false, or leave the text as it is.

Write a note’s text as plain prose. The engine links each note the text mentions by name, at its first mention. links_added lists the links. This review of Charade names two actors, each a row, and the director, a plain note.

insert_row

{
"table": "film",
"values": { "title": "Charade", "year": 1963 },
"body": "Cary Grant and Audrey Hepburn in Paris, directed by Stanley Donen.\n\nCary Grant has never been more charming.\n"
}

Answer:

{
"ok": true,
"op": "insert_row",
"files_created": 1,
"planned_creates": ["datanotes/main/public/film/Charade.md"],
"commits": [{ "table": "main.public.film", "version": 2, "operation": "WRITE" }],
"migration_id": "20261012T165228-insert_row-insert-into-film-2",
"links_added": [
{
"path": "datanotes/main/public/film/Charade.md",
"text": "Cary Grant",
"target": "datanotes/main/public/actor/Cary Grant.md",
"link": "[[Cary Grant]]"
},
{
"path": "datanotes/main/public/film/Charade.md",
"text": "Audrey Hepburn",
"target": "datanotes/main/public/actor/Audrey Hepburn.md",
"link": "[[Audrey Hepburn]]"
},
{
"path": "datanotes/main/public/film/Charade.md",
"text": "Stanley Donen",
"target": "People/Stanley Donen.md",
"link": "[[Stanley Donen]]"
}
]
}

datanotes/main/public/film/Charade.md now reads:

---
table: main.public.film
id: 01a13107-a933-7412-93d7-5e9c53bdc049
title: Charade
year: 1963
---
[[Cary Grant]] and [[Audrey Hepburn]] in Paris, directed by [[Stanley Donen]].
Cary Grant has never been more charming.

The second mention of Cary Grant stays as text: one link per note is enough. Paris has no note, so it stays as text too.

update_row links the text it writes, and leaves the rest of the note alone. A note the body links already doesn’t get a second link.

update_row

{
"table": "film",
"path": "datanotes/main/public/film/Charade.md",
"body_append": "Audrey Hepburn wears Givenchy. Watch it with Notorious, the other Cary Grant thriller.\n"
}

Answer:

{
"ok": true,
"op": "update_row",
"notes_changed": 1,
"planned_edits": [
{
"path": "datanotes/main/public/film/Charade.md",
"set": {},
"remove": [],
"body": true
}
],
"commits": [{ "table": "main.public.film", "version": 3, "operation": "UPDATE" }],
"migration_id": "20261012T165228-update_row-update-film",
"links_added": [
{
"path": "datanotes/main/public/film/Charade.md",
"text": "Notorious",
"target": "datanotes/main/public/film/Notorious.md",
"link": "[[Notorious]]"
}
]
}

datanotes/main/public/film/Charade.md now reads:

---
table: main.public.film
id: 01a13107-a933-7412-93d7-5e9c53bdc049
title: Charade
year: 1963
---
[[Cary Grant]] and [[Audrey Hepburn]] in Paris, directed by [[Stanley Donen]].
Cary Grant has never been more charming.
Audrey Hepburn wears Givenchy. Watch it with [[Notorious]], the other Cary Grant thriller.

When the engine can’t tell which note a name means, it leaves the text alone and asks. This review of Notorious names Ingrid Bergman. The vault has your friend Ingrid and the director Bergman, but no note for the actress. The engine links neither.

update_row

{
"table": "film",
"path": "datanotes/main/public/film/Notorious.md",
"body": "Ingrid Bergman and Cary Grant, directed by Alfred Hitchcock.\n"
}

Answer:

{
"ok": true,
"op": "update_row",
"notes_changed": 1,
"planned_edits": [
{
"path": "datanotes/main/public/film/Notorious.md",
"set": {},
"remove": [],
"body": true
}
],
"commits": [{ "table": "main.public.film", "version": 4, "operation": "UPDATE" }],
"migration_id": "20261012T165228-update_row-update-film-2",
"links_added": [
{
"path": "datanotes/main/public/film/Notorious.md",
"text": "Cary Grant",
"target": "datanotes/main/public/actor/Cary Grant.md",
"link": "[[Cary Grant]]"
}
],
"ambiguous_mentions": [
{
"path": "datanotes/main/public/film/Notorious.md",
"text": "Ingrid Bergman",
"reason": "adjacent",
"candidates": ["Directors/Bergman.md", "Friends/Ingrid.md"],
"context": "Ingrid Bergman and Cary Grant, directed by Alfred Hitchcock.",
"hint": "\"Ingrid Bergman\" may name one thing, and each note names a part of it: read the notes and the text around it, then write the link with update_row and autolink: false, or leave the text as it is."
}
]
}

The agent reads both candidates and the line around the mention. Neither note is the actress, so it adds her as an actor:

insert_row

{ "table": "actor", "values": { "name": "Ingrid Bergman" } }

Now the longest name wins. The same text links the actress, and nobody is asked:

update_row

{
"table": "film",
"path": "datanotes/main/public/film/Notorious.md",
"body": "Ingrid Bergman and Cary Grant, directed by Alfred Hitchcock.\n"
}

Answer:

{
"ok": true,
"op": "update_row",
"notes_changed": 1,
"planned_edits": [
{
"path": "datanotes/main/public/film/Notorious.md",
"set": {},
"remove": [],
"body": true
}
],
"commits": [{ "table": "main.public.film", "version": 5, "operation": "UPDATE" }],
"migration_id": "20261012T165228-update_row-update-film-3",
"links_added": [
{
"path": "datanotes/main/public/film/Notorious.md",
"text": "Ingrid Bergman",
"target": "datanotes/main/public/actor/Ingrid Bergman.md",
"link": "[[Ingrid Bergman]]"
},
{
"path": "datanotes/main/public/film/Notorious.md",
"text": "Cary Grant",
"target": "datanotes/main/public/actor/Cary Grant.md",
"link": "[[Cary Grant]]"
}
]
}

datanotes/main/public/film/Notorious.md now reads:

---
table: main.public.film
id: 01a13107-a929-72a6-9809-ddeee0b36fab
title: Notorious
year: 1946
---
[[Ingrid Bergman]] and [[Cary Grant]], directed by Alfred Hitchcock.

To link a candidate yourself, write the link in the text and pass autolink: false. The engine then writes the text as you give it. If no candidate fits and the name deserves no note, leave the text as it is.

A table’s rows are link targets by default. A table whose rows are named by a date or a time is the exception, such as a table of meals or screenings. The table option autolink_target decides for one table. These tasks say no, so the task Call Ingrid never takes a mention of Ingrid:

create_table

{
"name": "task",
"columns": [
{
"name": "title",
"type": "string",
"required": true,
"description": "What to do, in a few words"
}
],
"filename": "{title}",
"description": "Things I have to do",
"autolink_target": false
}

Answer:

{ "ok": true }

insert_row

{ "table": "task", "values": { "title": "Call Ingrid" } }

insert_row

{
"table": "film",
"values": { "title": "Persona", "year": 1966 },
"body": "After the film, call Ingrid to talk about it.\n"
}

Answer:

{
"ok": true,
"op": "insert_row",
"files_created": 1,
"planned_creates": ["datanotes/main/public/film/Persona.md"],
"commits": [{ "table": "main.public.film", "version": 6, "operation": "WRITE" }],
"migration_id": "20261012T165228-insert_row-insert-into-film-3",
"links_added": [
{
"path": "datanotes/main/public/film/Persona.md",
"text": "Ingrid",
"target": "Friends/Ingrid.md",
"link": "[[Ingrid]]"
}
]
}

datanotes/main/public/film/Persona.md now reads:

---
table: main.public.film
id: 01a13107-a970-73a0-8601-8b115ec716dc
title: Persona
year: 1966
---
After the film, call [[Ingrid]] to talk about it.

Screenings are named by the time they start, so they are no targets. This one has an alias you mention in your notes:

create_table

{
"name": "screening",
"time_column": "starts_at",
"columns": [
{
"name": "starts_at",
"type": "datetime",
"required": true,
"description": "When the screening starts"
},
{
"name": "aliases",
"type": "list",
"items": "string",
"description": "Other names of the screening"
}
],
"description": "Film club screenings"
}

insert_row

{
"table": "screening",
"values": { "starts_at": "2026-10-09T21:00", "aliases": ["Swedish night"] }
}

autolink_target: true makes the screenings link targets:

alter_table

{ "table": "screening", "autolink_target": true }

Answer:

{ "ok": true }

update_row

{
"table": "film",
"path": "datanotes/main/public/film/Persona.md",
"body_append": "We watched it at Swedish night.\n"
}

Answer:

{
"ok": true,
"op": "update_row",
"notes_changed": 1,
"planned_edits": [
{
"path": "datanotes/main/public/film/Persona.md",
"set": {},
"remove": [],
"body": true
}
],
"commits": [{ "table": "main.public.film", "version": 7, "operation": "UPDATE" }],
"migration_id": "20261012T165228-update_row-update-film-4",
"links_added": [
{
"path": "datanotes/main/public/film/Persona.md",
"text": "Swedish night",
"target": "datanotes/main/public/screening/2026-10-09T2100.md",
"link": "[[2026-10-09T2100|Swedish night]]"
}
]
}

datanotes/main/public/film/Persona.md now reads:

---
table: main.public.film
id: 01a13107-a970-73a0-8601-8b115ec716dc
title: Persona
year: 1966
---
After the film, call [[Ingrid]] to talk about it.
We watched it at [[2026-10-09T2100|Swedish night]].

metastore.yaml keeps the option in the table’s options:

datanotes/metastore.yaml now reads:

# Metastore (Datanotes): catalogs, schemas, tables, columns, views.
id: 01a13107-a905-725e-b35f-61ac5942597f
config:
history: true
catalogs:
main:
schemas:
public:
tables:
actor:
description: Actors of the classic films I watch
options:
filename: "{name}"
archive: _archive
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: name
type: string
required: true
description: The actor's name, as in the credits
film:
description: Classic films I watched, with my reviews
options:
filename: "{title}"
archive: _archive
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: title
type: string
required: true
description: The film's title, as on its poster
- name: year
type: integer
description: Year the film first came out
task:
description: Things I have to do
options:
filename: "{title}"
archive: _archive
autolink_target: false
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: title
type: string
required: true
description: What to do, in a few words
screening:
description: Film club screenings
options:
filename: "{starts_at:YYYY-MM-DD[T]HHmm}"
archive: _archive
autolink_target: true
columns:
- name: id
type: string
unique: true
identity: uuid
required: true
dense: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
- name: starts_at
type: datetime
required: true
description: When the screening starts
- name: aliases
type: list
items: string
description: Other names of the screening

Pass autolink_target: "default" to alter_table to follow the default again. Archived rows are never link targets, whatever the option says.

autolink: false writes one text as you give it. To turn the links off for every write, call configure_database with autolink: false. A write can still ask for them with autolink: true.

insert_row

{
"table": "film",
"values": { "title": "His Girl Friday", "year": 1940 },
"body": "Cary Grant and Rosalind Russell, the fastest talkers in film.\n",
"autolink": false
}

Answer:

{
"ok": true,
"op": "insert_row",
"files_created": 1,
"planned_creates": ["datanotes/main/public/film/His Girl Friday.md"],
"commits": [{ "table": "main.public.film", "version": 8, "operation": "WRITE" }],
"migration_id": "20261012T165228-insert_row-insert-into-film-4"
}

datanotes/main/public/film/His Girl Friday.md now reads:

---
table: main.public.film
id: 01a13107-a9a5-7efc-9f42-a66d3adf6b42
title: His Girl Friday
year: 1940
---
Cary Grant and Rosalind Russell, the fastest talkers in film.

configure_database

{ "autolink": false }

Answer:

{
"default_catalog": "main",
"default_schema": "public",
"default_partition": "",
"default_filename": "YYYY-MM-DD[T]HHmm",
"archive_folder": "_archive",
"timezone": "",
"history": true,
"autolink": false
}

metastore.yaml keeps the setting under config::

datanotes/metastore.yaml now reads:

# Metastore (Datanotes): catalogs, schemas, tables, columns, views.
id: 01a13107-a905-725e-b35f-61ac5942597f
config:
history: true
autolink: false
catalogs:
main:
schemas:
public:
tables:
actor:
description: Actors of the classic films I watch
options:
filename: "{name}"
archive: _archive
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: name
type: string
required: true
description: The actor's name, as in the credits
film:
description: Classic films I watched, with my reviews
options:
filename: "{title}"
archive: _archive
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: title
type: string
required: true
description: The film's title, as on its poster
- name: year
type: integer
description: Year the film first came out
task:
description: Things I have to do
options:
filename: "{title}"
archive: _archive
autolink_target: false
columns:
- name: id
type: string
required: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
unique: true
identity: uuid
dense: true
- name: title
type: string
required: true
description: What to do, in a few words
screening:
description: Film club screenings
options:
filename: "{starts_at:YYYY-MM-DD[T]HHmm}"
archive: _archive
autolink_target: true
columns:
- name: id
type: string
unique: true
identity: uuid
required: true
dense: true
description: "Unique id of the row (UUIDv7): set when the row is created, never changes"
- name: starts_at
type: datetime
required: true
description: When the screening starts
- name: aliases
type: list
items: string
description: Other names of the screening

This call turns them on again:

configure_database

{ "autolink": true }

Answer:

{ "autolink": true }