Learn how to read annotations in OData metadata documents
- How annotations are defined & structured
- How annotations are used to extend the information in the schema
In all cases, annotations consist of two parts - the term and the value. In this tutorial we’ll examine the terms, along with their types and possible values, in our Northbreeze OData metadata document.
This tutorial belongs to the OData Deep Dive mission, a re-write of the original. The re-write is a work in progess, please proceed with caution! More info can be found in the blog post OData Deep Dive rewrite in the open.
- Step 1
One of the annotations we took a brief first look at in the previous tutorial was “Core.Links”, where “Core” is the vocabulary alias, referring here to “Org.OData.Core.V1”.
xmlCopy<Schema Namespace="Main" xmlns="http://docs.oasis-open.org/odata/ns/edm"> <Annotation Term="Core.Links"> <Collection> <Record> <PropertyValue Property="rel" String="author"/> <PropertyValue Property="href" String="https://cap.cloud.sap"/> </Record> </Collection> </Annotation> ... </Schema>The XML element structure contained within the
<Annotation>element is the annotation term’s value. Staring at this structure for a bit, we see that it’s a collection of records, each of which have two properties (or fields) “rel” and “href”, for which there are corresponding string values.There’s only a single record in the collection, and it looks like this:
rel href authorhttps://cap.cloud.sap"Core" is the alias for the "Org.OData.Core.V1" vocabulary - in what XML element is that connection made (you'll find the answer in the Northbreeze OData metadata document)?
- Step 2
Let’s first go the hard way round to understand what this term is, and how the value structure is defined. To do that, we should look at the XML representation of the Org.OData.Core.V1 vocabulary, and search for the relevant term.
As we search, we also will notice that the schema within this resource, specifically the “Org.OData.Core.V1” schema, is adorned with a “Core.Links” annotation itself, too! And it works because of the OData namespace “Core” provides a pointer to itself. Further beauty, which we’ll see more of shortly, too.
Within the “Org.OData.Core.V1” schema, we find two elements, right next to each other, that are relevant to our search - a
<Term>and a<ComplexType>:xmlCopy<Term Name="Links" Type="Collection(Core.Link)" Nullable="false"> <Annotation Term="Core.Description" String="Link to related information" /> </Term> <ComplexType Name="Link"> <Annotation Term="Core.Description" String="The Link type is inspired by the `atom:link` element, see [RFC4287](https://tools.ietf.org/html/rfc4287#section-4.2.7), and the `Link` HTTP header, see [RFC5988](https://tools.ietf.org/html/rfc5988)" /> <Property Name="rel" Type="Edm.String" Nullable="false"> <Annotation Term="Core.Description" String="Link relation type, see [IANA Link Relations](http://www.iana.org/assignments/link-relations/link-relations.xhtml)" /> </Property> <Property Name="href" Type="Edm.String" Nullable="false"> <Annotation Term="Core.IsURL" /> <Annotation Term="Core.Description" String="URL of related information" /> </Property> </ComplexType>This is where the “Links” term, in the “Core” namespace (representing the vocabulary), is defined, i.e. in a
<Term>element. The<Term>element itself is defined in the OData standards document that has accompanied us on this journey of discovery: “OData Version 4.0. Part 3: Common Schema Definition Language (CSDL)”, specifically in section 14.1 Element edm:Term.Here’s what we can we discern from this
<Term>element:- the name is “Links”
- the type is defined as being a collection (an array) of individual “Core.Link” items
- it’s annotated with a “Core.Description” term (which is a part of this very vocabulary, more beauty!) that tells us this term is for “links to related information”
The “Core.Link” item is defined with a corresponding
<ComplexType>element, which:- is annotated with a “Core.Description” term telling us more about it
- includes, in that description, a reference to the
atomnamespace and RFC 4287 (Atom Syndication Format) which we looked at in the first tutorial in this mission on the Origins of OData - is defined as having two properties “rel” and “href”, each of which has the type “edm.String” and each of which are also annotated with “Core.Description” (the latter is also annotated with “Core.IsURL”)
Here’s what that looks like, pictorally:
textCopyNamespace: Core +-- Description: "Link to related information" | | +-- Description: "The Link type is inspired by ..." V | +-------+ +------V----------------+ | | | +----------+ ++ | Links +--->| | Link | || | | | | | || +-------+ | +-+--------+ || | | +----------+ || | +-----+ rel |<---- Description: "Link relation type ..." | | | (String) | || | | +----------+ || | | +----------+ || | +-----+ href |<---- Description: "URL of related information ..." | | (String) |<---- IsURL: true | +----------+ || ++----------------------+| +-----------------------+ (Collection)According to the referenced section (14.1 Element edm.Term) of the OData standards document describing the Common Schema Definition Language, which of these are attributes of the edm:Term element?
- Step 3
As a bonus, and to help drive home how OData vocabularies and metadata metadata (yes, that is deliberately written twice) works, let’s spend a moment of practice following the other annotation with which the “Core.Link” complex type’s property “href” is annotated, namely “Core.IsURL”.
Look again through the Org.OData.Core.V1 schema XML for the “IsURL” term. You should find this:
xmlCopy<Term Name="IsURL" Type="Core.Tag" Nullable="false" DefaultValue="true" AppliesTo="Property Term"> <Annotation Term="Core.Description" String="Properties and terms annotated with this term MUST contain a valid URL" /> <Annotation Term="Core.RequiresType" String="Edm.String" /> </Term>Descend yet one level deeper to find out what the “Core.Tag” type is, whereupon you should find:
xmlCopy<TypeDefinition Name="Tag" UnderlyingType="Edm.Boolean"> <Annotation Term="Core.Description" String="This is the type to use for all tagging terms" /> </TypeDefinition>And with the definition of this type being a Boolean (in the “edm” namespace, see the Metadata tutorial earlier in this mission), we’ve bottomed out our investigation.
Tracking back up to where we started this descent, in the schema in our OData metadata document, we can now confidently understand that:
Level 0 (our OData metadata)
- that schema is annotated with the “Links” term
- that “Links” term is in the Org.OData.Core.V1 vocabulary
Level 1 (the “Core” vocabulary)
- that vocabulary has the short alias “Core”
- within the “Core” vocabulary, the “Links” term is defined as a Collection of the “Link” complex type
- that “Link” complex type has two properties, “url” and “href”
Level 2 (annotations used for vocabulary content)
- the “Links” term, the “Link” complex type, and both properties are also themselves annotated with terms from “Core”
- the predominant term used in these (meta) annotations within the “Core” vocabulary is “Description”
- but there’s also the term “IsURL”, which is defined as being of type “Tag”
Level 3 (annotations used for building blocks of annotations)
- and the “Tag” type is defined as a Boolean
- as well as being annotated itself too (with the “Description” term)
Phew!
What is the default value for the IsURL term?
- Step 4
Now that we’ve done the hard work of examining the XML representation of the “Core” vocabulary, let’s take a breather and look at the HTML representation (we saw how these are related in the previous tutorial on Vocabularies), at https://oasis-tcs.github.io/odata-vocabularies/vocabularies/Org.OData.Core.V1.html.
We see some familiar information that should help clarify and cement our understanding.

