Service Bindings
RAP / CDS / ODataintermediate

Service Bindings Part 6: Preview, $metadata and Fiori Elements Annotations

Key takeaway

Turn the published service into a real Fiori Elements app: add UI annotations in metadata extensions, verify them in the binding preview, read $metadata to see exactly what the UI receives, and generate and deploy the app.

Explanation

📌 KEY POINTS
✅ Preview renders a Fiori Elements app from your annotations.

✅ UI annotations belong in metadata extensions of the projection.

✅ $metadata shows exactly what consumers receive.

⚠️ If a field is missing in the UI, check @UI annotations before code.

▶ WHERE WE ARE
The Purchase Request service is published and tested (Parts 2–5). The preview works but looks empty and generic. In this part you describe the screens with annotations, check them in preview, and understand how Fiori Elements turns metadata into UI.

━━━━━━━━━━━━━━━━━━━━
▶ HOW FIORI ELEMENTS WORKS

Fiori Elements does not have hand-written screens. It reads the service's $metadata — entity types, keys, navigation, actions and UI annotations — and generates standard floorplans: a List Report (search + table) and an Object Page (header, sections, item table). So what you see in the app is a direct function of the annotations you publish. If something is missing on screen, look at the annotations first.

━━━━━━━━━━━━━━━━━━━━
▶ WHERE ANNOTATIONS LIVE

Put UI annotations in a metadata extension (DDLX) on the projection view, not in the CDS view itself and never on the ZR_ base views:

  • The projection must allow it: @Metadata.allowExtensions: true (added in Managed RAP Part 5).
  • One metadata extension per projection entity: ZC_PurchReq and ZC_PurchReqItem.
  • @Metadata.layer: #CORE for your own development.

This keeps the data model clean and lets UI and API projections differ.

━━━━━━━━━━━━━━━━━━━━
▶ THE ANNOTATIONS YOU NEED FOR A COMPLETE APP

  • @UI.headerInfo — the object's name, plural name and title field (shown on the Object Page header).
  • @UI.selectionField — filter fields in the List Report filter bar.
  • @UI.lineItem — table columns in the List Report (and in the Items table on the Object Page). Also used to place action buttons above the table with type #FOR_ACTION.
  • @UI.facet — the sections of the Object Page: e.g. a "General" section showing the identification fields and an "Items" section showing the item table through the _Item association.
  • @UI.identification — fields in the "General" section, plus action buttons on the Object Page header.
  • @UI.hidden — hide technical fields like UUIDs.
  • @Consumption.valueHelpDefinition — value help (F4) for a field, e.g. currency from the released view I_CurrencyStdVH.

Action buttons appear only if the action exists in the projection BDEF ("use action approve") AND is annotated with #FOR_ACTION. Their enabled state comes from feature control (Managed RAP Part 3) — you do not code it in the UI.

━━━━━━━━━━━━━━━━━━━━
▶ STEP BY STEP

  1. Right-click ZC_PurchReq → New → Metadata Extension, name ZC_PURCHREQ. Add the annotations from the code example.
  2. Do the same for ZC_PurchReqItem.
  3. Activate both. (No need to republish the binding for annotation changes, but you may need to clear the metadata cache — see Part 5.)
  4. Open ZUI_PURCHREQ_O4, select PurchaseRequest, click Preview.
  5. Check: filter bar shows Request ID and Status; table shows columns and Approve/Reject buttons; clicking a row opens the Object Page with General and Items sections; Submit appears in the header; Approve is greyed out for a New request (feature control working); Currency has a value help.

READING $METADATA — WHAT TO LOOK FOR
Open <service URL>$metadata in the browser or /IWFND/GW_CLIENT:

  • EntityType Name="PurchaseRequestType" — the entity; <Key> shows RequestUUID and, for draft services, IsActiveEntity.
  • Property elements — every exposed field with its Edm type (Edm.Guid, Edm.String, Edm.Decimal…). A field missing here is missing from the projection or service definition.
  • NavigationProperty Name="_Item" — the link to items. Missing = child not exposed in the service definition.
  • Action elements — submit, approve, reject, and the draft actions. Missing = not in the projection BDEF.
  • Annotations Target="…PurchaseRequestType" — UI.LineItem, UI.Facets, UI.HeaderInfo. Missing = annotation not active, not in a metadata extension of THIS projection, or stale cache.

