Primary reference: https://cap.cloud.sap/docs/guides/uis/fiori OData annotation vocabulary: https://ui5.sap.com/#/topic/030faebe70b34198b17a93b4c6e7b4d7 Feature showcase: https://github.com/SAP-samples/fiori-elements-feature-showcase
Always annotate in app/<app-name>/annotations.cds — never inline in entity definitions,
never in webapp/annotations.xml in CAP projects.
Essential annotation structure for List Report + Object Page
using OrderService from '../../srv/order-service';
annotate OrderService.Orders with @(
// ── List Report ──────────────────────────────────────────────── UI.HeaderInfo: { TypeName: 'Order', TypeNamePlural: 'Orders', Title: { Value: orderNumber }, Description: { Value: customer.name } },
// Columns in the list table UI.LineItem: [ { Value: orderNumber, Label: 'Order #' }, { Value: customer.name, Label: 'Customer' }, { Value: orderDate }, { Value: totalAmount }, { Value: currency_code }, { Value: status, Criticality: statusCriticality }, // Inline action button { $Type: 'UI.DataFieldForAction', Action: 'OrderService.submitOrder', Label: 'Submit' } ],
// Filter fields above the table UI.SelectionFields: [ status, customer_ID, orderDate ],
// ── Object Page ──────────────────────────────────────────────── UI.Facets: [ { $Type: 'UI.ReferenceFacet', Label: 'General', ID: 'GeneralFacet', Target: '@UI.FieldGroup#General' }, { $Type: 'UI.ReferenceFacet', Label: 'Items', ID: 'ItemsFacet', Target: 'items/@UI.LineItem' // child entity table } ],
UI.FieldGroup #General: { $Type: 'UI.FieldGroupType', Data: [ { Value: orderNumber }, { Value: orderDate }, { Value: totalAmount }, { Value: status } ] } );
---
## Criticality — traffic light colours
```cds
// In your service entity or as calculated element:
entity Orders {
status : String enum { Draft; Open; Approved; Rejected; };
statusCriticality : Integer; // 0=None 1=Red 2=Yellow 3=Green 5=Purple
}
// In the handler — set criticality based on status:
this.after('READ', Orders, orders => {
for (const o of orders) {
o.statusCriticality =
o.status === 'Approved' ? 3 : // green
o.status === 'Rejected' ? 1 : // red
o.status === 'Open' ? 2 : 0 // yellow / none
}
})
// In annotations — reference the criticality field:
{ Value: status, Criticality: statusCriticality }
Value Help (ValueList)
// Simple value help from an associated entity
annotate OrderService.Orders:customer_ID with @(
Common.ValueList: {
CollectionPath: 'Customers',
Parameters: [
{ $Type: 'Common.ValueListParameterOut',
LocalDataProperty: customer_ID,
ValueListProperty: 'ID' },
{ $Type: 'Common.ValueListParameterDisplayOnly',
ValueListProperty: 'name' }
]
},
Common.ValueListWithFixedValues: false
);
Common.Text — show description next to ID
// Show customer name next to customer ID in all UI contexts
annotate OrderService.Orders:customer_ID with
@Common.Text: customer.name
@Common.TextArrangement: #TextFirst; // "ACME Corp (C001)"
Field labels via @title
// Set once on the entity — applies everywhere in the UI
annotate OrderService.Orders with {
orderNumber @title: 'Order Number';
orderDate @title: 'Order Date';
totalAmount @title: 'Total Amount';
status @title: 'Status';
}
Hiding fields
// Always hidden
annotate OrderService.Orders:technicalField with @UI.Hidden;
// Conditionally hidden (expression)
annotate OrderService.Orders:internalNote with @UI.Hidden: (status != 'Admin');
Multi-line text
annotate OrderService.Orders:description with @UI.MultiLineText;
Header Facets on Object Page
UI.HeaderFacets: [
{
$Type: 'UI.ReferenceFacet',
Target: '@UI.DataPoint#Amount'
},
{
$Type: 'UI.ReferenceFacet',
Target: '@UI.DataPoint#Status'
}
],
UI.DataPoint #Amount: {
Value: totalAmount,
Title: 'Total Amount'
},
UI.DataPoint #Status: {
Value: status,
Criticality: statusCriticality,
Title: 'Status'
}
Actions — bound vs unbound
// Bound action (acts on selected record)
{ $Type: 'UI.DataFieldForAction',
Action: 'OrderService.submitOrder',
Label: 'Submit',
Determining: true } // shows in footer of Object Page
// Unbound action (global, in table toolbar)
{ $Type: 'UI.DataFieldForAction',
Action: 'OrderService.EntityContainer/createDraftOrder',
Label: 'New Draft' }
Annotation file organisation
For large apps, split annotations by concern:
app/orders/
├── field-annotations.cds ← @title, @UI.Hidden, Common.Text
├── list-report.cds ← UI.LineItem, UI.SelectionFields
├── object-page.cds ← UI.Facets, UI.FieldGroup, UI.HeaderFacets
└── value-helps.cds ← Common.ValueList
Import all in a single index.cds:
using from './field-annotations';
using from './list-report';
using from './object-page';
using from './value-helps';
Common mistakes to avoid
-
❌ Putting annotations in
webapp/annotations.xml— not compiled by CAP, silently ignored -
✅ Always use
app/<name>/annotations.cds -
❌ Hardcoded label strings in
UI.LineItem— not translatable -
✅ Use
@titleon the field +'{i18n>key}'for labels -
❌ Using
statusCriticalityas a plain Integer without@UI.Hidden— shows as a column -
✅ Always hide technical helper fields:
@UI.Hidden -
❌ Referencing a child entity table without
@UI.LineItemon the child entity -
✅ Child entity needs its own
UI.LineItemannotation for the table facet to render -
❌ Forgetting
IDon facets when usingUI.CollectionFacet— breaks rendering -
✅ Every
CollectionFacetmust have a uniqueID -
❌ Mixing OData V2 annotation syntax (
@sap.label) with V4 vocabulary -
✅ V4 only:
@title,@Common.Text,@UI.*