Use @title, @description, @mandatory, @readonly, @assert.range, and UI annotations. Understand annotation propagation.
How to apply UI5/Fiori annotations, OData vocabulary annotations, and validation annotations in CDS — and how they affect the generated metadata and UI.
Annotations are metadata attached to entities, fields, or services. They don't change the data structure — they describe how data should be displayed, validated, and behaved.
@title: 'Books Catalog' // OData Core annotation
entity Books {
key ID : UUID;
@title: 'Book Title'
@mandatory // validation
title : String(100);
@Measures.ISOCurrency: currency // SAP UI5 annotation
price : Decimal(10,2);
currency : String(3);
}
// Simple value
@title: 'My Title'
// Record value
@UI.LineItem: [{ Value: title, Label: 'Book Title' }]
// Inline on a field
entity Books {
@(title: 'Price', Measures.ISOCurrency: currency)
price : Decimal(10,2);
}
@Core.Description: 'The main catalog of books' // service/entity description
@Core.LongDescription: '...'
@Core.Computed // field is server-managed
@Core.Immutable // field cannot be updated after create
entity Books {
key ID : UUID @Core.Computed;
@Core.Description: 'The full book title'
title : String(100);
@Core.IsURL
coverUrl : String(500);
}
These annotations drive SAP Fiori Elements apps — the page layout, columns, forms, and navigation are all generated from these.
annotate CatalogService.Books with @(
UI: {
// List Report columns
LineItem: [
{ Value: title, Label: 'Title' },
{ Value: price, Label: 'Price' },
{ Value: author.name,Label: 'Author' },
{ Value: stock, Label: 'Stock' }
],
// Object Page header
HeaderInfo: {
TypeName : 'Book',
TypeNamePlural: 'Books',
Title : { Value: title },
Description : { Value: author.name }
},
// Object Page sections
Facets: [
{
$Type : 'UI.ReferenceFacet',
Label : 'General',
Target: '@UI.FieldGroup#General'
}
],
FieldGroup#General: {
Data: [
{ Value: title },
{ Value: price },
{ Value: stock },
{ Value: author }
]
}
}
);
entity Products {
key ID : UUID;
@mandatory // must be provided on create
@title: 'Product Name'
name : String(100);
@assert.range: [0.01, 99999.99] // value must be in range
price : Decimal(10,2);
@assert.format: '^[A-Z]{2}[0-9]{4}$' // regex validation
sku : String(6);
@assert.unique // unique across all rows
barcode : String(13);
}
CAP enforces these constraints on CREATE and UPDATE automatically — no handler code needed.
entity Orders {
key ID : UUID;
@Common.ValueList: {
CollectionPath: 'Customers',
Parameters: [
{ $Type: 'Common.ValueListParameterOut', LocalDataProperty: customer_ID, ValueListProperty: 'ID' },
{ $Type: 'Common.ValueListParameterDisplayOnly', ValueListProperty: 'name' }
]
}
customer : Association to Customers;
}
This creates a searchable dropdown in Fiori UI — no UI code needed.
entity Books {
key ID : UUID;
@Search.defaultSearchElement // included in $search queries
title : String(100);
@Search.defaultSearchElement
author : String(100);
price : Decimal(10,2); // not searchable
}
GET /odata/v4/catalog/Books?$search=clean code
// Searches title and author fields only
Best practice: put UI annotations in a separate file to keep the data model clean:
db/
schema.cds ← entity definitions only
srv/
catalog-service.cds ← service definition
fiori-annotations.cds ← all UI annotations
// srv/fiori-annotations.cds
using CatalogService from './catalog-service';
annotate CatalogService.Books with @(
UI.LineItem: [ ... ],
UI.HeaderInfo: { ... }
);
@title, @mandatory, and @assert.range to fields?@UI.LineItem with 4 columns?@Search.defaultSearchElement to 2 fields?