Skip to content
iQBus HomeDocumentationBlogContact
Updated August 1, 2026
Build & extend/Custom functions
Developer Reference

Custom functions

Extend Art2link ESB with reusable C# functions, callable from maps, port and adapter properties, pipeline component configuration, and routing expressions.

A Custom Function is a C# method that becomes callable from anywhere inside its Application that accepts a {{Function.fnX}} binding token, inside maps, on port and adapter properties, in pipeline component configuration, and in routing and subscription expressions. Inside Maps, custom functions can also be called natively from XSLT via art:invoke.

Typical use cases include database lookups, external API calls, custom string manipulation, cryptographic helpers, and any business rule that is easier to express in C# than in XSLT or XPath. The built-in XSLT 3.0 / XPath 3.1 function library is comprehensive, reach for a custom function when it isn't.

Custom Functions are written and edited in the Art2link ESB UI, compiled and deployed automatically when you save, no build steps, no restarts. Once deployed, a function can be referenced from any map, port, pipeline, or expression in the Application that owns it.

AUTHOR IN AN APPLICATION: CALL FROM ANYWHERE INSIDE IT MyFunctions C# [CustomFunction("fnLookup")] public static string Lookup(string id) { // … } Save in the UI, no build step. COMPILE THIS APPLICATION Function library fnLookup fnParseBool fnFormatDate not visible elsewhere CALL Maps {{Function.fn…}} / art:invoke Port & adapter properties {{Function.fn…}} Pipeline component config {{Function.fn…}} Routing & subscriptions predicate / expression every call site inside the same Application One Application, one library. Edit the code and every caller in that Application picks up the new version on its next invocation.
Custom functions are Application-scoped. A function belongs to exactly one Application, the same as maps, pipelines and every other artifact. Any map, port, pipeline component setting, or expression in that Application can call it by its [CustomFunction] name, and editing the code propagates to every caller inside that Application and nowhere else. There is no shared function library and no cross-Application reuse: if two Applications need the same helper, create it in each.
APPLICATION LEVEL: EVERY FUNCTION BELONGS TO ONE APPLICATION APPLICATION A CUSTOM FUNCTIONS fnLookup SQL lookup fnFormatDate date helper CALL SITES MAP: ORDER_TO_ERP fnLookup → fnFormatDate PORT: SFTP_OUT fnFormatDate APPLICATION B CUSTOM FUNCTIONS fnFormatDate own copy, own code fnParseBool string helper CALL SITES MAP: CLAIM_TO_X12 fnFormatDate SUBSCRIPTION FILTER fnParseBool APPLICATION C CUSTOM FUNCTIONS fnHashId crypto helper fnRedactSsn masking CALL SITES MAP: HR_FEED fnHashId → fnRedactSsn COMPONENT CONFIG fnRedactSsn Nothing crosses an Application boundary. fnFormatDate exists in Application A and again in Application B, as two separate artifacts that happen to share a name. Editing one leaves the other untouched.

The list view follows the platform's Selected Application behavior, with an Application selected, the list filters to it and the Application column is hidden; with no selection, the list spans every Application you have access to and the column is shown. Creating a new function uses the same flow: with an Application selected, the editor opens with that Application preemptively chosen; without one, the form exposes an Application picker. Custom functions are managed by the Application Owner and the Application Contributor, the same permission that governs every other artifact in the Application, no system-level role is involved.


QUICK START: FOUR STEPS 1 Start from template New function in the Custom Functions UI 2 Write the function [CustomFunction( "fnLookup")] public static string Lookup(string id) { … } Public static method 3 Save Auto-compiled and deployed NO BUILD · NO RESTART 4 Call it {{Function. fnLookup(id) }} From a map, port, pipeline, or expression Edit → save → the next caller sees the new code. The whole loop is the UI; no deploy step.

1. Create a new Custom Function, start from the template provided in the Art2link ESB UI.

2. Write your function, add a public static method to the static class and decorate it with the [CustomFunction("fnName")] attribute.

3. Save, Art2link ESB compiles and deploys the function automatically.

4. Reference it, invoke the function from any binding-aware location: an XSLT map, a port or adapter property, a pipeline component setting, or a routing expression.