This is the fastest way to answer "is the problem in the backend or in the UI?": if it is correct in $metadata, the backend is done.

━━━━━━━━━━━━━━━━━━━━
▶ WHAT PREVIEW PROVES AND WHAT IT DOES NOT

Proves: annotations, navigation, draft flow, actions, feature control and validations work end-to-end for your user.

Does not prove: launchpad integration, other users' roles, custom UI extensions, performance with real volumes, or deployment in QAS/PRD.

━━━━━━━━━━━━━━━━━━━━
▶ FROM PREVIEW TO A REAL APP

  1. In SAP Business Application Studio or VS Code with SAP Fiori tools, create a Fiori Elements "List Report Page" (OData V4).
  2. Connect to the system, choose service ZUI_PURCHREQ_O4, main entity PurchaseRequest, navigation entity _Item.
  3. Run locally; the app looks like the preview because both read the same metadata.
  4. Deploy to the ABAP repository and make it available: on-premise through a launchpad catalog, space/page and target mapping; in the cloud through an IAM app and business catalog (Part 3).

━━━━━━━━━━━━━━━━━━━━
▶ WHAT YOU HAVE AFTER THE SERIES

A complete, annotated Fiori Elements app on a RAP business object, and the skills to publish, move, troubleshoot and verify service bindings.

Code example

ABAP Code
// ===== Metadata extension ZC_PURCHREQ (header) =====@Metadata.layer: #CORE@UI.headerInfo: {  typeName:       'Purchase Request',  typeNamePlural: 'Purchase Requests',  title:          { type: #STANDARD, value: 'Description' },  description:    { type: #STANDARD, value: 'RequestID' }}annotate entity ZC_PurchReq with{  @UI.facet: [    { id: 'General', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE,      label: 'General', position: 10 },    { id: 'Items',   purpose: #STANDARD, type: #LINEITEM_REFERENCE,      label: 'Items', position: 20, targetElement: '_Item' }  ]   @UI.hidden: true  RequestUUID;   @UI: { lineItem:       [ { position: 10 } ],         identification: [ { position: 10 } ],         selectionField: [ { position: 10 } ] }  RequestID;   @UI: { lineItem:       [ { position: 20 } ],         identification: [ { position: 20 } ] }  Description;   @UI: { lineItem:       [ { position: 30 },                           { type: #FOR_ACTION, dataAction: 'approve', label: 'Approve' },                           { type: #FOR_ACTION, dataAction: 'reject',  label: 'Reject' } ],         identification: [ { position: 30 },                           { type: #FOR_ACTION, dataAction: 'submit',  label: 'Submit' } ],         selectionField: [ { position: 20 } ] }  Status;   @UI: { lineItem:       [ { position: 40 } ],         identification: [ { position: 40 } ] }  TotalAmount;   @Consumption.valueHelpDefinition: [ { entity: { name: 'I_CurrencyStdVH', element: 'Currency' } } ]  @UI.identification: [ { position: 50 } ]  CurrencyCode;   @UI.hidden: true  LocalLastChangedAt;} // ===== Metadata extension ZC_PURCHREQITEM (items) =====@Metadata.layer: #CORE@UI.headerInfo: { typeName: 'Item', typeNamePlural: 'Items',                  title: { type: #STANDARD, value: 'ItemText' } }annotate entity ZC_PurchReqItem with{  @UI.facet: [ { id: 'Item', purpose: #STANDARD, type: #IDENTIFICATION_REFERENCE,                 label: 'Item', position: 10 } ]   @UI.hidden: true  ItemUUID;  @UI.hidden: true  RequestUUID;   @UI: { lineItem: [ { position: 10 } ], identification: [ { position: 10 } ] }  ItemNo;  @UI: { lineItem: [ { position: 20 } ], identification: [ { position: 20 } ] }  ItemText;  @UI: { lineItem: [ { position: 30 } ], identification: [ { position: 30 } ] }  Quantity;  @Consumption.valueHelpDefinition: [ { entity: { name: 'I_UnitOfMeasureStdVH', element: 'UnitOfMeasure' } } ]  @UI.identification: [ { position: 35 } ]  Unit;  @UI: { lineItem: [ { position: 40 } ], identification: [ { position: 40 } ] }  Price;  @UI.hidden: true  LocalLastChangedAt;} // ===== What to find in $metadata after activation =====// <EntityType Name="PurchaseRequestType">//   <Key> RequestUUID, IsActiveEntity </Key>          draft-enabled key//   <Property Name="Description" Type="Edm.String" .../>//   <NavigationProperty Name="_Item" .../>              child exposed?// <Action Name="approve" IsBound="true" ...>             in projection BDEF?// <Annotations Target="...PurchaseRequestType">//   <Annotation Term="UI.LineItem"> ... </Annotation>    annotations active?//   <Annotation Term="UI.Facets">   ... </Annotation>

