Bases
A base is a named set of columns that tables inherit, like a parent class. One query reads every table that extends a base, on the base’s columns, and a new table that extends it appears in the results without changes to any view.
The examples on this page keep a diary of classic films in its own catalog, diary.
Share columns between tables with bases
Section titled “Share columns between tables with bases”A base is a named set of columns that tables inherit, like a parent class. Every table that extends entry gets added_on and note with the same meaning. select_rows on base:entry reads all those tables at once, here films and actors.
define_base
{ "name": "entry", "description": "A note of the film diary", "columns": [ { "name": "added_on", "type": "date", "description": "When I added it to the diary" }, { "name": "note", "type": "string", "description": "Free text" } ]}Answer:
{ "ok": true, "op": "define_base", "planned_texts": ["datanotes/metastore.yaml"], "migration_id": "20261012T165235-define_base-create-base-entry"}set_table_bases
{ "table": "film", "bases": ["entry"] }Answer:
{ "ok": true, "op": "set_table_bases", "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.film", "version": 6, "operation": "SET TABLE BASES" } ], "migration_id": "20261012T165235-set_table_bases-diary-classics-film-extends-en"}create_table
{ "catalog": "diary", "schema": "classics", "name": "actors", "description": "Actors of the films in the diary", "columns": [ { "name": "name", "type": "string", "required": true, "description": "The actor's name, as credited" } ], "filename": "{name}"}set_table_bases
{ "table": "actors", "bases": ["entry"] }insert_row
{ "table": "actors", "values": { "name": "Max Schreck", "added_on": "2026-10-05", "note": "The face of Nosferatu" }}update_row
{ "table": "film", "path": "datanotes/diary/classics/film/Nosferatu.md", "set": { "added_on": "2026-10-05" }}select_rows
{ "table": "base:entry", "order_by": "path" }Answer:
{ "table": "base:entry", "total": 2, "rows": [ { "path": "datanotes/diary/classics/actors/Max Schreck.md", "id": "01a13107-c616-7110-8f4d-019ced2f41b9", "added_on": "2026-10-05", "note": "The face of Nosferatu", "_source": "diary.classics.actors" }, { "path": "datanotes/diary/classics/film/Nosferatu.md", "id": "01a13107-c599-7aa2-824d-cebe56b78f22", "added_on": "2026-10-05", "note": null, "_source": "diary.classics.film" } ]}Change a base and every table that extends it
Section titled “Change a base and every table that extends it”Replacing a base changes every table that extends it, in one operation. rename_columns renames a column on their rows. define_base refuses to drop a column unless drop_columns names it, and then the engine removes the column’s values from the rows too.
define_base
{ "name": "entry", "columns": [ { "name": "added_on", "type": "date", "description": "When I added it to the diary" }, { "name": "comment", "type": "string", "description": "Free text" } ], "rename_columns": { "note": "comment" }}Answer:
{ "ok": true, "op": "define_base", "notes_changed": 1, "planned_edits": [ { "path": "datanotes/diary/classics/actors/Max Schreck.md", "set": { "comment": "The face of Nosferatu" }, "remove": ["note"] } ], "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.actors", "version": 3, "operation": "DEFINE BASE" } ], "migration_id": "20261012T165235-define_base-alter-base-entry"}select_rows
{ "table": "actors", "columns": ["name", "comment"] }Answer:
{ "table": "diary.classics.actors", "total": 1, "rows": [ { "path": "datanotes/diary/classics/actors/Max Schreck.md", "name": "Max Schreck", "comment": "The face of Nosferatu" } ]}define_base refuses a base without comment until the call says the values may go:
define_base
{ "name": "entry", "columns": [ { "name": "added_on", "type": "date", "description": "When I added it to the diary" } ]}Refused:
base "entry" would lose column "comment": list it in drop_columns (its values are removed from the rows) or rename_columnsHint: Check the arguments of define_base: name (string, required), columns (list of objects, required), bases (list of strings), description (string), rename_columns (object), drop_columns (list of strings), dry_run (boolean).Docs: https://datanotes.dev/reference/operations/schema.md#define_base (offline: read_docs("reference/operations/schema#define_base"))define_base
{ "name": "entry", "columns": [ { "name": "added_on", "type": "date", "description": "When I added it to the diary" } ], "drop_columns": ["comment"]}Answer:
{ "ok": true, "op": "define_base", "notes_changed": 1, "planned_edits": [ { "path": "datanotes/diary/classics/actors/Max Schreck.md", "set": {}, "remove": ["comment"] } ], "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.actors", "version": 4, "operation": "DEFINE BASE" } ], "migration_id": "20261012T165235-define_base-alter-base-entry-2"}Build bases on bases and link a table to itself
Section titled “Build bases on bases and link a table to itself”bases builds a base on others. A table that extends credited gets films, plus added_on from entry, and base:entry still reads it. In a base, ref: "self" means the table that inherits it, so tree gives each table a parent link to its own rows.
define_base
{ "name": "credited", "bases": ["entry"], "description": "Someone in the credits of a film", "columns": [ { "name": "films", "type": "list", "items": "link", "ref": "diary.classics.film", "description": "The films they’re credited in" } ]}Answer:
{ "ok": true, "op": "define_base", "planned_texts": ["datanotes/metastore.yaml"], "migration_id": "20261012T165235-define_base-create-base-credited"}set_table_bases
{ "table": "actors", "bases": ["credited"] }Answer:
{ "ok": true, "op": "set_table_bases", "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.actors", "version": 5, "operation": "SET TABLE BASES" } ], "migration_id": "20261012T165235-set_table_bases-diary-classics-actors-extends--2"}define_base
{ "name": "tree", "description": "Rows arranged in a tree", "columns": [ { "name": "parent", "type": "link", "ref": "self", "hierarchy": true, "description": "The row above this one" } ]}Answer:
{ "ok": true, "op": "define_base", "planned_texts": ["datanotes/metastore.yaml"], "migration_id": "20261012T165235-define_base-create-base-tree"}create_table
{ "catalog": "diary", "schema": "classics", "name": "genre", "description": "Eras and genres of cinema, as a tree", "columns": [ { "name": "name", "type": "string", "required": true, "description": "The name of the era or genre" } ], "filename": "{name}"}set_table_bases
{ "table": "genre", "bases": ["tree"] }Answer:
{ "ok": true, "op": "set_table_bases", "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.genre", "version": 1, "operation": "SET TABLE BASES" } ], "migration_id": "20261012T165235-set_table_bases-diary-classics-genre-extends-t"}insert_row
{ "table": "genre", "values": { "name": "Cinema" } }insert_row
{ "table": "genre", "values": { "name": "Silent film", "parent": "[[Cinema]]" } }In genre, parent links rows of diary.classics.genre:
describe_table
{ "table": "genre" }Answer:
[ { "name": "id", "type": "string" }, { "name": "parent", "type": "link", "ref": "diary.classics.genre", "hierarchy": true }, { "name": "name", "type": "string" }]select_rows
{ "table": "base:entry", "columns": ["added_on"], "order_by": "path" }Answer:
{ "table": "base:entry", "total": 2, "rows": [ { "path": "datanotes/diary/classics/actors/Max Schreck.md", "added_on": "2026-10-05", "_source": "diary.classics.actors" }, { "path": "datanotes/diary/classics/film/Nosferatu.md", "added_on": "2026-10-05", "_source": "diary.classics.film" } ]}bases: [] removes a table’s bases. set_table_bases refuses it while rows hold values of the inherited columns, like the parent of Silent film:
set_table_bases
{ "table": "genre", "bases": [] }Refused:
set_table_bases refused — nothing was written:invalid datanotes/diary/classics/genre/Silent film.md: parent is not a column of table "genre"Hint: Write only the table's columns: describe_table lists them. To keep a new front matter key, add_column first. Then call set_table_bases again (dry_run: true checks without writing).Docs: https://datanotes.dev/guides/database-objects/tables.md#describe-a-table-before-writing-to-it (offline: read_docs("guides/database-objects/tables#describe-a-table-before-writing-to-it"))On a table with no such values, set_table_bases removes the inherited columns from the definition. Here that table is an empty copy of genre:
clone_table
{ "source": "genre", "target": "genre_draft", "copy_rows": false }set_table_bases
{ "table": "genre_draft", "bases": [] }Answer:
{ "ok": true, "op": "set_table_bases", "planned_texts": ["datanotes/metastore.yaml"], "commits": [ { "table": "diary.classics.genre_draft", "version": 1, "operation": "SET TABLE BASES" } ], "migration_id": "20261012T165235-set_table_bases-diary-classics-genre_draft-ext"}