A single plain-text document that is simultaneously your ontology source and its documentation.
ELOT enables literate ontology engineering: a workflow, inspired by Knuth's literate programming, in which a plain-text notebook file is the single source for both ontology and documentation.
Headlines are the taxonomy; description lists are the axioms and annotations. Documentation, including SPARQL queries and rdfpuml diagrams, lives alongside the formal content. Because the source is an outline, ontology changes produce clean, human-readable diffs under version control.
ELOT has been used across scores of ontology projects, including the ISO 23726-3 Industrial Data Ontology.
ELOT was developed as an Emacs package, and is now also a Visual Studio Code extension in the Visual Studio Marketplace.
- Emacs — install from MELPA, or
clone the repository and
(require 'elot-mode). This is the reference implementation and the maintainer's daily driver, so it gets new features first! - VS Code — search for "Elot" in the Extensions panel, or install from the Marketplace.
| VS Code | Emacs | |
|---|---|---|
| Install | VS Code Marketplace | MELPA |
| Org→OWL | Built-in (WASM) | Built-in (Elisp) |
| OWL→Org | elot-exporter JAR | elot-exporter JAR |
| Label display | F5 toggle | Menu / opt-in F5 |
| Global label display | Any file via shared DB | Any buffer via shared DB |
| Lint for errors | automatic | elot-org-lint |
| Folding | Tab / gutter | Native Org cycling |
| Go to definition | F12 / Ctrl+Click | M-. (xref) |
| IntelliSense | Ctrl+Space | completing-read |
| SPARQL queries | — (planned) | org-babel |
| AI-assisted authoring | — (planned) | elot-gptel (LLM tools) |
| Diagrams | — (planned) | rdfpuml integration |
| HTML export | Built-in (Pandoc) | org-export |
- Headlines are the taxonomy. Outline headings declare classes, properties, and individuals; their nesting expresses the subclass/subproperty hierarchy.
- Description lists are the axioms and annotations. A list item attaches an annotation or an OWL axiom in Manchester Syntax to the entity under whose headline it appears.
- Nesting can relate individuals, too. In an Individuals
section, add
:ELOT-subheading-relation: skos:broaderto a heading's:PROPERTIES:drawer, and every individual nested beneath it asserts that relation to its immediate parent — inherited to any depth, and overridable per subtree. This is ideal for SKOS concept schemes, where the outline becomes the single source of truth for theskos:broaderhierarchy. Seeexamples/SKOS-example.org.
A small excerpt — Dog is nested under Animal (so ex:dog rdfs:subClassOf ex:animal follows from the structure), while the description-list items
add a definition and a Manchester-Syntax restriction:
*** Animal (ex:animal)
- iof-av:naturalLanguageDefinition :: A living organism
- skos:example :: mouse, elephant
**** Dog (ex:dog)
- iof-av:naturalLanguageDefinition :: A domesticated carnivorous mammal
- SubClassOf :: ex:chases some ex:cat
This plain-text source pairs well with any chat-based or in-editor AI
assistant. In Emacs, ELOT now ships dedicated tools for LLM-assisted
authoring via elot-gptel — an LLM
can inspect, validate, and (with your confirmation) edit your ontology
from a chat session. See the enable snippet
below.
- Org→OWL — Generate OWL Manchester Syntax directly from the Org source (and, via OBO ROBOT, Turtle).
- Content derived from structure — Ontology content comes
directly from Org headlines and description lists. A single file
may declare multiple ontologies. Under Individuals, the
:ELOT-subheading-relation:property extends this to relations such asskos:broader, deriving a SKOS taxonomy from the outline nesting. - Readable label display — Show human-readable labels instead of opaque CURIEs, with an in-buffer toggle in both editors. This works in any file — Turtle, SPARQL, CSV, source code, log files. A shared SQLite index fills up silently as you work. BCP-47 language tag preferences are honoured. See README-global-label-display.org for the Emacs guide.
- OWL→Org —
elot-exporter(Java/OWLAPI) converts any existing OWL ontology into the ELOT Org format so you can use ELOT for ontologies you already have. Download from releases; source intools/elot-exporter/. - Documentation export — With Emacs or Pandoc, a range of output formats can be generated from Org documents.
- In-place SPARQL and diagrams — Use the existing Org support
for SPARQL
SELECT/CONSTRUCTqueries, and render rdfpuml/PlantUML diagrams directly in the document (Emacs only for now). - AI-assisted authoring (Emacs) —
elot-gptelexposes a roster of tools that let an LLM inspect, lint, reason over, and edit ELOT files from a gptel chat session. Read-only tools work out of the box; file-modifying tools are gated behind explicit user confirmation (elot-gptel-allow-side-effects), and every edit is revalidated with automatic rollback on failure. See documentation/elot-gptel.org. - Editor-independent CLI —
elot-cli(TypeScript/WASM) provides the Org→OWL pipeline and HTML export outside any editor, plus adbsub-command that manages the shared label index. Source intools/elot-cli/.
Note: Java 21 or newer is required for elot-exporter. Many
enterprise laptops ship with an older Java (8, 11, or 17); check with
java -version and upgrade if needed.
- Install the extension from the Visual Studio Marketplace
- Open an
.orgontology file — try the examples intools/elot-cli/examples/ - Hover over a CURIE to see labels and annotations
- Press F5 to toggle visual label display
- Press Tab on a heading to fold/unfold
- Press F12 on a CURIE to jump to its definition
- Press Ctrl+Space to search and insert entities
- Open the Command Palette (
Ctrl+Shift+P) and run Elot: Tangle to Manchester Syntax to produce an OWL ontology.
- Open the Command Palette (
Ctrl+Shift+P) and run Elot: Import OWL Ontology. - Choose a local OWL file, or paste in a URL to a published ontology.
- The extension downloads
elot-exporter.jarautomatically and converts the OWL file into ELOT's Org format. - Save the resulting
.orgfile; every ELOT feature activates as soon as it is saved.
For more details, see extension README.
Video.webm
The quickest way is from MELPA: once MELPA
is in your package-archives, run M-x package-install RET elot RET,
then add (require 'elot-mode) to your init file. With elot-mode
turned on, check out the ELOT menu.
- Install Java to enable advanced features
- Turtle output, ontology metrics, and more: install ROBOT
- Ontology diagrams: install PlantUML and rdfpuml
- Open OWL files: Download
elot-exporterfrom releases
(This is for Emacs, but should transfer to VS Code well enough.)
-
Select a directory that you want to work in. Open a new Org Mode file, for instance
myontology.org. -
Insert the ELOT template for a document header. From the ELOT menu, select Insert New Ontology Document Header. Answer the prompts, and a header is produced, like the following.
#+title: My new ontology #+subtitle: An OWL ontology #+author: John Doe #+date: WIP (version of 2024-04-04 12:34) -
Insert the ELOT template for an ontology skeleton: menu entry Insert New Ontology Skeleton. Answer the prompts — be ready to provide namespaces/prefixes for your ontology and the resources it will contain. You should see something like this screenshot:
Now create an OWL file from your new document.
- Menu entry Export to OWL will "tangle" the ontology to a file
in OWL Manchester Syntax, e.g., with filename
myontology.omn. - If you have installed ROBOT, a Turtle file named
myontology.ttlwill also be generated. - Have Protégé ready, and open the ontology file to inspect it.
- Export to an HTML document with menu entry Export to HTML. The document will open in your web browser.
- Navigate to the
Classesheading - Select Insert Primitive/Defined Class template from the menu to declare a class. Some appropriate annotation properties from the Industrial Ontology Foundry Annotation Vocabulary (IOF-AV) are included.
- Navigate to the
Object propertiesheading - Select Insert Property template for an object, data, or annotation property.
- The screenshot shows how checkboxes are included for tracking progress. These are completely optional and won't appear in output.
ELOT makes it easy to add annotations to both annotations and axioms:
just put them in a description list ( - term :: value).
In this screenshot, two annotations are added to the "transitive" characteristic axiom:
- Navigate to the
Prefixesheading and insert a new heading named "Queries". - Select menu item Insert SPARQL Select Block, then write a query over the ontology.
- Hit
C-c C-cto run the query.
- A SPARQL construct query will produce Turtle code for a diagram. Select Insert SPARQL Construct Block.
- Write a query and hit
C-c C-cto run it.
- Select Insert RDFPUML Diagram Block.
- When prompted, provide the name of the Turtle source (the name of the construct query) and a figure caption.
- Place the cursor on the row of the
#+call:and hitC-c C-c. A diagram is generated.
- Select Export to HTML to view your ontology in a browser.
ELOT can display readable labels instead of prefixed identifiers
(which are unreadable if the identifiers are not informative),
and offers quick search across the ontology resources. Toggle
from the ELOT menu or with M-x elot-toggle-label-display.
ELOT does not assign a default key for the label-display toggle. To
get a keybinding, customize elot-toggle-labels-key (M-x customize-variable RET elot-toggle-labels-key) to a kbd-notation
string — for example <f5>, <f9>, or C-c t l. When set, ELOT
installs the binding in elot-mode-map and in the ELOT-managed
*xref* / *ELOT Describe* buffers.
ELOT's label-display is no longer confined to Org buffers. The minor mode
elot-global-label-display-mode lights up readable labels in any buffer
— .ttl files, SPARQL queries, CSV exports, even source code and log files
that mention ontology identifiers. Toggle from the ELOT menu or via
M-x elot-toggle-label-display (or your chosen keystroke if you
set one — see elot-toggle-labels-key).
The feature that makes this practical in daily work: id/label mappings are
collected silently and automatically as you edit ELOT Org files. Every
ontology you open, tangle, or save contributes its declarations to a
persistent SQLite index (elot-db) that lives across sessions. The more
ontologies you touch, the richer the index becomes — with no explicit
import step, no manual curation, and no rebuild when you come back
tomorrow. Additional sources (TTL via ROBOT, SPARQL endpoints, CSV/TSV/JSON
exports) can be registered per-buffer via .dir-locals.el.
Beyond the visual overlays, the mode provides:
elot-label-lookup(C-c C-x r) — Insert an existing resource identifier by searching on its label. Scope is configurable (current buffer only, external sources only, or a union of both). When many identifiers share a label — common in industrial asset data — a two-stage picker lets you drill down with full attribute context.- Attribute-driven hover — Idle the cursor on any identifier to see
its
rdf:type, definition, and source provenance in the echo area. - Language preferences — Multi-lingual ontologies (e.g. English +
Korean) display the right variant based on
elot-preferred-languages; the default policy is untagged first, then@en, then alphabetical.
See README-global-label-display.org for configuration, source registration, and language-preference details.
Different ontology projects use very different conventions for the
local-name part of their resource IRIs — OBO uses zero-padded
counters like GO_0000001, ISO 15926-style libraries use prefixed
counters like RDS123456789, some projects use UUIDs, some use
human-readable slugs. ELOT does not bake in a single convention.
Instead, each ontology declares the scheme it uses, as a property
of its top-level heading (sibling of ELOT-default-prefix):
* my-ont
:PROPERTIES:
:ELOT-context-type: ontology
:ELOT-id-scheme: counter GO_0000000
:ELOT-context-localname: my-ont
:ELOT-default-prefix: ex
:END:
The property value is SCHEME [FORMAT...]. Four built-in schemes
are available:
| Spec | Sample local name | Notes |
|---|---|---|
uuid |
9af6a481-c172-4858-9d44-... |
RFC 4122 UUID; maximum collision resistance |
slug |
donkey, dog-2 |
kebab-case from label; numeric suffix on collision |
counter GO_0000000 |
GO_0000001, GO_0000002 |
OBO/PCA-style: literal alpha part + zero-padded N |
acme |
C_028QZQ8C4 |
11-char date+random+checksum, no project state |
acme slug:t |
C_dogxx028QZQ8C4 |
16-char acme with 5-char label slug prepended |
The counter template literally shows the output shape: leading
non-digit run = alpha prefix, trailing run of 0 s = pad width.
Numeric-only counters (no alpha prefix) are technically invalid XML
NCNames, so declaring a template such as GO_0000000, ABC_00000,
or CHEBI00000 is recommended.
The declaration is consumed by ELOT's LLM-facing tools
(elot_mint_identifier, elot_verify_identifier in
elot-gptel.el) so that AI-assisted
authoring produces identifiers matching the project's convention
automatically. When no scheme is declared the tools refuse to
guess and direct the agent to ask the user.
See documentation/elot-id.org for the
full reference, including the CONTEXT plist surface and how to
register a custom scheme.
ELOT ships a set of tools that let a large language model (LLM) inspect, validate, and — with your confirmation — edit your ontology files, driven from a chat session inside Emacs. The tools are built on gptel, the Emacs LLM client.
Both gptel and ROBOT are optional: ELOT works fully without them. If gptel is installed, enable the tools with:
(with-eval-after-load 'gptel
(require 'elot-gptel)
(elot-gptel-register-tools))or interactively with M-x elot-gptel-register-tools. Read-only
tools (lint, search, label lookup) work out of the box; tools that
modify files are additionally gated behind the user option
elot-gptel-allow-side-effects, and the reasoning/validation tools
require ROBOT to be installed.
See documentation/elot-gptel.org for the full user guide — how to work with the LLM on an ELOT file, recommended workflows, the safety model, and a reference of all tools.
ELOT ships a small Library of Babel file
(elot-package/elot-lob.org)
that defines a handful of named source blocks intended to be invoked
from your ontology Org files via Org's #+call: syntax. When
elot-mode is enabled in a buffer, ELOT automatically ingests the
file (via org-babel-lob-ingest) so the helpers below are available
without any manual setup — no M-x org-babel-lob-ingest step
required.
The supported helpers are:
rdfpuml-block— render a Turtle (or SPARQLCONSTRUCT) source block as an rdfpuml/PlantUML diagram. Takes the name of another named block as itsttlblockargument, plus optionalconfig,add-options,epilogueandformatarguments. Produces an image file referenced from the surrounding caption.kill-prefixes— strip leading@prefix/PREFIXdeclarations from a Turtle string. Most commonly used as a:posthook on a SPARQLCONSTRUCTblock to keep the visible result compact.robot-metrics— runrobot measureon an OMN file and return the resulting table.robot-sparql-select— run a named SPARQL query through ROBOT against a local OMN file and return the result as an Org table.theme-elot— expands to the right#+SETUPFILE:line for the HTML theme.current-date/current-datetime— format the current date/time, useful inpav:lastUpdateOnannotations.
A minimal example, taken from
examples/pets.org, showing
rdfpuml-block consuming a CONSTRUCT block whose result is also
post-processed by kill-prefixes:
#+name: my-construct
#+begin_src sparql :url "my-ont.omn" :format ttl :wrap "src ttl" \
:cache yes :post kill-prefixes(data=*this*)
construct {
?class a owl:Class .
?subclass rdfs:subClassOf ?class .
} {
?class a owl:Class .
optional { ?subclass rdfs:subClassOf ?class }
}
#+end_src
#+name: rdfpuml:my-construct
#+call: rdfpuml-block(ttlblock="my-construct")
#+caption: Animal diagram
Place point on the #+call: line and hit C-c C-c to render the
diagram. See examples/pets.org for the complete working file.
id/label mappings are collected silently and automatically as you
edit ELOT Org files, into a persistent SQLite index (elot-db) that
lives across sessions. The more ontologies you touch, the richer the
index becomes. Default sources can be registered per project via
.dir-locals.el.
ELOT supports cross-referencing and navigation using Emacs's built-in
xref system. This allows users to find where a resource is used
and to jump directly to its definition from anywhere in an ELOT buffer.
To find all references to a CURIE (e.g., :BFO_0000015), place the cursor
on it and type M-? (xref-find-references). The *xref* buffer will open
and show each occurrence, along with the heading and flattened description
list entry for context.
Inside the *xref* buffer:
- Press
RETto jump to the occurrence. - Move the cursor to any other CURIE and press
M-.(xref-find-definitions) to jump to its definition (typically the Org headline where it's declared).
Label overlays are enabled automatically in the *xref* buffer, so
identifiers appear with readable labels if available. This makes it easier
to explore large ontologies interactively.
This functionality is activated automatically for Org files when elot-mode is active.
HTML output from ELOT produces stable anchors for ontology resources,
and shows readable labels with internal, clickable links.

Two command-line tools are included under the tools/ directory:
-
elot-exporter — A Java CLI (OWLAPI + Maven) that converts existing OWL ontologies into ELOT's Org-mode format, producing a shaded JAR.
-
elot-cli — A TypeScript CLI and VS Code extension that generates OWL Manchester Syntax and HTML documentation from Org-mode files (uses orgize WASM + esbuild + Pandoc). Also provides a
dbsub-command (elot-cli db init|register|refresh|list|lookup|attr|remove) that manages the persistent SQLite label index used by the global label-display feature; see theLabel Databasesection in the elot-cli README. Available on the Visual Studio Marketplace.