First, the description “Core terms needed to write vocabularies” explains why so many aspects of the “Core” vocabulary were indeed annotated themselves with terms from that very same vocabulary.
Next, in the list of terms, we see the “Links” term with its type defined as “[Link]”, i.e. an array (
[...]) or collection of “Link” types. Following the hyperlinked “Link” we are taken to the Link type definition:
The keen observers amongst you will realise that the descriptions in this HTML representation are taken directly from the values of the “Core.Description” terms that adorn the XML representation, suggesting that the HTML representation is generated from the XML representation too.
In fact, the source of the HTML representation is in Markdown format, which makes sense too, given that there is Markdown in some of the “Core.Description” string values (the description for this “Core.Link” type is a good example of this).
If you follow the link to the HTML representation of the Org.OData.Core.V1 vocabulary and select the "Description" term, you'll be taken to the XML representation, specifically for that term, which is itself annotated with two "Core" terms - "Description", and which other?
- Step 5
Now we understand how to read, interpret and navigate annotations, let’s turn our attention to the other annotations in our Northbreeze OData metadata document:
xmlCopy<Annotations Target="Main.EntityContainer/Categories"> <Annotation Term="Capabilities.DeleteRestrictions"> <Record Type="Capabilities.DeleteRestrictionsType"> <PropertyValue Property="Deletable" Bool="false"/> </Record> </Annotation> <Annotation Term="Capabilities.InsertRestrictions"> <Record Type="Capabilities.InsertRestrictionsType"> <PropertyValue Property="Insertable" Bool="false"/> </Record> </Annotation> <Annotation Term="Capabilities.UpdateRestrictions"> <Record Type="Capabilities.UpdateRestrictionsType"> <PropertyValue Property="Updatable" Bool="false"/> </Record> </Annotation> </Annotations>From our first look at annotations in the previous tutorial on Vocabularies we understand that these annotations are targeting the “Categories” entityset. XML has a reputation for being verbose, and that reputation is earned here.
However, with the ability we now have to read and understand annotation terms & values, we can see that all these annotation terms are from the Capabilities vocabulary (Org.OData.Capabilities.V1) and they are all of the same theme of operational limitations, with the terms being “DeleteRestrictions”, “InsertRestrictions” and “UpdateRestrictions”.
The entityset is annotated with three terms, each of which has a record structure as its type. Let’s dig in to the first occurring term which is “Capabilities.DeleteRestrictions”.
The way this works for the other terms (for insert and update operations) is very similar; digging into those is left as an exercise for you, dear reader.
Starting with the annotation target, which is “Main.EntityContainer/Categories”, we see that the first of the three annotations that are being applied is “Capabilities.DeleteRestrictions”:
xmlCopy<Annotations Target="Main.EntityContainer/Categories"> <Annotation Term="Capabilities.DeleteRestrictions"> <Record Type="Capabilities.DeleteRestrictionsType"> <PropertyValue Property="Deletable" Bool="false"/> </Record> </Annotation> ... </Annotations>If we look at the HTML representation of the Org.OData.Capabilities.V1 vocabulary we see that “DeleteRestrictions” is indeed listed in the Terms section, and is defined as having the DeleteRestrictionsType, which looks like this:

One of the properties in this type (this record structure, effectively) is the Boolean “Deletable”, a value for which (
false) is provided in the annotation for the entityset.Note that the “DeleteRestrictionsType” type is defined as being derived from DeleteRestrictionsBase. The difference between this base type and the derived type is that the derived type has one additional property NonDeletableNavigationProperties which is a detail we don’t have to worry over at this level of exploration.
Following these annotations terms, with their Boolean
falsevalues, we can see that the entityset is effectively read-only.If at this point you’re still looking at the metadata document for the the publicly available read-only service (mentioned in the Northbreeze tutorial) at https://odd.cfapps.eu10.hana.ondemand.com/northbreeze/$metadata, all the entitysets are read-only and you’ll see the same
<Annotations Target="...">pattern for the other entitysets.By the way, this OData service is being served by a CAP Node.js server, where the entity projection(s) in the service definition have the simple CDS-level annotation
@readonlyassigned. This is translated into these triplets of “Capabilities” based delete, insert and update restriction annotations at the OData level.For each of these DeleteRestrictions, InsertRestrictions and UpdateRestrictions terms in the "Capabilites" vocabulary, how many records can exist for a single term instance?
- Step 6
- the @readonly section of the Input Validation topic in Capire
- if you’re wondering why there’s a third vocabulary com.sap.vocabularies.Common.v1 included in the references section of this OData service’s metadata document, but there are no “Common” terms used, that’s just because this is served by a CAP server, and the CAP compiler will by default always include references to both the “Core” and “Common” vocabularies (see the
csn2annotationEdmfunction in@sap/cds-compiler/lib/edm/annotations/genericTranslations.jsfor details)