Draft Handling enables users to temporarily save changes to Business Objects without immediately writing them to the database. This is particularly important for complex input forms in Fiori applications. With Draft Handling, users can interrupt their work, continue at a later time, and only activate the changes when all inputs are complete and validated.
In this article, you will learn how Draft Handling works in RAP, how to create draft-enabled CDS Views and Behavior Definitions, and which Draft Actions are available for typical workflows.
The Draft Concept: Active vs. Draft Instances
The central concept in Draft Handling is the distinction between active instances and draft instances:
-
Active Instances (Active Entities): The persistent data in the database table. These represent the current, valid state of the Business Object and are visible to all users.
-
Draft Instances (Draft Entities): Temporary working copies stored in a separate draft table. A draft is only visible to the editing user and contains changes that are not yet validated or complete.
The lifecycle of a draft typically looks like this:
- Edit: User starts editing → System creates draft copy
- Modify: User changes data → Changes are automatically saved in the draft
- Prepare: System executes validations → Errors are displayed, but draft remains
- Activate: User activates → Draft becomes the active instance, draft entry is deleted
Alternatively, the user can choose Discard at any time to discard the draft without changing the active instance.
When Do I Need Draft?
Draft Handling makes sense for:
- Complex input forms with many fields
- Multi-step workflows where users want to save intermediate states
- Long-running transactions that cannot be completed in a single session
- Validations that should only be executed upon activation
Draft vs. Non-Draft Scenarios
| Aspect | Draft | Non-Draft |
|---|---|---|
| Storage | Separate draft table | Directly in database table |
| Intermediate saving | Yes, automatic | No |
| Validation | At Prepare/Activate | At every Save |
| Complexity | Higher | Lower |
| Fiori UX | Enhanced (Auto-Save) | Standard |
| Use Case | Complex forms | Simple CRUD apps |
Non-Draft Scenario (Standard)
managed implementation in class zbp_i_travel unique;strict ( 2 );
define behavior for ZI_Travel alias Travelpersistent table ztravellock masterauthorization master ( instance )etag master LastChangedAt{ create; update; delete;}Draft Scenario
managed implementation in class zbp_i_travel unique;strict ( 2 );with draft;
define behavior for ZI_Travel alias Travelpersistent table ztraveldraft table zdraft_travellock master total etag LastChangedAtauthorization master ( instance )etag master LastChangedAt{ create; update; delete;
draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume; draft determine action Prepare;}Exclusive vs. Shared Locks
RAP supports two lock modes for draft scenarios:
| Lock Type | Description | Use Case |
|---|---|---|
| Exclusive | Only one user can edit the object | Standard for critical data |
| Shared | Multiple users can create drafts | Collaborative scenarios |
Exclusive Lock (Standard)
With Exclusive Lock, only one user can edit a Business Object at a time:
define behavior for ZI_Travel alias Travelpersistent table ztraveldraft table zdraft_travellock master total etag LastChangedAtauthorization master ( instance ){ // Exclusive Lock is the default // Only one draft per instance allowed}Shared Lock
With Shared Lock, multiple users can create independent drafts:
define behavior for ZI_Travel alias Travelpersistent table ztraveldraft table zdraft_travellock master total etag LastChangedAtauthorization master ( instance )late numbering{ // Shared Draft: Multiple users can // edit different versions with unmanaged save;}Creating Draft-Enabled CDS Views
To use Draft Handling, your CDS Views must meet certain requirements. The most important requirement is a Total ETag field that tracks changes to the entire entity.
Interface View with Draft Support
@AccessControl.authorizationCheck: #NOT_REQUIRED@EndUserText.label: 'Travel - Interface View'define root view entity ZI_Travel as select from ztravel
association [0..*] to ZI_Booking as _Booking on $projection.TravelUUID = _Booking.TravelUUID{ key travel_uuid as TravelUUID, travel_id as TravelId, agency_id as AgencyId, customer_id as CustomerId, begin_date as BeginDate, end_date as EndDate, @Semantics.amount.currencyCode: 'CurrencyCode' total_price as TotalPrice, currency_code as CurrencyCode, description as Description, overall_status as OverallStatus,
// Administrative fields @Semantics.user.createdBy: true created_by as CreatedBy, @Semantics.systemDateTime.createdAt: true created_at as CreatedAt, @Semantics.user.lastChangedBy: true last_changed_by as LastChangedBy, @Semantics.systemDateTime.lastChangedAt: true last_changed_at as LastChangedAt,
// Important for Draft: Total ETag for Concurrency Control @Semantics.systemDateTime.localInstanceLastChangedAt: true local_last_changed_at as LocalLastChangedAt,
// Associations _Booking}The field LocalLastChangedAt with the annotation @Semantics.systemDateTime.localInstanceLastChangedAt is used as the Total ETag. It enables the RAP framework to detect conflicts during concurrent access.
Projection View for Draft
The Projection View must expose the draft-relevant fields:
@AccessControl.authorizationCheck: #NOT_REQUIRED@EndUserText.label: 'Travel - Projection View'@Metadata.allowExtensions: truedefine root view entity ZC_Travel provider contract transactional_query as projection on ZI_Travel{ key TravelUUID, TravelId, AgencyId, CustomerId, BeginDate, EndDate, TotalPrice, CurrencyCode, Description, OverallStatus, CreatedBy, CreatedAt, LastChangedBy, LastChangedAt, LocalLastChangedAt,
_Booking : redirected to composition child ZC_Booking}Draft Table Definition
For each entity with draft support, you need a separate draft table. This table stores the temporary working copies and must have the same structure as the main table, supplemented with draft-specific administrative fields:
@EndUserText.label : 'Travel Draft'@AbapCatalog.enhancement.category : #NOT_EXTENSIBLE@AbapCatalog.tableCategory : #TRANSPARENTdefine table zdraft_travel { key client : abap.clnt not null; key travel_uuid : sysuuid_x16 not null; // Draft UUID travel_id : abap.numc(8); agency_id : abap.numc(6); customer_id : abap.numc(6); begin_date : abap.dats; end_date : abap.dats; total_price : abap.curr(16,2); currency_code : abap.cuky; description : abap.string(256); status : abap.char(1);
// Draft-specific fields (automatically used by the framework) "%admin" : include sych_bdl_draft_admin_inc;}Draft-Admin Include
The include sych_bdl_draft_admin_inc contains important administrative fields:
| Field | Description |
|---|---|
draftentityuuid | Unique draft ID |
draftentitycreationdatetime | Creation timestamp |
draftentitylastchangedatetime | Last modification |
draftentitycreatedby | Created by |
draftentitylastchangedby | Changed by |
draftentityownershipstate | Draft ownership |
hasactiveentity | Has active version |
isactiveentity | Is active version |
Draft Actions
RAP automatically provides Draft Actions:
Edit Action
Starts edit mode and creates a draft:
" In the Behavior Definitiondraft action Edit;
" EML callMODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE Edit FROM VALUE #( ( TravelId = '00000001' ) ) MAPPED DATA(mapped) FAILED DATA(failed) REPORTED DATA(reported).Activate Action
Activates the draft and writes the changes to the database:
" In the Behavior Definitiondraft action Activate optimized;
" EML callMODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE Activate FROM VALUE #( ( %key-TravelId = '00000001' ) ) MAPPED mapped FAILED failed REPORTED reported.
COMMIT ENTITIES.The optimized keyword ensures that only changed fields are written.
Discard Action
Discards the draft without saving:
" In the Behavior Definitiondraft action Discard;
" EML callMODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE Discard FROM VALUE #( ( %key-TravelId = '00000001' ) ) FAILED failed REPORTED reported.Resume Action
Continues editing an existing draft:
" In the Behavior Definitiondraft action Resume;
" EML callMODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE Resume FROM VALUE #( ( %key-TravelId = '00000001' ) ) RESULT DATA(result) FAILED failed REPORTED reported.Prepare Action
Executes validations without activating:
" In the Behavior Definitiondraft determine action Prepare { validation validateDates; validation validateCustomer;}
" EML call - checks all draft validationsMODIFY ENTITIES OF zi_travel ENTITY Travel EXECUTE Prepare FROM VALUE #( ( %key-TravelId = '00000001' ) ) FAILED failed REPORTED reported.Complete Draft Example
1. Behavior Definition
managed implementation in class zbp_i_travel unique;strict ( 2 );with draft;
define behavior for ZI_Travel alias Travelpersistent table ztraveldraft table zdraft_travellock master total etag LastChangedAtauthorization master ( instance )etag master LastChangedAt{ // Standard operations create; update; delete;
// Field mapping field ( readonly ) TravelId; field ( readonly ) CreatedBy, CreatedAt, LastChangedBy, LastChangedAt; field ( numbering : managed ) TravelId;
// Actions action ( features : instance ) acceptTravel result [1] $self; action ( features : instance ) rejectTravel result [1] $self;
// Validations - executed at Prepare/Activate validation validateDates on save { field BeginDate, EndDate; } validation validateCustomer on save { field CustomerId; }
// Determinations determination setStatusNew on modify { create; }
// Draft Actions draft action Edit; draft action Activate optimized; draft action Discard; draft action Resume; draft determine action Prepare { validation validateDates; validation validateCustomer; }}2. Projection with Draft
projection;strict ( 2 );use draft;
define behavior for ZC_Travel alias Travel{ use create; use update; use delete;
use action acceptTravel; use action rejectTravel;
use action Edit; use action Activate; use action Discard; use action Resume; use action Prepare;}3. Implementing Draft Validation
CLASS lhc_travel DEFINITION INHERITING FROM cl_abap_behavior_handler. PRIVATE SECTION. METHODS validateDates FOR VALIDATE ON SAVE IMPORTING keys FOR Travel~validateDates.ENDCLASS.
CLASS lhc_travel IMPLEMENTATION. METHOD validateDates. " Read draft data (not active data!) READ ENTITIES OF zi_travel IN LOCAL MODE ENTITY Travel FIELDS ( BeginDate EndDate ) WITH CORRESPONDING #( keys ) RESULT DATA(lt_travel).
LOOP AT lt_travel INTO DATA(ls_travel). " Draft validation: Warning instead of error possible IF ls_travel-EndDate < ls_travel-BeginDate. APPEND VALUE #( %tky = ls_travel-%tky %element-EndDate = if_abap_behv=>mk-on ) TO failed-travel.
APPEND VALUE #( %tky = ls_travel-%tky %state_area = 'VALIDATE_DATES' %element-EndDate = if_abap_behv=>mk-on %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error text = 'End date must be after begin date' ) ) TO reported-travel. ENDIF. ENDLOOP. ENDMETHOD.ENDCLASS.Draft in Fiori Elements
When Draft is activated, Fiori Elements automatically shows:
- Auto-Save: Changes are automatically saved
- Draft Indicator: Shows that unsaved changes exist
- Discard Button: Discards all changes
- Save Button: Activates the draft (executes validations)
Concurrent Access and Locking in Detail
The RAP framework automatically manages locks to ensure data consistency. With Draft Handling, there are two critical scenarios:
Scenario 1: Two Users Editing the Same Instance
With Exclusive Lock (Standard):
- User A starts Edit → Draft is created, lock is set
- User B tries Edit → Error message “Object is being edited by User A”
- User A activates or discards → Lock is released
- User B can now edit
Scenario 2: Draft Timeout
When a user creates a draft and leaves the session:
- The draft remains in the draft table
- On renewed access, editing can be resumed with Resume
- Other users remain blocked (with Exclusive Lock)
To avoid orphaned drafts, you can set up a background job that automatically deletes old drafts:
" Example: Delete drafts older than 7 daysDELETE FROM zdraft_travel WHERE draftentitylastchangedatetime < @lv_cutoff_timestamp.Best Practices
- Draft only when necessary - For simple CRUD apps without complex forms, Draft is often unnecessary and increases complexity
- Validations in Prepare - Enables early feedback without activation, improves user experience
- Define Draft Table correctly - Always use the admin include
sych_bdl_draft_admin_inc - Optimized Activate - The
optimizedkeyword with Activate improves performance as only changed fields are written - Use State Areas - Group validation messages for better UX with
%state_area - Set up Draft Timeout - Configure a background job for automatic deletion of old drafts
- Don’t forget Testing - Test all draft scenarios: Edit, Activate, Discard, Resume, and Concurrent Access
Summary
Draft Handling is a powerful feature of the RESTful ABAP Programming Model that significantly improves the user experience for complex data entry scenarios. The key points:
- Draft vs. Active: Drafts are temporary working copies that only become persistent data upon activation
- Draft Table: A separate table stores the draft instances with the admin include for administrative fields
- Draft Actions: Edit, Activate, Discard, Resume, and Prepare enable the complete draft workflow
- Locking: Exclusive Lock prevents simultaneous editing, Shared Lock enables collaborative scenarios
- Validations: With Prepare, validations can be executed without activating the draft
With the proper use of Draft Handling, you create professional Fiori applications that provide an optimal user experience even in complex input scenarios.
Related Topics
- RAP Basics - Introduction to the RESTful ABAP Programming Model
- RAP Actions and Functions - Implementing business logic
- Feature Control & Side Effects - Dynamic UI control
- RAP Determinations and Validations - Automatic field calculations and checks
- RAP Tutorial Part 1 - Step-by-step to your first RAP application