Skip to main content

Custom Functions

Custom functions are components designed to reduce BFF (Backend for Frontend) API usage in the vNext platform. They work on instance data to provide endpoints to other domains or integrated services.

Table of Contents

  1. Overview
  2. Function Definition
  3. Function Properties
  4. Consumption Endpoints
  5. System Functions
  6. Usage Examples
  7. Best Practices

Overview

Custom functions are used for:

  • BFF API Reduction: Reduces intermediate API layers by providing direct data access
  • Data Transformation: Presents instance data in desired format via mapping
  • Task Execution: Runs the defined task when function is called
  • Service Integration: Provides endpoints to other domains or external services
tip

Each function can execute a task and the task result data can be returned in the desired format via mapping.


Function Definition

Basic Structure

{
"key": "function-get-user-info",
"flow": "sys-functions",
"domain": "core",
"version": "1.0.0",
"flowVersion": "1.0.0",
"tags": [
"system",
"core",
"users",
"lookup"
],
"attributes": {
"scope": "I",
"task": {
"order": 1,
"task": {
"key": "get-user-info",
"domain": "core",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/GetUserInfoMapping.csx",
"code": "<BASE64_ENCODED_MAPPING_CODE>"
}
}
}
}

Function Properties

Core Properties

PropertyTypeDescription
keystringUnique identifier for the function
flowstringFlow stream information (default: sys-functions)
domainstringDomain the function belongs to
versionstringVersion information (semantic versioning)
flowVersionstringFlow version information
tagsstring[]Tags for categorization and searching
attributesobjectFunction configuration

Attributes Properties

PropertyTypeDescription
scopestringFunction scope (I = Instance, F = Workflow, D = Domain)
taskobjectSingle task to execute (legacy shape; use with one task)
onExecutionTasksarrayOrdered tasks to execute; see Multi-task execution below
outputobjectOptional output mapping script: location / code; implements IOutputHandler

Scope Values

ValueDescriptionAccess Level
IInstanceWorks for a specific instance
FWorkflowWorks at workflow level
DDomainWorks at domain level

Task Structure

{
"task": {
"order": 1,
"task": {
"key": "task-key",
"domain": "core",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/MappingFile.csx",
"code": "<BASE64_ENCODED_CODE>"
}
}
}
PropertyTypeDescription
ordernumberTask execution order
taskobjectTask reference
mappingobjectInput/Output transformation mapping

Multi-task execution and output mapping

A function may run multiple tasks in order using attributes.onExecutionTasks instead of a single task. Each entry has order, a task reference, and optional mapping. Later tasks can consume outputs from earlier ones in the same function execution.

Optional attributes.output references a script that implements IOutputHandler. In OutputHandler, read per-task results from context.OutputResponse (keys follow the executed task keys, typically camelCase).

"attributes": {
"scope": "I",
"onExecutionTasks": [
{
"order": 1,
"task": {
"key": "validate-account-policies",
"domain": "core",
"flow": "sys-tasks",
"version": "1.0.0"
},
"mapping": {
"location": "./src/FunctionValidatePoliciesMapping.csx",
"code": ""
}
},
{
"order": 2,
"task": {
"key": "get-data-from-workflow",
"domain": "core",
"flow": "sys-tasks",
"version": "1.0.0"
},
"mapping": {
"location": "./src/FunctionGetInstanceDataMapping.csx",
"code": ""
}
}
],
"output": {
"location": "./src/FunctionOutputMapping.csx",
"code": ""
}
}
using System.Threading.Tasks;
using BBT.Workflow.Scripting;

public class FunctionOutputMapping : IOutputHandler
{
public Task<ScriptResponse> OutputHandler(ScriptContext context)
{
var policies = context.OutputResponse["validateAccountPolicies"].data;
var instanceData = context.OutputResponse?["getDataFromWorkflow"].data;
return Task.FromResult(new ScriptResponse
{
Key = "multi-task-function-output",
Data = new { policyValidation = policies, instanceSnapshot = instanceData }
});
}
}

Consumption Endpoints

Domain Level Functions

Returns all domain instances and data:

