Navigation: Home · Publishing guide · API docs
A practical checklist for building OWL ontologies that are interoperable, resolvable, and maintainable. These are the things askwol checks, and why they matter.
Two tags mark where a rule comes from, and two colored boxes flag extra context:
Spec what an OWL, RDF, RDFS, or other W3C/IETF specification actually defines or requires.
TDCC guideline a recommendation from the TDCC ontology engineers that askwol checks for, but that goes beyond what any specification mandates. A W3C Note (such as Cool URIs, cited below) is informative, not normative, so guidance drawn from one counts as a TDCC guideline here too.
Give the ontology itself good metadata
Your ontology is itself a published research object. It should say what it is, who made it, which version it is, and under which license it can be reused.
Spec rdf:type owl:Ontology and
owl:versionIRI/owl:versionInfo come from OWL 2;
dcterms:title, dcterms:description,
dcterms:creator, dcterms:created,
dcterms:modified, and dcterms:publisher are
defined by the
DCMI Metadata Terms
vocabulary, not OWL itself. Licensing gets its own dedicated check; see
Open license below.
TDCC guideline Neither spec says any of these properties is mandatory on an ontology header. The Required / Recommended split below, and askwol’s choice to fail on the first group and only warn on the second, is this guide’s own editorial judgement of what a well-published ontology needs.
askwol evaluates SHACL shapes for the ontology header and checks these properties:
rdf:type owl:Ontology,
dcterms:title (or rdfs:label, or the legacy
dc:title), dcterms:description (or
rdfs:comment, or the legacy dc:description),
dcterms:creator (or dc:creator), and
owl:versionInfo or owl:versionIRI.dcterms:created
(or dcterms:issued, or dc:date),
dcterms:modified (or dc:date), and
dcterms:publisher (or dc:publisher).Pre-2010 ontologies (e.g. FOAF, OWL-Time) often use the older
Dublin Core Elements
(dc:) properties instead of DCMI Terms; askwol accepts
either, since they carry the same meaning. One nuance: Elements 1.1’s
single, generic dc:date property doesn’t distinguish created
from modified, so askwol accepts it for either (or both) of the two
recommended date checks above.
Version information is part of good ontology metadata.
Spec OWL 2 defines
owl:versionIRI
to give each release its own dereferenceable URL, and
owl:versionInfo
to hold “a string that describes the IRI’s version,” with no
format required.
TDCC guideline This guide recommends
Semantic Versioning
(MAJOR.MINOR.PATCH) as a practical convention, not an OWL
requirement: bump major when you remove or redefine a
term, minor when you add terms without breaking existing
ones, and patch for fixes such as corrected labels or
comments.
<http://example.org/my-ontology> a owl:Ontology ;
owl:versionIRI <http://example.org/my-ontology/2.0.0> ;
owl:versionInfo "2.0.0" .
Some ontologies version by date instead, especially W3C
specifications on a fixed release schedule: PROV-O’s
owl:versionIRI is
http://www.w3.org/ns/prov-o-20130430. A date is simpler than
semver but only says when something changed, not whether it is
safe to upgrade. Pick one scheme per ontology and stick to it.
Declare imports, and keep them resolving
Spec If your ontology uses terms from
another vocabulary,
OWL 2
says to declare it with owl:imports in the ontology header:
<https://example.org/my-ontology> a owl:Ontology ;
owl:imports <http://xmlns.com/foaf/0.1/> .
This tells reasoners and tools where your external terms are defined, so they can load the imported ontology and reason over it together with yours.
TDCC guideline Deciding which
vocabularies deserve a formal import is a modelling judgement call askwol
does not second-guess. What it does check is whether every
owl:imports target you already declared is actually
reachable: each IRI is fetched over HTTP and parsed as RDF, the same way a
reasoner would follow it. This reachability check is askwol’s own
addition; OWL 2 does not require import targets to be checked at
authoring time.
Pick one IRI strategy (hash or slash) and stick to it
What askwol checks: every term defined inside your
ontology’s own namespace is classified as either hash style
(http://example.org/ont#Person) or slash style
(http://example.org/ont/Person), per declared
owl:Ontology base IRI. The check passes
when all defined terms under one base IRI use the same pattern and
warns when a single base IRI mixes both. An ontology
that legitimately bundles more than one base IRI (a rare pattern, e.g.
the W3C PROV family) may use a different, consistent style for each
without being flagged.
/Person is the same term
as #Person.TDCC guideline Neither RDF nor OWL requires hash or slash IRIs; both are valid (the W3C “Cool URIs for the Semantic Web” Interest Group Note describes both). They differ in how the identifier behaves over HTTP and how the vocabulary scales.
ont#Person.
The fragment is stripped before the HTTP request, so the server always
returns the whole file in one request: zero server configuration, but a
client asking about one term gets
every term in the vocabulary. Fine for 50 terms, painful
for 50 000. Used by OWL and RDF Schema.ont/Person.
Each term is its own HTTP resource, typically served via a
303 See Other redirect per the W3C TAG’s
httpRange-14
resolution (2005). More flexible for large or growing vocabularies,
at the cost of server-side redirect rules and content negotiation. Used
by FOAF, Schema.org, Dublin Core, and DBpedia.TDCC guideline If in doubt, go with slash: it scales. Use hash only for a vocabulary you know will stay small. The Cool URIs note concludes:
“Hash URIs should be preferred for rather small and stable sets of resources that evolve together. The ideal case are RDF Schema vocabularies and OWL ontologies. […] 303 URIs may also be used for [large] data sets, making neater-looking URIs.”
Either way, pick one per base IRI and don’t mix styles within it. Use a domain you control, or a persistent ID service like w3id.org or purl.org, so your IRIs survive domain changes.
Use http or https, but be consistent per host
What askwol checks: every IRI used in the ontology
(in subject, predicate, or object position, plus every bound namespace)
is grouped by host. The check passes when each host
appears under exactly one URI scheme and warns when the
same host is referenced under both http:// and
https://. The report lists the conflicting hosts with
examples of each scheme so you can pick one canonical form and migrate
the others.
Spec http://example.org/ont/Person
and https://example.org/ont/Person are
different IRIs: RDF identifies resources by their exact
IRI string, per
RFC 3987,
and OWL never treats two IRIs as the same resource unless you assert
owl:sameAs yourself. Mixing schemes breaks joins that rely on
exact IRI matches, including SPARQL and any tool that does string
comparison on URIs.
Most foundational vocabularies were minted before HTTPS was ubiquitous.
OWL, RDFS, FOAF, Dublin Core, SKOS and PROV-O all use
http://. Changing them would break billions of existing
triples, so they stay on http.
http://xmlns.com/foaf/0.1/name to https://.
You would be creating a different IRI that matches nothing in
other people’s data.https:// is fine and recommended.
Schema.org switched to https as its canonical scheme.http://, use http://.http → https), so both resolve, but always
use the canonical form in your RDF.Adopt an open license
TDCC guideline Your ontology should be released under an open license; this guide recommends the DCMI property dcterms:license. To meet the Linked Open Data principles, the license must satisfy the Open Definition: “anyone can freely access, use, modify, and share [the ontology] for any purpose (subject, at most, to requirements that preserve provenance and openness).”
A license is open if it’s equivalent to the public domain, or at most adds attribution or share-alike restrictions; non-commercial or no-derivatives clauses are not open. For maximum reuse, this guide recommends one of the two Creative Commons licenses below.
https://creativecommons.org/publicdomain/zero/1.0/
https://creativecommons.org/licenses/by/4.0/
askwol also recognizes other licenses that meet the Open Definition but are less suitable for ontologies, for the reasons below.
https://creativecommons.org/licenses/by-sa/4.0/; too restrictive due to the share-alike.
http://opendatacommons.org/licenses/pddl/1.0/; only covers data and not content.
http://opendatacommons.org/licenses/by/1.0/; only covers data and not content.
http://opendatacommons.org/licenses/odbl/1.0/; too restrictive due to the share-alike.
Declare it with dcterms:license in the ontology header:
<https://example.org/my-ontology> a owl:Ontology ;
dcterms:license <https://creativecommons.org/publicdomain/zero/1.0/> .
Make namespaces resolvable
A namespace is the URI that a prefix expands to. In
@prefix foaf: <http://xmlns.com/foaf/0.1/> the prefix is
foaf and the namespace is
http://xmlns.com/foaf/0.1/. This check looks at those namespace
URIs; the short prefix aliases themselves are covered by
Unused prefixes below.
TDCC guideline Every namespace URI should return something useful when fetched with HTTP. Ideally it returns RDF (content-negotiated), so tools can discover term definitions automatically. Neither RDF nor OWL requires a namespace to be dereferenceable; this expectation comes from the Linked Data principles and the W3C Note linked below.
http://xmlns.com/foaf/0.1/ returns RDF when asked with
Accept: application/rdf+xml.If you host your own ontology, configure your server to support content negotiation, serving RDF to machines and HTML to browsers; see Server configuration below for the details.
Keep your prefixes clean
Spec Each
@prefix
line binds a short prefix to a namespace URI.
TDCC guideline Turtle itself does not
forbid declaring a prefix you never use; this check is about good
hygiene: only declare prefixes you actually use. Leftover
@prefix declarations clutter the file and confuse readers;
they suggest a dependency that doesn’t exist.
@prefix dct: <http://purl.org/dc/terms/> . # used below @prefix geo: <http://www.opengis.net/ont/geosparql#> . # unused, remove it
Make sure reused terms actually exist
TDCC guideline Don’t reinvent the wheel. Before defining a new term, check if an established vocabulary already covers it. No spec requires reuse, but the wider Semantic Web interoperates far better when ontologies share terms:
foaf:nme instead
of foaf:name breaks interoperability.foaf:nme, made-up terms like
owl:MadeUpClass, and namespace hijacking
(asserting a brand-new term such as foaf:MadeUpConcept under
someone else’s namespace instead of your own). Terms from your own
namespace are checked separately (see
Internal term definitions).owl:deprecated is an
OWL 2
annotation property, and
owl:DeprecatedClass/owl:DeprecatedProperty come
from the
OWL 1 Reference
(used by e.g. VIVO).
TDCC guideline A
vs:term_status of “deprecated”/“archaic”
is a community convention from a non-normative
(W3C Note,
used by e.g. FOAF),
not a REC. askwol checks for all three and flags a deprecated reused term
as a warning, so you don’t quietly build on a term the source
vocabulary is phasing out.Define every term you use in your own namespace
TDCC guideline Every term you use from your own namespace should also be defined there. Don’t just use a term; define it. RDF’s open-world assumption actually permits referencing an IRI with no triples about it at all; askwol still flags it, because within your own namespace an undefined term is almost always an authoring mistake, not an intentional stub.
<#Person> a owl:Class ;
rdfs:label "Person"@en ;
rdfs:comment "A human being."@en .
ex:Persom but
never define it, that’s probably a typo. askwol catches these.rdfs:label a owl:AnnotationProperty, as PROV-O and FOAF do).
askwol correctly recognizes this as reuse of an existing term, keeping
your own namespace scoped to what you actually define. New terms always
belong in your own namespace: the
External term definitions check confirms
that anything asserted under an external prefix, like
foaf:MadeUpConcept, genuinely exists there already, catching
namespace hijacking along the way.Categorize your terms and name them consistently
Spec Every term you define falls into a
category OWL 2 itself defines: a class, an
object property, a datatype property, an
annotation property, a datatype, or a
named individual. askwol lists each internal term with the
category it detected, so you can spot a term that was never typed
(rdf:type missing) or typed as the wrong kind of thing.
<#Person> a owl:Class . <#hasParent> a owl:ObjectProperty . <#birthDate> a owl:DatatypeProperty .
TDCC guideline Capitalization is a
community convention, not an OWL or RDFS rule; nothing stops you from
naming a class person or a property HasName.
askwol checks it anyway, because consistent casing is one of the fastest
ways to tell a class from a property at a glance. A mismatch is reported
as a warning, not a failure: it’s worth fixing, but it won’t
block publication.
Person, Dataset, Organization.hasParent, birthDate, title.Some vocabularies use stable, coded identifiers instead: CIDOC CRM’s
properties look like P2_has_type (the number is the permanent
identifier; the wording after it can change between versions), and
Wikidata’s are fully opaque, like P19, deliberately avoiding
any one language’s naming bias. askwol recognizes this pattern, an
uppercase letter directly followed by a digit, and does not flag it.
has or is prefix, or an of/by
form, gives a single unambiguous reading: hasWife,
wifeOf, lovedBy. This is a readability convention,
not something askwol enforces; askwol only checks the case of the first
letter, via SHACL shapes.person, or an uppercase property like HasName,
with no digit following the capital) makes an ontology harder to read and
to reuse.Give properties a domain and a range
Spec An
rdfs:domain
says what kind of subject a property applies to; an
rdfs:range
says what kind of value it takes.
TDCC guideline RDFS does not require
either one; declaring both is this guide’s recommendation because it
makes a property self-documenting and lets tools reason about it.
<#hasParent> a owl:ObjectProperty ;
rdfs:domain <#Person> ;
rdfs:range <#Person> . # a class
<#birthDate> a owl:DatatypeProperty ;
rdfs:domain <#Person> ;
rdfs:range xsd:date . # a datatype
rdfs:domain :Person on :birthDate tells a reasoner
that anything with a :birthDate is a :Person. Pick
domains and ranges that are actually true of every use.rdfs:domain and
rdfs:range declared directly on a property, not ones
inherited from a super-property. A reasoner does follow that inheritance,
so this check can occasionally flag something a reasoner would treat as
fine. Checked against SHACL shapes.
Deprecated properties (see
External term definitions) are exempt.Use recognized datatypes
Spec Datatype property ranges and typed
literals can use any
XSD
built-in (xsd:string, xsd:integer,
xsd:date, xsd:boolean, …),
rdfs:Literal,
rdf:langString, or a custom datatype you declare with
rdfs:Datatype.
TDCC guideline RDF places no closed list
on datatype IRIs; any IRI is technically legal there. askwol checks
against this recognized set instead, because an IRI outside it is almost
never intentional.
<#age> rdfs:range xsd:nonNegativeInteger . <#born> rdfs:range xsd:date . "42"^^xsd:integer
xsd:stirng,
xsd:dateTiem) is treated as a brand-new, unknown datatype.
Filters and validators that expect the real datatype then skip your
values. askwol lists every datatype it sees and flags the ones it does not
recognize.Keep concept schemes separate from the schema
TDCC guideline An OWL ontology is the schema: it defines classes, properties, and datatypes (the terminology). Named individuals are a different kind of thing, actual instances rather than schema, and most are fine to keep in the same file. SKOS concepts, the members of a controlled vocabulary or thesaurus, are the one case worth pulling out. A concept scheme is typically much larger than the schema and grows on its own schedule: new terms get added without ever touching a class or property definition. This guide recommends keeping SKOS concepts in their own SKOS concept scheme, separate from the ontology file, so schema and data can each evolve and be reused independently.
<#Dataset> a owl:Class . # schema, belongs here <#biology> a skos:Concept . # concept, belongs in a SKOS scheme <#Monday> a <#DayOfWeek> . # named individual, fine to keep here
skos:Concept instances defined
in your own namespace; a full concept scheme has usually outgrown the schema
file and is better managed on its own. Named individuals are left alone:
many ontologies deliberately define a small, fixed set of them alongside
their schema (days of week, units of measure, as OWL-Time does), a
legitimate modeling pattern rather than accidental instance data. External
terms are ignored either way. Checked against SHACL shapes.Give every class and property a human-readable label
TDCC guideline Every class and
property you define should carry a human-readable
rdfs:label:
a short name a person can read. RDFS defines what the property means but
does not require any resource to have one; this guide recommends adding
one to everything you define.
<#hasMother> a owl:ObjectProperty ;
rdfs:label "has mother"@en ;
rdfs:domain <#Person> ;
rdfs:range <#Person> .
@en, @de)
to support multilingual ontologies. Consider
skos:prefLabel and skos:altLabel for richer
labeling.rdfs:label. Reused external vocabulary terms, and terms you
have marked deprecated yourself, are ignored.Give every class and property a clear description
TDCC guideline Every class and
property you define should also carry an
rdfs:comment:
a brief description of what it means. As with labels, RDFS defines the
property but does not require you to use it; this guide recommends it for
every term you define.
<#hasMother> a owl:ObjectProperty ;
rdfs:label "has mother"@en ;
rdfs:comment "Relates a person to their biological mother."@en ;
rdfs:domain <#Person> ;
rdfs:range <#Person> .
rdfs:comment. Reused external vocabulary terms, and terms you
have marked deprecated yourself, are ignored.owl:inverseOf) doesn’t need its own comment when its
partner already has one: the relationship is documented either way.Use language tags consistently
Spec If your ontology includes
human-readable labels and descriptions, tag every literal that carries
natural-language text with a
BCP 47 language tag
(@en, @nl, @de, …),
producing an RDF 1.1
language-tagged string.
:Person a owl:Class ;
rdfs:label "person"@en ,
"persoon"@nl ;
rdfs:comment "A human being."@en ,
"Een menselijk wezen."@nl .
TDCC guideline Neither RDF nor BCP 47 requires every subject to carry the same set of languages; askwol checks for it anyway, because an inconsistency here is usually a missed translation, not a deliberate choice:
rdfs:label uses @en on most subjects but one
subject has a plain "My label" without a tag, that’s
inconsistent.@en and @nl labels on most classes, every
class should have both. A missing @nl on one subject
is probably an oversight.FILTER(LANG(?label) = "en") won’t match it. The triple
is still in your data, but any app or query that filters by language will
skip it, as if it doesn’t exist.rdfs:label, rdfs:comment,
skos:prefLabel, skos:definition, and other
standard annotation properties for tag consistency on terms defined in
your own namespace. Reused external vocabulary terms, and terms you have
marked deprecated yourself, are ignored.Check logical consistency
Spec OWL is a logical language. OWL 2 Direct Semantics formally defines when an ontology is consistent and when a class is satisfiable; a reasoner can derive consequences from your axioms and detect contradictions you didn’t intend. askwol reports these as three separate facets in the Reasoner checks section of the report:
Person
with two values for a functional property, or membership in two
owl:disjointWith classes). Each offending individual is
listed by IRI so you can locate the contradiction.owl:Nothing (e.g. via disjoint
superclasses). The class is syntactically valid but can never have
instances.owl:imports. This is an implementation
tradeoff askwol makes, not a spec requirement: it catches the obvious
self-contained contradictions without the cost of loading every imported
vocabulary. For deeper checks (against imports, with HermiT or Pellet), use
a desktop tool like Protégé.Run askwol during development, not just before release
Run askwol on your ontology during development, not just before release.
askwol check my-ontology.ttlServe it right: content negotiation
Once published, your ontology’s IRI should resolve: a client that dereferences it must get the RDF back. Two server-side settings make that work.
text/turtle, RDF/XML as application/rdf+xml,
JSON-LD as application/ld+json. A file served as
text/plain or application/octet-stream will not be
recognised as RDF.Accept header: return RDF to tools that ask for it, and an
HTML documentation page to browsers. A common pattern is one term IRI
(https://example.org/onto#Term) that redirects
(303 See Other) to either the .ttl or an HTML view
depending on Accept.https://lod-4tu.tudelft.nl/dataset#, and
dereferencing https://lod-4tu.tudelft.nl/dataset (no fragment)
returns the ontology itself, directly or via redirect.text/plain
for .ttl. Set the MIME type explicitly in your web server or
reverse proxy, otherwise namespace resolution (including askwol’s) fails.Generate human-readable documentation
Good rdfs:label and rdfs:comment annotations pay
off once turned into a browsable HTML page: a documentation generator reads
your Turtle file and produces one, ready to serve alongside the RDF via
content negotiation.
pyLODE is a lightweight Python tool: a single command turns a Turtle file into one static HTML page that needs no server or JavaScript to view (example output).
python3 -m venv .venv && source .venv/bin/activate python -m pip install pylode pylode my-ontology.ttl -o my-ontology.html
WIDOCO
is a Java-based documentation generator that produces a multi-page bundle:
metadata extraction, an optional WebVOWL diagram, and a ready-to-deploy
.htaccess for content negotiation.
java -jar widoco.jar -ontFile my-ontology.ttl -outFolder docs \
-getOntologyMetadata -rewriteAll -uniteSections -webVowl -htaccess
python3 -m http.server 8080) rather than from the
file system.For W3C specification-style output, ReSpec renders technical documents in the browser, and OntoReSpec generates the markup it needs from an RDF ontology or data model (example). OntoReSpec is early-stage, with no stable release yet.