Class Rules
RuleDetails
Static classThe class must be declared static.
NamespaceCustom.Functions is required. The class name is yours to choose and serves as a logical grouping for your functions.
One class per functionEach custom function saved in the UI contains exactly one class; the class may expose multiple [CustomFunction] methods.
MethodsEach function must be public static. Async methods are not supported.
[CustomFunction] Attribute
RuleDetails
NamespaceCC.Art2link.Attributes
ParameterThe function name used to invoke the method from any binding-aware location. Must follow standard C# method naming rules (no spaces, no special characters).
UniquenessMust be unique within the Application that owns the function. Another Application can define a function with the same name, it is a separate artifact with its own code.
ConventionThe fn prefix (e.g., fnLookupCustomer) is recommended but not required.
Supported Types

Both parameters and return types support: string, int, bool, double, decimal, and collections. Multiple parameters are supported.

⚠️
Changing the function name after deployment will break any caller that references it. Treat it as an immutable identifier once in use.
Basic Template
C#
using CC.Art2link.Attributes;

namespace Custom.Functions;

public static class MyFunctions
{
    [CustomFunction("fnMyFunction")]
    public static string MyFunction(string input)
    {
        // TODO: Implement your logic here.
        return input;
    }
}
Multiple Functions Example
C#
using CC.Art2link.Attributes;

namespace Custom.Functions;

public static class StringFunctions
{
    [CustomFunction("fnToTitleCase")]
    public static string ToTitleCase(string input)
    {
        if (string.IsNullOrWhiteSpace(input)) return input;
        return System.Globalization.CultureInfo.CurrentCulture
            .TextInfo.ToTitleCase(input.ToLower());
    }

    [CustomFunction("fnParseBool")]
    public static bool ParseBool(string input)
    {
        return input?.Trim().ToLower() is "1" or "true" or "yes" or "y";
    }
}

Unhandled exceptions thrown by a custom function will surface as errors at the call site, a map execution error, a port-property resolution error, a pipeline component error, or a routing-expression failure, depending on where the function was invoked. Implement exception handling within your functions and either return a meaningful fallback value or rethrow with additional context.

C#
[CustomFunction("fnSafeParseDate")]
public static string SafeParseDate(string input, string format)
{
    try
    {
        var date = DateTime.ParseExact(input, format, CultureInfo.InvariantCulture);
        return date.ToString("yyyy-MM-dd");
    }
    catch (FormatException ex)
    {
        throw new InvalidOperationException(
            $"fnSafeParseDate: could not parse '{input}' with format '{format}'", ex);
    }
}

Once a custom function is saved and compiled, it can be called from anywhere in its Application that resolves binding tokens, and from inside an XSLT map natively. Every call site references the function by its [CustomFunction] attribute name, no class or namespace needed. Call sites in other Applications cannot see it.

Where functions can be called from
Call siteHow to call
MapsBinding token {{Function.fnX(…)}} or native XSLT call art:invoke('fnX', …).
Port & adapter propertiesBinding token in any property value field on a receive, send, loopback, or null port, including adapter-specific settings.
Pipeline component configurationBinding token in any component property value, resolved at runtime before the component executes.
Routing & subscription expressionsUsed as the predicate or value in a message type subscription expression. Returns are coerced as needed.
Pipeline component code cannot call custom functions. A pipeline component’s C# body has no way to import or reference a custom function directly. Only the component’s property values can, the platform resolves any {{Function.fnX}} token to its return value before the component executes, so the component receives the final string, never the function itself.

The two invocation styles below apply specifically inside maps; the other call sites accept the binding-token form only.

Binding syntax (preprocessor)

