# Automatic links

> The text you write links the notes it mentions by name. See what the engine links, how it asks about a doubtful name, and how to turn it off.

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.

## What the engine links

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.

## Which notes a link may point to

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.

## Writes from sync programs

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](https://datanotes.dev/guides/security/tokens/#turn-automatic-links-off-for-a-sync-program) shows how.

## When the engine asks

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.

## Link the notes a text mentions

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`**

```json
{
  "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:

```json
{
  "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:

```markdown
---
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.

## Link only the text you add

`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`**

```json
{
  "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:

```json
{
  "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:

```markdown
---
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.
```

## Resolve a doubtful mention

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`**

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

Answer:

```json
{
  "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`**

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

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

**`update_row`**

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

Answer:

```json
{
  "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:

```markdown
---
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.

## Choose the tables a link may point to

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`**

```json
{
  "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:

```json
{ "ok": true }
```

**`insert_row`**

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

**`insert_row`**

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

Answer:

```json
{
  "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:

```markdown
---
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`**

```json
{
  "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`**

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

`autolink_target: true` makes the screenings link targets:

**`alter_table`**

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

Answer:

```json
{ "ok": true }
```

**`update_row`**

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

Answer:

```json
{
  "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:

```markdown
---
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:

```yaml
# 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.

## Turn automatic links off

`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`**

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

Answer:

```json
{
  "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:

```markdown
---
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`**

```json
{ "autolink": false }
```

Answer:

```json
{
  "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:

```yaml
# 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`**

```json
{ "autolink": true }
```

Answer:

```json
{ "autolink": true }
```