Real project scenario

A developer reported that the Approve button did not appear in the Purchase Request app, although the action worked in EML tests. The UI team suspected a Fiori bug.
Opening $metadata answered it in two minutes: the Action element "approve" was there, but UI.LineItem contained no DataFieldForAction for it. The #FOR_ACTION annotation had been added to the metadata extension of the API projection by mistake, not to ZC_PurchReq. After moving it and clearing the V4 metadata cache, the button appeared, correctly greyed out for requests in status New.

A week later the same team used the same technique the other way round: a missing Items section turned out to be a NavigationProperty missing from $metadata, which pointed to the service definition, not the UI.

Common mistakes

  • Putting UI annotations directly into CDS views or on ZR_ base views instead of metadata extensions on the projection.
  • Forgetting @Metadata.allowExtensions: true on the projection view.
  • Annotating an action with #FOR_ACTION but not exposing it in the projection BDEF (or the reverse).
  • Coding button enablement in the UI instead of using feature control in the BDEF.
  • Missing @UI.facet, which leaves the Object Page empty.
  • Wrong targetElement in the Items facet (must be the association name, e.g. '_Item').
  • Assuming a UI problem without first checking $metadata.
  • Expecting annotation changes to appear without clearing the metadata cache.

Best practices

  • One metadata extension per projection entity, layer #CORE for own development.
  • Hide technical keys with @UI.hidden; show semantic numbers instead.
  • Use released value help views (…StdVH) for currencies, units and other standard fields.
  • Let feature control drive button state; the UI just displays it.
  • Check $metadata first whenever something is missing on screen: backend or UI?
  • Use the binding preview for fast iteration, then generate the real app with SAP Fiori tools.

Interview angle

Common questions: "How does Fiori Elements know what to display?", "Where do you put UI annotations in RAP and why?", "Which annotations do you need for a List Report and Object Page?", "How do you show an action button?"
Strong answer: Fiori Elements renders from $metadata — entity types, navigation, actions and UI annotations. Annotations go into metadata extensions on the projection so the BO stays UI-neutral and different projections can have different UIs. Name headerInfo, selectionField, lineItem, facet and identification, and explain that an action button needs both the projection BDEF entry and a #FOR_ACTION annotation, while its enabled state comes from feature control. Saying you check $metadata to decide whether an issue is backend or UI is a strong practical signal.

Practice this lesson

Quick check

1. What is required for an Approve action button to appear in a Fiori Elements app built on a RAP service?

2. What is the fastest way to check what a RAP service really exposes?

3. A field exists in the projection but the preview shows no column for it. What is the most likely cause?

Interview questions

1. The Approve action works in EML tests, but the button does not appear in the Fiori Elements app. How do you analyze it, and what controls whether the button is enabled?

Answer it in your own words first, then compare.

2. How do you test a newly published RAP OData V4 service end to end before handing it to the UI team?

Answer it in your own words first, then compare.

Practice more with Interview AI →

Reference-verified: the steps are checked against official SAP documentation and tutorials, and object and field names in the sample code are checked for consistency. The code has not been compiled or run on an SAP system by ERPClimb. Syntax marked release-dependent varies by SAP release, so check it in your own system.

More lessons in this topic

Interview questions on this topic

Related SAP topics