Defining Controllers
Controllers are components that define how to retrieve the values available for mappings, as well as how to store and retrieve the mappings themselves. They're also responsible for validating the various parts of a mapping type instance definition and determining how the mapping type definition should be presented. This article explains the interfaces that must be implemented to define valid controllers.
Whenever you select a mapping type, its controllers are dynamically loaded and their functions are invoked as needed while you configure and save mappings.
Source Controllers
A source controller is responsible for managing the objects that should be presented as the mapping source. A valid source controller component must implement the SourceController interface.
type SourceController = {
getProperties: () => SourceValue[],
getKey: () => string,
getAvailableValues: (specificParameterValues: Value[]) => Promise<any[]>,
getFilters: () => FilterMetadata[]
};
type SourceValue = {
id: string,
label: string,
linkTemplate?: string // Optional URL template
};
type Value = {
id: number,
name: string
};
export type FilterMetadata = {
type: string,
column: string,
label: string,
datasourceFactory: (mappings: any[]) => any[],
filterPredicate?: (item: any, value: any) => boolean
};
-
The getProperties function provides a list of properties that should be shown as read-only columns in the mappings table. They're shown in the order in which they're listed. Each property must have the following:
-
A unique ID for the property: It enables you to retrieve its value. For example, a GL account may be an object with properties accountName and accountType. Given the names of these two properties, you can retrieve their value from any GL account object. The ID also serves as an identifier for the corresponding table column. Therefore, it must be unique among the property IDs defined for the considered mapping type. Other mapping types may safely use the same ID for their properties.
-
A label for the column header: It's translated according to your language preferences.
-
-
The getKey function provides the property name to be used as the primary key for mappings in the considered mapping type.
-
The getAvailableValues function returns the list of objects representing the rows of the mapping table, displayed in the order in which they're returned. For each object to be properly displayed in a table, all objects must contain properties that are returned by the getProperties function. This function accepts the list of values selected for any specific parameters that you've set up.
-
The getFilters function returns a list of filters used to filter the table data.
Source Value Link Template
Source columns in the mapping table can now be configured as clickable links to NetSuite records. This functionality is enabled by specifying a linkTemplate property on the SourceValue.
-
Usage: Add linkTemplate to any property returned by SourceController.getProperties():
export function getProperties(): SourceValue[] { return [ { id: "accountId", label: Translator.translate(TRANSLATION.TERMS.GL_ACCOUNT), linkTemplate: `/app/accounting/account/account.nl?id={accountId}` }, { id: "accountType", label: Translator.translate(TRANSLATION.TERMS.ACCOUNT_TYPE) // No linkTemplate - this column is plain text } ]; } -
Template Syntax: Templates use placeholder syntax: {fieldName}
-
Placeholders are replaced with values from the source item's data.
-
Multiple placeholders are supported: /record?id={id}&type={type}
-
Values are automatically URL-encoded.
-
If any placeholder value is missing, null, or undefined, no link is rendered. This ensures graceful degradation.
-
-
Examples:
Template
Data
Result
/app/account.nl?id={accountId}
{accountId: 123}
/app/account.nl?id=123
/
some/other/link.nl?id={paramId}&type={paramType} {paramId: 123, paramType: "abc"}
/
some/other/link.nl?id=123&type=abc /search?q={query}
{query: "hello world"}
/search?q=hello%20world
/app/account.nl?id={accountId}
{accountId: null}
No link (plain text)
-
Behavior
-
Any column with a defined linkTemplate becomes clickable.
-
Links open in a new browser tab (target="_blank").
-
Columns without linkTemplate are rendered as plain text, ensuring backward compatibility.
-
-
Template Resolution (src/services/LinkTemplateResolver.ts)
export function resolveLinkTemplate( template: string | undefined, dataItem: any ): string | null-
Returns the resolved URL string, or null if the template is undefined or any placeholder is missing.
-
Used by Mappings.tsx at render time.
-
-
UI Rendering (src/spas/assistant/components/Mappings.tsx): In loadColumns(), columns with a linkTemplate are created as TEMPLATED columns, using a custom render function that calls resolveLinkTemplate().
-
Adding Links to a New Mapping Type:
-
Identify the source property that should be clickable.
-
Identify the field containing the record ID in your source data.
-
Add a linkTemplate to that property in your SourceController.getProperties().
{ id: 'taxCodeName', label: 'Tax Code', linkTemplate: '/app/setup/taxcode.nl?id={taxCodeId}' } -
Target Controllers
Each target controller is responsible for managing one of the objects presented as targets in a mapping. If the target consists of two or more fields, there should be a separate target controller for each field. For example, in the GL Accounts SAF-T 2.0 mapping, every GL account is mapped to both a grouping code and a grouping category. Therefore, there will be one target controller for the grouping code and another for the grouping category.
A valid target controller component must implement the TargetController interface.
type TargetController<TMappingValue = unknown> = {
getId: () => string,
getLabel: () => string,
getReadOnlyColumns: () => ReadOnlyColumn[],
getAvailableValues: (mappingTypeInstance: MappingTypeInstance, mappingLine: any) => Promise<Value[]>,
getSingleValue: (mappingTypeInstance: MappingTypeInstance, mappingLine: any) => Promise<Value | null | undefined>,
resolveValueFromMapping: (mappingTypeInstance: MappingTypeInstance, mappingValue: TMappingValue) => Promise<Value | undefined>,
validateValues: (mappingType: MappingType, mappingTypeInstance: MappingTypeInstanceConfig) => Promise<ValidationResult>,
syncAvailableValues: (
mappingTypeInstanceConfig: MappingTypeInstanceConfig,
mappingTypeInstance: MappingTypeInstance
) => Promise<MappingTypeInstanceDeleterRequest[]>
};
type Value = {
id: number,
name: string
};
type MappingTypeInstance = {
internalId?: number,
id: string,
type: number,
name: string,
translationCollection: string | null,
suggester: string | null,
configuration: string
};
type MappingTypeInstanceConfig = {
values: any, // The format of this object depends on the specific Mapping Type Instance.
type: string,
id: string,
name: string,
translationCollection: string | null,
suggester: string | null,
configuration: any
};
type ValidationErrorDetail = {
stage: string,
entity: string,
reason: string,
uid: string | null,
[key: string]: unknown
};
type ValidationResult = {
valid: boolean,
error?: ValidationErrorDetail
};
type MappingTypeInstanceDeleterRequest = {
instanceId: number,
entity: string,
externalIds: string[]
};
export type ReadOnlyColumn = {
id: string,
label: string,
mappingLineValueId: string,
};
-
The getId function provides a unique ID for the target column in the mappings table. This ID must be unique only among the columns defined for the specific mapping type. Other mapping types may use the same ID for their columns.
-
The getLabel function returns a label for the column header, translated based on language preferences.
-
The getAvailableValues function returns the values available for the target. These values can be filtered according to the previous selections. For example, in the GL Accounts SAF-T 2.0 mapping, after you select a grouping category, only the grouping codes belonging to that category are shown. The previous selections are provided in the mappingLine parameter.
-
The getSingleValue function may return a single value (or null) to be set for the target. For example, in the GL Accounts SAF-T 2.0 mapping, when you select a grouping code, the corresponding grouping category is also determined. The previous selections are provided in the mappingLine parameter. If there's no single value to return, either because no values are available or you must choose among multiple options, this function returns undefined.
-
The getValueById function retrieves a value by its ID (the string identifier as declared by the mapping type instance, not the internal record ID). It returns undefined if the value isn't found.
-
The validateValues function is called when a client app installs a mapping type instance, passing the list of available target values. For more information, see Defining a New Mapping Type Instance. The outcome of this function is a ValidationResult. When validation succeeds, the valid is true. Otherwise the valid is false and an error can provide structured context about the failure.
-
The setAvailableValues function is called after values have been successfully validated. It reconciles the available target values for the mapping type instance. It can return requests for deleting obsolete values that are omitted from the submitted payload.
The mapping type configuration is expected to provide a deleter component. The Universal Mapping Assistant invokes that the component with the obsolete value requests is returned by the TargetControllers. Thus, the mapping type can safely remove obsolete values and any related data.
-
The getReadOnlyColumns function returns a list of read-only columns that are also displayed in the Universal Mapping Assistant setup table. These columns represent values from the target data and depend on another column that you select. In the Methods of Payment mapping type, when you select a payment method identifier, a corresponding read-only column displays its description. The mappingLineValueId attribute specifies the key accessor for the column value, the label defines the column header text, and the ID represents the column identifier.
Mapping Controllers
A mapping controller is responsible for managing the mappings that you set up. It also validates and implements some of the foundational mapping type instance properties. A valid mapping controller must implement the MappingController interface.
type MappingController = {
getInstanceFieldLabelOverride: () => FieldLabelInfo | null;
getParameters: () => Promise<Parameter[]>,
getParameterValues: (mappingTypeInstance: MappingTypeInstance) => Promise<Value[][]>,
validateConfiguration: (configuration: any) => boolean,
validateEnvironment: () => BannerMessage | null,
validateParameters: (parameters: any) => boolean,
getMappings: (mappingTypeInstanceInternalId: number, parameters: any) => Promise<any[]>,
validateMapping: (mapping: any) => string | null,
getSaveParameters: (mappingTypeInstanceInternalId: number, parameters: any[], mappings: any[]) => { taskType: string, scriptId: string, deploymentId: string, params: any }
getCacheKey: () => string
};
type BannerMessage = {
title?: string;
description: string;
};
type FieldLabelInfo = {
fieldLabel: string;
fieldLabelHelp: string;
};
type Parameter = {
id: string,
label: string,
help: string
};
type ParameterValue = {
id: number,
name: string
};
type MappingTypeInstance = {
internalId?: number,
id: string,
type: number,
name: string,
translationCollection: string | null,
configuration: string
};
Before displaying the mappings table, the Universal Mapping Assistant user interface prompts you to select a mapping type and a mapping type instance. For some mapping types, this may be sufficient. However, in most cases, additional information is required. The getParameters function defines the specific parameters that the Universal Mapping Assistant user interface needs to display. Therefore, it enables you to set up mappings for the chosen mapping type. Each of these parameters is also shown as a column in the mapping table.
For example, in the GL Accounts SAF-T 2.0 mapping, you must also select a subsidiary. In this scenario, the getParameters function returns a single parameter instance, representing the subsidiary record along with a list of available values. Each parameter must have a unique ID, label, and field-level help text.
-
The getInstanceFieldLabelOverride function overrides the default field label and help text for the mapping type instance field.
-
The getParameterValues function provides the available values for each parameter. It accepts a mapping type instance as input, which can be used to filter the available values. For example, in the GL Accounts SAF-T 2.0 mapping, the instance may specify a set of countries that narrows the list of available subsidiaries. For more information, see Defining a New Mapping Type Instance.
-
The validateConfiguration function determines whether the configuration section of a mapping type instance definition is valid. The specific validation rules depend on the mapping type. For more information, see Defining a New Mapping Type Instance.
-
The validateEnvironment function checks if the NetSuite account is properly configured for the selected mapping type. For example, it does so by examining the enabled features. This function is called as soon as the mapping type is selected. If validation fails, an error is displayed and you can't proceed. When an error is found, the function returns a translated error banner description and an error banner title. Otherwise, it returns null.
-
When retrieving mappings, the validateParameters function ensures that the parameters provided by the caller are correct. In the GL Accounts SAF-T 2.0 mapping, the caller must provide at least a subsidiary ID to retrieve the mappings. The retrieval then occurs in the getMappings function.
-
The validateMapping function validates an individual mapping. It's used to check each mapping in the UI and to validate mappings on the server side during the save operation. If validation succeeds, it returns null. Otherwise, it returns a validation error specific to the selected mapping type, which should be translated before being displayed in the UI.
-
The getSaveParameters function prepares the necessary information to save mappings. It must return the configuration required to invoke a scheduled or map/reduce script that processes a set of mappings. The returned object is passed to the create function of the N/task library.
-
The getCacheKey function returns a key used to store unsaved mappings for the current user using the N/cache module. These unsaved mappings can later be retrieved from the cache and displayed in the UI as selected and ready to be saved.