Fiori Elements
RAP / CDS / ODataintermediate

Extending Fiori Elements Apps with the Flexible Programming Model

Learn how to add custom logic, actions, and UI fragments to a generated Fiori Elements app without breaking the annotation-driven contract, using the flexible programming model's controller extensions and extension points.

Explanation

Fiori Elements apps are generated from CDS annotations, but real projects almost always need something the standard floorplan cannot express purely declaratively: a custom validation message tied to a button, a field that becomes read-only based on complex cross-entity logic, or a custom fragment embedded in the object page header. The flexible programming model exists precisely for this gap. It lets you keep 90% of the app annotation-driven while injecting targeted JavaScript/TypeScript extensions or backend custom logic at well-defined extension points, rather than ejecting into a fully freestyle SAPUI5 app. On the backend side, custom logic surfaces through RAP behavior definitions: custom actions (defined with 'action' in the behavior definition and implemented in the behavior pool), determinations that run on save or on field change, and validations that block save with messages. These are annotated with UI.Hidden, UI.Applicable expressions, or OData semantic actions so Fiori Elements automatically renders a button or triggers logic without any frontend coding. For example, a custom action 'Approve' with a precondition based on status field appears as a toolbar button only when the row is in a draft-eligible state, and calling it triggers ABAP logic that changes status and writes an audit record via EML MODIFY inside the behavior implementation class. On the frontend side, when annotations cannot express the requirement, you use the SAPUI5 flexible programming model's extension API: controller extensions (extending the generated page controller with lifecycle hooks like onInit or onBeforeRendering), custom sections and fragments referenced through manifest.json's 'sap.ui.generic.app' or newer 'sap.fe' extension points, and custom columns/fields registered declaratively via annotations combined with a fragment XML view. Extensions are configured almost entirely in manifest.json under controlConfiguration and extends nodes, keeping the extension traceable and upgrade-safe because you are not modifying generated code directly. A critical governance concern is the extensibility boundary: key-user extensibility (Adaptation Projects using the UI Adaptation editor, available in S/4HANA Cloud public edition) is layer-safe and does not require developer access, but is limited to simple field/label changes and simple custom fields. Developer extensions (controller extensions, custom actions, custom fragments) require an ABAP Cloud developer extensibility model in the public cloud, or classic developer extensibility in on-premise/private cloud, and must go through the released extension points documented for the given app to remain upgrade-stable. Using undocumented or side-loading techniques breaks upgrade compatibility and is explicitly against the extensibility contract. A further nuance: side effects. When a custom action or determination changes a field that also affects other fields' visibility or values, you must declare an explicit Common.SideEffects annotation identifying which fields to refresh, and which action or field change triggers it. Omitting this causes stale UI state where users see incorrect values until a manual refresh, a very common and hard-to-diagnose defect in field service and sales order apps. Testing these extensions requires both a unit test for the RAP behavior implementation (using CDS test doubles / EML-based test class) and a manual or automated SAPUI5 test verifying the frontend extension point actually mounts (since a misconfigured manifest 'extends' node fails silently, simply not rendering the custom fragment, with no runtime error in most cases).

Real project scenario

On a sales order management RAP app, business asked for an 'Escalate to Manager' button that appears only for orders over a threshold value and in 'Pending' status, sends a custom action to update escalation flag and log a note, then automatically refreshes the approval status field and re-evaluates the exception icon in the list report line.

Common mistakes

โ€ข Implementing an entire custom action in JavaScript on the frontend instead of exposing it as a proper RAP custom action, bypassing draft and authorization consistency checks โ€ข Forgetting Common.SideEffects annotations after a custom action changes dependent fields, leaving the UI showing stale data โ€ข Modifying generated fragment/controller files directly instead of using controller extensions, which breaks on the next code regeneration or app update โ€ข Mixing key-user adaptation changes with developer extensions in a way that creates layer conflicts and unpredictable precedence at runtime โ€ข Not testing custom actions with concurrent drafts, causing lock or ETag conflicts in production that were never seen in single-user dev testing

Best practices

โ€ข Prefer annotation-driven solutions (UI.Applicable, UI.Hidden, Validation, Determination) before reaching for controller extensions โ€ข Always pair a custom action or determination that changes dependent fields with an explicit Common.SideEffects annotation โ€ข Keep custom logic in the RAP behavior pool, not scattered in frontend JavaScript, to preserve consistency, authorization checks, and testability โ€ข Use manifest.json extension points and controller extensions rather than touching generated artifacts directly โ€ข Document each extension point used and its extensibility category (key user vs developer) for upgrade impact assessments

Interview angle

Interviewers probe whether you understand the difference between annotation-only design and flexible programming model extensions, and whether you know when NOT to extend (i.e., prefer declarative solutions). Be ready to explain SideEffects, extension points in manifest.json, and the layered extensibility model (key user vs developer, cloud vs on-premise) as this signals real hands-on RAP+Fiori Elements delivery experience rather than tutorial-level exposure.