The same double-curly binding used for variables, promoted properties, and other map references. After typing {{, intellisense exposes the available custom functions alongside the other binding sources. Calls written this way are resolved by a preprocessor pass before the XSLT runs, so the function executes outside the XSLT context and no XPath context is shared with it.

Syntax
{{Function.fnFunctionName(parameter)}}

For example, to call fnParseBool:

Example
{{Function.fnParseBool(parameter)}}
XSLT function call (art:invoke)

Custom functions can also be called inline from XSLT as native functions. This requires declaring the urn:art2link:functions namespace on the stylesheet and excluding its prefix from the result tree:

XSLT
<xsl:stylesheet version="3.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:art="urn:art2link:functions"
                exclude-result-prefixes="xs art">

With the namespace declared, invoke any custom function via art:invoke, passing the function name as the first argument and the function's parameters after it:

XSLT
art:invoke('fnFormatDateTime', string(CreatedAt))

Parameters follow standard XPath nomenclature, so any XPath expression, field references, casts, computed values, can be passed in. The function executes and its return value is used inline in the map.


A common use case for custom functions is performing database lookups during map execution, for example, enriching a message with customer data from a SQL database.

One deliberate, bounded call like this is fine; capture map calls once in an xsl:variable, and leave bulk per-record enrichment to a port. See Keep custom functions lean.

C#
using CC.Art2link.Attributes;
using System.Data;
using Microsoft.Data.SqlClient;

namespace Custom.Functions;

public static class DatabaseLookup
{
    [CustomFunction("fnLookupCustomerName")]
    public static string LookupCustomerName(
        string customerId,
        string connectionString)
    {
        try
        {
            using var conn = new SqlConnection(connectionString);
            conn.Open();

            using var cmd = conn.CreateCommand();
            cmd.CommandText = "SELECT Name FROM Customers WHERE Id = @Id";
            cmd.Parameters.AddWithValue("@Id", customerId);

            var result = cmd.ExecuteScalar();
            return result?.ToString() ?? "Unknown";
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException(
                $"fnLookupCustomerName failed for ID '{customerId}': {ex.Message}", ex);
        }
    }
}
📦
NuGet package required. The Microsoft.Data.SqlClient package must first be registered in the Application's NuGet catalog through the Art2link ESB UI (name and version). Once registered, reference it with a using statement at the top of your function code. See the NuGet packages article for the catalog and impact analysis.
Tip: Pass the connection string as a parameter rather than hardcoding it. This lets you use different databases per environment by configuring the value in the Art2link ESB UI.

AspectDetails
Target framework.NET 8
Mapping engineSaxonHE 12.9 (XSLT 3.0, XPath 3.1, XQuery 3.1)
Available librariesBuilt-in .NET libraries. Additional NuGet packages are registered in the Application's own catalog via the Art2link ESB UI (name and version), then referenced with using statements at the top of your function code. See the NuGet packages article.
AsyncNot supported. All custom function methods must be synchronous.
External resourcesHTTP calls, database queries, and file system access are permitted.
LoggingNo logging interface is available at this time.
Hot-reloadSaving a function compiles and deploys it immediately. Same in-flight behavior caveats as pipeline components apply.
Save-to-live deployment
HOT RELOAD: SAVE-TO-LIVE PATH STEP 1: YOU Save the function edit in the Art2link UI STEP 2: AUTO Art2link compiles no build step, no restart SECONDS STEP 3: PROPAGATE Next call uses new code maps · ports · pipelines routing · subscriptions every caller in the Application picks up the new version Edit → save → next message uses the new code. Same model as pipeline components.
⚠️
In-flight message behavior. Saving overwrites the previous version immediately. If a message is mid-flight when you save, for example, partway through a map that calls the function, or queued behind a pipeline component that does, the outcome depends on which step has been reached at the moment of the save. The result is unpredictable on a case-by-case basis. Deploy function changes during periods of no message activity to avoid inconsistent behavior. Same model as pipeline components.

Always handle exceptions

Wrap function logic in try/catch. Unhandled exceptions surface as errors at the call site, map, port, pipeline, expression, and are harder to diagnose than a clear error message from your code.

Choose stable function names

The [CustomFunction] name is immutable once any caller depends on it. Use descriptive, stable identifiers.

Parameterize configuration

Pass connection strings, API keys, and environment-specific values as parameters rather than hardcoding them. This makes functions reusable across environments.

Prefer built-in functions first

SaxonHE 12.9 includes a comprehensive function library. Use custom functions only when built-in XSLT/XPath functions cannot meet the requirement.

Group related functions

Use the class name as a logical namespace. Group related functions together (e.g., StringFunctions, DatabaseLookup, DateHelpers).

Deploy during quiet periods

Same as pipeline components, save changes when no messages are actively being processed to avoid unpredictable in-flight behavior.

Ready to transform

Write your logic in the function editor, save, and reference it from your XSLT maps, port and adapter properties, pipeline components, or routing expressions. Art2link ESB handles compilation and deployment automatically.