GET /api/v1/{domain}/functions

Returns result of a specific function:

GET /api/v1/{domain}/functions/{function}

Workflow level custom function URL (removed in )

The pattern GET /api/v1/{domain}/workflows/{workflow}/functions/{function} (calling a registered function by name without an instance id) is removed as of . Use instance-scoped function endpoints below, workflow instance listing (GET .../workflows/{workflow}/instances), or other documented APIs instead.

Instance Level Functions

Executes a function for a specific instance:

GET /api/v1/{domain}/workflows/{workflow}/instances/{instance}/functions/{function}

System Functions

The vNext platform provides ready-to-use system functions for every workflow instance:

State Function

Returns the current state information of an instance.

Endpoint:

GET /api/v1/{domain}/workflows/{workflow}/instances/{instance}/functions/state

Response:

{
"data": {
"href": "/core/workflows/account-opening/instances/d4b161a8-7705-4bfb-9ba4-d76461bb35eb/functions/data?extensions=extension-user-session"
},
"view": {
"loadData": true,
"href": "/core/workflows/account-opening/instances/d4b161a8-7705-4bfb-9ba4-d76461bb35eb/functions/view"
},
"state": "account-type-selection",
"status": "A",
"activeCorrelations": [],
"transitions": [
{
"name": "select-demand-deposit",
"href": "/core/workflows/account-opening/instances/d4b161a8-7705-4bfb-9ba4-d76461bb35eb/transitions/select-demand-deposit"
},
{
"name": "execute-sub",
"href": "/core/workflows/account-opening/instances/d4b161a8-7705-4bfb-9ba4-d76461bb35eb/transitions/execute-sub"
}
],
"eTag": "01KCHWT3QQFM6J9QQD9G4T0VRP"
}

Response Fields:

FieldTypeDescription
data.hrefstringData function endpoint
view.loadDatabooleanWhether view requires data loading
view.hrefstringView function endpoint
statestringCurrent state name
statusstringInstance status (A=Active, C=Completed)
activeCorrelationsarrayActive sub-correlations
transitionsarrayAvailable transitions
eTagstringETag value for cache control

View Function

Returns the view data for the current state or transition of an instance.

Endpoint:

GET /api/v1/{domain}/workflows/{workflow}/instances/{instance}/functions/view?transitionKey={transition}&platform={platform}

Query Parameters:

ParameterTypeDescription
transitionKeystringView for specific transition (optional)
platformstringTarget platform: web, ios, android

Response:

{
"key": "account-type-selection-view",
"content": "{\"type\":\"form\",\"title\":{\"en-US\":\"Choose Your Account Type\",\"tr-TR\":\"Hesap Türünüzü Seçin\"},\"fields\":[...]}",
"type": "Json",
"display": "full-page",
"label": ""
}

Response Fields:

FieldTypeDescription
keystringView identifier
contentstringView content (in JSON format)
typestringContent type (Json, Html, etc.)
displaystringDisplay mode (full-page, popup, bottom-sheet, etc.)
labelstringLocalized label

Schema Function

Returns the schema data for the current state or transition of an instance.

Endpoint:

GET /api/v1/{domain}/workflows/{workflow}/instances/{instance}/functions/schema?transitionKey={transition}

Response:

{
"key": "account-type-selection",
"type": "workflow",
"schema": {
"$id": "https://schemas.vnext.com/banking/account-type-selection.json",
"type": "object",
"title": "Account Type Selection Schema",
"$schema": "https://json-schema.org/draft/2020-12/schema",
"required": ["accountType"],
"properties": {
"accountType": {
"type": "string",
"oneOf": [
{
"const": "demand-deposit",
"description": "Vadesiz Hesap - Demand Deposit Account"
},
{
"const": "time-deposit",
"description": "Vadeli Hesap - Time Deposit Account"
},
{
"const": "investment-account",
"description": "Fonlu Hesap - Investment Account"
},
{
"const": "savings-account",
"description": "Tasarruf Hesabı - Savings Account"
}
],
"title": "Account Type",
"description": "Type of account to be opened"
}
},
"description": "Schema for account type selection input",
"additionalProperties": false
}
}

