Typed Graph Schema (TGS) 0.1
| Version | 0.1 (published 2026-10-06; immutable) |
| This version | https://volland.github.io/obsigraph/spec/tgs/v0.1/ |
| JSON Schema | https://volland.github.io/obsigraph/spec/tgs/v0.1/tgs.schema.json |
| Namespace | https://volland.github.io/obsigraph/ns/tgs# (prefix tgs:) |
| Editor | Volodymyr Pavlyshyn |
| Source | spec/tgs/ in github.com/Volland/obsigraph |
| License | Text: CC BY 4.0. JSON Schema and examples: MIT. |
TGS describes the types of a knowledge graph kept as markdown notes: what properties a note of a type has, which typed links it may have to which other types, and what properties those links carry. A schema is ordinary YAML frontmatter in ordinary notes, so people can write and review it by hand, and it maps to and from W3C SHACL so the same ontology can be exchanged with RDF tools.
TGS is independent of any one application. Typed Graph (an Obsidian plugin and CLI) is the reference implementation.
1. Conventions
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be read as described in RFC 2119 when they appear in capitals.
- A vault is a folder of markdown files. Paths are vault-relative and use
/. - A note is a markdown file. Its title is its file name without the
.mdextension. - Frontmatter is a YAML 1.2 mapping between a first line
---and the next line---or.... - A data note is any note that is described by a schema. A schema note is a note that declares schemas.
- A reader is a program that reads schema notes. Section 14 defines conformance levels.
2. Data model
TGS types a labelled property graph built from notes:
- Each note is a node. Its labels are the type names in its frontmatter
type(a string or a list of strings), followed by those intypes, without duplicates. Its properties are its frontmatter keys. - An edge has a source note, a target note, an edge type (a name) and properties (a mapping of scalars or lists). How edges are written in markdown is up to the host. In the reference implementation an edge is one line,
worksAt:: [[Acme]] {since: 2020}. - A link to a note that does not exist creates a stub node with no labels.
3. Schema notes
A schema note is a markdown note located directly in the schema folder. The schema folder is a reader setting whose default is Types/. Notes in subfolders of the schema folder, and notes elsewhere in the vault, MUST NOT be read as schema notes.
A schema note's frontmatter MAY contain these keys, in any combination:
| Key | Value | Declares |
|---|---|---|
schema |
type declaration (section 4) | the type named by the note's title |
schemas |
mapping: type name → type declaration | any number of types |
edgeTypes |
mapping: edge type name → edge type declaration (section 6) | edge types |
prefixes |
mapping: prefix → IRI | CURIE prefixes for the whole vault (section 7) |
tgs |
version string, e.g. "0.1" |
the TGS version the note is written for (section 12) |
A schema note with none of schema, schemas, edgeTypes and prefixes declares an empty type named by its title. All other frontmatter keys belong to the note itself and readers MUST ignore them. The body of a schema note is free text, except that the body of a note with schema is the template of that type (section 8).
One note can therefore describe a single type:
# Types/Person.md
---
schema:
properties:
name: text
email: {kind: text, required: true}
edges: {worksAt: Company, knows: Person}
---
## Notes
or a whole subsystem:
# Types/People and Orgs.md
---
schemas:
Person:
properties: {name: text, email: {kind: text, required: true}}
edges: {worksAt: Company, knows: Person}
Company:
uri: schema:Organization
properties: {name: text, founded: date}
edgeTypes:
worksAt:
from: Person
to: Company
properties: {since: date, role: {kind: text, values: [engineer, manager]}}
---
How people and organisations relate.
3.1 Duplicates
When the same type name, edge type name or prefix is declared more than once, readers MUST use the declaration from the schema note whose path sorts first (by code point) and MUST report a duplicate-declaration naming both notes. A prefix declared twice with the same IRI is not a duplicate.
4. Type declarations
A type declaration is a mapping with these optional keys:
| Key | Value | Meaning |
|---|---|---|
uri |
IRI or CURIE | identifier of the type; default: base IRI + type name |
properties |
section 5 | frontmatter properties of notes of this type |
edges |
section 5.3 | allowed outgoing edges; absent means any edge is allowed |
template |
link | template note for new notes of this type (section 8) |
visualization |
mapping | display hints (section 11); style is an alias |
An empty mapping ({}) or a null value declares a type with no constraints.
5. Properties and edges
5.1 Property declarations
properties is either a mapping from property name to a property declaration, or a list whose items are a property name (a text property) or a mapping with a name key. A property declaration is either a kind shorthand (born: date) or a mapping:
| Key | Value | Default | Meaning |
|---|---|---|---|
kind |
one of the kinds below | text |
value type |
required |
boolean | false |
the note must have a non-empty value |
many |
boolean | false |
the value is a list (always true for kind list) |
values |
list of scalars | none | allowed values (an enumeration) |
default |
any YAML value | none | value written into new notes |
uri |
IRI or CURIE | base IRI + name | identifier of the property |
A null declaration (name:) is a text property. Within one type, the first declaration of a property name wins.
5.2 Kinds
| Kind | Values | Obsidian property type | XSD datatype |
|---|---|---|---|
text |
string | Text | xsd:string |
number |
number | Number | xsd:decimal |
boolean |
true / false |
Checkbox | xsd:boolean |
date |
YYYY-MM-DD |
Date | xsd:date |
datetime |
ISO 8601 date and time | Date & time | xsd:dateTime |
link |
a link to a note or a URL | Text (link) | none; an IRI |
list |
list of strings | List | xsd:string, many |
An unknown kind MUST be read as text and reported as unknown-kind. TGS 0.1 does not require readers to check that values match their kind.
5.3 Edges of a type
edges lists the edge types a note of this type may have as outgoing edges. Two forms are allowed.
The list form names edge types, each accepting any target: edges: [knows, worksAt].
The map form maps each edge type to its target type, or to a list of target types, or to null (any target), or to a mapping:
| Key | Value | Default | Meaning |
|---|---|---|---|
target |
type name or list | any | allowed target types |
many |
boolean | true |
more than one edge of this type is allowed |
required |
boolean | false |
at least one edge of this type is required |
edges:
worksAt: Company # one target type, many, optional
knows: [Person, Bot] # several target types
mentor: {target: Person, many: false, required: true}
likes: # any target
Properties default to a single value and edges default to many, because repeated edges of one type are normal (knows) and frontmatter values usually are not. An empty list or mapping (edges: []) allows no outgoing edges.
6. Edge type declarations
An edge type declaration describes an edge type everywhere it is used:
| Key | Value | Meaning |
|---|---|---|
from |
type name or list | allowed source types; absent means any |
to |
type name or list | allowed target types; absent means any |
properties |
as in 5.1 | properties written on the edge |
uri |
IRI or CURIE | identifier of the edge type (an RDF predicate); default: base IRI + name |
visualization |
mapping | display hints for edges of this type (section 11); style is an alias |
Undeclared edge properties are allowed. When both a type's edges entry and the edge type declare targets, the type's entry wins for sources of that type.
7. Identifiers
Every type, property and edge type has an IRI, so that schemas can be exchanged.
uriholds a full IRI (https://schema.org/Person,urn:isbn:0451450523) or a CURIE (schema:Person).- A value whose part after the first colon starts with
//is a full IRI. Otherwisep:localexpands to the IRI of prefixpfollowed bylocalwhenpis a known prefix, and is a full IRI whenpis one of the schemeshttp,https,urn,mailto,tag,didandfile. - Built-in prefixes:
rdf,rdfs,xsd,sh(SHACL),schema(https://schema.org/),foafandtgs. Aprefixeskey in any schema note adds prefixes for the whole vault and MAY override a built-in one. - A CURIE with an unknown prefix MUST be reported as
unknown-prefix. - A declaration without
uriis named by the base IRI followed by its percent-encoded name. The base IRI is a reader setting whose default isurn:tgs:.
8. Templates
A reader that creates notes from types MUST build the new note's frontmatter from type: <name> and every declared default, and MUST take the body from the first of:
- the note that the type's
templatelink points to, without its frontmatter; - the body of the schema note, when the type is declared with
schema, if that body is not empty; - a template generated from the schema.
A template link is a wikilink ([[Templates/Person]], resolved as the host resolves links), a markdown link ([Person](../Templates/Person.md), relative to the schema note), or a vault-relative path (Templates/Person, .md optional).
A generated template SHOULD write a key for every declared property (its default, [] for many, empty otherwise) and one placeholder per declared edge that does not itself create an edge, so that a new note is complete but creates no links until it is filled in. The reference implementation writes ## Notes followed by ## Relations with one - <edgeType>:: line per edge.
9. Validation
Validation is advisory. A validator MUST report diagnostics and MUST NOT remove notes or edges from the graph. Stub targets and targets without labels are never reported as having the wrong type.
For a node with labels L1, L2, …, the effective schema merges the schemas of its labels in order: the first declaration of a property wins; edge rules are unioned per edge type, with targets unioned (any target if one rule allows any), many if any rule allows many and required if any rule requires it; edges is unrestricted only if no label declares it.
| Diagnostic | Reported when |
|---|---|
invalid-declaration |
a key has a value of the wrong shape (e.g. edges: 5) |
unknown-kind |
a property declares an unsupported kind |
duplicate-declaration |
section 3.1 |
unknown-prefix |
section 7 |
unsupported-version |
section 12 |
unknown-key |
section 12 |
missing-property |
a required property is absent, null or empty |
value-not-allowed |
a property value, or an item of a list value, is not in values |
unexpected-list |
a property that is not many holds a list |
edge-not-allowed |
an outgoing edge type is not in the effective edges |
missing-edge |
a required edge has no edge of its type |
too-many-edges |
an edge that is not many has more than one edge of its type |
wrong-target-type |
the target has labels and none is an allowed target type |
wrong-source-type |
the source has labels and none is in the edge type's from |
missing-edge-property |
a required edge property is absent from the edge |
edge-value-not-allowed |
an edge property value is not in its values |
Readers SHOULD report each diagnostic with the note path and, for edges, the line of the edge. Message text is not specified.
10. Multiple labels
A note with type: [Person, Employee] is checked against the merged schema of section 9. The style of a node is taken attribute by attribute from the first label that supplies it.
11. Visualization (informative)
visualization is a hint for graph displays and does not affect validation. The reference implementation reads color (CSS color), shape, icon (a Lucide icon name) and label (a property shown as the node label) for types, color and line (solid, dashed, dotted) for edge types, and an edges mapping inside a type's visualization that styles edge types. An edge type's own visualization wins over a type's visualization.edges. Other readers MAY ignore visualization or read further keys.
12. Versioning
A schema note MAY declare tgs: "<major>.<minor>". A reader implementing version M.N:
- MUST read a note that declares the same major version and a higher minor version, and MUST report each key inside a type or edge type declaration that it does not know as
unknown-key; - MUST report a note that declares another major version as
unsupported-versionand MUST NOT use its declarations; - MUST read notes without
tgsas version 0.1.
Minor versions only add optional keys. A published version never changes.
13. SHACL mapping
TGS maps to the W3C Shapes Constraint Language (SHACL) so that schemas can be exchanged with RDF tools. An exporter writes a schema set as SHACL in Turtle. An importer reads SHACL into TGS declarations.
13.1 Types and properties
Each type becomes a sh:NodeShape named base IRI + type name + Shape:
| TGS | SHACL |
|---|---|
type Person |
sh:targetClass = the type's IRI; sh:name "Person" |
| property | a property shape with sh:path = the property's IRI and sh:name = its name |
kind text, number, boolean, date, datetime |
sh:datatype per section 5.2 |
kind link |
sh:nodeKind sh:IRI |
kind list |
sh:datatype xsd:string and tgs:kind "list", no sh:maxCount |
required: true |
sh:minCount 1 |
not many |
sh:maxCount 1 |
scalar default |
sh:defaultValue |
list default |
tgs:default with the value as a JSON literal |
values |
sh:in ( … ) |
| declaration order | sh:order 0, 1, … |
13.2 Edges of a type
Each entry of a type's edges becomes a property shape on the type's shape:
| TGS | SHACL |
|---|---|
| edge type | sh:path = the edge type's IRI; sh:name = its name |
| one target | sh:class = the target type's IRI |
| several targets | sh:or ( [ sh:class A ] [ sh:class B ] ) |
| any target | sh:nodeKind sh:IRI and tgs:edge true |
required: true |
sh:minCount 1 |
many: false |
sh:maxCount 1 |
edges declared |
tgs:edgesClosed true on the node shape |
sh:closed is not used, because it would also close frontmatter properties, which are open in TGS. tgs:edgesClosed records the allow-list without changing what SHACL engines validate.
13.3 Edge types
Each edge type becomes a sh:NodeShape named base IRI + name + EdgeShape that describes the edge as an RDF reification (rdf:Statement):
:worksAtEdgeShape
a sh:NodeShape ;
sh:name "worksAt" ;
sh:property [ sh:path rdf:predicate ; sh:hasValue schema:worksFor ] ;
sh:property [ sh:path rdf:subject ; sh:class :Person ] ;
sh:property [ sh:path rdf:object ; sh:class schema:Organization ] ;
sh:property [ sh:path :since ; sh:name "since" ; sh:datatype xsd:date ; sh:maxCount 1 ; sh:order 0 ] .
The rdf:predicate constraint identifies the edge type; rdf:subject carries from and rdf:object carries to (with sh:or for several types); the remaining property shapes are the edge's properties. The shape declares no target, so validators apply it to reified statements on request. This uses only SHACL 1.0 and the RDF reification vocabulary; a later TGS version may add RDF 1.2 reifiers.
13.4 Annotations
What SHACL cannot express is kept as annotations, which SHACL engines ignore:
| Term | On | Value |
|---|---|---|
tgs:note |
node shape | path of the schema note that declared it |
tgs:template |
node shape | the template link |
tgs:templateBody |
node shape | the schema note body of a schema type |
tgs:visualization |
node shape | the visualization mapping as a JSON literal |
tgs:edgesClosed |
node shape | true when the type declares edges |
tgs:edge |
property shape | true for an edge entry without target types |
tgs:kind |
property shape | "list" for kind list |
tgs:default |
property shape | a non-scalar default as a JSON literal |
13.5 Import
An importer MUST read:
- a node shape with
sh:targetClassas a type named bysh:name, else by the local name of the class IRI; - a property shape with
sh:datatype(mapped back per section 5.2; integer, double and similar numeric types arenumber) orsh:nodeKind sh:IRIas a property; - a property shape with
sh:class,sh:node(a shape with a target class), ansh:orof singlesh:classalternatives, ortgs:edge true, as an edge of the type; its name issh:name, else the name of the edge type with that predicate, else the local name of the path; - a node shape whose property shape on
rdf:predicatehassh:hasValueas an edge type, named bysh:name, else by the local name of the predicate; sh:minCount≥ 1 asrequired, the absence ofsh:maxCount 1asmany, and the annotations of 13.4;- a
uriwhenever an IRI differs from the base IRI + name, written as a CURIE when a prefix matches.
An importer MUST report, and otherwise skip, every construct outside this subset, including sh:closed, sh:pattern, sh:minLength, sh:qualifiedValueShape, sh:sparql, sh:and, sh:xone, sh:not, property paths other than a single IRI, unknown datatypes (read as text), sh:maxCount greater than 1 (read as many) and node shapes without a target class. It MUST import the rest of each shape.
An importer that writes notes SHOULD write one note per type by default (or group types by tgs:note), MUST replace only the schema, schemas, edgeTypes and prefixes keys of an existing note, MUST NOT delete notes, and SHOULD refuse to rewrite a note that declares types missing from the import unless the user asks. A note that declares several types has no template body, so an importer that groups types into one note SHOULD report each tgs:templateBody it cannot keep.
13.6 Round trip
For schemas written in TGS, export followed by import MUST reproduce the same types, edge types, properties, edges, identifiers, templates and visualization. Exporting the result again yields the same RDF graph; the reference implementation produces byte-identical Turtle.
14. Conformance
| Level | Requirements |
|---|---|
| Reader | sections 3 to 8 and 12: reads every key, applies defaults, expands identifiers, reports declaration diagnostics |
| Validator | Reader, plus section 9 |
| SHACL exchanger | Validator, plus section 13 |
The examples/ folder next to this document contains conformance cases. Each case has a vault/ folder (schema folder Types/, base IRI urn:tgs:) and an expected.json with:
typesandedgeTypes: the declarations a reader must produce, with defaults applied and identifiers expanded to IRIs;diagnostics: what a validator must report, each aspath,line(the 1-based line of the edge, or null for a note-level diagnostic) anddiagnostic(a name from section 9), in any order;invalidAgainstJsonSchema: the schema notes that must fail the JSON Schema of section 15.
SHACL cases also have a shapes.ttl that an exporter's output must be isomorphic to.
15. JSON Schema
tgs.schema.json (JSON Schema draft 2020-12) validates the TGS keys of a schema note's frontmatter. It is strict about TGS 0.1 keys and leaves visualization and all non-TGS keys open. Notes declaring a newer minor version may not validate against it.
16. References
- W3C, Shapes Constraint Language (SHACL), https://www.w3.org/TR/shacl/
- W3C, RDF 1.1 Turtle, https://www.w3.org/TR/turtle/
- W3C, RDF 1.1 Concepts, reification vocabulary, https://www.w3.org/TR/rdf11-mt/#reification
- Schema.org, https://schema.org/
- JSON Schema 2020-12, https://json-schema.org/draft/2020-12
- RFC 2119, Key words for use in RFCs to Indicate Requirement Levels