When using WSDataSource, the custom function ExternalLookupCustomFunction_<fieldInternalName> can return not only a request body, but also URL/header/parameter overrides or a pre-fetched response. The function may be async (return a Promise); the result is awaited before processing.
The custom function return type is ExternalLookupCustomFunctionResult:
| Return value | Meaning |
|---|---|
undefined |
No body; URL and method from configuration. |
| plain object or string | Used as request body (backward compatible). |
{ request: ExternalLookupRequestOverride } |
Override URL / headers / body / query params; missing values fall back to configuration. |
{ response: string | object } |
Direct response — no HTTP call is made; the value is passed straight to result mapping. See WSDirectResponseExample. |
Obtain a token inside the function (from your own logic, storage, or a token endpoint) and pass it via headers using ExternalLookupRequestOverride:
function ExternalLookupCustomFunction_myApiLookup(searchText) {
var token = getAccessToken(); // your logic: cache, refresh, etc.
return {
request: {
headers: {
"Authorization": "Bearer " + token,
"Content-Type": "application/json"
},
body: { query: searchText }
}
};
}
The URL is taken from WSDataSourceConfiguration.Url unless request.url is specified. For GET requests you can use request.params instead of a body.
URL and parameters can be constructed in code:
function ExternalLookupCustomFunction_mySearch(searchText) {
var baseUrl = "https://api.example.com/v2/search";
return {
request: {
url: baseUrl,
params: {
q: searchText,
limit: "20"
}
}
};
}
request.url overrides the Url from configuration; request.params are used as query parameters (for both GET and POST).
Use request.method to send a request with an HTTP method other than the one configured in MethodName (e.g. PUT, PATCH, DELETE):
function ExternalLookupCustomFunction_myLookup(searchText) {
return {
request: {
method: "PATCH",
url: "https://api.example.com/search",
body: { query: searchText }
}
};
}
request.method is ignored when RequestType is "SOAP" — SOAP requests always use "POST".
Body, headers, and query parameters can be combined freely:
function ExternalLookupCustomFunction_myLookup(searchText) {
return {
request: {
body: { filter: searchText },
params: { format: "json" },
headers: { "X-API-Key": "..." }
}
};
}
SOAP calls are driven by setting RequestType to "SOAP" in WSDataSourceConfiguration. When RequestType is "SOAP", the request is always sent as an HTTP POST with Content-Type: text/xml; charset=utf-8 and the SOAPAction header. The custom function must return { request: { soapAction, soapEnvelope } } — both fields are required; without them the SOAP call cannot be constructed and a warning is emitted.
Set RequestType to "SOAP" and ResultSetType to "XML". The ResultSetItems path uses dot-delimited local element names (namespace prefixes are ignored) to navigate to the repeating elements within the SOAP response body.
{
"DataSourceType": "WS",
"DataSourceConfiguration": {
"Url": "https://example.com/ws/SupplierService",
"Cache": "1:0:0",
"RequestType": "SOAP",
"MethodName": "POST",
"ResultSetItems": "Body.SearchSupplierResponse.Suppliers.Supplier",
"ResultSetType": "XML",
"ResultSet": [
{ "Name": "CompanyName", "Path": "Name" },
{ "Name": "Id", "Path": "SupplierId" },
{ "Name": "City", "Path": "Address.City" }
]
},
"IndexValue": { "Expression": "{Id}" },
"TextValue": { "Expression": "{CompanyName}" },
"RenderMode": "DataTable",
"DataTableElements": [
{ "ColumnTitle": "Name", "Expression": "{CompanyName}" },
{ "ColumnTitle": "ID", "Expression": "{Id}" },
{ "ColumnTitle": "City", "Expression": "{City}" }
]
}
The custom function builds the SOAP envelope and returns it via request.soapAction and request.soapEnvelope. Both fields are required when RequestType is "SOAP" — omitting either will cause a console warning and the SOAP call will not be constructed correctly:
function ExternalLookupCustomFunction_s_soapSupplier(searchText) {
return {
request: {
soapAction: "http://example.com/ws/SearchSupplier",
soapEnvelope:
'<?xml version="1.0" encoding="utf-8"?>' +
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://example.com/ws/">' +
'<soap:Body>' +
'<ws:SearchSupplier>' +
'<ws:Query>' + searchText + '</ws:Query>' +
'</ws:SearchSupplier>' +
'</soap:Body>' +
'</soap:Envelope>'
}
};
}
Note: If
searchTextmay contain XML special characters (<,>,&,",'), escape it before embedding in the envelope to avoid malformed XML. Example:searchText.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>')
Given the configuration above, a SOAP response like the following is parsed using ResultSetItems to find repeating Supplier elements, and ResultSet to extract values from child elements:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
<soap:Body>
<SearchSupplierResponse xmlns="http://example.com/ws/">
<Suppliers>
<Supplier>
<SupplierId>42</SupplierId>
<Name>Aricoma a.s.</Name>
<Address><City>Praha</City></Address>
</Supplier>
<Supplier>
<SupplierId>99</SupplierId>
<Name>Acme Corp.</Name>
<Address><City>Brno</City></Address>
</Supplier>
</Suppliers>
</SearchSupplierResponse>
</soap:Body>
</soap:Envelope>
The XML path resolution uses local element names — namespace prefixes (like soap:) are stripped automatically, so Body.SearchSupplierResponse.Suppliers.Supplier matches regardless of the namespace prefix used.
Both ResultSetItems and ResultSet[].Path use the same dot-delimited path syntax based on local element names:
| Path | Resolves to |
|---|---|
Body.SearchSupplierResponse.Suppliers.Supplier |
All <Supplier> elements |
Name |
<Name> child of each item |
Address.City |
<City> inside <Address> inside each item |
Existing custom functions that return only a body object (or undefined) continue to work without any changes:
// This still works exactly as before
function ExternalLookupCustomFunction_s_dodavatel(searchText) {
if (searchText.match(/\d+/)) return { ico: [searchText] };
return { obchodniJmeno: searchText };
}
soapAction / soapEnvelope