Usage Examples

Example 1: User Info Function

{
"key": "function-get-user-info",
"flow": "sys-functions",
"domain": "core",
"version": "1.0.0",
"flowVersion": "1.0.0",
"tags": ["system", "core", "users", "lookup"],
"attributes": {
"scope": "I",
"task": {
"order": 1,
"task": {
"key": "get-user-info",
"domain": "core",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/GetUserInfoMapping.csx",
"code": "<BASE64>"
}
}
}
}

Mapping Example:

using System.Threading.Tasks;
using BBT.Workflow.Scripting;
using BBT.Workflow.Definitions;

public class GetUserInfoMapping : IMapping
{
public Task<ScriptResponse> InputHandler(WorkflowTask task, ScriptContext context)
{
try
{
var httpTask = task as HttpTask;
if (httpTask == null)
throw new InvalidOperationException("Task must be an HttpTask");

var userId = context.Body?.userId;

// Update URL with userId
httpTask.SetUrl(httpTask.Url.Replace("{userId}", userId?.ToString() ?? ""));

// Set Headers
var headers = new Dictionary<string, string?>
{
["Content-Type"] = "application/json",
["Accept"] = "application/json",
["X-Request-Id"] = Guid.NewGuid().ToString()
};

httpTask.SetHeaders(headers);

return Task.FromResult(new ScriptResponse());
}
catch (Exception ex)
{
return Task.FromResult(new ScriptResponse
{
Key = "user-info-error",
Data = new { error = ex.Message }
});
}
}

public async Task<ScriptResponse> OutputHandler(ScriptContext context)
{
try
{
var statusCode = context.Body?.statusCode ?? 500;
var responseData = context.Body?.data;

if (statusCode >= 200 && statusCode < 300)
{
return new ScriptResponse
{
Key = "user-info-success",
Data = new
{
user = responseData,
phoneNumber = responseData?.phoneNumber,
hasRegisteredDevices = ((object[])responseData?.registeredDevices).Length > 0,
language = responseData?.language ?? "tr-TR"
},
Tags = new[] { "users", "lookup", "success" }
};
}
else
{
return new ScriptResponse
{
Key = "user-info-failure",
Data = new
{
error = "Failed to get user information",
errorCode = "user_info_failed",
statusCode = statusCode,
hasRegisteredDevices = false
},
Tags = new[] { "users", "lookup", "failure" }
};
}
}
catch (Exception ex)
{
return new ScriptResponse
{
Key = "user-info-exception",
Data = new
{
error = "Internal processing error",
errorCode = "processing_error",
errorDescription = ex.Message,
hasRegisteredDevices = false
},
Tags = new[] { "users", "lookup", "error" }
};
}
}
}

Example 2: Account Balance Function

{
"key": "function-get-account-balance",
"flow": "sys-functions",
"domain": "banking",
"version": "1.0.0",
"flowVersion": "1.0.0",
"tags": ["banking", "accounts", "balance"],
"attributes": {
"scope": "I",
"task": {
"order": 1,
"task": {
"key": "get-balance",
"domain": "banking",
"version": "1.0.0",
"flow": "sys-tasks"
},
"mapping": {
"location": "./src/GetBalanceMapping.csx",
"code": "<BASE64>"
}
}
}
}

Best Practices

1. Function Design

PracticeDescription
Single responsibilityEach function should do one thing
Meaningful namingDescriptive names with function- prefix
Appropriate scopeCorrect scope selection based on need (I, W, D)
Version managementUse semantic versioning

2. Mapping Development

PracticeDescription
Error handlingCatch errors with try-catch blocks
Null checkingWrite null-safe code (?. operator)
LoggingAdd appropriate log messages
PerformanceAvoid unnecessary operations

3. Security

PracticeDescription
AuthorizationProper authorization checks
Data validationPerform input validation
Sensitive dataMask sensitive data
Rate limitingApply request limits

4. Performance

PracticeDescription
CachingUse appropriate cache strategy
Async operationsUse async/await for asynchronous operations
TimeoutSet appropriate timeout values
Resource managementProperly release resources