api-reference/developer-guides/customize-TSOM-connector-dev-guide.md
On this page
— title: Customizing TSOM connector behavior with extension points description: Learn how to use extension points to extend ServiceNow Telecommunications Service Operations Management (TSOM) connector behavior without modifying any default code.The resolution chain, the order field, the three dispatch patterns, and the scope restrictions determine whether a registered implementation runs and which implementation’s result is applied at runtime. Review these mechanisms before implementing an extension point.Four extension points—one each for the Meraki, Fortinet, VeloCloud, and Catalyst connectors—control the lifecycle stage status assigned to discovered CIs. All four share the same contract.Assign a lifecycle stage status that varies by CI class for CIs discovered by a TSOM vendor connector. This procedure uses the Meraki connector; the steps are identical for Fortinet, VeloCloud, and Catalyst.Apply one set of lifecycle stage status rules across the Meraki, Fortinet, VeloCloud, and Catalyst connectors by centralizing the logic in a helper Script Include and delegating to it from thin per-connector wrappers.The EventFieldMapping extension point binds an incoming event to a configuration item. Use it to change how an existing vendor’s events resolve to CIs, or to add CI mapping for a new vendor.Contract methods and shipped implementations for the EventFieldMapping extension point.Provide CI binding for a vendor that TSOM does not ship an event-to-CI mapper for.Replace how TSOM binds a supported vendor’s events to configuration items.WebhookFieldMapping maps a vendor webhook payload to TMF688 event fields. This extension point is same-scope only, so you can’t implement it from a custom scope.Two extension points control metric behavior: MetricsAggrSEP customizes aggregation, and UplinkThroughputPercentage customizes uplink throughput calculation.Replace the OOB metric aggregation logic with your own using the MetricsAggrSEP extension point.Five extension points customize narrow, vendor-specific behaviors: firmware version formatting, contract parsing, tag transformation, Nokia MPN formula handling, and recovery handling.Format the Fortinet firmware version string differently from the OOB default.Three extension points let you add API endpoints to Fortinet discovery, register new entity types, and reshape discovered data, without editing default code.Add a FortiManager API endpoint to Fortinet discovery so the connector collects data it doesn’t collect by default and stores it on configuration items.Map extra fields from a FortiManager response onto Fortinet configuration items, and register transform functions to reshape the values.Properties that define a step in a Fortinet collection plan: how the endpoint is called, which entity it iterates over, and when its data is written.The five handlers a FortinetParserHooks implementation must provide, the arguments each receives, and what each returns.Avoid the mistakes that most often break a TSOM extension point implementation, and understand what an upgrade does and doesn’t touch.Confirm that your custom implementation is registered, active, and resolving at the priority you expect before you rely on it in production.Once an implementation is confirmed active, use this table to confirm it produces the expected result for its category.Run this script in Scripts - Background to audit every order-based TSOM extension point on your instance in one pass.Disable a misbehaving custom implementation and fall back to the default without waiting for a fix.API name, scope, restrict_scope, and dispatch pattern for every TSOM extension point, in one table. locale: en-US canonical_url: www.servicenow.com/docs/r/australia/api-reference/developer-guides/customize-TSOM-connector-dev-guide.html release: australia product: Developer Guides classification: developer-guides topic_type: concept last_updated: “2026-10-09” reading_time_minutes: 36
breadcrumb: [Developer guides, API implementation and reference]
Customizing TSOM connector behavior with extension points
Learn how to use extension points to extend ServiceNow Telecommunications Service Operations Management (TSOM) connector behavior without modifying any default code.
TSOM ships 16 extension points across 5 connector scopes.
Each TSOM extension point controls one aspect of connector behavior, from life cycle status assignment, to event-to-CI mapping, to metric aggregation. Implement an extension point when the default behavior doesn’t match how your organization models or processes discovered data.
For the full attribute matrix, see TSOM extension point quick reference. The extension points are grouped by function:
-
Lifecycle management (4)
-
Event-to-CI mapping (1)
-
Webhook field mapping (1)
-
Metrics customization (2)
-
Vendor-specific customizations (5)
-
Fortinet discovery extensibility (3)—see Fortinet discovery extensibility.
Requirements
This guide describes how to implement the extension points that TSOM offers. To define new extension points in your own application, see the platform documentation on scripted extension points in server-side scripts.
Confirm the prerequisites: which plugin(s) / store apps deliver each connector scope (sn_sgc_meraki, sn_sgc_fortinet, sn_sgc_vcloud, sn_sgc_catalyst, sn_tsom_em_conns), and the activation steps. The source Pocket Guide assumes the connectors are already installed.
⚠ Writer / dev verificationConfirm the role(s) required to create Script Includes and Extension Instance records (admin, or a scoped-app developer role). Not stated in the source material.
Key terminology
The following table describes the key terms and tables that back them when you implement a TSOM extension point.
Term Table Description Extension point sys_extension_point The contract definition. Declares the methods an implementation must provide. Extension instance sys_extension_instance A registration record that links a Script Include to an extension point. Script Include sys_script_include The code that implements the extension point contract. Order sys_extension_instance.order Numeric priority. A lower number is a higher priority. restrict_scope sys_extension_point.restrict_scope Whentrue, only implementations from the same scope can register.
TSOM extension point resolution at runtime
The resolution chain, the order field, the three dispatch patterns, and the scope restrictions determine whether a registered implementation runs and which implementation’s result is applied at runtime. Review these mechanisms before implementing an extension point.
When TSOM invokes an extension point, it calls GlideScriptedExtensionPoint to retrieve all active sys_extension_instance records linked to that extension point, each instantiated as a Script Include object. The calling code then selects one or more implementations from the returned array according to the extension point’s dispatch pattern.
var ep = new GlideScriptedExtensionPoint();
var implementations = ep.getExtensions('scope.ExtensionPointName');
The resolution chain
The resolution chain proceeds from contract to registration to implementation: sys_extension_point (contract) → sys_extension_instance (registration, active=true) → sys_script_include (implementation).
The order field
Every sys_extension_instance has an integer order field. OOB implementations use order = 100. Lower numbers take priority.
1–49
ServiceNow emergency hotfixes. Do not use.
50–99
Customer overrides. Recommended: order = 50.
100
OOB default implementations.
101+
Fallback or lower-priority extensions.
Dispatch patterns
The dispatch pattern of an extension point determines how the framework selects among registered implementations and, in turn, how a custom implementation affects runtime behavior.
-
Handler pattern (all implementations invoked): The framework iterates over all active implementations and invokes the handler method on each. A custom implementation’s result takes precedence when it is registered at an
ordervalue below 100. Used by the four lifecycle status extension points and by the firmware-version and contract-parsing extension points. -
Vendor-dispatch pattern (matched by vendor or rule name): A resolver iterates over all implementations, calls
getVendor()(and, where applicable,getRuleName()), and returns the first match. When more than one implementation matches, the implementation with the lowestordervalue is selected. Used by the event field mapping, webhook field mapping, and recovery handler extension points. -
Single-override pattern (lowest order selected): The framework uses only the implementation with the lowest
ordervalue; exactly one implementation runs. Used by the metrics aggregation, uplink throughput, Nokia MPN formula, and tag transformation extension points. -
Validated-contract pattern (first complete implementation selected): The framework uses the first implementation it finds that satisfies the entire contract, and exactly one implementation runs. An implementation that omits any required method is rejected in full rather than partially merged; an error names the missing method or key and the OOB implementation is used instead. Used by the three Fortinet discovery extension points:
sn_sgc_fortinet.FortinetCollectionPlan,sn_sgc_fortinet.FortinetParserHooks, andsn_sgc_fortinet.FortinetFieldMappings. See Fortinet discovery extensibility.
Note:
Under the validated-contract pattern, order still governs the sequence in which implementations are examined, so register a custom implementation below 100 to have it considered before the OOB implementation. Selection then depends on whether that implementation is complete.
Scope restrictions
The restrict_scope field on the extension point controls who can register an implementation.
false (14 of 16)
Implementations from any scoped app are accepted.
Create a custom scoped app with your Script Include; it works cross-scope.
true (2 of 16)
Same scope only.
You can’t override from your own scope. Contact ServiceNow support.
Important:
The two extension points with restrict_scope = true are
sn_tsom_em_conns.WebhookFieldMapping and sn_tsom_em_conns.RecoveryHandlerSEP. An instance registered from a different scope is ignored silently; no error is raised.
Lifecycle stage status extension points
Four extension points—one each for the Meraki, Fortinet, VeloCloud, and Catalyst connectors—control the lifecycle stage status assigned to discovered CIs. All four share the same contract.
By default, each connector assigns the status In Use to every discovered CI, regardless of class. Implement a lifecycle stage status extension point when the lifecycle status must vary by CI class. For example, to distinguish operational routers from interfaces undergoing maintenance.
These extension points use the handler dispatch pattern: a custom implementation runs alongside the out-of-the-box logic, and its returned value takes precedence when its extension instance is registered at a lower order value. All four extension points have restrict_scope = false, so implementations can be registered from a custom scoped application.
For the procedure to override a single connector, see Override the lifecycle stage status. To apply identical logic across all four connectors without duplication, see Share lifecycle logic across vendor connectors. For the contract and per-connector API names, see Lifecycle stage status contract.
Lifecycle stage status contract
Reference the shared contract and per-connector API names for the four lifecycle stage status extension points.
Attribute Value Description Methodhandlers.getLifeCycleStageStatus(entityClass)
Returns the lifecycle stage status for CIs of the given class.
Parameter
entityClass (String)
The CMDB class of the discovered CI, for example cmdb_ci_ip_router.
Returns
String
The lifecycle stage status to assign.
OOB default
In Use
Returned for all entity classes.
Dispatch
Handler
All active implementations are called.
Connector
API name
Scope
Since
Meraki
sn_sgc_meraki.MerakiCustomizedLifeCycleStageStatus
sn_sgc_meraki
v3.2
Fortinet
sn_sgc_fortinet.FortinetCustomizedLifeCycleStageStatus
sn_sgc_fortinet
v3.2
VeloCloud
sn_sgc_vcloud.VeloCloudCustomizedLifeCycleStageStatus
sn_sgc_vcloud
v3.2
Catalyst
sn_sgc_catalyst.CatalystCustomizedLifeCycleStageStatus
sn_sgc_catalyst
v3.4
Override the lifecycle stage status
Assign a lifecycle stage status that varies by CI class for CIs discovered by a TSOM vendor connector. This procedure uses the Meraki connector; the steps are identical for Fortinet, VeloCloud, and Catalyst.
Before you begin
Role required: XX.
Confirm the target connector (for Meraki, sn_sgc_meraki) is installed and active. Because this extension point has restrict_scope = false, you can register your implementation from a custom scoped application; you don’t need to work inside the connector scope.
About this task
The extension point uses the handler pattern, so your implementation supplements rather than replaces default logic. Returning a value with an order below 100 makes your value win.
Procedure
-
Create a Script Include in your scoped application that implements the contract method handlers.getLifeCycleStageStatus(entityClass).
-
Return the status string for each CI class and fall through to
In Usefor unmatched classes.var MyMerakiLifeCycleOverride = Class.create(); MyMerakiLifeCycleOverride.prototype = { initialize: function() {}, handlers: { getLifeCycleStageStatus: function(entityClass) { // Routers -> Operational, interfaces -> In Maintenance if (entityClass == 'cmdb_ci_ip_router') return 'Operational'; if (entityClass == 'cmdb_ci_ni_interface') return 'In Maintenance'; return 'In Use'; } }, type: 'MyMerakiLifeCycleOverride' }; -
Set the
typevalue to exactly match the Script Include record name.Note: The resolver looks up
orderbytype. A mismatch causes your order to be ignored and to default to 100. -
Navigate to System Definition > Extension Instances and create a registration record.
Field Value Pointsn_sgc_meraki.MerakiCustomizedLifeCycleStageStatusScript Include Your Script Include Order50(recommended customer-override range is 50–99) Activetrue
Result
On the next Meraki discovery sync, discovered CIs receive the lifecycle status your implementation returns. Verify on a CI’s life_cycle_stage_status field.
To confirm the override is loaded, see Verify that an extension instance is active. To reuse this logic across the other three connectors, see Share lifecycle logic across vendor connectors.
Share lifecycle logic across vendor connectors
Apply one set of lifecycle stage status rules across the Meraki, Fortinet, VeloCloud, and Catalyst connectors by centralizing the logic in a helper Script Include and delegating to it from thin per-connector wrappers.
Before you begin
Role required: user role
About this task
Each connector has its own extension point, but all four share an identical contract. Rather than duplicate the mapping logic four times, put it in a shared helper and register one thin wrapper per connector extension point.
Procedure
-
Create a shared helper Script Include that returns a status for a CI class.
This is a plain helper, not an extension point implementation.
var MyLifeCycleHelper = Class.create(); MyLifeCycleHelper.prototype = { initialize: function() {}, getStatus: function(entityClass) { var mapping = { 'cmdb_ci_ip_router': 'Operational', 'cmdb_ci_ip_switch': 'Operational', 'cmdb_ci_ni_interface': 'In Maintenance' }; return mapping[entityClass] || 'In Use'; }, type: 'MyLifeCycleHelper' };
-
Create one thin wrapper Script Include per connector extension point that delegates to the helper.
The example shows the Fortinet wrapper; repeat for Meraki, VeloCloud, and Catalyst.
var MyFortinetLifeCycle = Class.create(); MyFortinetLifeCycle.prototype = { initialize: function() {}, handlers: { getLifeCycleStageStatus: function(entityClass) { return new MyLifeCycleHelper().getStatus(entityClass); } }, type: 'MyFortinetLifeCycle' };
-
Register each wrapper against its connector's extension point with
order = 50andactive = true.
Result
All four connectors apply the same lifecycle rules, and you maintain the logic in one place.
event-to-CI mapping extension point
The EventFieldMapping extension point binds an incoming event to a configuration item. Use it to change how an existing vendor’s events resolve to CIs, or to add CI mapping for a new vendor.
sn_tsom_em_conns.EventFieldMapping uses the vendor-dispatch pattern: a resolver matches on getVendor() and getRuleName(), and the lowest order wins among matches. The extension point has restrict_scope = false and is available since TSOM v3.4.
To replace mapping for a vendor that TSOM already supports, see Override event-to-CI mapping for a vendor. To add a vendor, see Add event-to-CI mapping for a new vendor. For the contract and OOB implementations, see Event field mapping contract.
Note:
In an EventFieldMapping implementation, set fields on the event and return true or false only. Calling eventGr.update() corrupts the event pipeline.
Event field mapping contract
Contract methods and shipped implementations for the EventFieldMapping extension point.
Attribute Value Description API namesn_tsom_em_conns.EventFieldMapping
restrict_scope
false
Dispatch
Vendor-dispatch
Match vendor and rule name; lowest order wins.
Since
TSOM v3.4
Method
Parameters
Returns
getVendor()
none
String, for example Fortinet
getRuleName()
none
String. Must match em_mapping_rule.name exactly.
mapCi(eventGr, origEventSysId, fieldMappingRuleName)
eventGr (GlideRecord, temp), origEventSysId (String), fieldMappingRuleName (String)
Boolean
Script Include
Vendor
Rule name
FortinetCIMapper
Fortinet
Fortinet CI Mapper
FortinetWebhookEventCIMapper
Fortinet
Fortinet Webhook Event CI Mapper
FortinetWebhookMetricCIMapper
Fortinet
Fortinet Webhook Metric CI Mapper
MerakiCIMapper
Meraki
Meraki CI Mapper
VelocloudCIMapper
VeloCloud
VeloCloud CI Mapper
VelocloudWebhookEventCIMapper
VeloCloud
VeloCloud Webhook Event CI Mapper
Event field mapping contract
Contract methods and shipped implementations for the EventFieldMapping extension point.
Attribute Value Description API namesn_tsom_em_conns.EventFieldMapping
restrict_scope
false
Dispatch
Vendor-dispatch
Match vendor and rule name; lowest order wins.
Since
TSOM v3.4
Method
Parameters
Returns
getVendor()
none
String, for example Fortinet
getRuleName()
none
String. Must match em_mapping_rule.name exactly.
mapCi(eventGr, origEventSysId, fieldMappingRuleName)
eventGr (GlideRecord, temp), origEventSysId (String), fieldMappingRuleName (String)
Boolean
Script Include
Vendor
Rule name
FortinetCIMapper
Fortinet
Fortinet CI Mapper
FortinetWebhookEventCIMapper
Fortinet
Fortinet Webhook Event CI Mapper
FortinetWebhookMetricCIMapper
Fortinet
Fortinet Webhook Metric CI Mapper
MerakiCIMapper
Meraki
Meraki CI Mapper
VelocloudCIMapper
VeloCloud
VeloCloud CI Mapper
VelocloudWebhookEventCIMapper
VeloCloud
VeloCloud Webhook Event CI Mapper
Add event-to-CI mapping for a vendor
Provide CI binding for a vendor that TSOM does not ship an event-to-CI mapper for.
Before you begin
Role required: xxx
About this task
Return a new vendor and rule name that no OOB implementation matches, then implement your binding logic in mapCi().
Procedure
-
Create a Script Include for the new vendor.
var PaloAltoCIMapper = Class.create(); PaloAltoCIMapper.prototype = { initialize: function() {}, getVendor: function() { return 'PaloAlto'; }, getRuleName: function() { return 'PaloAlto CI Mapper'; }, mapCi: function(eventGr, origEventSysId, ruleName) { // Your Palo Alto CI binding logic here return true; }, type: 'PaloAltoCIMapper' }; -
Register the extension instance against
sn_tsom_em_conns.EventFieldMapping,active = true.
Result
Events from the new vendor resolve to CIs through your mapper.
Override event-to-CI mapping for a vendor
Replace how TSOM binds a supported vendor’s events to configuration items.
Before you begin
Identify the OOB rule you are replacing—its name must match an em_mapping_rule.name value exactly.Tell the writer/developer where to find valid em_mapping_rule.name values (list view or navigation path). Not specified in the source.
Role required: xxx
About this task
Match the vendor and rule name of the OOB implementation, and register with a lower order so your implementation wins.
Procedure
-
Create a Script Include that returns the target vendor and rule name and binds the CI in
mapCi().var MyFortinetCIMapper = Class.create(); MyFortinetCIMapper.prototype = { initialize: function() {}, getVendor: function() { return 'Fortinet'; }, getRuleName: function() { return 'Fortinet CI Mapper'; }, mapCi: function(eventGr, origEventSysId, fieldMappingRuleName) { var deviceName = eventGr.getValue('node'); if (!deviceName) return false; var ciGr = new GlideRecord('cmdb_ci_ip_firewall'); ciGr.addQuery('name', deviceName); ciGr.query(); if (ciGr.next()) { eventGr.cmdb_ci = ciGr.getUniqueValue(); eventGr.ci_type = 'cmdb_ci_ip_firewall'; return true; } return false; }, type: 'MyFortinetCIMapper' }; -
Set
typeto exactly match the Script Include record name.The resolver uses
impl.typeto look up the order. A mismatch means your order is ignored and defaults to 100. -
Register the extension instance against
sn_tsom_em_conns.EventFieldMappingwithorder = 50andactive = true.
Result
Create a test event and confirm the em_event.cmdb_ci and ci_type values resolve through your implementation.
About the webhook field mapping extension point
WebhookFieldMapping maps a vendor webhook payload to TMF688 event fields. This extension point is same-scope only, so you can’t implement it from a custom scope.
sn_tsom_em_conns.WebhookFieldMapping uses the vendor-dispatch pattern (matched by getVendor()) and is available since TSOM v3.2. It ships implementations for Fortinet, Meraki, VeloCloud, and Elastic.
Restriction: This extension point has restrict_scope = true. You cannot register an implementation from your own scoped app. To customize webhook field mapping for a new vendor, contact ServiceNow support.
For the contract, see Webhook field mapping contract.
OOB implementations: Fortinet, Meraki, VeloCloud, Elastic.
Webook field mapping contract
Contract methods for the WebhookFieldMapping extension point (same-scope only).
Attribute Value Description API namesn_tsom_em_conns.WebhookFieldMapping
Scope
sn_tsom_em_conns
restrict_scope
true
Same scope only.
Dispatch
Vendor-dispatch
Match getVendor().
Since
TSOM v3.2
Method
Returns
Description
getVendor()
String
Vendor identifier.
getFieldMapping()
Object (TMF688 structure)
Maps the vendor webhook payload to TMF688 event fields.
getDetectionRules()
Object
Rules for detecting event type from the payload.
Metrics customization extension points
Two extension points control metric behavior: MetricsAggrSEP customizes aggregation, and UplinkThroughputPercentage customizes uplink throughput calculation.
Both extension points have restrict_scope = false and use the single-override pattern. Only the lowest-order implementation runs.
-
sn_tsom_em_conns.MetricsAggrSEP(since v3.1) customizes how a metric is aggregated. The OOB default aggregatesmeraki_uplink_statusfromcmdb_ci_ni_interfacechild CIs using Decision Table rules and Clotho time-series data. -
sn_tsom_em_conns.UplinkThroughputPercentage(since v3.3) processes and aggregates uplink throughput data.
To customize aggregation, see Override metric aggregation. For contracts, see Metrics aggregation contract and Uplink throughput contract.
Metrics aggregation contract
Contract methods for the MetricsAggrSEP extension point.
Attribute Value Description API namesn_tsom_em_conns.MetricsAggrSEP
restrict_scope
false
Dispatch
Single-override
Lowest order wins.
Since
TSOM v3.1
Method
Parameters
Returns
aggrMetric(period)
period (String)
void
metricData()
none
Object: { metricName, childClassName }
Default behavior: aggregates meraki_uplink_status from cmdb_ci_ni_interface child CIs using Decision Table rules and Clotho time-series data.
Uplink throughput contract
Contract methods for the UplinkThroughputPercentage extension point.
Attribute Value Description API namesn_tsom_em_conns.UplinkThroughputPercentage
restrict_scope
false
Dispatch
Single-override
Since
TSOM v3.3
Method
Parameters
Returns
process(data)
data (Array)
Array (modified data)
aggrMetric(period)
period (String)
void
metricData()
none
Object
Override metric aggregation
Replace the OOB metric aggregation logic with your own using the MetricsAggrSEP extension point.
Before you begin
Role required: xcdfsa
About this task
Because this extension point uses the single-override pattern, only your lowest-order implementation runs. Implement both metricData() (to declare the metric and child class) and aggrMetric(period) (your aggregation logic).
Procedure
-
Create a Script Include implementing both contract methods.
var MyMetricsAggr = Class.create(); MyMetricsAggr.prototype = { initialize: function() {}, aggrMetric: function(period) { var inputs = this.metricData(); gs.info('Custom agg: ' + inputs.metricName); // Your custom aggregation logic }, metricData: function() { return { metricName: 'my_custom_metric', childClassName: 'cmdb_ci_ni_interface' }; }, type: 'MyMetricsAggr' }; -
Register the extension instance against
sn_tsom_em_conns.MetricsAggrSEPwith the lowest activeorder(for example50).
Result
After the scheduled aggregation job runs, confirm the results on sa_metric_instance.
Vendor-specific extension points
Five extension points customize narrow, vendor-specific behaviors: firmware version formatting, contract parsing, tag transformation, Nokia MPN formula handling, and recovery handling.
-
sn_sgc_fortinet.FortinetCustomizedFirmwareVersion(handler)—format the firmware version string. See Override firmware version formatting. -
sn_sgc_fortinet.FortinetCustomizedContractParsing—parse contract items into license attributes. See Contract parsing contract. -
sn_sgc_meraki.TagTransformationExtensionPoint(single-override, filtered byhandles())—transform discovered tags. See Transform discovered tags. -
sn_tsom_em_conns.NokiaMpnFormulaEngineSEP(single-override)—clean and validate MPN formulas. See Clean and validate Nokia MPN formulas. -
sn_tsom_em_conns.RecoveryHandlerSEP(vendor-dispatch, same-scope only)—handle recovery queue records. See About the recovery handler extension point.
All except the recovery handler have restrict_scope = false.
Override firmware version formatting
Format the Fortinet firmware version string differently from the OOB default.
Before you begin
Role required: xxcxcx
About this task
Return a formatted string from the handler. The handler’s return value is used exactly as returned, including an empty or null result — the built-in default format ([device.os_ver, device.mr, device.patch].join('.'), for example 7.4.11) is used only when no custom handler is active. This extension point uses the handler pattern.
Procedure
-
Create a Script Include implementing
handlers.formatFirmwareVersion(device).var MyFirmwareVersion = Class.create(); MyFirmwareVersion.prototype = { initialize: function() {}, handlers: { formatFirmwareVersion: function(device) { if (device.os_ver && device.mr && device.patch) { return 'FortiOS v' + device.os_ver + '.' + device.mr + ' Patch ' + device.patch; } return null; // results in an empty firmware value (no fallback) } }, type: 'MyFirmwareVersion' }; -
Register the extension instance against
sn_sgc_fortinet.FortinetCustomizedFirmwareVersionwithorder = 50andactive = true.
Result
Discovered Fortinet devices display the formatted firmware version.
Fortinet discovery extensibility
Three extension points let you add API endpoints to Fortinet discovery, register new entity types, and reshape discovered data, without editing default code.
Before these extension points, adding an endpoint to Fortinet discovery meant rebuilding the discovery configuration from the start. You can now augment discovery incrementally: declare a new endpoint, describe how its response is parsed, and map the result onto configuration item attributes.
The three extension points
Each extension point covers one stage of the discovery pipeline. All three are in the sn_sgc_fortinet scope, have restrict_scope = false, and are available starting with TSOM v3.5. Each ships a default implementation registered at order = 100.
sn_sgc_fortinet.FortinetCollectionPlan
Which API calls the connector makes, in what order, and how each response is routed.
getCollectionPlan(parser). getConstants() is optional.
sn_sgc_fortinet.FortinetParserHooks
How a raw response is unwrapped, enriched, filtered, and stored. Registers additional entity types.
All five of unwrapResponse, beforeExtract, afterExtract, postProcess, and shouldStore. getEntityTypes(constants) is optional.
sn_sgc_fortinet.FortinetFieldMappings
The declarative field mappings that shape an API response into a configuration item payload.
getFieldMappings(). getTransforms() is optional.
How a discovery run uses the extension points
A discovery run reads the collection plan first, then processes each response through the parser hooks and the field mappings:
-
The active
FortinetCollectionPlanreturns the plan. The connector sorts the steps by theirdependsOndeclarations into execution levels. A circular dependency stops collection. -
Each step calls the shared Data Fetch action. The step’s
urlfunction returns a request-body template key and the values substituted into that template. -
If the step defines
onResponse, that function handles the response and nothing else runs for it. Otherwise the response goes toparser.parseData(). -
Responses that reach
parser.parseData()pass through theFortinetParserHookshandlers, and each item is mapped byFortinetFieldMappings. -
Steps marked
write: 'deferred'are written after every step and post-collection hook finishes.
Note:
In the default collection plan, only the devices step reaches the parser hooks. The contracts and adoms steps handle their own responses in onResponse and populate parser state for later steps to read.
Contract validation and fallback
These three extension points don’t resolve by order alone. The connector uses the first implementation it finds that satisfies the whole contract, and it uses only one implementation per extension point.
Important:
Validation is all or nothing. A FortinetParserHooks implementation missing any of the five handlers is rejected in full, and a FortinetFieldMappings implementation that doesn’t return all five resource types is rejected in full. The connector logs an error naming the missing handlers or keys and falls back to the default implementation.
Because a partial implementation is discarded rather than merged, start from a copy of the default implementation and add to it. Omitting the contracts, adoms, or devices steps from a custom collection plan silently drops license, ADOM, and device collection.
For how this compares with the other TSOM dispatch patterns, see TSOM extension point resolution at runtime.
Attributes you can’t remap
Custom field mappings and custom transforms are for additional_attributes entries. Changing the mapping of a core configuration item attribute can break identity resolution, deduplication, and downstream processing.
Warning:
Don’t remap key, name, serial_number, ip_address, model_name, class, company, devices, or net_mask.
A custom transform whose name matches a built-in transform is skipped and a warning is logged. The built-in transform is used instead.
Retired extension point
The sn_sgc_fortinet.FortinetCustomAttributes extension point is retired. Its registration record and its default implementation, FortinetDefaultCustomAttributes, are removed during upgrade.
Warning:
If you implemented FortinetCustomAttributes to enrich Fortinet configuration items with custom attributes, move that logic to FortinetFieldMappings. The retired extension point isn’t invoked after upgrade.
Requirements
Role required:
Plugin:
Navigation path:
Related tasks
Add an API endpoint to Fortinet discovery
Add a FortiManager API endpoint to Fortinet discovery so the connector collects data it doesn’t collect by default and stores it on configuration items.
Before you begin
-
Identify the FortiManager JSON-RPC call you want to add and the parameters it takes.
-
Decide whether the endpoint is fetched once per run or once per ADOM.
-
Open the default
FortinetCollectionPlan,FortinetParserHooks, andFortinetFieldMappingsscript includes so you can copy their contents.
Role required:
About this task
Adding an endpoint touches four things: the collection plan declares the call, a request-body template defines the payload, a parser hook shapes the response, and a field mapping turns each item into a configuration item payload. You must configure all four components. Skipping any component means the endpoint either isn’t called or its data isn’t stored.
Important:
Each of these extension points accepts one implementation only, and rejects an implementation that doesn’t satisfy its whole contract. Copy the default implementation and add to the copy rather than writing a new implementation that returns only your additions.
For background on how the three extension points work together, see Fortinet discovery extensibility.
Procedure
-
Create a script include in your own scoped application that implements
sn_sgc_fortinet.FortinetCollectionPlan, and copy the default collection plan into itsgetCollectionPlan(parser)method.Warning:
Return the complete plan. Omitting the
contracts,adoms, ordevicessteps drops license, ADOM, and device collection with no error. -
Add your entity type key and request-body template key to
getConstants().Custom constants are merged into a per-run constants object rather than written to the global
FortinetConstantsobject. Read them asparser.constants.YOUR_KEY, never asFortinetConstants.YOUR_KEY. Default constants win on a name collision, so you can't redefine an existing constant.getConstants: function() { return { FIRMWARE_UPGRADES: 'firmware_upgrades', GET_FIRMWARE_UPGRADES: 'sgfortinet_get_firmware_upgrades' }; } -
Add a step for your endpoint to the plan you copied.
Name the step after the entity type it collects. The
urlproperty is always a function that returns the request-body template key and the values substituted into it. For a step fetched once per ADOM, the function receives the ADOM name.firmware_upgrades: { dependsOn: ['adoms'], fetch: { scope: 'per_parent', parentEntity: 'adoms', strategy: 'single' }, write: 'deferred', url: function(adomName) { return { apiName: parser.constants.GET_FIRMWARE_UPGRADES, inputs: { adom_name: adomName } }; } }For every step property and its accepted values, see Fortinet collection plan step properties.
-
Register the request-body template for your
apiNamekey.Every Fortinet API call goes through one shared Data Fetch action. The
apiNamereturned by yoururlfunction selects which FortiManager JSON-RPC call is made by looking up a template in the active request-body extension. The default templates are inActionRequestBodyFortinetDefault. -
Create a script include that implements
sn_sgc_fortinet.FortinetParserHooks, copy all five default handlers into it, and add a branch for your entity type.At minimum, add a branch to
unwrapResponsethat returns the array your endpoint nests its items under. Keep every existingdevicesbranch, or you lose port attachment, license attribute lookup, and network site creation.unwrapResponse: function(responseJson, type, parser) { if (type === parser.constants.FIRMWARE_UPGRADES) { return responseJson?.result?.[0]?.data ?? []; } return responseJson; } -
Return your entity type from
getEntityTypes(constants)so it's registered in the shared store.This optional handler receives the merged constants object and returns an array of entity type keys. Add it when your collection plan step introduces a type that isn't in the default plan.
getEntityTypes: function(constants) { return [constants.FIRMWARE_UPGRADES]; } -
Create a script include that implements
sn_sgc_fortinet.FortinetFieldMappings, copy the default mappings intogetFieldMappings(), and add an entry for your entity type.Return mappings for all five default resource types and your own:
port,network_site,network_service_instance,organization, anddevices. Add your custom fields inside anadditional_attributesblock and leave core configuration item attributes as they are.For field mapping syntax and transform registration, see Extend Fortinet field mappings.
-
Register each script include by creating an extension instance record that points to its extension point.
Set
orderto a value between 50 and 99 so your implementation is found before the default implementation atorder = 100, and setactiveto true. -
Run Fortinet discovery and confirm your endpoint's data appears on the expected configuration items.
Because nothing is cached between runs, a change to your implementation takes effect on the next run.
Result
The connector calls your endpoint on every discovery run, and the data it returns is stored as additional attributes on the configuration items you mapped.
What to do next
If discovery runs but your data is missing, check the connector logs for a rejection message naming a missing handler or resource type. A rejected implementation is replaced by the default implementation for that extension point, and the rest of discovery continues normally.
Extend Fortinet field mappings
Map extra fields from a FortiManager response onto Fortinet configuration items, and register transform functions to reshape the values.
Before you begin
-
Identify the field in the FortiManager response you want to capture, and its path within the response object.
-
Open the default
FortinetFieldMappingsscript include so you can copy the default mappings.
Role required:
About this task
Use this procedure when the data you want is already in a response the connector collects, but isn’t mapped onto a configuration item. This is also the replacement path for the retired sn_sgc_fortinet.FortinetCustomAttributes extension point, described in Fortinet discovery extensibility.
Procedure
-
Create a script include in your own scoped application that implements
sn_sgc_fortinet.FortinetFieldMappings. -
Copy the default mappings into
getFieldMappings().Important:
Return mappings for all five resource types:
port,network_site,network_service_instance,organization, anddevices. If any key is missing, the whole implementation is rejected, an error names the missing keys, and the default mappings are used instead. -
Add your fields to the
additional_attributesblock of the resource type you're extending.Each entry names the attribute and where its value comes from. Use
sourcefor a path into the raw response object,valuefor a fixed value, andtransformto name a function that post-processes the resolved value.additional_attributes: { additional_attributes: [ { status: { source: 'conn_status' } }, { hostname: { source: 'hostname' } }, { site_code: { source: 'meta fields.SiteCode' } }, { region: { source: 'adomName', transform: 'prefixRegion' } } ] }Warning:
Add fields only inside
additional_attributes. Changing the mapping ofkey,name,serial_number,ip_address,model_name,class,company,devices, ornet_maskcan break configuration item identity resolution and deduplication. -
If a value needs reshaping, add a transform function to
getTransforms()and reference it by name from your mapping.A transform receives the resolved source value as its first argument. When a mapping lists several sources, the resolved values are passed in the order they're listed. Inside a transform,
thisgives access to the active parser and to the built-in transforms.getTransforms: function() { return { prefixRegion: function(adomName) { return 'Region-' + adomName; }, buildDisplayName: function(value1, value2) { return value1 + ' (' + value2 + ')'; } }; }Return
nullif you have no custom transforms.Note:
A custom transform whose name matches a built-in transform is skipped and a warning is logged. Pick a name that isn't already in use.
-
Read multiple source values into one transform by passing an array to
source.To read from the parent context object rather than the raw item, prefix the path with
$parent.. UsedefaultValuefor a fallback when the source resolves to null or is absent.Note:
defaultValuedoesn’t catch an empty string. When the API can return an empty string instead of omitting the key, handle it in a transform. -
Create an extension instance record that points your script include at
sn_sgc_fortinet.FortinetFieldMappings, and setorderto a value between 50 and 99. -
Run Fortinet discovery and confirm your attributes appear on the configuration items.
Result
Your fields are written as additional attributes on the mapped configuration items on every discovery run.
What to do next
If an additional_attributes list resolves to nothing because every entry was excluded, it resolves to null rather than an empty array. An empty array is rejected during load, so don’t replace this behavior with one that returns an empty array.
Fortinet collection plan step properties
Properties that define a step in a Fortinet collection plan: how the endpoint is called, which entity it iterates over, and when its data is written.
A collection plan is an object keyed by step name. The step name is also the entity type passed to the parser hooks. The following properties are available on each step.
Property Type Descriptionurl
Function
Required. Returns an object with apiName and inputs. apiName is a request-body template key that selects which FortiManager call is made. inputs supplies the values substituted into that template. For a step whose scope is per_parent, the function receives the parent ID.
fetch.scope
String
root fetches once per run with no iteration. per_parent fetches once per ID of another entity type.
fetch.strategy
String
single sends exactly one request per parent ID. This is the only strategy Fortinet supports.
fetch.parentEntity
String
Required when fetch.scope is per_parent. The entity type whose IDs are iterated over. For adoms, the IDs are the ADOM names.
dependsOn
Array of strings
Step names in the same plan that must finish first. Used to sort steps into execution levels. A circular dependency stops collection. A dependency on a name that isn’t in the plan is ignored.
write
String
deferred writes after every step and post-collection hook finishes. immediate with writeKeys writes as soon as a response is parsed. Omit the property for a step that only populates parser state and produces no configuration items of its own.
onResponse
Function
Receives the entity ID, the result, and the active parser. When present, no other processing happens for that response unless this function triggers it. Omit it to send the response through the standard parser hooks and field mappings.
Note:
The immediate value for write is supported by the underlying orchestrator but isn’t used by the default plan.
Default steps
The default collection plan contains three steps. A custom plan must return all three and any steps you add.
Step Behaviorcontracts
Populates the license expiration map. Parses its own response in onResponse and produces no configuration items directly.
adoms
Builds the filtered ADOM list that later steps iterate over. Parses its own response in onResponse and produces no configuration items directly.
devices
Collects devices per ADOM and writes with deferred. Defines onResponse to unwrap the response, then calls the parser explicitly so the response passes through the parser hooks and field mappings.
Warning:
Omitting contracts, adoms, or devices from a custom plan drops license, ADOM, and device collection without raising an error.
Fortinet parser hook handlers
The five handlers a FortinetParserHooks implementation must provide, the arguments each receives, and what each returns.
An implementation of sn_sgc_fortinet.FortinetParserHooks must provide all five handlers. An implementation missing any handler is rejected in full and the default implementation is used instead. In every handler, type is the step name from the active collection plan.
unwrapResponse
responseJson, type, parser
Once per fetched response, before any item is mapped. Returns the value to iterate over, usually an array.
beforeExtract
rawItem, type, store, parser
Once per raw item, before field mapping. Mutates rawItem in place so a mapping can reference the added data as a normal source path. Returns nothing.
afterExtract
mappedItem, type, store, rawItem, parser
Once per item, after field mapping. Mutates mappedItem in place to add attributes or relationships that depend on the mapped shape. Returns nothing.
postProcess
results, type, parser
Once per response, with every item already mapped for that type. Returns the items to store or write, optionally filtered or transformed.
shouldStore
mappedItem, type, parser
Per item. Returns false to exclude the item from the shared store.
Default handler behavior for devices
In the default collection plan, only the devices step reaches these handlers. Keep the following default behavior in any copy you customize.
-
beforeExtractfetches the device’s ports and stamps them onto the raw item, and stamps the device’s license attributes from the map built by thecontractsstep. -
afterExtractstores the port relationships, merges the response’s own metadata fields intoadditional_attributes, and creates the network site and network service instance for the device. -
shouldStoreexcludes any mapped item without akey.
Warning:
Removing the devices branches from a custom implementation silently drops port attachment, port relationships, license attributes, and network site and network service instance creation.
Registering additional entity types
The optional getEntityTypes(constants) handler registers extra entity type keys in the shared store. It receives the merged constants object and returns an array of keys. Add it when a custom collection plan step introduces an entity type that isn’t in the default plan.
getEntityTypes: function(constants) {
return [constants.FIRMWARE_UPGRADES];
}
Extension point safety and best practices
Avoid the mistakes that most often break a TSOM extension point implementation, and understand what an upgrade does and doesn’t touch.
Common mistakes
Mistake What happens Fix Modifying the OOB Script Include directly Overwritten on the next TSOM upgrade. Create a new Script Include and a new extension instance instead. Registering against arestrict_scope = true extension point from a custom scope
The platform silently ignores the registration. No error is logged.
Check restrict_scope before implementing. See TSOM extension point quick reference.
The type field doesn’t match the Script Include name
The resolver can’t look up the order for the implementation, so it defaults to 100.
Ensure type is an exact match.
Forgetting to set active = true
The implementation never loads.
Verify the record in System Definition > Extension Instances.
Calling eventGr.update() inside an EventFieldMapping implementation
Corrupts the event pipeline.
Only set fields on the passed-in eventGr; return true or false and let the framework persist the record.
Upgrade safety
Note:
Your custom extension instances aren’t touched during upgrades. OOB instances registered at order = 100 may be updated, but a lower-order custom override keeps its priority. New extension points may be introduced in a release; check TSOM extension point quick reference after each upgrade for additions.
Security considerations
-
Privilege model. An extension point implementation runs with the calling process’s context, typically the TSOM integration user or a MID Server. Your code inherits that context’s privileges.
-
Audit regularly. Review
sys_extension_instancerecords periodically through System Definition > Extension Instances. -
Validate inputs. A malformed payload passed into your implementation can cause cascading exceptions further down the pipeline. Validate before acting on it.
Verify that an extension point implementation is active
Confirm that your custom implementation is registered, active, and resolving at the priority you expect before you rely on it in production.
Before you begin
Role required: admin
About this task
Run both scripts in System Definition > Scripts - Background. The first confirms the runtime sees your implementation at all; the second confirms its order and active state.
Procedure
-
List the runtime implementations for the extension point, substituting its API name.
var epName = 'sn_sgc_meraki.MerakiCustomizedLifeCycleStageStatus'; var ep = new GlideScriptedExtensionPoint(); var impls = ep.getExtensions(epName); if (!impls || !impls.length) { gs.info('No implementations for: ' + epName); } else { gs.info('Found ' + impls.length + ' impl(s) for: ' + epName); for (var i = 0; i < impls.length; i++) { gs.info(' [' + i + '] type=' + impls[i].type); } } -
Check the registration order and active state for that same extension point.
var gr = new GlideRecord('sys_extension_instance'); gr.addQuery('point', 'sn_tsom_em_conns.EventFieldMapping'); gr.addActiveQuery(); gr.orderBy('order'); gr.query(); while (gr.next()) { gs.info('SI: ' + gr.script_include.name + ' | order: ' + gr.getValue('order') + ' | active: ' + gr.getValue('active')); } -
Confirm your implementation appears in both outputs, with the
orderyou registered andactive = true.To audit every TSOM extension point at once instead of one at a time, see Extension point diagnostic script.
Result
Once your implementation is confirmed active, verify the resulting behavior for its category. See Verify behavior by extension point category.
Verify behavior by extension point category
Once an implementation is confirmed active, use this table to confirm it produces the expected result for its category.
Extension point category How to verify Lifecycle stage status Run a vendor discovery sync, then check the discovered CI’slife_cycle_stage_status field.
Event field mapping
Create a test event, then check the resulting em_event record’s cmdb_ci and ci_type fields.
Webhook field mapping
Send a test webhook, then check the resulting em_event field mapping.
Metrics aggregation
Wait for the scheduled aggregation job to run, then check the sa_metric_instance table.
Tag transformation
Run a discovery sync on a CI with tags, then check its additional_attributes field.
Nokia MPN formula engine
Import a Nokia MPN Excel file, then check the resulting formula table.
Recovery handler
Create a test recovery queue record, then confirm your handler fires.
Extension point diagnostic script
Run this script in Scripts - Background to audit every order-based TSOM extension point on your instance in one pass.
(function() {
var EPS = [
'sn_sgc_meraki.MerakiCustomizedLifeCycleStageStatus',
'sn_sgc_fortinet.FortinetCustomizedLifeCycleStageStatus',
'sn_sgc_vcloud.VeloCloudCustomizedLifeCycleStageStatus',
'sn_sgc_catalyst.CatalystCustomizedLifeCycleStageStatus',
'sn_tsom_em_conns.EventFieldMapping',
'sn_tsom_em_conns.WebhookFieldMapping',
'sn_tsom_em_conns.MetricsAggrSEP',
'sn_tsom_em_conns.UplinkThroughputPercentage',
'sn_sgc_fortinet.FortinetCustomizedFirmwareVersion',
'sn_sgc_fortinet.FortinetCustomizedContractParsing',
'sn_sgc_meraki.TagTransformationExtensionPoint',
'sn_tsom_em_conns.NokiaMpnFormulaEngineSEP',
'sn_tsom_em_conns.RecoveryHandlerSEP'
];
gs.info('=== TSOM Extension Point Diagnostic ===');
gs.info('Date: ' + new GlideDateTime().getDisplayValue());
for (var e = 0; e < EPS.length; e++) {
var epName = EPS[e];
gs.info('--- ' + epName + ' ---');
try {
var ep = new GlideScriptedExtensionPoint();
var impls = ep.getExtensions(epName);
var cnt = (impls && impls.length) ? impls.length : 0;
gs.info(' Runtime impls: ' + cnt);
for (var i = 0; i < cnt; i++)
gs.info(' ['+i+'] type='+impls[i].type);
} catch(ex) {
gs.info(' ERROR: ' + ex.message);
}
var gr = new GlideRecord('sys_extension_instance');
gr.addQuery('point', epName);
gr.orderBy('order');
gr.query();
while (gr.next())
gs.info(' Instance: ' + gr.script_include.name +
' | order=' + gr.getValue('order') +
' | active=' + gr.getValue('active'));
}
gs.info('=== Diagnostic Complete ===');
})();
Recover from a failing custom extension point
Disable a misbehaving custom implementation and fall back to the default without waiting for a fix.
Before you begin
Role required: TSOM admin
About this task
Because the default implementation for every order-based extension point is registered at order = 100, deactivating your lower-order override immediately restores default behavior. No other configuration change is required.
Procedure
-
Navigate to System Definition > Extension Instances and open your extension instance record.
-
Set
activetofalseand save.The OOB implementation at
order = 100takes over immediately; no cache flush or restart is needed. -
Check the system log for exceptions thrown by your Script Include to diagnose the failure.
-
Fix the implementation and test it in a non-production instance before you set
activeback totruein production.
TSOM extension point quick reference
API name, scope, restrict_scope, and dispatch pattern for every TSOM extension point, in one table.
Extension point API name Scoperestrict_scope
Dispatch pattern
Meraki lifecycle stage status
sn_sgc_meraki.MerakiCustomizedLifeCycleStageStatus
sn_sgc_meraki
false
Handler
Fortinet lifecycle stage status
sn_sgc_fortinet.FortinetCustomizedLifeCycleStageStatus
sn_sgc_fortinet
false
Handler
VeloCloud lifecycle stage status
sn_sgc_vcloud.VeloCloudCustomizedLifeCycleStageStatus
sn_sgc_vcloud
false
Handler
Catalyst lifecycle stage status
sn_sgc_catalyst.CatalystCustomizedLifeCycleStageStatus
sn_sgc_catalyst
false
Handler
Event field mapping
sn_tsom_em_conns.EventFieldMapping
sn_tsom_em_conns
false
Vendor-dispatch
Webhook field mapping
sn_tsom_em_conns.WebhookFieldMapping
sn_tsom_em_conns
true
Vendor-dispatch
Metrics aggregation
sn_tsom_em_conns.MetricsAggrSEP
sn_tsom_em_conns
false
Single-override
Uplink throughput percentage
sn_tsom_em_conns.UplinkThroughputPercentage
sn_tsom_em_conns
false
Single-override
Fortinet firmware version formatting
sn_sgc_fortinet.FortinetCustomizedFirmwareVersion
sn_sgc_fortinet
false
Handler
Fortinet contract parsing
sn_sgc_fortinet.FortinetCustomizedContractParsing
sn_sgc_fortinet
false
Handler
Tag transformation
sn_sgc_meraki.TagTransformationExtensionPoint
sn_sgc_meraki
false
Single-override, filtered by handles()
Nokia MPN formula engine
sn_tsom_em_conns.NokiaMpnFormulaEngineSEP
sn_tsom_em_conns
false
Single-override
Recovery handler
sn_tsom_em_conns.RecoveryHandlerSEP
sn_tsom_em_conns
true
Vendor-dispatch
Fortinet collection plan
sn_sgc_fortinet.FortinetCollectionPlan
sn_sgc_fortinet
false
Contract validation (single implementation)
Fortinet parser hooks
sn_sgc_fortinet.FortinetParserHooks
sn_sgc_fortinet
false
Contract validation (single implementation)
Fortinet field mappings
sn_sgc_fortinet.FortinetFieldMappings
sn_sgc_fortinet
false
Contract validation (single implementation)
The three Fortinet discovery extensibility rows don’t resolve by order; see Fortinet discovery extensibility for how they’re validated and how they fall back to the default implementation.
Keep exploring.
Find another answer, or bring this documentation into your own tools.