Custom form fields

Shop Floor Production  •  Article  •  8/27/2026  • 

Custom form fields allow you to add custom fields to Shop Floor Production's registration screens via a per-tenant extension (PTE). This lets customers and partners define fields that users must populate during the production registration process, entirely in AL.

Supported screens

Custom form fields can be added to the following screens:

  • Register Input
  • Register Output

Read-only custom fields can also be shown on the following correction screens:

  • Correct Consumption
  • Correct Output

Registering custom form elements

Custom form elements are registered through one of two events:

SFPBCGetFormElements14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnAddCustomFormElementsForConsumption(var TempCustomFormElements: Record TempCustomFormElements14FDW temporary; var CustomFormFields: Dictionary of [Text, Text]; ProdOrderComponent: Record "Prod. Order Component")
begin
end;

[IntegrationEvent(false, false)]
local procedure OnAddCustomFormElementsForOutput(var TempCustomFormElements: Record TempCustomFormElements14FDW temporary; ProdOrderRoutingLine: Record "Prod. Order Routing Line")
begin
end;

The CustomFormFields parameter on OnAddCustomFormElementsForConsumption carries any values already pre-populated by custom consumption scan logic. If a value is present for your field's key, pass it to SetDefaultValue so the scanned value carries through onto the rendered field.

Read-only fields can be registered on the correction screens through these two events:

SFPBCGetFormElements14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnGetConsumptionCorrectionCustomFields(ItemLedgerEntry: Record "Item Ledger Entry"; var CustomFields: Record TempCustomFormElements14FDW temporary)
begin
end;

[IntegrationEvent(false, false)]
local procedure OnGetOutputCorrectionCustomFields(ItemLedgerEntry: Record "Item Ledger Entry"; ProductionOrderLine: Record "Prod. Order Line"; var CustomFields: Record TempCustomFormElements14FDW temporary)
begin
end;

TempCustomFormElements record

All of the events above pass a TempCustomFormElements14FDW temporary record. This table stores the definition of every custom field and is parsed and returned to the React frontend so it can render the fields on screen.

Helper functions

FunctionDescription
AddNewCustomFormElement(FieldKey, FieldName, ElementType, RequiredValue, VisibleValue, IsInvalidValue)Initializes a new custom form element. FieldKey is a unique camelCase identifier; FieldName is the label shown in the app.
SetDefaultValueSets the default value for the current custom form element (text or date).
SetNumberMinValue / SetNumberMaxValueSets the minimum/maximum acceptable value for a numeric field.
SetNumberMaxDecimalPlacesSets the maximum number of decimal places accepted for a numeric field.
SetTextAreaRowsAndMaxLengthSets the number of rows and maximum length for a text area field.
AddSelectOptionAdds a selectable option to a select field.
AddCustomFormElementErrorValidationAdds a custom validation error message for the field.

ElementType (FormElementTypes14FDW)

FormElementTypes14FDW defines the supported field types:

  • Text — text field input
  • Number — text field input formatted to only accept numbers
  • Date — date picker input
  • Select — select box with multiple options
  • TextArea — large text field input
  • ReadOnly — read-only field that cannot be modified
  • Boolean — Yes/No field

Example: defining custom form elements

The following example defines a required text field and a select field on the Register Output screen:

[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPBCGetFormElements14FDW, OnAddCustomFormElementsForOutput, '', false, false)]
local procedure AddCustomFormElementsForOutput(var TempCustomFormElements: Record TempCustomFormElements14FDW temporary)
begin
    TempCustomFormElements.AddNewCustomFormElement('caseNo', 'Case No.', FormElementTypes14FDW::Text, true, true, false);

    TempCustomFormElements.AddNewCustomFormElement('shift', 'Shift', FormElementTypes14FDW::Select, false, true, false);
    TempCustomFormElements.AddSelectOption('1', 'First shift');
    TempCustomFormElements.AddSelectOption('2', 'Second shift');
end;

Custom field validation

Custom fields can be validated using one of the two events defined in SFPBCProductionReg14FDW.Codeunit.al:

  • OnValidateCustomFormElementsForConsumption
  • OnValidateCustomFormElementsForOutput

Both events pass the TempCustomFormElements record. Use the FieldKey you defined earlier to find the correct record; the Field Value field holds the user-entered value.

These events run after the frontend's own checks — a field registered with RequiredValue = true on AddNewCustomFormElement already blocks submission of a blank value, so validation here doesn't need to re-check for a value being present. Use it for rules the frontend can't enforce on its own, such as format or business-rule checks.

[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPBCProductionReg14FDW, OnValidateCustomFormElementsForOutput, '', false, false)]
local procedure ValidateCustomFormElementsForOutput(var TempCustomFormElements: Record TempCustomFormElements14FDW)
var
    CaseNoLengthErr: Label 'Case No. must be exactly 6 characters.';
begin
    if not TempCustomFormElements.Get('caseNo') then
        exit;

    if StrLen(TempCustomFormElements."Field Value") <> 6 then
        TempCustomFormElements.AddCustomFormElementErrorValidation('caseNo', CaseNoLengthErr);
end;

Custom field logic

Finally, define what to do with the values entered into custom fields through the OnAfterPostProductionRegistration event. This event passes the TempCustomFormElements table along with the Item Ledger Entry number generated by the registration posting.

  • ItemLedgerEntryNo is 0 in two situations, and the event is raised in both. Always test for 0 before using the value, as in the example below.
  • The registration failed. The event is raised whether or not the posting succeeded, and 0 is how a failed posting is reported. Do not treat the event as confirmation that anything was posted.
  • No Item Ledger Entry was created. Output registered on a routing operation that is not the last operation of the routing produces only a Capacity Ledger Entry, so there is no Item Ledger Entry to write to. If you need to store a custom value against intermediate operations, write it to your own table keyed on the production order, line, and operation instead of to the Item Ledger Entry.
[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPBCProductionReg14FDW, OnAfterPostProductionRegistration, '', false, false)]
local procedure OnAfterPostProductionRegistration(var TempCustomFormElements: Record TempCustomFormElements14FDW; ItemLedgerEntryNo: Integer)
var
    ItemLedgerEntry: Record "Item Ledger Entry";
begin
    if ItemLedgerEntryNo = 0 then
        exit;

    ItemLedgerEntry.Get(ItemLedgerEntryNo);

    if TempCustomFormElements.Get('caseNo') then begin
        // "Case No." is assumed here to be a custom field added to Item Ledger Entry via a table extension
        ItemLedgerEntry."Case No." := TempCustomFormElements."Field Value";
        ItemLedgerEntry.Modify();
    end;
end;