Custom columns

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

Custom columns allow you to add additional data columns to Shop Floor Production list tables via a per-tenant extension (PTE). Shop Floor Production supports two mechanisms for this.

List profile columns are the preferred approach. Once registered, a custom column can be placed anywhere in the list configuration — including as the secondary or tertiary value stacked inside an existing column's cell — entirely through setup, with no further code changes. The legacy table-level events remain fully supported and are the only option for the four other tables that list profiles do not yet reach, but a value registered this way always appears as its own new column at the end of the table.

List profile columns (preferred)

List profiles currently cover the Production Order list page. Registering a column through this mechanism makes it available, without any further code changes, wherever a column can be selected on the SFP List Profile Columns setup page — as the primary value of its own column, or as the secondary or tertiary value layered onto an existing column.

Register a column ID

Extend the SFPListColumnID14FDW enum with a new value. The value's ID must be 50000 or higher — values below 50000 are reserved for standard, built-in columns and are ignored by the partner column resolver.

enumextension 50100 MyColumnsExt extends SFPListColumnID14FDW
{
    value(50100; myFinishedQuantity)
    {
        Caption = 'Finished Quantity';
    }
}

The enum value's Caption is what appears in the Column ID, Secondary Column ID, and Tertiary Column ID dropdowns on the SFP List Profile Columns page, and the value's name is used as the React data key.

Resolve the column's value

Subscribe to OnGetProductionOrderListColumnValue and, for your enum value(s), set ColumnValue and Handled := true. This event is only raised for columns that the active user's profile actually references (at any level), so an unused custom column costs nothing.

SFPListColumnProvider14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnGetProductionOrderListColumnValue(ColumnId: Enum SFPListColumnID14FDW; var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var ColumnValue: Text; var Handled: Boolean)
begin
end;

Example

The following example registers a Finished Quantity column and resolves its value from the production order line:

[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPListColumnProvider14FDW, OnGetProductionOrderListColumnValue, '', false, false)]
local procedure PopulateFinishedQuantityColumn(ColumnId: Enum SFPListColumnID14FDW; var ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var ColumnValue: Text; var Handled: Boolean)
var
    ProdOrderLine: Record "Prod. Order Line";
    FinishedQtyLbl: Label 'Finished: %1', Comment = '%1 = Finished Quantity';
begin
    case ColumnId of
        SFPListColumnID14FDW::myFinishedQuantity:
            begin
                if ProdOrderLine.Get(ProdOrderRoutingLine.Status, ProdOrderRoutingLine."Prod. Order No.", ProdOrderRoutingLine."Routing Reference No.") then
                    ColumnValue := StrSubstNo(FinishedQtyLbl, ProdOrderLine."Finished Quantity");
                Handled := true;
            end;
    end;
end;

This event is raised once for every row while the list is being built, so a subscriber must never raise an error. A single Error() — including an unguarded Get() on a record that does not exist — stops the whole Production Order list from loading, not just that one cell. Always test for existence before reading a related record, and return a blank ColumnValue when the value cannot be resolved.

Once the extension is published, an administrator opens the SFP List Profile Columns page (see Set up list profiles) and selects Finished Quantity as the Column ID, Secondary Column ID, or Tertiary Column ID of any line. Setting it as the Secondary Column ID on the Production Order No. column, for example, stacks Finished: 42 underneath the production order number instead of adding a new column to the table.

Table-level custom columns (legacy)

For the tables list profiles do not yet cover, Shop Floor Production also supports the original per-table extensibility events described below. These remain fully supported, but a column registered this way is always appended as a new column at the end of the table — it cannot be composed into an existing column the way a list profile column can.

Supported tables

Custom columns can be added to the following tables:

  • Production Order list
  • Component list (consumption)
  • Previous consumption registrations list
  • Previous output registrations list (last operation)
  • Previous output registrations list (intermediate operations)

CustomColumn object

Each custom column is represented as a JsonObject with the following fields:

FieldTypeDescription
columnCaptionstringThe column header displayed in the app.
columnValuestringThe value to display for the current row.
isFilterablebooleanWhen true, the column's values are included in the app's search functionality. This value must be consistent across all rows for columns with the same caption.

Events

To add columns to a table, subscribe to the relevant event and append CustomColumn objects to the CustomColumns parameter.

SFPBCProdOrderRoutingLine14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnGetProductionOrderListCustomColumns(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var CustomColumns: JsonArray)
begin
end;
SFPBCProdOrderComponents14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnGetConsumptionListCustomColumns(ProdOrderComponent: Record "Prod. Order Component"; var CustomColumns: JsonArray)
begin
end;

[IntegrationEvent(false, false)]
local procedure OnGetConsumptionCorrectionListCustomColumns(ItemLedgerEntry: Record "Item Ledger Entry"; var CustomColumns: JsonArray)
begin
end;
SFPBCProdOrderLine14FDW.Codeunit.al

[IntegrationEvent(false, false)]
local procedure OnGetRegisteredOutputListCustomColumns(ItemLedgerEntry: Record "Item Ledger Entry"; var CustomColumns: JsonArray)
begin
end;

[IntegrationEvent(false, false)]
local procedure OnGetRegisteredOutputListCustomColumnsNotLastOperation(CapacityLedgerEntry: Record "Capacity Ledger Entry"; var CustomColumns: JsonArray)
begin
end;

The record parameter in each event represents the row currently being rendered — the routing line, component, item ledger entry, or capacity ledger entry, depending on the table. On the output list, entries for the last operation of a routing are backed by Item Ledger Entries, while entries for intermediate operations are backed by Capacity Ledger Entries; use whichever event matches the table you're extending.

Example

The following example adds custom columns to the Production Order list and the Component list:

[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPBCProdOrderRoutingLine14FDW, OnGetProductionOrderListCustomColumns, '', false, false)]
local procedure PopulateProductionOrderColumns(ProdOrderRoutingLine: Record "Prod. Order Routing Line"; var CustomColumns: JsonArray)
begin
    CustomColumns.Add(AddCustomColumn('Operation No.', ProdOrderRoutingLine."Operation No.", true));
end;

[EventSubscriber(ObjectType::Codeunit, Codeunit::SFPBCProdOrderComponents14FDW, OnGetConsumptionListCustomColumns, '', false, false)]
local procedure PopulateConsumptionColumns(ProdOrderComponent: Record "Prod. Order Component"; var CustomColumns: JsonArray)
begin
    CustomColumns.Add(AddCustomColumn('Item No.', ProdOrderComponent."Item No.", true));
end;

local procedure AddCustomColumn(ColumnCaption: Text; ColumnValue: Text; IsFilterable: Boolean) ColumnObject: JsonObject
begin
    Clear(ColumnObject);
    ColumnObject.Add('columnCaption', ColumnCaption);
    ColumnObject.Add('columnValue', ColumnValue);
    ColumnObject.Add('isFilterable', IsFilterable);
end;