How to create and call an Epicor Function
how to create an epicor function
Also searched as
- epicor kinetic functions tutorial
- epicor function studio vs bpm
- reusable c# logic epicor functions
- call epicor function from bpm directive
Short answer
Open Function Studio from the main menu (Customization > Function Studio) or from inside a BPM designer's Call Context widget, define a Library and Function with typed inputs/outputs, write the logic in the C# widget, then compile and test with the built-in test harness before calling it from a BPM directive, a dashboard, or another function.
Applies to: Epicor Kinetic 2021.1 and later (Function Studio replaced ad-hoc BPM C# for reusable logic); similar patterns exist in 10.2.700+.
Build a reusable Epicor Function
- 1Open Function Studio: Customization > Function Studio (or the wrench icon inside a BPM Custom Code widget's Call Context).
- 2Create a new Library (a logical grouping, e.g. "CustomerHold") if one does not exist, then a new Function inside it (e.g. "GetCreditHoldReason").
- 3Define Inputs on the Inputs tab: name, data type (string, int, DateTime, decimal, custom BO type). Mark required inputs.
- 4Define Outputs the same way on the Outputs tab; a function can return multiple named outputs.
- 5Switch to the Data Definitions / Widgets designer if you need to query the database (Invoke DB / Query widget) before dropping into code, or go straight to the C# Method widget for pure logic.
- 6In the C# widget, write logic against the inputs; use Db (the Epicor DbContext-style accessor) for reads, and set output variables at the end.
- 7Click Test at the top of Function Studio, supply sample input values, and run - the trace pane shows each widget's execution and any exception with line number.
- 8Save and compile. The function is now selectable from any BPM directive's "Invoke Epicor Function" widget, from another function, or from a dashboard's custom code.
- 9For reuse across companies, keep server-side logic in the function rather than client customizations so it applies uniformly to UI, BPM, and REST calls.
Why Functions replaced raw BPM C#
Before Function Studio, reusable logic in Epicor lived either as a Method Directive's Custom Code widget (copy-pasted across BPMs, hard to version) or as a compiled custom DLL that required a server deployment. Epicor Functions give you a middle ground: logic is written and compiled inside the application, versioned as part of the customization layer, and callable from anywhere - BPM, dashboards, UBAQs, and even external REST callers if you expose the function as an API.
A Function is really a mini business object: it has a signature (inputs/outputs), can hold multiple widgets (queries, invoke BO, condition, C# method), and gets compiled to an assembly the same way BPM directives do. That means the same debugging discipline applies - check the trace, check for null Db results, and watch execution context (some functions run in a different company/session context than you expect).
Common function patterns
Validation functions: take a key field (part number, customer ID) and return a bool plus a message; call from a Pre-Processing directive's Invoke Function widget with a Condition widget checking the bool output, then Raise Exception if false.
Lookup/enrichment functions: query a related table (e.g. get the primary contact's email for a customer) and return it as an output for a BPM or dashboard to consume, avoiding repeated inline SQL in multiple places.
Calculation functions: centralize a formula (e.g. custom margin or lead-time calculation) so Kinetic screens, BAQ custom code, and BPMs all call the same function instead of drifting out of sync.
// Inside the C# widget of a function
var cust = Db.Customer.FirstOrDefault(c => c.Company == callContextClient.CurrentCompany && c.CustNum == custNum);
if (cust == null) { outputMessage = "Customer not found"; outputIsValid = false; }
else { outputIsValid = cust.CreditHold != true; outputMessage = cust.CreditHold == true ? "Customer on credit hold" : "OK"; }Calling a function from BPM and from another function
In a Method Directive, drag the Invoke Epicor Function widget into the designer, pick the Library and Function, and map each input to a context field (e.g. ttOrderHed.CustNum) using the field chooser. Map each output to a temporary variable you can then branch on with a Condition widget.
Functions can call other functions - useful for building small composable utilities (a date-math function called by three higher-level functions) - but watch for circular references, which Function Studio will reject at compile time.
Functions can also be exposed as callable REST endpoints (Function as a Service) so external systems or a portal can invoke server-side logic without going through a full BO method - handy for lightweight lookups.
Common pitfalls
- !Forgetting to mark an input as required leaves it nullable in code, causing NullReferenceException if a caller omits it - always null-check even required inputs defensively.
- !Running heavy queries inside a function called from a Pre-Processing directive on a high-volume transaction (e.g. every part master save) can slow the whole screen - profile with the trace before deploying broadly.
- !Functions compiled against one company's customization layer may not exist for other companies until published/deployed there - check Multi-Company deployment after creating a function in a multi-company environment.
- !Db context inside a function is scoped to the current session; do not assume you can read data outside the caller's company without an explicit company filter.
- !Overwriting an existing function's signature (adding a required input) breaks every existing caller - add new inputs as optional with defaults, or version the function under a new name.
- !Testing only in Function Studio's test harness and never through the actual BPM path can miss context differences (e.g. ttOrderHed row state) that only appear in the real transaction.
How an ERP-grounded AI assistant handles this
SyteRay-style agents built for Epicor can read a library's existing Functions and BPM directives before generating a new one, so a requested "add a credit check function" reuses your existing GetCreditHoldReason pattern instead of duplicating logic, and flags when a proposed function would create a circular call chain - something that is easy to miss by hand until Function Studio's compiler rejects it.
Frequently asked questions
Can an Epicor Function replace a BAQ?
Not directly - a BAQ is for building result sets to display or export, while a Function is for callable logic with typed inputs/outputs. A function can internally use a Query widget similar to a BAQ, and a function can be exposed as a data source for a dashboard, but they solve different problems.
How do I debug a function that fails only in production?
Use the trace log (System Monitor or the BPM/Function execution log if enabled) rather than the design-time test harness, since production failures are usually context-dependent (missing row state, different company data). Add explicit try/catch with a logged message in the function itself for hard-to-reproduce cases.
Can I call a REST API from inside an Epicor Function?
Yes, using standard .NET HttpClient calls inside the C# widget, but be careful about timeouts blocking a synchronous BPM transaction - for slow external calls, prefer an async/queued pattern (e.g. a scheduled task or Kinetic's REST helper widgets) instead of calling out synchronously from a Pre-Processing directive.
Do Functions get included in a package/solution export?
Yes - Functions are part of the customization layer and are exported/imported through the same Solution Workbench or package export used for BPMs and dashboards, so they migrate between environments (dev to test to production) consistently.
Related
Fixing Epicor BusinessObjectException: BPM Directive Errors
A BusinessObjectException that says "A Business Process Management (BPM) directive has raised the following error" means a directive on that business object stopped the transaction, either deliberately via a Raise Exception widget or accidentally via an unhandled .NET error in Custom Code. Expand the InnerException on the error dialog to see the real message and the directive name, then open BPM Designer for that object and method to find the widget that fired.
AdvancedEpicor Application Studio: understanding customization layers
Kinetic UI is built in layers loaded in order - Base (Epicor-delivered), Customization (Application Studio, company/layer-wide), Personalization (user- or role-specific, applied on top), and optionally Extended (packaged add-on layers) - and a change you make will not appear if a higher layer overrides the same control or if you are testing in the wrong layer context.
How-toHow to import data with Epicor DMT (Data Migration Tool)
In DMT, pick the target data type (e.g. Part, Customer, PartWhse), Generate a template from the connected Epicor server so column names match your version's schema exactly, populate the spreadsheet, load it into DMT, run a Pre-Process/validate pass first to surface errors before committing, then Process to write the records.
How-toHow to build a Kinetic homepage dashboard in Epicor
Kinetic homepage dashboards are built from Active Homepage Maintenance: create a BAQ that returns the metric or list you want, add a KPI or List widget referencing that BAQ, arrange widgets on the homepage layout, and assign the homepage to a role or company so the right users see it.
How-toFixing a Slow Epicor BAQ (Business Activity Query)
A slow BAQ in Epicor is usually caused by unindexed join columns, a subquery or calculated field forcing a table scan, or the BAQ pulling far more rows than the dashboard actually displays before filtering client-side. Fix it by reading the SQL Server execution plan Epicor generates, moving filters into the BAQ criteria instead of the dashboard filter panel, and replacing subqueries with joins where possible.
How-toEpicor MRP Runs but Generates No Suggestions
When Epicor's MRP process completes without a job or purchase suggestion for a part you expect one for, the cause is almost always the part's own configuration (Part Class Type, Make Direct, Non-MRP flag, planning Time Fence) or the demand not being linked in a way MRP recognizes, not a defect in the MRP engine. Work through the part's Planning tab, its safety stock and lead time setup, and the demand source (sales order line status, job material requirement) before assuming the run itself failed.
AI for ERPAI for Epicor Kinetic, Beyond What Prism Covers
Add AI to Epicor Kinetic beyond Prism: private LLM over BAQs, BPM data, and REST v2, on-prem or private cloud, with honest guidance on when Prism already covers you.
AI for ERPAI shop floor assistant for operators working inside your ERP
An on-prem AI copilot answers operator questions against your ERP work instructions, travelers, and routings in plain language, at the machine, without a screen full of menus.
Stuck on Epicor Kinetic?
Talk to engineers who work inside Epicor Kinetic every week, and who build private AI that answers these questions from your own ERP data.