Workflow · Workflow

Acumatica Workflow — From Template to Custom Screen

How to take the workflow engine from a built-in Acumatica screen and apply it to a custom screen you have built — including status fields, transitions, and handler wiring.

John Kihiu12 min read

Acumatica ships several stock "template" workflows meant as starting points — the generic approval template you get when you create a new screen from the code-gen wizard, or a copied workflow from a similar stock screen — and almost every custom-screen project I do starts by cloning one of these rather than building a state graph from a blank canvas. This post is about that conversion process: what the template gives you for free, what it silently assumes about your DAC that you need to unwind, and where I've been burned skipping steps.

Templates assume a status field shape that your DAC may not have

The generic approval template (the one scaffolded when you add a new graph via the Customization Project code generator and tick "include approval workflow") expects a Status field on your primary DAC with specific list values — typically something resembling Hold/Open/PendingApproval/Closed. If your custom screen's DAC doesn't already have a status field shaped that way, the very first step isn't touching the workflow at all — it's adding a properly-attributed status field to the DAC:

C#
public class KGServiceRequest : PXBqlTable, IBqlTable
{
    #region Status
    public abstract class status : BqlString.Field<status> { }
    [PXDBString(1, IsFixed = true)]
    [PXDefault(KGServiceRequestStatus.Hold)]
    [PXUIField(DisplayName = "Status", Enabled = false)]
    [KGServiceRequestStatus.List]
    public string Status { get; set; }
    #endregion
}

public static class KGServiceRequestStatus
{
    public const string Hold = "H";
    public const string PendingApproval = "P";
    public const string Open = "O";
    public const string Closed = "C";

    public class ListAttribute : PXStringListAttribute
    {
        public ListAttribute() : base(
            new[] { Hold, PendingApproval, Open, Closed },
            new[] { "Hold", "Pending Approval", "Open", "Closed" }) { }
    }
}

Skipping this and trying to bolt a template workflow onto a field that already exists but has different underlying string values (say, your legacy status field uses "1"/"2"/"3" instead of named constants) means every transition and condition in the cloned template silently references values that don't exist on your data — the workflow deploys clean, throws no errors, and simply never transitions anything, which is a maddening thing to debug because there's no exception anywhere.

Delete stock states and transitions deliberately, don't just add on top

The template workflow usually includes states and menu categories tailored to its original screen — approval reason codes, specific action names — that don't map to your custom screen's actual business process. The mistake I see most from developers new to this is leaving the template's unused states and transitions in place "just in case" and layering new ones on top; the Workflow screen's diagram becomes unreadable with dead branches, and worse, an unused transition with a broad condition can still fire unexpectedly if your new logic doesn't fully supersede it. Explicitly remove states and transitions you're not using with context.Graph.WithTransitions(t => t.RemoveGroupFrom<...>()) or the equivalent screen-designer deletion, don't just leave them dormant.

Test the full state diagram after cloning, not just your new path

After adapting a template, walk every state in the Workflow screen's diagram view, not just the happy path you added. Templates often include edge states (a "Voided" or "Cancelled" branch) that reference stock actions your custom graph doesn't implement — Actions.Void that doesn't exist on your PXGraph — which throws a runtime error the first time someone reaches that state, not at publish time.

A cloned template's WithActions block often references stock action names (Actions.Save, Actions.Release) that exist on every graph, but also sometimes references screen-specific actions from the original source screen that don't exist on yours — these throw at graph initialization, not at runtime, so they surface immediately in a way the dead-transition problem above does not. Read every actions.Add(g => g.Actions.X, ...) line in the cloned Configure method and confirm X is an action your graph or graph extension actually declares.

Convert incrementally, verify each state before adding the next

My actual process: clone the template, strip it down to the two or three states the new screen genuinely needs first (often just Hold/Open, no approval yet), verify that skeleton works end to end with real test data, then add states back in one at a time — approval, rejection, closure — testing the diagram after each addition. Building the full target state graph in one pass and debugging it as a whole is much slower than this incremental approach, because failures compound and you can't tell which of five new states introduced the break.

Wrapping up

Converting a template workflow into something screen-specific is mostly subtraction and verification, not addition: fix the status field shape first, delete what you don't need rather than leaving it dormant, confirm every referenced action actually exists on your graph, and build the target state graph incrementally rather than in one large pass. The template saves you the boilerplate of WithStates/WithTransitions syntax — it does not save you from understanding your own DAC's status model.

John Kihiu
Acumatica ERP Developer · Laravel Engineer

Independent software engineer in Nairobi specialising in Acumatica customisations, Laravel backends, and tax fiscalisation integrations across East and Southern Africa.