1. Introduction
In Fiori elements applications built with CAP, there is often a requirement to control the visibility of buttons such as Create, Edit, or custom actions based on the user’s authorization.
While in SAP S/4HANA systems this can be achieved through “Adapt UI” and app variants, as described in this blog post, such options are not available in SAP BTP.
To meet this requirement, we can leverage a combination of annotations, a singleton entity that holds feature control flags, and logic implemented in event handlers.
This blog explains how to implement this approach to achieve dynamic button visibility in Fiori elements applications.
2. About Authorization Control with @restrict
The `@restrict` annotation in CAP allows you to define fine-grained access control for CRUD operations and custom actions based on user roles. While this ensures backend-level security, there is a limitation when using Fiori elements.
Even if a user does not have permission to perform a certain operation (such as Create or Execute Action), the corresponding button is still rendered in the UI. When the user clicks the button, they will encounter a “403 Forbidden” error.
Although the backend restriction works as expected, this experience can be confusing or frustrating for end users. Therefore, combining `@restrict` with UI-level control — such as dynamically hiding or disabling buttons based on authorization — leads to a much better user experience.
service CatalogService {
.draft.enabled
@restrict: [{ grant: [‘READ’,’WRITE’], to: ‘Admin’},
{ grant: ‘READ’}]
entity Books as projection on my.Books
actions {
action updateStock(stock : Integer) returns Books;
};
@restrict: [{ to: ‘Admin’}]
action autoFillStock();
}
Authorization Control with `@restrict`
Forbidden Error Displayed When User Lacks Authorization
3. Method for Button Control Based on Authorization
In this section, we will walk through a sample scenario where the visibility and availability of CRUD buttons and custom actions (both bound and unbound) are controlled based on the user’s authorization.
Only users with the Admin role can perform these operations. All other users will see limited UI options according to their access level.
3.1. Setup
To implement this control, we will perform the following steps:
Define the main entity and custom actionsservice CatalogService {
.draft.enabled
entity Books as projection on my.Books
actions {
action updateStock(stock : Integer) returns Books; // bound action
};
//unbound action
action autoFillStock();
}Create a singleton entity that will act as a container for UI control flags (e.g., whether Create or action buttons should be shown or hidden) .singleton
entity FeatureControl {
operationHidden : Boolean;
operationEnabled : Boolean;
}Implement an event handler that checks the current user’s roles and sets the corresponding flags in the singleton entity. this.on (‘READ’, FeatureControl, async (req) => {
let operationHidden = true
if (req.user.is(‘Admin’)) {
operationHidden = false
}
return {
operationHidden: operationHidden,
operationEnabled: !operationHidden,
}
})
3.2. Hiding Buttons Using Annotations
To control the visibility of CRUD buttons and custom actions, we use UI.xxxHidden annotations, combined with dynamic path-based expressions referring to the singleton entity defined in the previous step.
// CRUD operations
annotate CatalogService.Books with @(
UI.CreateHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } },
UI.UpdateHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } },
UI.DeleteHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } }
);
To hide custom action buttons, place the UI.Hidden annotation within the UI.LineItem or UI.Identification annotation, depending on where the button is rendered.
annotate CatalogService.Books with @(
UI.LineItem : [
…,
{
$Type : ‘UI.DataFieldForAction’,
Action : ‘CatalogService.updateStock’,
Label : ‘Update Stock’,
![@UI.Hidden]: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ }},
},
{
$Type : ‘UI.DataFieldForAction’,
Action : ‘CatalogService.EntityContainer/autoFillStock’,
Label : ‘Auto Fill Stock’,
![@UI.Hidden]: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ }},
},
This results in the following behavior:
When the user has the authorization, buttons are visible.When the user does not have the authorization, buttons are hidden.
3.3. Disabling Buttons (Visible but Inactive)
In some cases, instead of hiding buttons entirely, you may want to display them in the UI but disable them based on the user’s authorization—or another condition.
For standard operations like Update and Delete, you can control their availability using `Capabilities.UpdateRestrictions.Updatable` and `Capabilities.DeleteRestrictions.Deletable` annotations.
// CRUD Operations
annotate CatalogService.Books with @(
Capabilities.UpdateRestrictions: {
Updatable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
},
Capabilities.DeleteRestrictions: {
Deletable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
}
);
For both bound and unbound actions, the availability can be controlled using the `Core.OperationAvailable` annotation.
// Bound Action
annotate CatalogService.Books with actions {
updateStock @(
Core.OperationAvailable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
);
};
// Unbound Action
annotate CatalogService.autoFillStock with @(
Core.OperationAvailable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
);
This results in the following behavior:
When the user has the authorization, buttons are enabled.
The Delete button is disabled on the List Report but enabled on the Object Page.
When the user does not have the authorization, buttons are disabled.
In this case, the Delete button is also disabled on the Object Page.
Known Limitation: $Not Expressions Do Not Work as Expected
Although using $Not to negate a boolean path (e.g., to invert an opererationEnabled flag) may seem straightforward, it currently does not work as expected in Fiori elements. For example, the following annotation will not properly hide the Create button:
UI.CreateHidden : { $edmJson: { $Not: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } } } // Does not work
This is a known limitation, and a related discussion can be found in this SAP Community Q&A thread. As a workaround, handle the negation in your backend logic. Rather than inverting the flag in the annotation, expose a dedicated boolean property (e.g., operationHidden) that already represents the intended value for UI.
4. Conclusion
In this blog post, we explored how to control the visibility and availability of CRUD and action buttons in Fiori elements applications based on user authorization. By combining annotations, a singleton entity, and event handlers, you can provide a more secure and user-friendly UI experience tailored to each user’s role.
References
Use Roles to Toggle Visibility of UI elements (CAP Documentation)Dynamic CRUD Restrictions (GitHub – fiori-elements-feature-showcase)Enabling Custom Actions Defined Using Annotations (SAPUI5 SDK)
1. IntroductionIn Fiori elements applications built with CAP, there is often a requirement to control the visibility of buttons such as Create, Edit, or custom actions based on the user’s authorization.While in SAP S/4HANA systems this can be achieved through “Adapt UI” and app variants, as described in this blog post, such options are not available in SAP BTP.To meet this requirement, we can leverage a combination of annotations, a singleton entity that holds feature control flags, and logic implemented in event handlers.This blog explains how to implement this approach to achieve dynamic button visibility in Fiori elements applications.2. About Authorization Control with @restrictThe `@restrict` annotation in CAP allows you to define fine-grained access control for CRUD operations and custom actions based on user roles. While this ensures backend-level security, there is a limitation when using Fiori elements.Even if a user does not have permission to perform a certain operation (such as Create or Execute Action), the corresponding button is still rendered in the UI. When the user clicks the button, they will encounter a “403 Forbidden” error.Although the backend restriction works as expected, this experience can be confusing or frustrating for end users. Therefore, combining `@restrict` with UI-level control — such as dynamically hiding or disabling buttons based on authorization — leads to a much better user experience.service CatalogService {
.draft.enabled
@restrict: [{ grant: [‘READ’,’WRITE’], to: ‘Admin’},
{ grant: ‘READ’}]
entity Books as projection on my.Books
actions {
action updateStock(stock : Integer) returns Books;
};
@restrict: [{ to: ‘Admin’}]
action autoFillStock();
}Authorization Control with `@restrict`Forbidden Error Displayed When User Lacks Authorization3. Method for Button Control Based on AuthorizationIn this section, we will walk through a sample scenario where the visibility and availability of CRUD buttons and custom actions (both bound and unbound) are controlled based on the user’s authorization.Only users with the Admin role can perform these operations. All other users will see limited UI options according to their access level.3.1. SetupTo implement this control, we will perform the following steps:Define the main entity and custom actionsservice CatalogService {
.draft.enabled
entity Books as projection on my.Books
actions {
action updateStock(stock : Integer) returns Books; // bound action
};
//unbound action
action autoFillStock();
}Create a singleton entity that will act as a container for UI control flags (e.g., whether Create or action buttons should be shown or hidden) .singleton
entity FeatureControl {
operationHidden : Boolean;
operationEnabled : Boolean;
}Implement an event handler that checks the current user’s roles and sets the corresponding flags in the singleton entity. this.on (‘READ’, FeatureControl, async (req) => {
let operationHidden = true
if (req.user.is(‘Admin’)) {
operationHidden = false
}
return {
operationHidden: operationHidden,
operationEnabled: !operationHidden,
}
})3.2. Hiding Buttons Using AnnotationsTo control the visibility of CRUD buttons and custom actions, we use UI.xxxHidden annotations, combined with dynamic path-based expressions referring to the singleton entity defined in the previous step.// CRUD operations
annotate CatalogService.Books with @(
UI.CreateHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } },
UI.UpdateHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } },
UI.DeleteHidden: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ } }
);To hide custom action buttons, place the UI.Hidden annotation within the UI.LineItem or UI.Identification annotation, depending on where the button is rendered.annotate CatalogService.Books with @(
UI.LineItem : [
…,
{
$Type : ‘UI.DataFieldForAction’,
Action : ‘CatalogService.updateStock’,
Label : ‘Update Stock’,
![@UI.Hidden]: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ }},
},
{
$Type : ‘UI.DataFieldForAction’,
Action : ‘CatalogService.EntityContainer/autoFillStock’,
Label : ‘Auto Fill Stock’,
![@UI.Hidden]: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationHidden’ }},
},This results in the following behavior:When the user has the authorization, buttons are visible.When the user does not have the authorization, buttons are hidden.3.3. Disabling Buttons (Visible but Inactive)In some cases, instead of hiding buttons entirely, you may want to display them in the UI but disable them based on the user’s authorization—or another condition.For standard operations like Update and Delete, you can control their availability using `Capabilities.UpdateRestrictions.Updatable` and `Capabilities.DeleteRestrictions.Deletable` annotations.// CRUD Operations
annotate CatalogService.Books with @(
Capabilities.UpdateRestrictions: {
Updatable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
},
Capabilities.DeleteRestrictions: {
Deletable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
}
);For both bound and unbound actions, the availability can be controlled using the `Core.OperationAvailable` annotation.// Bound Action
annotate CatalogService.Books with actions {
updateStock @(
Core.OperationAvailable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
);
};
// Unbound Action
annotate CatalogService.autoFillStock with @(
Core.OperationAvailable: { $edmJson: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } }
); This results in the following behavior:When the user has the authorization, buttons are enabled.The Delete button is disabled on the List Report but enabled on the Object Page. When the user does not have the authorization, buttons are disabled.In this case, the Delete button is also disabled on the Object Page. Known Limitation: $Not Expressions Do Not Work as ExpectedAlthough using $Not to negate a boolean path (e.g., to invert an opererationEnabled flag) may seem straightforward, it currently does not work as expected in Fiori elements. For example, the following annotation will not properly hide the Create button:UI.CreateHidden : { $edmJson: { $Not: { $Path: ‘/CatalogService.EntityContainer/FeatureControl/operationEnabled’ } } } // Does not workThis is a known limitation, and a related discussion can be found in this SAP Community Q&A thread. As a workaround, handle the negation in your backend logic. Rather than inverting the flag in the annotation, expose a dedicated boolean property (e.g., operationHidden) that already represents the intended value for UI.4. ConclusionIn this blog post, we explored how to control the visibility and availability of CRUD and action buttons in Fiori elements applications based on user authorization. By combining annotations, a singleton entity, and event handlers, you can provide a more secure and user-friendly UI experience tailored to each user’s role.ReferencesUse Roles to Toggle Visibility of UI elements (CAP Documentation)Dynamic CRUD Restrictions (GitHub – fiori-elements-feature-showcase)Enabling Custom Actions Defined Using Annotations (SAPUI5 SDK) Read More Technology Blog Posts by Members articles
#SAP
#SAPTechnologyblog