Integration How To
Last updated: rev 63
Introduction
This document explains how to develop an integration for FieldTwin. An integration is a web page, which is loaded inside the main interface of FieldTwin through an iFrame.
There are two kinds of integrations:
- global, these integrations are loaded when the application starts, and are kept alive through the whole life cycle of the application. These can receive a message when a project is created or deleted from the Dashboard.
- local, these integrations are loaded only when a project is opened. They only receive messages that relate to the project that is currently open. These generally have a user interface (UI) which is visible in FieldTwin Design's main interface, but integrations aren't required to have a UI. In this document, integrations without a UI are referred to as headless.
Setting up an integration
You need to be the administrator of an account to be able to setup a new integration.
Link: FieldTwin Online Documentation
By default, and for security reasons, an integration receives a JWT that only gives access to the sub project the user is currently editing. Through the administration panel, you can also:
- Give access to the whole project, all of its sub projects
- Give access to all the projects a user can access across the account
Integration Manifest
An integration can provide a manifest endpoint to allow administrators to quickly import its configuration. This endpoint should return a JSON object describing the integration's properties.
Manifest Endpoint Requirements
- Method:
GET - Response Format:
application/json
Example Manifest
{
"name": "Asset Inspector",
"url": "https://asset-inspector.example.com",
"logo": "https://asset-inspector.example.com/logo.png",
"tabPosition": "property-panel",
"showInDesigner": true,
"showInOperation": true,
"resourceTypes": ["stagedAssets"],
"projectWideAccess": true,
"allowAccessToClipboard": true,
"allowPopout": true
}
Manifest Properties
| Property | Type | Description |
|---|---|---|
name |
string |
Required. The display name of the integration. |
url |
string |
The main entry point URL for the integration. Required unless dynamicPagesUrl is used instead. If runInBackground is enabled and backgroundUrl is omitted, FieldTwin uses this URL for the hidden background instance as well. |
logo |
string |
URL to an image to be used as the integration's logo. |
tabPosition |
string |
Where the integration appears (bottom, property-panel, hidden, global, main-toolbar-dialog). main-toolbar-dialog integrations open from the main toolbar rather than being added from the Layout menu. |
showInDesigner |
boolean |
Whether the integration is available outside operation mode, including designer and presenter. Defaults to true. |
showInOperation |
boolean |
Whether the integration is available in operation mode. Defaults to true. |
dynamicPagesUrl |
string |
URL to fetch dynamic pages from (see Dynamic Pages section). The returned pages control the visible tabs, while the parent integration can still be loaded in the background when runInBackground, backgroundUrl, hidden, or global behavior is configured. |
projectWideAccess |
boolean |
If true, the JWT gives access to the entire project (all sub-projects). |
projectAllFromUser |
boolean |
If true, the JWT gives access to all projects the user can access in the account. |
proxy |
boolean |
If true, FieldTwin will proxy requests to the integration (useful for HTTP or CORS issues). |
useGET |
boolean |
Use GET instead of POST when loading the iframe. |
noURLParams |
boolean |
If true and useGET is true, don't pass parameters in the URL; use window messages instead. |
resourceTypes |
array |
List of resource types (stagedAssets, connections) this integration applies to (for property-panel). |
width / height |
string |
CSS dimensions for the dialog (for main-toolbar-dialog). |
allowAccessToClipboard |
boolean |
If true, the iframe is allowed access to the system clipboard. |
allowPopout |
boolean |
If true, the integration can be popped out of the embedded panel into a separate browser window. Defaults to false (opt-in). When popped out the integration runs as a top-level window and must send messages to window.opener instead of window.parent - see Pop-out windows. |
doNotUseSubprojectApiEndpoints |
boolean |
If true, indicates the integration doesn't need to wait for sub-project API pods to be ready. |
runInBackground |
boolean |
If true, FieldTwin also loads an additional hidden copy of the parent integration for background work. |
backgroundUrl |
string |
Optional URL used by the hidden background copy. When omitted, the background copy falls back to url. |
projectSettingsUrl |
string |
URL for an optional settings page in the Project settings. |
accountSettingsUrl |
string |
URL for an optional settings page in the Account settings. |
compatibleWithChildAccount |
boolean |
If true, indicates compatibility with child accounts in multi-account setups. |
Dynamic Pages
Dynamic Pages allow an integration to provide multiple pages/tabs that are dynamically fetched from an external endpoint. Instead of defining a static url for the integration, you can configure a dynamicPagesUrl endpoint that returns a list of available pages.
When dynamic pages are enabled, each page can override where it appears and whether it is available in designer or operation mode. If a page omits one of these fields, FieldTwin falls back to the integration-level configuration. If the integration also omits it, FieldTwin uses the default value.
Placement and visibility resolution
For each resolved dynamic page, FieldTwin uses the following fallback rules:
page.tabPosition→integration.tabPosition→bottompage.showInDesigner→integration.showInDesigner→truepage.showInOperation→integration.showInOperation→true
Supported tabPosition values are bottom, property-panel, hidden, global, and main-toolbar-dialog.
main-toolbar-dialog pages are opened from the main toolbar. They are not added through the Layout menu.
Configuration
In the integration settings, set the dynamicPagesUrl field to your endpoint URL instead of using the static url field for visible tabs.
When dynamic pages are returned, the visible default tab from url is replaced by the resolved dynamic pages. However, the parent integration definition is still used for headless/background behavior. In practice this means:
- if
runInBackgroundistrue, FieldTwin still loads a hidden parent integration in the background - if
backgroundUrlis set, that URL is used for the hidden background copy - if
backgroundUrlis omitted, the hidden background copy falls back tourl - hidden or global parent integrations continue to behave as headless/background integrations even when they also provide dynamic pages
Endpoint Requirements
Your endpoint must:
- Accept POST requests
- Accept the
Authorizationheader containing the user's JWT token (Bearer <token>) - Return a JSON array of page definitions
Request Format
FieldTwin will send a POST request to your dynamicPagesUrl endpoint:
POST /your-dynamic-pages-endpoint HTTP/1.1
Content-Type: application/json
Authorization: Bearer <user-jwt-token>
Accept: application/json
{}
The request body is currently an empty JSON object {} but is reserved for future use.
Response Format
Your endpoint should return a JSON array of page objects:
[
{
"title": "Dashboard",
"iframeUrl": "https://your-server.com/dashboard",
"path": "dashboard",
"tabPosition": "bottom",
"showInDesigner": true,
"showInOperation": true
},
{
"title": "Reports",
"iframeUrl": "https://your-server.com/reports",
"path": "reports"
},
{
"title": "Settings",
"iframeUrl": "https://your-server.com/settings",
"path": "settings"
}
]
Page Object Properties
| Property | Required | Description |
|---|---|---|
title |
Yes | Display name shown in the tab |
iframeUrl |
Yes | Full URL to load in the iframe for this page |
path |
No | Unique identifier for the page (used internally). If omitted, FieldTwin generates a stable fallback path using the page order (page-1, page-2, ...). |
tabPosition |
No | Overrides the integration tabPosition for this page. Falls back to the integration setting, then bottom. |
showInDesigner |
No | Overrides whether this page is available outside operation mode, including designer and presenter. Falls back to the integration setting, then true. |
showInOperation |
No | Overrides whether this page is available in operation mode. Falls back to the integration setting, then true. |
Caching
Dynamic pages are cached for 5 minutes by default. After the cache expires, FieldTwin will fetch the list again on the next request.
Example Server Implementation (Node.js + Express)
const express = require('express')
const jwt = require('jsonwebtoken')
const app = express()
app.use(express.json())
app.post('/dynamic-pages', (req, res) => {
// Extract and verify the JWT from Authorization header
const authHeader = req.headers.authorization
if (!authHeader || !authHeader.startsWith('Bearer ')) {
return res.status(401).json({ error: 'Missing authorization' })
}
const token = authHeader.split(' ')[1]
// Decode the JWT to get user information
// Note: In production, verify the token signature using FieldTwin's public key
const decoded = jwt.decode(token)
const userId = decoded?.userId
// Return pages based on user permissions or other logic
const pages = [
{
title: 'My Dashboard',
iframeUrl: `https://your-server.com/dashboard?user=${userId}`,
path: 'dashboard',
},
{
title: 'Analytics',
iframeUrl: 'https://your-server.com/analytics',
path: 'analytics',
},
]
res.json(pages)
})
app.listen(3000)
Use Cases
Dynamic Pages are useful when:
- You need to show different pages based on user permissions
- The available pages change based on external data or configuration
- You want to provide a personalized set of tools per user
- The integration serves multiple distinct features that should appear as separate tabs
Pop-out windows
An integration that sets allowPopout: true in its manifest can be detached from its
embedded panel into a separate browser window. This is opt-in: integrations without
the flag never show the pop-out control. The user pops a panel out (and docks it back)
from the panel/tab controls in the FieldTwin UI.
What changes when popped out
When popped out, FieldTwin opens a new top-level window with window.open and bootstraps
the integration there with the same flow as the iframe (it receives the same loaded
event, tokenRefresh, selection updates, and every other host → integration message).
The only thing that changes for the integration is the window it talks to:
| State | Host window seen by the integration |
|---|---|
| Embedded (iframe) | window.parent (and window.parent !== window) |
| Popped out (separate window) | window.opener (and window.parent === window) |
Sending messages in both states
Because the same integration code runs in both states, it must not assume the host is
window.parent. Pick the target at send time - window.opener first (popped out), then
window.parent (embedded):
export function sendToHost(message) {
const targetOrigin = getHostTargetOrigin()
// Popped-out window: the host is the opener.
if (window.opener && !window.opener.closed) {
window.opener.postMessage(message, targetOrigin)
return true
}
// Embedded iframe: the host is the parent.
if (window.parent && window.parent !== window) {
window.parent.postMessage(message, targetOrigin)
return true
}
console.warn('No host window found')
return false
}
Receiving messages does not change: keep listening on window for message events
(and validate event.origin as shown in Messaging)
- the host posts to whichever window currently hosts the integration.
If your integration only ever sends
window.parent.postMessage(...), host calls will silently stop working once the panel is popped out, because in a top-level windowwindow.parentis the window itself. Route all outgoing messages through a helper likesendToHostabove.
How to serve an integration for use in FieldTwin
Depending on how the integration was setup, FieldTwin will create an iFrame that either generates a GET or a POST request to the integration URL.
This request will contain the following attributes:
token, frontendUrl, backendUrl, stream, subProject, project, account, canEdit, projectWideAccess, projectAllFromUser
- As query parameters for GET
- As the request body for POST
| attribute | FT version | description |
|---|---|---|
token |
Security token (JWT) needed for making a FieldTwin API call | |
frontendUrl |
URL of the window that contains the iframe. Also the link for viewing the subproject | |
backendUrl |
URL of FieldTwin backend. Use this as the base of the JWT public key URL and the API URL | |
designerUrl |
8.1 | URL of FieldTwin designer. |
dashboardUrl |
8.1 | URL of FieldTwin dashboard. |
frontendUrl |
8.1 | URL of the frontends that host the integration |
stream |
8.0 | In FieldTwin 8.0 and later - the ID of the subproject branch |
subProject |
The currently open sub project ID | |
project |
The currently open project ID | |
account |
The ID of the account that contains the project | |
canEdit |
Whether the user's role enables 'edit' rights for the integration. To be handled by the integration itself, FieldTwin does not enforce this | |
projectWideAccess |
Whether the integration is granted access to the whole project (not just the current sub project) | |
projectAllFromUser |
Whether the integration is granted access to all of the current user's projects in the account |
token is a JWT and contains information about the user and user rights.
You can parse it with any JWT library but to be secure you must ensure that the token has been
signed by FieldTwin. The public key to validate this can be found at:
| FieldTwin version | Public key URL |
|---|---|
| <= 8.2 | https://backend.[name-of-instance].fieldtwin.com/token/publicKey |
| >= 9.0 | https://[name-of-instance].fieldtwin.com/backend/token/publicKey |
The FieldTwin API can be accessed at:
| FieldTwin version | API URL |
|---|---|
| <= 8.2 | https://backend.[name-of-instance].fieldtwin.com/API/v1.10/... |
| >= 9.0 | https://[name-of-instance].fieldtwin.com/API/v1.10/... |
https://[name-of-instance].fieldtwin.com/API/v2.0/... |
Link for API v1.10: FieldTwin API Online Documentation.
Link for API v2.0: https://[name-of-instance].fieldtwin.com/API/v2.0/documentation/
Integration example, using NodeJS + Express:
const express = require('express')
const app = express()
app.use(express.json())
// POST verb
app.post('/', function (request, response) {
response.send(`
<html>
<body>
<h1>TOKEN: ${request.body.token}</h1>
<h1>PROJECT ID:${request.body.project}</h1>
<h1>SUB-PROJECT ID:${request.body.subProject}</h1>
</body>
</html>
`)
})
// or ...
// GET verb
app.get('/', function (request, response) {
response.send(`
<html>
<body>
<h1>TOKEN: ${request.query.token}</h1>
<h1>PROJECT ID:${request.query.project}</h1>
<h1>SUB-PROJECT ID:${request.query.subProject}</h1>
</body>
</html>
`)
})
app.listen()
This example webserver will reply to a POST request on /, and return HTML that contains the
token sent by FieldTwin.
Note that this reference implementation depends on
npm install expressIf you do not have access to a nodejs backend, and just want to have a one page integration, you can receive these same attributes by listening for the window message
loaded. Example: https://github.com/XvisionAS/FieldTwin-Integration-Demo/blob/75fb43e1b31014753354789078f646d325075eae/doc-tab/index.html#L37-L42
Refreshing the JWT
By default the JWT has an expiration time of one (1) hour after it was created. You can manually refresh the token by calling this endpoint:
| FieldTwin version | Endpoint |
|---|---|
| <= 8.2 | https://backend.[name-of-instance].fieldtwin.com/token/refresh |
| >= 9.0 | https://[name-of-instance].fieldtwin.com/backend/token/refresh |
Pass the JWT in the usual way (using header Authentication: Bearer ${JWT}) and you receive
back a JSON object with the new JWT inside the attribute token.
Since FieldTwin 5.5, a new message is posted by the application to the integration tokenRefresh
that passes a new JWT, so if you handle this message you do not need to refresh the token.
Generate a JWT using an API token
An API token does not carry user permissions and can be more powerful than necessary for some tasks. To carry out actions as a user, it is possible to generate a JWT using an API token. In FieldTwin 8.0+ this needs to be an API token that is not restricted to a user role, and in 9.0+ it additionally needs to have the Generate User JWT flag enabled.
Send a POST request to this endpoint:
| FieldTwin version | Generate Token URL |
|---|---|
| <= 8.2 | https://backend.[name-of-instance].fieldtwin.com/token/generate |
| >= 9.0 | https://[name-of-instance].fieldtwin.com/backend/token/generate |
Pass the API token using header token: [API Token]. The body of the request must contain:
userId: ID of the user the JWT will be generated for.- optional
subProjectId: ID of the sub project the JWT will be generated for. Usually required. If blank, generates a JWT that can be used to call a limited number of account related API calls. - optional
customTabId: integration ID to generate the token for. Integration ID can be looked up by an API call or in the account settings.
On success, the query returns a JSON object that contains an attribute token containing
a JWT for the requested user.
User rights management
Within the JWT passed to the integration, there is an attribute userRights that contains what the
user has access to. These rights will be enforced by the API you are using, but in case you need to
check them, this is the list of possible values:
- Account
canAdminAccount: User is an administrator of the account- Projects and sub-projects
canCreateProject: User can create projectcanCloneProject: User can clone an existing projectcanDeleteProject: User can delete projectscanCreateSubProject: User can create sub-projectscanCloneSubProject: User can clone existing sub-projectscanExportAsGLTF: User can export sub-projects as GLTF backdropscanManageViewOnlyLinks: User can manage view-only share links- Generic rights
canAdmin: User is an administrator of the project, if this is true, user can also edit everythingcanEdit: User can edit the project, if this is true, user can edit everythingcanView: User can view all project resourcescanLock: User can lock resources they are allowed to edit- Asset library
canCreateAssets: User can create assets in the account asset librarycanEditAssets: User can edit assets in the account asset library- Connections
canViewConnections: User can view connectionscanViewConnectionsMetaData: User can view connections meta datacanViewConnectionsCosts: User can view connections costscanEditConnections: User can edit connectionscanEditConnectionsMetaData: User can edit connections meta datacanEditConnectionsCosts: User can edit connections costs- Staged assets
canViewStagedAssetscanViewStagedAssetsMetaDatacanViewStagedAssetsCostscanEditStagedAssetscanEditStagedAssetsMetaDatacanEditStagedAssetsCosts- Layers - general
canViewLayerscanViewLayersMetaDatacanViewLayersCostscanEditLayerscanEditLayersMetaDatacanEditLayersCosts- Bitmap image layers
canCreateLayersImagecanEditLayersImagecanViewLayersImage- Bathymetry / XVB layers
canCreateLayersBathymetrycanEditLayersBathymetrycanViewLayersBathymetry- ArcGIS layers
canCreateLayersArcGIScanEditLayersArcGIScanViewLayersArcGIS- WMS layers
canCreateLayersWMScanEditLayersWMScanViewLayersWMS- Text Layers
canViewOverlayscanEditOverlays- Reservoirs
canViewReservoirscanEditReservoirs- Ports
canViewPortscanEditPorts- Shapes
canViewShapescanViewShapesMetaDatacanViewShapesCostscanEditShapescanEditShapesMetaDatacanEditShapesCosts- Wells
canViewWellscanViewWellsMetaDatacanViewWellsCostscanEditWellscanEditWellsMetaDatacanEditWellsCosts- Activities
canViewActivitiescanViewActivitiesCostscanEditActivitiescanEditActivitiesCosts- Custom Costs
canEditCustomCostscanViewCustomCostscanEditCustomCostsCostscanViewCustomCostsCosts- Documents
canViewDocumentscanEditDocuments- View Points
canEditBookmarkscanViewBookmarks- Workflow tasks
canCreateWorkflowTaskscanEditWorkflowTaskscanViewWorkflowTasks- Annotations
canEditAnnotationscanViewAnnotations
Not all attributes will be present in
userRights. IfcanEditis true for the project, you can assume that allcanViewThingandcanEditThingare true. IfcanAdminis true for the project, allcanViewandcanEditandcanCreateare true. IfcanAdminAccountis true, all other permissions are true.
Samples
Follow this link : GitHub Repository
Messaging
The main interface of FieldTwin can send and receive messages from the integration using postMessage.
Each message definition includes a direction label:
- Integration → FieldTwin: the integration sends the message.
- FieldTwin → Integration: the integration receives the message.
FieldTwin identifies each integration via its customTabId (provided in the loaded event).
All messages sent from the integration to the host are automatically tagged with this ID by the host.
This ID is used by the host to group search results, manage progress indicators, and isolate visual filters
for each integration instance.
Here's how an integration can receive these messages from FieldTwin :
window.addEventListener('message', function (event) {
// IMPORTANT: Check the origin of the data!
if (~event.origin.indexOf('https://backend.app.fieldtwin.com')) {
if (event.data instanceof Blob) {
// this is only use by `exportToGLTF`
} else {
console.log(JSON.stringify(event.data, null, 2))
}
} else {
// Not coming from correct origin
return
}
})
Follow this link for a sample event viewer
Definition of the different element type that can be sent:
- for the
selectevent: annotationconnectionconnectionSegmentcustomCostlayeroverlayshapestagedAssetwellwellBorewellBoreSegment- for the other events you can also in addition have:
bookmarkdocumentdocumentRevisionmetaDatumValueprojectsubProjectreservoirpointCloud
Definitions of the different attributes for these types can be found in the API docs
Integrations are also able to call functions in FieldTwin using the postMessage mechanism.
To do this, use postMessage on the window that hosts the integration.
window.parent.postMessage({ event: 'getProjectData' }, '*')
Important - target the correct window. When the integration is embedded as an iframe the host is
window.parent. When the integration is popped out into a separate window the host iswindow.openerandwindow.parentis equal towindow. To work in both cases, use thesendToHosthelper function described in the Pop-out windows section.The examples below use
window.parent.postMessage(...)for brevity, but every one of them should go throughsendToHost(...)(or equivalent) if the integration enablesallowPopout.
The results, if any, will then be sent from FieldTwin via another postMessage
back to the integration.
Integration lifecycle
loaded
Direction: FieldTwin → Integration
This event is sent when an integration iframe is fully loaded. It contains information about subProject, project and tokens used to communicate with API. The argument will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to loaded |
| subProject | is set to subproject ID, if a sub project is loaded |
| subProjectDocument | is set to subproject document ID |
| stream | is set to the subproject branch ID in FieldTwin 8.0 and later |
| project | is set to project ID, if a project is loaded |
| account | is set to account ID, if a project is loaded |
| token | is set to the JWT that the integration can use to query the API |
| backendUrl | the FieldTwin backend URL (JWT public key and API) |
| frontendUrl | the URL hosting the iframe (URL to view the subproject) |
| designerUrl | FieldTwin designer URL |
| dashboardUrl | FieldTwin dashboard URL |
| projectorUrl | FieldTwin projection service URL |
| canEdit | whether the user's role has 'edit' rights for the integration |
| projectWideAccess | whether the JWT grants access to the whole project |
| projectAllFromUser | whether the JWT grants access to all of the current user's projects |
| customTabId | unique identifier for this integration tab instance |
| selection | what is currently selected. Array of object |
| cssUrl | main CSS url |
| cssThemeUrl | current theme CSS url |
| cloudType | azure, gcloud, s3, onpremise |
| superAdmin | true if current user is super admin |
| sessionId | unique session identifier for this integration instance |
| globalSessionId | session identifier of the host application's main data adapter |
| userId | current user's ID |
| userMail | current user's email address |
| APIServerIsReady | if set to true, the API server is ready to receive requests |
| APIVersion | the version of the API server in the form "vx.y" e.g. "v1.10" |
sessionId and globalSessionId intentionally identify different sessions:
sessionIdis generated for the integration tab/window instance when theloadedevent is sent. It is unique to that integration instance and can change when the integration iframe or pop-out is reloaded.globalSessionIdis the session ID of FieldTwin's main data-layer adapter for the host application tab. Use this value when the integration needs to correlate with the main FieldTwin adapter session, for example when matching host-side realtime or API activity to the currently loaded FieldTwin graph.
tokenRefresh
Direction: FieldTwin → Integration
This message is sent before the previous JWT expires. It contains a new refreshed JWT that the integration can use to communicate with FieldTwin backend.
| Attribute | Decription |
|---|---|
| event | is set to tokenRefresh |
| subProject | is set to subProject ID, if a sub project is loaded |
| project | is set to project ID, if a project is loaded |
| account | is set to account ID, if a project is loaded |
| token | is set to the JWT that the integration can use to query the API |
| backendUrl | is set to the address of the backend the JWT is refering to |
| isFrameActive | true if the frame is currently selected and active in the UI |
apiPodIsReady
Direction: FieldTwin → Integration
This event is sent periodically when the API server pod is ready to receive requests.
| Attribute | Description |
|---|---|
| event | is set to apiPodIsReady |
| subProject | is set to subProject ID |
| APIServerReady | is set to true |
| APIVersion | the version of the API server in the form "vx.y" e.g. "v1.10" |
apiPodIsNotReady
Direction: FieldTwin → Integration
This event is sent periodically when the dynamic API server pod is warming up or unavailable.
| Attribute | Description |
|---|---|
| event | is set to apiPodIsNotReady |
| subProject | is set to subProject ID |
| APIServerReady | is set to false |
| APIVersion | the version of the API server in the form "vx.y" e.g. "v1.10" |
siblingApiPodIsReady
Direction: FieldTwin → Integration
This event is sent to integrations with project-wide access when the API pod for a sibling subProject is ready.
| Attribute | Description |
|---|---|
| event | is set to siblingApiPodIsReady |
| subProject | is set to the sibling subProject ID |
| sibling | always true to indicate this is a sibling pod status |
| siblingAPIServerIsReady | is set to true |
| APIVersion | the version of the API server in the form "vx.y" e.g. "v1.10" |
siblingApiPodIsNotReady
Direction: FieldTwin → Integration
This event is sent to integrations with project-wide access when the API pod for a sibling subProject is warming up or unavailable.
| Attribute | Description |
|---|---|
| event | is set to siblingApiPodIsNotReady |
| subProject | is set to the sibling subProject ID |
| sibling | always true to indicate this is a sibling pod status |
| siblingAPIServerIsReady | is set to false |
| APIVersion | the version of the API server in the form "vx.y" e.g. "v1.10" |
Project data
getProjectData
Direction: Integration → FieldTwin
Allows you to get some information about a project without calling the API. Set these attributes:
| Attribute | Description |
|---|---|
| event | is set to getProjectData |
The result format is defined in the projectData message section.
Calling getProjectData
{
event: 'getProjectData'
}
projectData
Direction: FieldTwin → Integration
This message is sent after the integration posted a message getProjectData.
The result will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to projectData |
| data | contains data about the event |
| data.project | information about the current project |
| data.project.subProjectName | current sub-project name |
| data.project.subProjectTags | aggregation of all sub-project tags |
| data.project.developmentLocation | country defined in the project |
| data.project.toSeabed | seabed depth defined in the project |
| data.project.CRS | CRS used in the project |
| data.stagedAssets | array of information about staged assets |
| data.stagedAssets.[].id | id of staged asset |
| data.stagedAssets.[].name | name of staged asset |
| data.stagedAssets.[].tags | tags of staged asset |
| data.stagedAssets.[].metaData | meta data of staged asset |
| data.stagedAssets.[].type | asset type of staged asset |
| data.connectionSegments | array of information about connections |
| data.connections.[].id | id of connection |
| data.connections.[].name | name of connection |
| data.connections.[].tags | tags of connection |
| data.connections.[].metaData | meta data of connection |
| data.connection.[].length | length of connection |
| data.connectionSegments | array of information about connection segments |
| data.connectionSegments.[].id | id of segment |
| data.connectionSegments.[].connectionId | id of parent connection |
| data.connectionSegments.[].name | name of segment |
| data.connectionSegments.[].tags | tags of segment |
| data.connectionSegments.[].metaData | meta data of segment |
| data.connectionSegments.[].length | length of segment |
| data.layers | array of information about layers |
| data.layers.[].id | id of layer |
| data.layers.[].name | name of layer |
| data.layers.[].tags | tags of layer |
| data.layers.[].metaData | meta data of layer |
| data.shapes | array of information about shapes |
| data.shapes.[].id | id of shape |
| data.shapes.[].name | name of shape |
| data.shapes.[].tags | tags of shape |
| data.shapes.[].metaData | meta data of shape |
| data.wells | array of information about wells |
| data.wells.[].id | id of well |
| data.wells.[].name | name of well |
| data.wells.[].tags | tags of well |
| data.wells.[].metaData | meta data of well |
| data.wellBores | array of information about well bores |
| data.wellBores.[].id | id of bore |
| data.wellBores.[].wellId | id of parent well |
| data.wellBores.[].name | name of bore |
| data.wellBores.[].tags | tags of bore |
| data.wellBores.[].metaData | meta data of bore |
| data.wellBores.[].length | length of bore |
| data.wellBoreSegments | array of information about well bore segments |
| data.wellBoreSegments.[].id | id of segment |
| data.wellBoreSegments.[].wellBoreId | id of parent well bore |
| data.wellBoreSegments.[].name | name of segment |
| data.wellBoreSegments.[].tags | tags of segment |
| data.wellBoreSegments.[].metaData | meta data of segment |
| data.wellBoreSegments.[].length | length of segment |
Cost calculation
getCostQuery
Direction: Integration → FieldTwin
Request a JSON object that contains a cost server query of the whole sub project.
You can pass a query id for tracking that will be returned in the reply costQuery.
Requesting cost query
{
event: "getCostQuery",
data: {
queryId: "id_of_the_query"
}
}
The result format is defined in the costQuery message section.
costQuery
Direction: FieldTwin → Integration
This message is sent after the integration posted a message getCostQuery.
The result will contain these attributes:
| Attribute | Decription |
|---|---|
| event | is set to costQuery |
| isFrameActive | true if the frame is currently selected and active in the UI |
| data | is an object that contains: |
| queryId | is the value that you can pass when calling getCostQuery. It allows you to identify a query when posting getCostQuery multiple times |
| removeEmptyItem | do not include items that have no meta data defined |
| query | is the actual query and is composed of : |
| stagedAssets | contains an array of assets and their meta data |
| connections | contains an array of connections and their meta data |
computeCostUsingServer
Direction: Integration → FieldTwin
Launch a cost computation on a cost server. A cost server needs to be defined first.
Calling computeCostUsingServer
{
event: 'computeCostUsingServer'
}
Selection
Selecting resources (select)
Direction: Integration → FieldTwin
Select and focus on one or multiple items.
| Attribute | Description |
|---|---|
| event | is set to select |
| event.data.items | array of item(s) to select |
| event.data.items[].id | ID of the item to select |
| event.data.items[].type | type of the item to select (stagedAsset, connection, connectionSegment, well, wellBore, wellBoreSegment, layer, overlay, shape) |
| event.data.focusSelection | (optional) whether to move the camera to the selection. Defaults to true; set it to false for selection-only interactions. |
Selecting and focusing on a well
{
event: "select",
data:{
items:[{
type: "well",
id: "id_of_the_well"
}],
focusSelection: true
}
}
Selection updates (select)
Direction: FieldTwin → Integration
When one or more objects are selected in Design, a select event is sent.
The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to select |
| isFrameActive | true if the frame is currently selected and active in the UI |
| id | ( obsolete ) unique id of the first selected item |
| type | ( obsolete ) type of the first selected item |
| cursorPosition | {x,y,z} value of cursor where selection happened (values in project space) |
| cursorPosition.x | x position |
| cursorPosition.y | y position |
| cursorPosition.z | z position, on seabed ( height sampled ) |
| cursorPosition.intersection | {x,y,z} value of cursor of intersecting point on a resource if any |
| data | contains an array of selected items |
| data.[].type | contains the type of the selected item |
| data.[].id | unique id of the selected item |
| data.[].name | display name of selected item |
| data.[].isForeign | true if the selected item comes from a linked parent project |
| data.[].project | ID of the parent project when isForeign is true |
| data.[].subProject | ID of the parent subproject when isForeign is true |
| data.[].stream | ID of the parent subproject branch when isForeign is true in FieldTwin 8.0 and later |
| senderId | echoed from the triggering select / selectByTag request; match it to your request |
| customTabId | customTabId of the integration tab whose request triggered this response (echoed) |
When a select/unselect is the host's response to your own select or selectByTag request, the host echoes the request's senderId and the originating customTabId back on the message, so you can recognise it as the answer to your request rather than a fresh user selection. Both fields are absent for selections made directly in the host UI.
Example of single selection
{
event: "select",
isFrameActive: true,
data: [
{
type: "stagedAsset",
id: "-LvCAe-JPACMW-F74Ocs",
name: "6 Slot Manifold - Diverless Vertical Connection System #1"
}
],
id: "-LvCAe-JPACMW-F74Ocs",
type: "stagedAsset",
cursorPosition: {
x: 665000
y: 400000
z: 90
}
}
Example of multi-selection
{
event: "select",
isFrameActive: true,
data: [
{
type: "stagedAsset",
id: "-LvCAe-JPACMW-F74Ocs",
name: "6 Slot Manifold - Diverless Vertical Connection System #1"
},
{
type: "stagedAsset",
id: "-LvCAbWf-Rf78H59Rnqj",
name: "6 Slot Manifold - Diverless Horizontal Connection System #1"
}
],
id: "-LvCAe-JPACMW-F74Ocs",
type: "stagedAsset",
cursorPosition: {
x: 665000
y: 400000
z: 90
}
}
didDrag
Direction: FieldTwin → Integration
This event is sent once per second while a selection is dragged. It contains data for the selection being dragged.
| Attribute | Description |
|---|---|
| event | is set to didDrag |
| resources | array of dragged resources; see the Change event resource attributes section |
clearSelection
Direction: Integration → FieldTwin
Clears the current selection.
| Attribute | Description |
|---|---|
| event | is set to clearSelection |
Example
{
event: 'clearSelection'
}
unselect
Direction: FieldTwin → Integration
Sent when the selection is reset (no more items are selected). The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to unselect |
| isFrameActive | true if the frame is currently selected |
| cursorPosition | {x,y,z} value of cursor where unselect click happened (values in project space) |
selectByTag
Direction: Integration → FieldTwin
Select resources based on their tags. This message allows you to filter and select resources that match the specified tags. The event sent by the integration should contain these attributes:
| Attribute | Description |
|---|---|
| event | must be set to selectByTag |
| tags | Array of tag names (strings). Case-insensitive match |
| matchAll | Optional. If true (default), resources must have ALL specified tags. If false, resources need ANY of the tags |
| resourceTypes | Optional. Array of resource type names to search (e.g., ["well", "connection"]). If not specified, all types are searched |
| focusSelection | Optional. If true, the camera will zoom to show the selected resources |
| senderId | Optional. Opaque id echoed back unchanged on the resulting select/unselect response so you can match it to this request |
Example selecting all wells with a specific tag
window.parent.postMessage(
{
event: 'selectByTag',
data: {
tags: ['Production'],
},
},
'*'
)
Example selecting resources with multiple tags (ALL required)
window.parent.postMessage(
{
event: 'selectByTag',
data: {
tags: ['High Priority', 'Phase 1'],
matchAll: true, // resources must have both tags
resourceTypes: ['connection', 'stagedAsset'],
focusSelection: true,
},
},
'*'
)
Example selecting resources with any of the tags
window.parent.postMessage(
{
event: 'selectByTag',
data: {
tags: ['Production', 'Development', 'Test'],
matchAll: false, // resources need at least one of these tags
focusSelection: true,
},
},
'*'
)
Resource queries
getResources
Direction: Integration → FieldTwin
Allow integration to request informations from a list of resources using their ids and type. The response will be returned to the integration through message resources.
| Attribute | Description |
|---|---|
| event | is set to getResources |
| data.items | array of object |
| data.items.[].id | id of the resource for which to get informations |
| data.items.[].type | resource's type for which to get informations |
| data.items.[].resourceType | alias for type |
| data.queryId | id that will be sent back with the reply |
{
event:"getResources",
data: {
items: [
{
id: "id_of_the_resource",
resourceType: "type_of_the_resource",
}
],
queryId:"id_of_the_query"
}
}
Account, project, and subproject IDs may use the fully qualified resourceId:streamId form. For a root resource's
main stream, where streamId is the same as resourceId, getResources also resolves a resource stored under its
short ID. A different stream ID only matches a resource stored under that exact fully qualified ID.
resources
Direction: FieldTwin → Integration
This message is sent in response to an integration sending getResources command.
It contains an array of resources, as defined in Change event resource attributes section.
| Attribute | Description |
|---|---|
| event | is set to resources |
| data | contains the raw data of requested items |
| data.resources | array of resources |
| data.queryId | same value defined in getResources query |
getVisibleResources
Direction: Integration → FieldTwin
Request minimal information about resources visible from the current camera position.
| Attribute | Description |
|---|---|
| event | is set to getVisibleResources |
| data | request data object |
| data.queryId | optional value returned unchanged in visibleResources |
visibleResources
Direction: FieldTwin → Integration
This message is sent in response to an integration sending getVisibleResources command.
It contains an array of resources ( with minimum information) that are visible at the current camera position.
| Attribute | Description |
|---|---|
| event | is set to visibleResources |
| data.resources | array of resources |
| data.resources[].type | contains the type of the selected item |
| data.resources[].id | unique id of the selected item |
| data.resources[].name | display name of selected item |
| data.resources[].isForeign | true if the selected item comes from a linked parent project |
| data.resources[].project | ID of the parent project when isForeign is true |
| data.resources[].subProject | ID of the parent subproject when isForeign is true |
| data.resources[].stream | ID of the parent subproject branch when isForeign is true in FieldTwin 8.0 and later |
| data.resources[].well | ID of parent well if resource is a well bore |
| data.resources[].connection | ID of parent connection if resource is a connection segment |
| data.resources[].wellBore | ID of parent well bore if resource is a well bore segment |
| data.queryId | same value defined in getResources query |
getResourceAttributesBulk
Direction: Integration → FieldTwin (
getResourceAttributesBulk), then FieldTwin → Integration (resourceAttributesBulk).
Get selected attributes of every resource in one call, without selecting them or fetching their full data. This is a lighter alternative to getProjectData for integrations that only need a few fields per resource.
The event sent by the integration should contain these attributes:
| Attribute | Description |
|---|---|
| event | must be set to getResourceAttributesBulk |
| attributes | Array of attribute names to include for each resource (e.g., ["tags", "operatorTags"]). tags resolves to the aggregated tag set the host matches on; any other attribute is read directly and is null when absent |
| resourceTypes | Optional. Array of resource type names to include (e.g., ["wells", "connections"]). If not specified, all types are included |
| queryId | Optional. An identifier that will be returned in the response for correlation |
The response message will contain:
| Attribute | Description |
|---|---|
| event | is set to resourceAttributesBulk |
| data.results | Object keyed by resourceId |
| data.results[id].resourceType | the resource's type (always included) |
| data.results[id].<attribute> | one key per requested attribute; tags is the aggregated set matched by selectByTag / annotations |
| data.queryId | The queryId from the request (if provided) |
Example
window.parent.postMessage(
{
event: 'getResourceAttributesBulk',
data: {
attributes: ['tags', 'operatorTags'],
resourceTypes: ['stagedAsset', 'connection'],
queryId: 'attrs-query-1',
},
},
'*'
)
Response example
{
event: 'resourceAttributesBulk',
data: {
results: {
'asset-abc-123': {
resourceType: 'stagedAssets',
tags: ['Production', 'Phase 1'],
operatorTags: ['VALVE-001']
},
'conn-xyz-456': {
resourceType: 'connections',
tags: ['Phase 1'],
operatorTags: []
}
},
queryId: 'attrs-query-1'
}
}
Resource changes
createResource
Direction: Integration → FieldTwin
Allow the integration to create a resource in FieldTwin Design.
When the optional volatile flag is true the resource is temporary and will not be saved.
This can be used to create display-only features that are controlled by the integration.
On success a didCreate message will follow containing the created resource object.
| Attribute | Description |
|---|---|
| event | is set to createResource |
| data | object |
| data.volatile | optional: do not save the resource in the database |
| data.draggable | optional: allow the resource to be dragged even if locked. Only work if volatile is true |
| data.resourceType | resource type string |
| data.projectTreeViewCustomPath | array of strings, describing hiearchy in project tree view |
| data.attributes | object containing data attributes for the new resource |
{
event: "createResource",
data: {
volatile: true,
resourceType: "type_of_resource",
attributes: { <object_attributes> },
},
}
createResources
Direction: Integration → FieldTwin
Allow the integration to create a list of resources in FieldTwin Design.
When the optional volatile flag is true the resource is temporary and will not be saved.
This can be used to create display-only features that are controlled by the integration.
On success didCreate messages will follow containing the created resource objects.
| Attribute | Description |
|---|---|
| event | is set to createResources |
| data | array of objects |
| data.[].volatile | optional: do not save the resource in the database |
| data.[].draggable | optional: allow the resource to be dragged even if locked. Only work if volatile is true |
| data.[].projectTreeViewCustomPath | array of strings, describing hiearchy in project tree view |
| data.[].resourceType | resource type string |
| data.[].attributes | object containing data attributes for the new resource |
{
event: "createResources",
data: [
{
resourceType: "type_of_resource",
attributes: { <object_attributes> },
},
{
resourceType: "type_of_resource",
attributes: { <object_attributes> },
},
...
]
}
updateResource
Direction: Integration → FieldTwin
Allow the integration to update a resource in FieldTwin Design.
On success a didUpdate message will follow containing the updated resource object.
| Attribute | Description |
|---|---|
| event | is set to updateResource |
| data | object |
| data.resourceType | resource type string |
| data.resourceId | the id of the resource to be updated |
| data.projectTreeViewCustomPath | array of strings, describing hiearchy in project tree view, set to null to default |
| data.attributes | object containing updated attributes for the resource |
{
event: "updateResource",
data: {
resourceType: "type_of_resource",
resourceId: "id_of_resource",
attributes: { <object_attributes> },
},
}
updateResources
Direction: Integration → FieldTwin
Allow the integration to update a list of resource in FieldTwin Design.
On success didUpdate messages will follow containing the updated resource objects.
| Attribute | Description |
|---|---|
| event | is set to updateResources |
| data | array of objects |
| data.projectTreeViewCustomPath | array of strings, describing hiearchy in project tree view, set to null to default |
| data.[].resourceType | resource type string |
| data.[].resourceId | the id of the resource to be updated |
| data.[].attributes | object containing updated attributes for the resource |
{
event: "updateResources",
data: [
{
resourceType: "type_of_resource",
resourceId: "id_of_resource",
attributes: { <object_attributes> },
},
{
resourceType: "type_of_resource",
resourceId: "id_of_resource",
attributes: { <object_attributes> },
},
...
],
}
deleteResource
Direction: Integration → FieldTwin
Allow the integration to delete a resource from FieldTwin Design.
On success a didDelete message will follow containing the deleted resource object.
| Attribute | Description |
|---|---|
| event | is set to deleteResource |
| data | object |
| data.resourceType | resource type string |
| data.resourceId | the id of the resource to be deleted |
{
event: "deleteResource",
data: {
resourceType: "type_of_resource",
resourceId: "id_of_resource",
},
}
deleteResources
Direction: Integration → FieldTwin
Allow the integration to delete a list of resources from FieldTwin Design.
On success didDelete messages will follow containing the deleted resource objects.
| Attribute | Description |
|---|---|
| event | is set to deleteResources |
| data | array of objects |
| data.[].resourceType | resource type string |
| data.[].resourceId | the id of the resource to be deleted |
{
event: "deleteResources",
data: [
{
resourceType: "type_of_resource",
resourceId: "id_of_resource",
},
{
resourceType: "type_of_resource",
resourceId: "id_of_resource",
},
...
],
}
didClone
Direction: FieldTwin → Integration
Sent when a project or subproject is cloned (copied in 8.0).
The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to didClone |
| type | type of the cloned item (project or subProject) |
| id | unique ID of the newly created item |
| data | contains the raw data of the newly created item (as for didCreate) |
| fromSubProjectId | only for type subProject, the original subproject ID |
| fromSubProjectName | only for type subProject, the original subproject ID |
| toSubProjectId | only for type subProject, the newly created subproject ID |
| toSubProjectName | only for type subProject, the newly created subproject name |
| subProjectId | only for type subProject, the newly created subproject ID |
| fromProjectId | the original project ID |
| fromProjectName | the original project name |
| fromAccountId | the original account ID |
| toProjectId | the new (type project) or target (type subProject) project ID |
| toProjectName | the new (type project) or target (type subProject) project name |
| project | the new (type project) or target (type subProject) project ID |
| projectId | the new (type project) or target (type subProject) project ID |
| projectName | the new (type project) or target (type subProject) project name |
| toAccountId | the target account ID |
| idsMap | map of original IDs to newly created IDs |
didCreate
Direction: FieldTwin → Integration
Sent when an item is created in the user's browser.
Contains the same data as didUpdate, except it does not have previousData or diff.
The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to didCreate |
see the didUpdate event |
didCreateFromNetwork
Direction: FieldTwin → Integration
Sent when an item is created through another client or an API call. It has the same payload as didCreate.
| Attribute | Description |
|---|---|
| event | is set to didCreateFromNetwork |
see the didCreate event attributes |
didUpdate
Direction: FieldTwin → Integration
Sent when an item is modified in the user's browser.
The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to didUpdate |
| id | unique ID of the updated item |
| type | type of the updated item |
| data | contains the raw data of the updated item |
| previousData | contains the previous raw data of the updated item |
| diff | contains only the attributes that where modified |
| isFrameActive | true if the frame is currently selected |
| isForeign | true if the updated item comes from a linked parent project |
| project | ID of the parent project when isForeign is true |
| subProject | ID of the parent subproject when isForeign is true |
| stream | ID of the parent subproject branch when isForeign is true in FieldTwin 8.0 and later |
Example of an overlay updated through the user's client
{
event: "didUpdate",
id: "-LdIy8vwvUsX_A4DC4E3",
type: "overlay",
isFrameActive: true,
data: {
tags: [],
text: "BdN \nSouthern Template",
x: 388998.7275622606,
y: 5308658.38831538,
color: "#FFF",
backgroundColor: "#0000",
fontSize: 300,
width: 100,
height: 100,
visible: true,
subProject: "-LdIy8mGinoxAbbIK1JC"
},
previousData: {
tags: [],
text: "BdN \nSouthern Template",
x: 388998.7275622606,
y: 5308658.38831538,
color: "#FFF",
backgroundColor: "#0000",
fontSize: 300,
width: 100,
height: 100,
visible: false,
subProject: "-LdIy8mGinoxAbbIK1JC"
},
diff: {
visible: false
}
}
didUpdateFromNetwork
Direction: FieldTwin → Integration
Sent when an item is modified through another client or an API call. It has the same payload as didUpdate.
Example of a metaDataValue updated through the network
{
"event": "didUpdateFromNetwork",
"id": "-LvAW4EZXEXALeigTUzZ",
"type": "metaDatumValue",
"isFrameActive": true,
"data": {
"type": "string",
"options": {
"default": "blue"
},
"relateToId": "-K5uq-UAY4FhTHllT1xs",
"relateToType": "asset",
"ownerId": "-LvAOzgcvH3CzYP7IYCP",
"value": "227"
},
"previousData": {
"type": "string",
"options": { "default": "blue" },
"relateToId": "-K5uq-UAY4FhTHllT1xs",
"relateToType": "asset",
"ownerId": "-LvAOzgcvH3CzYP7IYCP",
"value": "226"
},
"diff": {
"value": "226"
}
}
didDelete
Direction: FieldTwin → Integration
Sent when an item is deleted in the user's browser.
Contains the same data as didUpdate. The data field corresponds to the time of the deletion, so some
of the relationships might be set to null. In this case, you will find this information inside the
previousData (if it was set previously, so for example if you create and then delete an element,
previousData will not be set).
The event will contain these attributes:
| Attribute | Description |
|---|---|
| event | is set to didDelete |
see the didUpdate event |
Example for deletion of a custom-cost from the user's browser
{
"event": "didDelete",
"id": "-LvKED-ci3fW07aNMgqh",
"type": "customCost",
"data": {
"tags": [],
"isValidForCost": true,
"costObject": {
"value": 2356,
"entries": [],
"costPerDay": 0,
"currency": "USD",
"stateText": "User input",
"stateType": "danger",
"startDate": "2019-12-09T23:00:00.000Z",
"endDate": "2019-12-19T23:00:00.000Z",
"supplier": "subsurface 42"
},
"assetName": "custom entry",
"kind": "SPS",
"userRight": {
"cost": true,
"metaData": true,
"layer": true,
"well": true,
"costGen": "-LvFdcBUG9Ign29XgVw2"
},
"subProject": "-LvA9E5njA5MwR38ClmA"
}
}
didDeleteFromNetwork
Direction: FieldTwin → Integration
Sent when an item is deleted through another client or an API call. It has the same payload and
deletion semantics as didDelete.
Change event resource attributes
Connection
event.typeis set toconnection.
| Attribute | Description |
|---|---|
| bendable | true if the connection is using bendable radius |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| costObject | Describe cost |
| costObject.value | Cost value |
| costObject.entries | Array of cost entry for cost breakdown |
| costObject.entries[].cost | Cost of the entry |
| costObject.entries[].description | Entry description |
| costObject.entries[].item | Item vendor id |
| costObject.entries[].notes | User notes |
| costObject.entries[].number | Part number |
| costObject.entries[].quantity | Numbers of item (Cost is multiplied by) |
| costObject.costPerDay | Define cost per day (for activity only) |
| costObject.currency | Define currency, taken from the list described in account/project |
| created | Creation date |
| creator | Creator email |
| customResults | Custom results are set by integrations using the Legacy API and allow an integration to display some data relative to the element |
| designType | One of the design from the account assigned to the connection |
| from | Staged asset the connection start from, if any |
| fromCoordinate | 3D coordinate of the start point of the connection |
| fromCoordinate.x | |
| fromCoordinate.y | |
| fromCoordinate.z | |
| fromSocket | Which socket on the from staged asset the connection is connected to |
| importParams | If connection was imported ( WFS, shapefile, etc. ) this JSON object contains all the meta data that were imported with it. This is a key->value store |
| intermediaryPoints | Array of 3d point that define |
| isInactive | true if the connection is not in use anymore (for old connections that are obsolete) |
| isLocked | If locked, the element cannot be edited anymore |
| isValidForCost | true if this element should be used for computing cost |
| metaDataValue | Contains an array of id of meta data value |
| noHeightSampling | If true the connection will not be height sampled |
| params | Parameters |
| params.type | Id of the connection type. Lists of valid types are defined in account and project |
| params.label | Label of the connection, if not set, type name will be used |
| params.textFollowConnection | true if label should follow connection path instead of being a separate entity |
| params.textFollowConnectionFlip | If textFollowConnection, true if the position on the spline (top/bottom) should be flipped |
| params.textFollowConnectionOffset | If textFollowConnection, add an offset to the text position along the connection |
| params.textFollowConnectionRepetition | If textFollowConnection, number of repetitions of the text along the connection |
| params.textFollowConnectionFlip | If textFollowConnection, indicate if the position on the spline (top/bottom) should be flipped |
| renderOrder | Rendering sort order to indicate which connection is on top of the other |
| showCustomResults | true if custom results (integration driven) should be displayed |
| showLabel | true if connection label should be displayed |
| showLength | true if connection length should be displayed |
| status | Status of the connection. This is used by the integration to display visual information about a connection. Value can be warning, danger, primary, success |
| straight | true if the connection is straight |
| subProject | Id of the sub project that contains this connection |
| tags | Array of tags |
| to | Staged asset where the connections ends at, if any |
| toCoordinate | 3D coordinate of the end point of the connection |
| toCoordinate.x | |
| toCoordinate.y | |
| toCoordinate.z | |
| fromSocket | Which socket on the to staged asset the connection is connected to |
| visible | If true, connection is visible |
Connection Segment
event.typeis set toconnectionSegment.
| Attribute | Description |
|---|---|
| connection | ID of parent connection |
| subProject | ID of subproject containing this item |
| name | Name of the segment (label) |
| length | Length of the segment in the project unit |
| opacity | Opacity of the segment, between 1 (fully opaque) and 0 (fully transparent) |
| startOffset | Starting point of the segment from the connection's "from" point (when relativeToEnd is false) |
| thickness | Rendering thickness |
| visible | If true, segment is visible |
| relativeToEnd | If true, startOffset is from the connection's "to" point |
| labelVisible | If true, label is visible |
| labelSize | Label font size |
| labelOffsetX | Label X offset |
| labelOffsetY | Label Y offset |
| metaDataValue | An array of IDs of attached metadata values |
Custom Cost
event.typeis set tocustomCost.
| Attribute | Description |
|---|---|
| assetName | Name of the custom cost |
| costObject.cost | |
| costObject.description | |
| costObject.item | |
| costObject.notes | |
| costObject.quantity | |
| costObject.entries | |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| created | Creation date |
| creator | Creator email |
| isValidForCost | true if this element should be use for computing cost |
| kind | What the cost relates to |
| subProject | Id of the sub project that contains this cost |
| tags | Array of tags |
Document
event.typeis set todocument.
| Attribute | Description |
|---|---|
| fileName | Filename of uploaded file (latest file details are in linked documentRevision) |
| subProject | ID of the sub project that contains this document |
| relateToType | Object type the document is linked to - connections, stagedAssets, etc |
| relateToId | Object ID the document is linked to (connection ID, staged asset ID, ...) or null |
| documentGroupId | A group ID relevant when the document is attached to a multiple selection and not a single relateToId |
| documentGroupCount | Number of documents in the group |
| tags | Array of tags |
| revisions | Array of documentRevision IDs stored for this document, oldest first |
Document Revision
event.typeis set todocumentRevision.
| Attribute | Description |
|---|---|
| document | ID of the parent document record |
| subProject | ID of the sub project that contains this revision |
| creator | Email address of user that uploaded the file |
| created | Timestamp of the upload |
| url | URL to access or download the file (signed and time limited with expiry) |
| description | User's description |
| documentRevisionId | A group ID linking all revisions that refer to the same file revision |
Layer
event.typeis set tolayer.
| Attribute | Description |
|---|---|
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| created | Creation date |
| creator | Creator email |
| fileName | Original imported file name |
| geoLocBoundingBox | If geo located, information about the layer real world coordinate |
| geoLocBoundingBox.min_x | |
| geoLocBoundingBox.min_y | |
| geoLocBoundingBox.min_z | |
| geoLocBoundingBox.max_x | |
| geoLocBoundingBox.max_y | |
| geoLocBoundingBox.max_z | |
| METConfiguration | MET layer configuration. For now there is no configuration value |
| arcgisConfiguration | ArcGIS layer configuration |
| gradientPalette | If isGradient is true, define the gradient to be used for rendering |
| gradientPalette.[].a | Depth of the gradient step |
| gradientPalette.[].c | Color of the gradient step |
| heightSample | If true, layer will be draped on another layer |
| heightSamplerLayerId | If heightSample, id of the layer this layer will be draped on |
| isArcGIS | If true, this layer is an ArcGIS layer |
| isBathymetry | If true, this layer is a bathymetry |
| isGeoLoc | If true, this layer is geo located |
| isGradient | If true and if isXVB is true, the layer will be rendered as a gradient |
| isInactive | true if the layer is in not in use anymore |
| isMET | If true, this layer is a MET layer |
| isWMS | If true, this layer is a WMS layer |
| isXVB | If true, this layer is a XVB ( 3D ) layer |
| kind | Id of the layer type. These are defined in the account settings |
| metaDataValue | Contains an array of id of meta data value |
| name | Layer name, as displayed in the UI |
| opacity | Opacity of the layer, between 1 (fully opaque) and 0 (fully transparent) |
| processingState | Status of conversion |
| processingStateMessage | Progress message of the conversion |
| rotation | Heading of the layer |
| scale | Scale of the layer |
| seaBedTextureName | If isXVB is true, which texture to apply to render the layer in 3D |
| seabedColor | if isXVB is true, which color to use to render the layer in 3D |
| useSeabedColor | if true use seabedColor instead of seaBedTextureName to render the layer in 3D |
| subProject | Id of the sub project that contains this layer |
| url | URI of the file |
| urlNormalMap | If isXVB is true. URI to the normal map to use to augment rendering |
| visible | Define layer visibility |
| wmsConfiguration | WMS layer configuration |
| x | X offset |
| y | Y offset |
| z | Z offset |
Meta Datum Value
A meta datum value is created or modified every time the user edits a meta data in the field. A value is link to a meta data using
metaDatumId.event.typeis set tometaDatumValue.Since 5.4 value are not directly attached to a definition directly, but to a definition id, so that mulitple definition can be use.
- The existing attribute of the message ( subCategory, subType, ownerId, options ) will reflect the first definition
- All the definition are available inside
definitionsarray.
| Attribute | Description |
|---|---|
| options | Options of the meta data the value refers to. Depends on type. See FieldTwin API |
| ownerId | Id of the element (connection, staged asset, layer, etc.) that holds this value |
| metaDatumId | Id of the meta data this value refers to |
| relateToId | Which id the meta data refers to |
| relateToType | Which type the meta data refers to |
| name | Name of the meta data the value refers to |
| type | Type of the meta data |
| category | If type is asset, and value is a valid asset, category of the asset |
| subCategory | If type is asset, and value is a valid asset, sub category of the asset |
| subType | If type is asset, and value is a valid asset, sub type of the asset |
| value | Actual value |
| valueBis | Some meta data represents two values, this is the second one |
| definitionId | Generic definition id to use across all FieldTwin instances |
| definitions | Array of one or more definition |
| definitions.name | Name of the meta data the value refers to |
| definitions.type | Type of the meta data |
| definitions.options | Options of the meta data the value refers to. Depends on type. See FieldTwin API |
| definitions.category | If type is asset, and value is a valid asset, category of the asset |
| definitions.subCategory | If type is asset, and value is a valid asset, sub category of the asset |
| definitions.subType | Type of the meta data |
| definitions.metaDatumId | of the meta data |
Project
event.typeis set toproject.
| Attribute | Description |
|---|---|
| CRS | Project coordinate reference system |
| account | Id of the parent account |
| canOverrideConnectionColor | If true user can override connection color |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| collaboration | N/A |
| consents | N/A |
| containsObsoleteAssets | If true this project contains at least one obsolete asset |
| costByLengthIsDefault | If true cost for connection is computed by length as default |
| country | Id of the country this project is in |
| created | Creation date |
| creator | Creator email |
| currencyTypes | Array of valid currency for this project |
| currencyTypes[].id | Id of the currency |
| currencyTypes[].name | Name of the currency |
| currencyTypes[].rate | Rate of the currency |
| customer | Id of the customer for this project |
| dynamicScaleConfiguration | Array containing overrride for dynamic scale values |
| dynamicScaleConfiguration[].factor | Dynamic scale factor |
| dynamicScaleConfiguration[].type | Which attribute this value applies to |
| dynamicScaleConfiguration[].factor | Which value of type attribute this value applies to |
| endDate | End date of the project |
| fieldName | Internal field name |
| havePerProjectAssetPrices | IF true, project contains override for asset costs |
| havePerProjectPrices | If true, project contains default prices |
| name | Name of the project |
| perProjectAssetPrices | Array of asset price override |
| perProjectAssetPrices[].id | Id of the asset this cost relates to |
| perProjectAssetPrices[].cost | Cost value |
| perProjectAssetPrices[].currency | Currency of the cost |
| perProjectAssetPrices[].entries | Array of cost entry for cost breakdown |
| perProjectAssetPrices[].entries[].cost | Cost of the entry |
| perProjectAssetPrices[].entries[].description | Entry description |
| perProjectAssetPrices[].entries[].item | Item vendor id |
| perProjectAssetPrices[].entries[].notes | User notes |
| perProjectAssetPrices[].entries[].number | Part number |
| perProjectAssetPrices[].entries[].quantity | Numbers of item (Cost is multiplied by) |
| perProjectConnectionPrices | Per project connection price |
| perProjectConnectionPrices.{connection id}.value | Cost value for {connection id} connection |
| perProjectCurrencyTypes | Extra per project currency |
| perProjectCurrencyTypes[].id | Id of the currency |
| perProjectCurrencyTypes[].name | Name of the currency |
| perProjectCurrencyTypes[].rate | Rate of the currency |
| perProjectPrices | Array of default project cost |
| perProjectPrices[].assetName | Name of the cost |
| perProjectPrices[].costObject | Cost definition |
| perProjectPrices[].costObject.cost | Cost value |
| perProjectPrices[].costObject.currency | Currency of the cost |
| perProjectPrices[].costObject.entries | Array of cost entry for cost breakdown |
| perProjectPrices[].costObject.entries[].cost | Cost of the entry |
| perProjectPrices[].costObject.entries[].description | Entry description |
| perProjectPrices[].costObject.entries[].item | Item vendor id |
| perProjectPrices[].costObject.entries[].notes | User note |
| perProjectPrices[].costObject.entries[].number | Part number |
| perProjectPrices[].costObject.entries[].quantity | Number of items (Cost is multiplied by) |
| perProjectPrices[].kind | Type of the cost |
| projectConnections | Per project connection override |
| projectCurrency | Project currency |
| projectPhase | Id of project phase, if any |
| projectType | Id of project type, if any |
| projectUnitSystem | Project unit system |
| realWorldCoordinate | deprecated |
| region | Id of region, if any |
| screenShot | Screenshot |
| seaBedLevel | Default seabed level |
| startDate | Start date of the project |
| uniqConnectionOnTypedSocket | If true, only one connection can be connected to a socket |
| updatedAt | Last updated timestamp |
| updatedBy | Email of the user that did the last update |
| userRoles | Object that contains assigned user role for each user |
| users | Array of id of users that have access to the project |
| wellmasterConfiguration | Deprecated |
| wfsConfigurationId | Deprecated |
| wmsConfigurationId | Deprecated |
Shape
event.typeis set toshape.
| Attribute | Description |
|---|---|
| subProject | ID of subproject containing this item |
| name | Name of the shape (label) |
| shapeType | One value from: Box, Sphere, Triangle, Circle, Rectangle, Cone, Cylinder, Ring, Torus, Polygon, FlatTube, Tube |
| opacity | Opacity of the shape, between 1 (fully opaque) and 0 (fully transparent) |
| x | X coordinate of shape |
| y | Y coordinate of shape |
| z | Z coordinate of shape |
| scale | Scale of shape, default 1 |
| labelVisible | If true, label is visible |
| labelSize | Label font size |
| labelOffsetX | Label X offset |
| labelOffsetY | Label Y offset |
| labelZLayerAlign | Vertical alignment of the label: default, top, seabed or a layer id |
| labelColor | Label color, e.g. "#FFF" |
| color | Shape color, e.g. "#0000FF". Used when its linked shape type does not define a color |
| visible | If true, shape is visible |
| metaDataValue | An array of IDs of attached metadata values |
| rotation.x | 3D rotation of shape |
| rotation.y | 3D rotation of shape |
| rotation.z | 3D rotation of shape |
| stagedAsset | Optional staged asset ID to snap to |
| connection | Optional connection ID to snap to |
| smoothTubeEnds | Rendering options |
| doNotCrossBathy | Rendering options |
| stickToBathy | Rendering options |
| invertClippingMask | Rendering options |
| useAsDepthMask | Rendering options |
| smoothPolygonEdgesEnabled | Rendering options |
| useAsLight | Rendering options |
| lightIntensity | Rendering options |
| spotlightDecay | Rendering options |
| pointLightDecay | Rendering options |
| spotlightPenumbra | Rendering options |
| shadowMappingEnabled | Rendering options |
| outlineColor | Rendering options |
| outlineOpacity | Rendering options |
| outlineRender | Rendering options |
| outlineRenderAsMesh | Rendering options |
| outlineRenderAsMeshRadius | Rendering options |
| outlineRenderAsMeshLightingEnabled | Rendering options |
| shadingEnabled | Rendering options |
| textureEnabled | Rendering options |
| textureScale | Rendering options |
| localParallaxMappingHeightScale | Rendering options |
| textureEdgeFade | Rendering options |
| assetAlignment | Asset snap options |
| connectionOffset | Connection snap options |
| connectionLength | Connection snap options |
| connectionRadius | Connection snap options |
| connectionRelativeToEnd | Connection snap options |
| connectionCoversEntire | Connection snap options |
| lineThickness | shapeType specific attributes |
| boxWidth | shapeType specific attributes |
| boxHeight | shapeType specific attributes |
| boxDepth | shapeType specific attributes |
| sphereRadius | shapeType specific attributes |
| circleRadius | shapeType specific attributes |
| rectangleWidth | shapeType specific attributes |
| rectangleHeight | shapeType specific attributes |
| coneRadius | shapeType specific attributes |
| coneHeight | shapeType specific attributes |
| cylinderRadiusTop | shapeType specific attributes |
| cylinderRadiusBottom | shapeType specific attributes |
| cylinderHeight | shapeType specific attributes |
| ringInnerRadius | shapeType specific attributes |
| ringOuterRadius | shapeType specific attributes |
| torusRadius | shapeType specific attributes |
| torusThickness | shapeType specific attributes |
| triangleWidth | shapeType specific attributes |
| triangleHeight | shapeType specific attributes |
| isLocked | shapeType specific attributes |
| polyIs3D | shapeType specific attributes |
| polyOuterRing | shapeType specific attributes |
| polyInnerRings | shapeType specific attributes |
Staged Asset
event.typeis set tostagedAsset.
| Attribute | Description |
|---|---|
| asset | Id of the asset (representation) of the staged asset |
| assetData | Information about the asset |
| assetData.name | Name of the asset |
| assetData.description | Description of the asset |
| assetData.category | Asset category |
| assetData.subCategory | Asset sub category |
| assetData.subType | Asset sub type |
| assetData.kind | Asset sub type |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| costObject | Cost definition |
| costObject.value | Cost value |
| costObject.currency | Currency of the cost |
| costObject.entries | Array of cost entry for cost breakdown |
| costObject.entries[].cost | Cost of the entry |
| costObject.entries[].description | Entry description |
| costObject.entries[].item | Item vendor id |
| costObject.entries[].notes | User note |
| costObject.entries[].number | Part number |
| costObject.entries[].quantity | Number of items (Cost is multiplied by) |
| costObject.costPerDay | Define cost per day (for activity only) |
| costObject.currency | Define currency, taken from the list described in account/project |
| created | Creation date |
| creator | Creator email |
| custom | (deprecated) |
| customResults | Custom results are set by integrations using the Legacy API and allows an integration to display some data relative to the element |
| havePerAssetSockets | If true the staged asset contains it own set of sockets |
| hiddenLabel | If true the label is hidden |
| initialState | Set of states (position, etc.) for the staged asset |
| initialState.height | Height of the staged asset |
| initialState.opacity | Opacity of the staged asset |
| initialState.rotation | Heading of the staged asset |
| initialState.scale | Scale of the staged asset |
| initialState.well | Id of the well the staged asset is connected to, if any |
| initialState.width | Width of the staged asset |
| initialState.x | X real world coordinate of the staged asset |
| initialState.y | Y real world coordinate of the staged asset |
| isInactive | true if the staged asset is not in use anymore |
| isLocked | If locked, the element cannot be edited anymore |
| isValidForCost | true if this element should be used for computing cost |
| metaDataValue | Contains an array of id of meta data value |
| name | Name of the staged asset |
| perAssetParams | Parameters for the staged asset |
| perAssetParams.fontSize | Font size for displaying label |
| perAssetSockets2d | If havePerAssetSockets is true, array of definition of socket |
| perAssetSockets2d[].name | Name of the socket, used in fromSocket and toSocket when connecting a connection to the staged asset |
| perAssetSockets2d[].x | X offset of the socket, relative to the center of the asset |
| perAssetSockets2d[].y | Y offset of the socket, relative to the center of the asset |
| perAssetSockets2d[].z | z offset of the socket, relative to the center of the asset |
| perAssetSockets2d[].types | array of type for the socket. Only the first item is used |
| port | Id of the port the staged asset is docked to, if any |
| renderOrder | Render order for the rendering |
| showCustomResults | true custom results are visible |
| showMetaInfo | true if meta data info box is visible |
| socketMetaDataValue | Array of meta data values for sockets |
| status | Status of the staged asset. This is used by the integration to display visual information about a connection. Value can be warning, danger, primary, success |
| subProject | "-MAR0MZqqqk-Tel75u4V" |
| tags | Array of tags |
| textColor | Color to use for render label |
| visible | Define staged asset visibility |
| wellmasterConfiguration | deprecated |
Sub Project
event.typeis set tosubProject.
| Attribute | Description |
|---|---|
| backgroundColor | Background color of the project |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| connectionAngleVisible | Global setting, indicate if angle between connection point are visible |
| created | Creation date |
| creator | Creator email |
| description | Description of the sub project |
| globalDepthScale | Depth scale |
| gridColor | Color of the grid |
| gridNotVisible | If true, do not render the grid |
| locked | if true, sub project is locked and user cannot make any modification |
| lockedBy | email of the user who locked the sub project |
| name | Name of the sub project |
| project | id of the parent project |
| seabedTextureName | deprecated |
| sunDirection | Direction of the sun for 3D rendering |
| sunDirection.x | |
| sunDirection.y | |
| sunDirection.z | |
| updatedAt | Last update timestamp |
| updatedBy | Email of the user that did the last update |
| viewDependantScale | If true, use dynamic scaling (scale is dependant of zoom) |
| wfsConfiguration | deprecated |
Text Layer
event.typeis set tooverlay.
| Attribute | Description |
|---|---|
| x | X position |
| y | Y position |
| z | Z position |
| subProject | ID of the sub project that contains this text |
| text | Text to be displayed |
| rotation | Rotation in radians |
| color | Foreground (text) color, e.g. "#FFF" |
| backgroundColor | Background color |
| backgroundOpacity | Background opacity, 0 to 1 |
| outlineColor | |
| outlineOpacity | |
| faceCamera | True to ignore rotation and face the viewer |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| fontSize | Font size |
| tags | Array of tags |
| visible | True when visible |
| viewDependantScale | True to enable dynamic scaling |
View Point
event.typeis set tobookmark.
| Attribute | Description |
|---|---|
| camera | if is3D is set to true, contains the camera definition of the bookmark |
| camera.center | 3d position where the camera is looking at |
| camera.center.x | |
| camera.center.y | |
| camera.center.z | |
| camera.fov | field of view, in degree, of the camera |
| camera.from | 3d position of where the camera is located |
| camera.from.x | |
| camera.from.y | |
| camera.from.z | |
| camera.viewBox | Camera view box in 2D. Same as viewBox attribute |
| clonedFroms | Array of ids that describes the parents this element was cloned from |
| created | Creation date |
| creator | Creator email |
| is3D | indicate if the bookmark was taken from the 3D view of the 2D view |
| name | name of the bookmark |
| subProject | id of the sub project that contains this bookmark |
| viewBox | View bounding box of the bookmark |
| viewBox.x1 | |
| viewBox.x2 | |
| viewBox.y1 | |
| viewBox.y2 |
Well
event.typeis set towell.
| Attribute | Description |
|---|---|
| activeWellBore | Id of the active bore |
| canBeDrag | If true, user can move the well by dragging it |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| color | Rendering color of the well |
| created | Creation date |
| creator | Creator email |
| fontSize | Label font size |
| isInactive | true if the well is in not in use anymore |
| kind | Id of the well type. These are defined in the account settings |
| labelOffsetX | X offset of the label, relative to the well position |
| labelOffsetY | Y offset of the label, relative to the well position |
| labelRotation | Label rotation |
| labelVisible | If true label is visible |
| metaDataValue | Contains an array of id of meta data value |
| name | Label / name of the well |
| radius | Display radius |
| radiusViewDependant | Dynamic scale of the radius according to the zoom |
| referenceLevel | reference level of the well. Can be set to sea, rkb or seabed. sea set initial depth to 0, rkb set inital depth to rkb attribute, seabed set inital depth to touch down ( height sampling ) value. |
| rkb | if referenceLevel is set to rkb initial depth will be using this value |
| subProject | Id of the sub project that contains this well |
| tags | Array of tags |
| visible | Define well visibility |
| x | Top hole X Position |
| y | Top hole Y position |
Well Bore
event.typeis set towellBore.
| Attribute | Description |
|---|---|
| casingShoes | Array of casing shoes |
| clonedFroms | Array of ids that describe the parents this element was cloned from |
| name | Name of the bore |
| path | Array of 3D point. Z value depends on referenceLevel set on the parent well |
| path[].x | x value of the point |
| path[].y | x value of the point |
| path[].z | x value of the point |
| tags | Array of tags |
| targets | Array of 2 target points. First point is reservoir penetration point. Second point is entry inside the reservoir |
| targets[].active | |
| targets[].x | |
| targets[].y | |
| targets[].z | |
| targets[].depth | |
| targets[].az | |
| targets[].incl | |
| metaDataValue | Contains an array of id of meta data value |
| kind | Id of the well bore type. These are defined in the account settings |
| subProject | Id of the sub project that contains this well bore |
| well | Id of the parent well |
Well Bore Segment
event.typeis set towellBoreSegment.
| Attribute | Description |
|---|---|
| wellBore | ID of parent well bore |
| subProject | ID of subproject containing this item |
| name | Name of the segment (label) |
| length | Length of the segment in the project unit |
| opacity | Opacity of the segment, between 1 (fully opaque) and 0 (fully transparent) |
| startOffset | Starting point of the segment from the well bore's initial point (when relativeToEnd is false) |
| thickness | Rendering thickness |
| visible | If true, segment is visible |
| relativeToEnd | If true, startOffset is from the well bore's end point |
| labelVisible | If true, label is visible |
| labelSize | Label font size |
| labelOffsetX | Label X offset |
| labelOffsetY | Label Y offset |
| metaDataValue | An array of IDs of attached metadata values |
Resource information
requestInfo
Direction: FieldTwin → Integration
Requests information about project items from the integration. The number of items is limited to 100
and multiple requestInfo may be sent. These requests are sent at initial loading and on selection.
It allows for an integration to return some information used in the UI (for now only documentCount).
The reply is expected to be sent using replyInfo (see below) and not as a return of this call.
| Attribute | Description |
|---|---|
| event | is set to requestInfo |
| isFrameActive | true if the frame is currently selected |
| data | contains data about the event |
| data.items | array of id/type the request is for |
| data.items.[].id | id of the record the request is for |
| data.items.[].type | type of the record the request is for |
Example of requestInfo
{
event: "requestInfo",
isFrameActive: true,
data: {
items:[{
type: "wells",
id: "id_of_the_well"
}, {
type: "stagedAssets",
id: "id_of_the_staged_asset"
}, {
type: "assets",
id: "id_of_the_asset"
}]
}
}
replyInfo
Direction: Integration → FieldTwin
This message is sent from the integration to provide information about particular item(s). This can
be sent whenever the integration decides, but typically it is in response to an earlier requestInfo
message. For now it only sets one metric:
| Attribute | Description |
|---|---|
| event | is set to replyInfo |
| data.items | array of object |
| data.items.[].id | id of the resource for which to update document count |
| data.items.[].type | resource's type for which to update document count |
| data.items.[].documentCount | number of documents held for a given resource |
| data.tags | array of tags that will be use by the subprojects |
| integrationId | optional integration ID (e.g. the customTabId from JWT) |
{
event: "replyInfo",
integrationId: "my-document-search",
data: {
items: [{
id: "id_of_the_resource",
type: "type_of_the_resource",
documentCount: 3
}]
}
}
If integrationId is not provided, 2 different integrations that send replyInfo for the same
object will overwrite each other's metrics. Providing a unique value for your integration in integrationId
ensures that your documentCount is counted separately instead of being replaced.
Tags and annotations
getResourcesByTags
Direction: Integration → FieldTwin (
getResourcesByTags), then FieldTwin → Integration (resourcesByTags).
Get resources grouped by tag without selecting them. This is useful when you need to query which resources have specific tags without modifying the current selection.
The event sent by the integration should contain these attributes:
| Attribute | Description |
|---|---|
| event | must be set to getResourcesByTags |
| tags | Array of tag names (strings). Case-insensitive match |
| resourceTypes | Optional. Array of resource type names to search (e.g., ["well", "connection"]). If not specified, all types are searched |
| queryId | Optional. An identifier that will be returned in the response for correlation |
The response message will contain:
| Attribute | Description |
|---|---|
| event | is set to resourcesByTags |
| data.results | Object where keys are the requested tags and values are arrays of { resourceType, resourceId } |
| data.queryId | The queryId from the request (if provided) |
| data.error | Error message if the request was invalid |
Example getting resources by tags
window.parent.postMessage(
{
event: 'getResourcesByTags',
data: {
tags: ['VALVE-001', 'PUMP-002'],
queryId: 'my-query-123',
},
},
'*'
)
Response example
{
event: 'resourcesByTags',
data: {
results: {
'VALVE-001': [
{ resourceType: 'stagedAssets', resourceId: 'asset-abc-123' },
{ resourceType: 'connections', resourceId: 'conn-xyz-456' }
],
'PUMP-002': [
{ resourceType: 'stagedAssets', resourceId: 'asset-def-789' }
]
},
queryId: 'my-query-123'
}
}
Example with resource type filter
window.parent.postMessage(
{
event: 'getResourcesByTags',
data: {
tags: ['Production', 'Phase 1'],
resourceTypes: ['stagedAsset', 'connection'],
},
},
'*'
)
updateTagsAnnotation
Direction: Integration → FieldTwin (
updateTagsAnnotation), then FieldTwin → Integration (tagsAnnotationUpdated).
Create or update volatile annotation resources on resources that have specific tags. This is useful when an integration wants to display visual indicators (like status icons, counts, or labels) on resources based on their tags. The annotations are created as actual annotation resources but marked as volatile, so they are not saved to the database and exist only in the current session. Annotations are tracked per integration, so each integration manages its own annotations independently.
The event sent by the integration should contain these attributes:
| Attribute | Description |
|---|---|
| event | must be set to updateTagsAnnotation |
| annotations | Object where keys are tag names (or resourceIds when byResourceId is true) and values are arrays of annotation definitions |
| byResourceId | Optional. When true, the annotations keys are treated as resourceIds instead of tag names; each is resolved with a direct lookup and annotated once |
| types | Optional. { resourceId: resourceType } map used together with byResourceId to resolve resources in O(1) (otherwise the resource is looked up by scanning) |
| queryId | Optional. An identifier that will be returned in the response for correlation |
By default keys are tags. In byResourceId mode the keys are resourceIds and each key annotates exactly that one resource.
Each annotation definition in the array can have these properties:
| Property | Description |
|---|---|
| icon | Optional. FontAwesome solid icon name (e.g., faExclamationTriangle, faCheckCircle, faWrench) |
| text | Optional. Text to display on the annotation |
| count | Optional. A number to display on the annotation |
| color | Optional. Hex color string for the annotation (e.g., #ff0000). Defaults to #ff9900 |
| outlineOverride | Optional. When true, the annotation draws the matched resource outline instead of a circular marker for connections, connectionSegments, and wellBores |
The response message will contain:
| Attribute | Description |
|---|---|
| event | is set to tagsAnnotationUpdated |
| data.success | Boolean indicating if the operation succeeded |
| data.queryId | The queryId from the request (if provided) |
| data.annotatedTags | Array of objects with tag, resourceCount, and annotationCount for each tag |
| data.error | Error message if the request was invalid |
Example creating status annotations
window.parent.postMessage(
{
event: 'updateTagsAnnotation',
data: {
annotations: {
'VALVE-001': [{ icon: 'faCheckCircle', color: '#00ff00', text: 'Open' }],
'VALVE-002': [{ icon: 'faTimesCircle', color: '#ff0000', text: 'Closed' }],
'PUMP-003': [{ icon: 'faExclamationTriangle', color: '#ffaa00', count: 3 }],
'PUMP-004': [{ color: '#ffaa00', outlineOverride: true }],
},
queryId: 'status-update-1',
},
},
'*'
)
Response example
{
event: 'tagsAnnotationUpdated',
data: {
success: true,
queryId: 'status-update-1',
annotatedTags: [
{ tag: 'VALVE-001', resourceCount: 2, annotationCount: 2 },
{ tag: 'VALVE-002', resourceCount: 1, annotationCount: 1 },
{ tag: 'PUMP-003', resourceCount: 1, annotationCount: 1 }
]
}
}
Example annotating specific resources by id (byResourceId)
window.parent.postMessage(
{
event: 'updateTagsAnnotation',
data: {
byResourceId: true,
annotations: {
'asset-abc-123': [{ icon: 'faCheckCircle', color: '#00ff00', text: 'Open' }],
'conn-xyz-456': [{ color: '#ffaa00', outlineOverride: true }],
},
// Optional type hints for O(1) resolution
types: {
'asset-abc-123': 'stagedAssets',
'conn-xyz-456': 'connections',
},
queryId: 'status-update-2',
},
},
'*'
)
clearTagsAnnotation
Direction: Integration → FieldTwin (
clearTagsAnnotation), then FieldTwin → Integration (tagsAnnotationCleared).
Remove annotations previously created by the integration via updateTagsAnnotation. You can clear annotations for specific tags or all annotations created by the integration.
The event sent by the integration should contain these attributes:
| Attribute | Description |
|---|---|
| event | must be set to clearTagsAnnotation |
| tags | Optional. Array of tag names to clear. If not provided, all annotations from this integration are cleared |
| queryId | Optional. An identifier that will be returned in the response for correlation |
The response message will contain:
| Attribute | Description |
|---|---|
| event | is set to tagsAnnotationCleared |
| data.success | Boolean indicating if the operation succeeded |
| data.queryId | The queryId from the request (if provided) |
| data.clearedTags | Array of tag names that had annotations removed |
Example clearing specific tag annotations
window.parent.postMessage(
{
event: 'clearTagsAnnotation',
data: {
tags: ['VALVE-001', 'VALVE-002'],
},
},
'*'
)
Example clearing all annotations from this integration
window.parent.postMessage(
{
event: 'clearTagsAnnotation',
data: {},
},
'*'
)
Viewport
getViewBox
Direction: Integration → FieldTwin
Request the current viewport. The response will be returned to the integration through message viewBox.
viewBox
Direction: FieldTwin → Integration
The message is sent in response to an integration sending the getViewBox command.
It contains the current view box of the application in project coordinates.
| Attribute | Description |
|---|---|
| event | is set to viewBox |
| isFrameActive | true if the frame is currently selected |
| data | contains data about the event |
| data.viewBox | viewBox object |
| data.viewBox.x1 | start x in project coordinate of viewbox |
| data.viewBox.y1 | start y in project coordinate of viewbox |
| data.viewBox.x2 | end x in project coordinate of viewbox |
| data.viewBox.y2 | end y in project coordinate of viewbox |
{
event: "viewBox",
isFrameActive: true,
data: {
viewBox: {
x1: 100000,
y1: 450000,
x2: 120000,
y2: 470000
}
}
}
zoomAt
Direction: Integration → FieldTwin
Focus the view on a given point. Z position will be height sampled, and the provided z value of the
point will be added to it. This means that if you set z as 100, the camera will be at position
100 + height sampled z.
| Attribute | Description |
|---|---|
| event | is set to zoomAt |
| event.data.x | X position of the center of the camera lookat |
| event.data.y | Y position of the center of the camera lookat |
| event.data.z | indicate the height distance from the center where the eye of the camera will be |
Focusing on a point
{
event: "zoomAt",
data: {
x: 15300,
y: 113105,
z: 300
}
}
zoomOn
Direction: Integration → FieldTwin
Focus the view on the given item. Set these attributes:
| Attribute | Description |
|---|---|
| event | is set to zoomOn |
| event.data.type | type of the item to focus on (stagedAsset, connection, connectionSegment, well, wellBore, wellBoreSegment, layer, overlay, shape) |
| event.data.id | ID of the item to focus on |
| event.data.distance | (optional) distance in world units from the item to the camera. Omit to auto-fit. 3D (perspective) view only. |
Calling zoomOn to focus on a well
{
event: "zoomOn",
data: {
type: "well",
id: "id_of_the_well"
}
}
Calling zoomOn with an explicit camera distance
{
event: "zoomOn",
data: {
type: "well",
id: "id_of_the_well",
distance: 50
}
}
Notifications
toast
Direction: Integration → FieldTwin
Display a temporary pop-up notification ("toast" message) in the FieldTwin Design UI.
| Attribute | Description |
|---|---|
| event | is set to toast |
| data.type | message type, can be danger, warning, info or success |
| data.message | message to display |
{
event: "toast",
data: {
type: "success",
message: "Win win"
}
}
Documents
displayDocument
Direction: Integration → FieldTwin, then FieldTwin → Integration with the success or error response.
Opens a document in FieldTwin's file viewer. The file viewer must be open and support the file type for this to succeed.
Returns a response indicating success or failure.
| Attribute | Description |
|---|---|
| event | is set to displayDocument |
| data | object |
| data.url | URL of the document to display (required) |
| data.fileType | File extension/type (optional, will be extracted from URL if not provided) |
| data.mimeType | MIME type (optional, recommended for URLs without a file extension) |
| data.tabId | File Viewer component ID to target (optional; defaults to the last-focused tab) |
Supported file types
The file viewer supports the following formats:
- 3D Models: STEP (.step, .stp), GLTF/GLB (.gltf, .glb), OBJ (.obj), FBX (.fbx), STL (.stl), PLY (.ply), Collada (.dae), 3DS (.3ds)
- Vector: SVG (.svg), GeoJSON (.geojson, .json), DXF (.dxf), DWG (.dwg), MicroStation DGN v7 and earlier (.dgn)
- Documents: PDF (.pdf)
- Images: PNG (.png), JPEG (.jpg, .jpeg), GIF (.gif), BMP (.bmp), WebP (.webp), ICO (.ico), TIFF (.tif, .tiff)
- Video: MP4 (.mp4), M4V (.m4v), WebM (.webm), OGG (.ogg, .ogv), MOV (.mov), AVI (.avi). Playback depends on the browser-supported codecs in each container.
- Spreadsheets: Excel (.xlsx, .xls, .xlsb, .xlsm), CSV (.csv)
For multi-page TIFF files, the viewer displays the first page.
MicroStation DGN v8 is not supported by the current converter and returns a specific compatibility error. Legacy DGN display is a 2D conversion; curves are approximated and complex cells are rendered as independent elements. For an opaque DGN URL, send fileType: "dgn" or mimeType: "application/vnd.microstation.dgn".
Request example
{
event: "displayDocument",
data: {
url: "https://example.com/download/42",
mimeType: "image/tiff"
}
}
Targeting a specific tab
{
event: "displayDocument",
data: {
url: "https://example.com/spreadsheet.xlsx",
fileType: "xlsx",
tabId: "FileViewerTab-abc123"
}
}
Response
The message handler returns a response object:
{
event: "displayDocument",
success: true, // or false if failed
error: null // or error message if failed
}
Error messages:
"No URL provided"- The url parameter was missing"Unable to open document - unsupported file type"- File type not supported"File viewer not available"- File viewer feature not loaded in this frontend"Requested file viewer not available"- The suppliedtabIddoes not match an open File Viewer
Exports
exportToGLTF
Direction: Integration → FieldTwin, then FieldTwin → Integration with the binary response.
Ask FieldTwin to export the whole design as GLTF. FieldTwin returns a Blob containing the GLTF data.
{
event:"exportToGLTF",
data: {
queryId: `[query_id_not_used_for_now]`,
},
}
Receiving the binary response
async function onMessage(message) {
if (message.data instanceof Blob) {
// use library "saveAs" to save the blob to a file
saveAs(message.data, `export.gltf`)
} else {
// handle message as JSON
}
}
exportToGeoJSON request
Direction: Integration → FieldTwin
Ask the host software to export the whole design as GeoJSON.
| Attribute | Description |
|---|---|
| event | is set to exportToGeoJSON |
| data.queryId | id that will be sent back with the reply |
| data.mergeParentProjects | default to true, export parent projects data |
| data.exportMetaData | default to true, export meta data as properties |
| data.onlyPublicMetaData | only export public metadata |
| data.onlyStdMetaData | only export standard metadata |
| data.filterMetaDataByTags | array of string, filter which resource are exported by tags |
| data.simplify | allow connection simplification |
| data.simplifyTolerance | simplification tolerance |
| data.disableConvertion | Do not convert coordinate to lat / long |
| data.onlyPublicMetaData | message to display |
| data.exportLayerAsContour | when true exports every layer as contour; when omitted/false only layers that have contour rendering enabled are exported |
| data.types | array of types to exports, default to 'wells', 'wellBores', 'connections', 'stagedAssets', 'shapes', 'layers' |
| data.resourceIds | array of resources id to export |
{
event:"exportToGeoJSON",
data: {
queryId: `[query_id_not_used_for_now]`,
},
}
exportToGeoJSON response
Direction: FieldTwin → Integration
This event is sent after an exportToGeoJSON request, it contains the exported data if any
| Attribute | Description |
|---|---|
| event | is set to exportToGeoJSON |
| GeoJSON | contains exported GeoJSON |
| queryId | same value as passed in the initial query |
Features exported from stagedAssets carry the asset's file links in their properties:
model3dUrl (the 3D model) and robertoUrl (the Roberto representation). Both are pre-signed
download URLs valid for the signed-URL lifetime configured on the instance, and are omitted when
the asset has no such file or when the user is not allowed to download it.
For a smart asset, smartAssets lists the assets docked onto it - directly or through another
docked asset - depth first:
| Attribute | Description |
|---|---|
| metaDatumId | id of the meta datum holding the docked asset |
| docking | name of the parent docking slot the asset is docked into |
| assetId | id of the docked asset |
| name | name of the docked asset |
| model3dUrl | pre-signed url to the docked asset's 3D model, omitted when it has none |
| robertoUrl | pre-signed url to the docked asset's Roberto representation, omitted when it has none |
| matrix | column-major 4x4 placement matrix, relative to the staged asset's origin (model space, Y up, metres) |
The matrices deliberately exclude the staged asset's own world placement, so they remain usable whatever CRS the feature geometry was reprojected into: position the staged asset from the feature geometry, then apply these matrices underneath it.
Charts
createChart
Direction: Integration → FieldTwin, then FieldTwin → Integration with the result.
Creates or updates a Chart.js graph billboard in the 3D viewport at a specified position. The chart is displayed as a plane that always faces the camera (billboard).
If an id is provided and a chart with that ID already exists, the chart will be updated with the new data and configuration. Otherwise, a new chart is created.
Returns a response with success status, a chart ID (auto-generated if not provided), and an updated flag indicating whether an existing chart was updated.
| Attribute | Description |
|---|---|
| event | is set to createChart |
| data | object |
| data.title | Chart title (required) |
| data.labels | Array of x-axis labels (required) |
| data.datasets | Array of dataset objects (required) |
| data.type | Chart type: 'line', 'bar', 'scatter', 'pie', etc. (optional, default: 'line') |
| data.xAxisLabel | X-axis label text (optional) |
| data.yAxisLabel | Y-axis label text (optional) |
| data.position | Object with {x, y, z} coordinates in world space (optional, default: {0, 0, 0}) |
| data.width | Billboard width in world units (optional, default: 2) |
| data.height | Billboard height in world units (optional, default: 1.5) |
| data.id | Chart ID for updates/reference (optional, auto-generated if not provided) |
Dataset format
Each dataset in data.datasets should follow the Chart.js dataset format:
{
label: 'Dataset Name', // Dataset label
data: [12, 19, 3, 5, 2, 3], // Data values
borderColor: 'rgb(75, 192, 192)', // Line/border color (optional)
backgroundColor: 'rgba(75, 192, 192, 0.2)', // Fill color (optional)
tension: 0.1 // Line tension for smooth curves (optional, line charts)
}
Supported chart types
- line - Line chart (default)
- bar - Bar chart
- scatter - Scatter plot
- pie - Pie chart
- doughnut - Doughnut chart
- radar - Radar chart
- polarArea - Polar area chart
- bubble - Bubble chart
Request example - Line chart
{
event: "createChart",
data: {
title: "Temperature Over Time",
type: "line",
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
datasets: [{
label: "Temperature (°C)",
data: [12, 19, 3, 5, 2, 3],
borderColor: "rgb(75, 192, 192)",
tension: 0.1
}],
xAxisLabel: "Month",
yAxisLabel: "Temperature (°C)",
position: { x: 100, y: 50, z: 0 },
width: 3,
height: 2,
id: "temp-chart-1"
}
}
Request example - Multi-dataset bar chart
{
event: "createChart",
data: {
title: "Production Comparison",
type: "bar",
labels: ["Q1", "Q2", "Q3", "Q4"],
datasets: [
{
label: "2023",
data: [65, 59, 80, 81],
backgroundColor: "rgba(255, 99, 132, 0.5)"
},
{
label: "2024",
data: [28, 48, 40, 19],
backgroundColor: "rgba(54, 162, 235, 0.5)"
}
],
xAxisLabel: "Quarter",
yAxisLabel: "Units",
position: { x: 200, y: 100, z: 10 }
}
}
Response
{
event: "createChart",
success: true,
chartId: "temp-chart-1", // provided ID or auto-generated like "chart-1234567890-abc123def"
updated: false // true if an existing chart was updated, false if newly created
}
Updating an existing chart
To update an existing chart, send a createChart message with the same id:
{
event: "createChart",
data: {
id: "temp-chart-1", // Same ID as before
title: "Updated Temperature Data",
labels: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul"],
datasets: [{
label: "Temperature (°C)",
data: [12, 19, 3, 5, 2, 3, 8],
borderColor: "rgb(255, 99, 132)",
tension: 0.1
}],
xAxisLabel: "Month",
yAxisLabel: "Temperature (°C)"
// position can be updated too
}
}
Error messages:
"Missing required fields: labels and datasets are required"- Missing required parameters"No active 3D viewport found"- No 3D view is currently open or focused- Other error messages describe specific Chart.js or rendering errors
deleteChart
Direction: Integration → FieldTwin, then FieldTwin → Integration with the result.
Deletes a chart billboard from the 3D viewport.
| Attribute | Description |
|---|---|
| event | is set to deleteChart |
| data | object |
| data.id | Chart ID to delete (required) |
Request example
{
event: "deleteChart",
data: {
id: "temp-chart-1"
}
}
Response
{
event: "deleteChart",
success: true,
chartId: "temp-chart-1"
}
Error messages:
"No chart ID provided"- Missing required ID parameter"Chart with ID 'xxx' not found"- No chart exists with the specified ID"No active 3D viewport found"- No 3D view is currently open or focused
Close button
All charts created with createChart include a close button in the top-right corner. When clicked, the chart is automatically removed from the scene. This provides a user-friendly way to dismiss charts without requiring integration code.
User settings
getUserSettings
Direction: Integration → FieldTwin (
getUserSettings), then FieldTwin → Integration (userSettings).
Return the user settings object stored inside the user. Can be use by integration to store transient data.
setUserSettings
Direction: Integration → FieldTwin (
setUserSettings), then FieldTwin → Integration (userSettings).
Merge the user settings with the passed object.
Document and model tag styling
requestTagsInfos
Direction: FieldTwin → Integration
Sent when a document is loaded and tags have been extracted. The integration receives this event to determine how to style the tags.
| Attribute | Description |
|---|---|
| event | is set to requestTagsInfos |
| data | object |
| data.documentUrl | URL of the document that was loaded |
| data.tags | Array of tag objects extracted from the document |
| data.tags[].text | The text content of the tag |
| data.tags[].source | Extraction source: 'direct' (PDF text) or 'ocr' (image) |
| data.tags[].confidence | OCR confidence score (0-100), only present for OCR tags |
| data.subProject | ID of the current subproject |
| data.project | ID of the current project |
| data.requestId | Unique request ID - include this in updateTagStyles response |
Example event received by integration
{
event: "requestTagsInfos",
data: {
documentUrl: "https://example.com/documents/P&ID-Rev3.pdf",
tags: [
{ text: "VALVE-001", source: "direct" },
{ text: "VALVE-002", source: "direct" },
{ text: "PUMP-A-01", source: "ocr", confidence: 92.5 },
{ text: "TANK-B-03", source: "ocr", confidence: 88.1 }
],
subProject: "-LvA9E5njA5MwR38ClmA",
project: "-LvA9E5njA5MwR38ClmB"
}
}
updateTagStyles
Direction: Integration → FieldTwin, then FieldTwin → Integration with the success or error response.
Sent by an integration to replace that integration instance's tag-style contribution. Every rule must declare a category; a rule
without one is ignored. The category is what the user turns on or off from the Operation toolbar's Integration tag
styles menu, so a rule that cannot be attributed to a category cannot be controlled and is not applied. The host merges
contributions from distinct integration tabs and dynamic pages, so one source cannot erase unrelated Roberto styles from
another. File viewers apply
the full style to matching text, while Roberto models use style.backgroundColor (falling back to style.color) as the
status-outline color for matching model parts. The model's surface material is not changed, and unstyled parts do not receive an integration outline. Pattern
matching supports wildcards for efficient bulk styling. Roberto outlines are static and opaque; the other CSS fields remain
File Viewer-only. Users can show or hide the integration status outlines from Viewport Settings without affecting document
styling or discarding the latest snapshot. Send an unscoped empty tagStyles array to clear that source's Roberto
contribution (and the currently broadcast File Viewer styles).
| Attribute | Description |
|---|---|
| event | is set to updateTagStyles |
| data | object |
| data.tagStyles | Array of tag style objects |
| data.tagStyles[].pattern | Tag pattern to match (supports wildcards with *) |
| data.tagStyles[].category | Required. Name of the user-toggleable style category this rule belongs to (1-64 characters after trimming). Rules without one are ignored. |
| data.tagStyles[].style | Style object containing CSS properties to apply |
| data.tagStyles[].style.color | Text color and fallback Roberto status-outline color (CSS color string, e.g., "#FF0000", "red", "rgb(255,0,0)") |
| data.tagStyles[].style.backgroundColor | Background color and preferred Roberto status-outline color (CSS color string) |
| data.tagStyles[].style.backgroundImage | Encoded inline SVG icon (url("data:image/svg+xml,...")) rendered outside the tag's left edge; remote image URLs are rejected |
| data.tagStyles[].style.backgroundRepeat | CSS background repeat behavior within the icon slot |
| data.tagStyles[].style.backgroundPosition | CSS background position within the icon slot |
| data.tagStyles[].style.backgroundSize | CSS background size within the icon slot |
| data.tagStyles[].style.border | Border style (CSS border value, e.g., "1px solid #00FF00") |
| data.tagStyles[].style.borderRadius | Border radius (CSS value, e.g., "4px") |
| data.tagStyles[].style.fontWeight | Font weight ("normal", "bold", "100"-"900") |
| data.tagStyles[].style.fontStyle | Font style ("normal", "italic", "oblique") |
| data.tagStyles[].style.fontSize | Font size (CSS size string, e.g., "14px", "1.2em") |
| data.tagStyles[].style.textDecoration | Text decoration ("none", "underline", "line-through") |
| data.tagStyles[].style.opacity | Opacity (0-1 or CSS value) |
| data.tagStyles[].style.padding | Padding (CSS value) |
| data.tagStyles[].style.margin | Margin (CSS value) |
| data.requestId | Optional request ID from requestTagsInfos. It scopes delivery to that File Viewer and does not replace any Roberto status-outline contribution. |
Pattern Matching
Patterns support wildcard matching using the * character:
"VALVE-*"matches all tags starting with "VALVE-" (e.g., "VALVE-001", "VALVE-ABC")"*-CRITICAL"matches all tags ending with "-CRITICAL""PUMP-*-01"matches tags like "PUMP-A-01", "PUMP-B-01""*"matches all tags (use for default styling)
For Roberto models, patterns are matched against canonical model-part identities: the part tag, its exact/generated full
name, and a tag recovered from a modelpart_ name. Shared display names and broad search aliases are ignored so a group
label cannot outline unrelated valves. Exact functional-location tags also match Roberto part names that carry the model's
generated identifier suffix. Structural children inherit their parent match until a child declares another tag or
functional location.
When system isolation is active in Operation mode, a status outline is drawn only if the matching part belongs to the active system. An integration style never reveals or makes selectable a part hidden by that isolation.
Styling Priority
When multiple patterns match a tag, the most specific pattern wins:
- Exact match (no wildcards)
- First matching wildcard pattern in the array order
If the same exact pattern appears more than once, the later definition replaces the earlier one. Integrations should not emit a shared group name once per member unless the group has one deliberately aggregated status.
Style Categories
category groups rules into something a user can recognize and switch off, such as Inspection status,
Valve status, or Work orders. Choose stable, human-readable names: they are shown verbatim in the
Operation toolbar menu and the user's on/off choice is stored per category name.
- A rule with no
category, a non-stringcategory, a blank one, or one longer than 64 characters is dropped. The rest of the message is still applied. - Every category an integration offers is listed in the Integration tag styles menu in the Operation toolbar. Categories start disabled: an integration's styles change nothing until the user turns the category on. The choice is stored in the user's settings and persists across sessions.
- Enabling or disabling a category adds or removes its rules from both Roberto status outlines and File
Viewer tag styling immediately, using the contribution the host already holds; no new
updateTagStylesmessage is needed. - The reply's
ignoredfield counts the rules that were dropped because they had no usable category or belonged to a category the user has not enabled. Use it while developing to detect a missingcategory; a fully-ignored reply on a correctcategoryusually just means the user has not switched it on yet. - An integration may spread one message across several categories. Use one category per meaning, not one per rule; at most 64 distinct categories are listed per session.
- Categories are not a delivery scope. The
requestIdfield still decides which File Viewer documents a reply reaches.
Request-scoped replies are not retained by the host, so toggling a category re-filters the unscoped contributions immediately while a request-scoped document picks up the change on the integration's next reply.
Request ID Behavior
The requestId field controls which File Viewer documents receive the styling. A request-scoped reply is usually only a
partial set of the tags in one document, so it never replaces a Roberto status-outline contribution:
- With
requestId: styles are applied only to the File Viewer document that sent the originalrequestTagsInfosevent. Roberto status outlines are unchanged. - Without
requestId: styles are applied to all open documents and replace this integration host's complete Roberto status-outline contribution. The host merges it with contributions from other integration hosts.
Example: Basic tag styling
window.parent.postMessage(
{
event: 'updateTagStyles',
data: {
tagStyles: [
{
pattern: 'VALVE-001',
category: 'Valve status',
style: {
color: '#00FF00',
backgroundColor: '#004400',
fontWeight: 'bold',
},
},
{
pattern: 'VALVE-002',
category: 'Valve status',
style: {
color: '#FF0000',
backgroundColor: '#440000',
fontWeight: 'bold',
},
},
],
},
},
'*'
)
Example: Wildcard patterns for bulk styling
window.parent.postMessage(
{
event: 'updateTagStyles',
data: {
tagStyles: [
// All valves - green (open status)
{
pattern: 'VALVE-*',
category: 'Valve status',
style: {
color: '#00FF00',
backgroundColor: 'rgba(0, 100, 0, 0.3)',
border: '1px solid #00FF00',
},
},
// All pumps - blue
{
pattern: 'PUMP-*',
category: 'Equipment status',
style: {
color: '#0088FF',
backgroundColor: 'rgba(0, 50, 150, 0.2)',
fontWeight: 'bold',
},
},
// Critical equipment - red background
{
pattern: '*-CRITICAL',
category: 'Criticality',
style: {
backgroundColor: '#FF0000',
color: '#FFFFFF',
fontWeight: 'bold',
textDecoration: 'underline',
},
},
// Default style for all tags
{
pattern: '*',
category: 'Default',
style: {
color: '#333333',
fontSize: '12px',
},
},
],
},
},
'*'
)
Example: Status-based valve styling
// Integration receives requestTagsInfos event with valve tags
// It queries its database for valve statuses and responds with styling
async function handleRequestTagsInfos(event) {
const { tags, documentUrl, requestId } = event.data
// Extract valve IDs from tags
const valveIds = tags.map((tag) => tag.text).filter((text) => text.startsWith('VALVE-'))
// Query valve statuses from your system
const valveStatuses = await fetchValveStatuses(valveIds)
// Build styling based on status
const tagStyles = valveStatuses.map((valve) => ({
pattern: valve.id,
category: 'Valve status',
style: {
color: valve.status === 'OPEN' ? '#00FF00' : valve.status === 'CLOSED' ? '#FF0000' : '#FFA500', // PARTIAL = orange
backgroundColor:
valve.status === 'OPEN'
? 'rgba(0, 100, 0, 0.2)'
: valve.status === 'CLOSED'
? 'rgba(100, 0, 0, 0.2)'
: 'rgba(255, 165, 0, 0.2)',
fontWeight: 'bold',
border: `2px solid ${valve.status === 'OPEN' ? '#00FF00' : valve.status === 'CLOSED' ? '#FF0000' : '#FFA500'}`,
},
}))
// Send styling back to File Viewer with requestId for targeted delivery
window.parent.postMessage(
{
event: 'updateTagStyles',
data: {
tagStyles,
requestId, // Include requestId to ensure response goes to correct document
},
},
'*'
)
}
// Listen for tag extraction events
window.addEventListener('message', (event) => {
if (event.data.event === 'requestTagsInfos') {
handleRequestTagsInfos(event)
}
})
Example: Proactive styling without requestId
Integrations can send styling updates at any time without waiting for tag extraction. This is useful when your integration's data changes and you want to update all open documents immediately:
// Example: Integration detects valve status changed in external system
function onValveStatusChanged(valveId, newStatus) {
// Update styling for all open documents immediately
window.parent.postMessage(
{
event: 'updateTagStyles',
data: {
tagStyles: [
{
pattern: valveId,
category: 'Valve status',
style: {
color: newStatus === 'OPEN' ? '#00FF00' : '#FF0000',
backgroundColor: newStatus === 'OPEN' ? 'rgba(0, 100, 0, 0.2)' : 'rgba(100, 0, 0, 0.2)',
fontWeight: 'bold',
},
},
],
// No requestId = apply to ALL open documents
},
},
'*'
)
}
// Integration can also send bulk updates for all equipment
function updateAllEquipmentStyling(equipmentStatuses) {
const tagStyles = equipmentStatuses.map((eq) => ({
pattern: eq.id,
category: 'Equipment status',
style: {
color: eq.isOperational ? '#00FF00' : '#FF0000',
backgroundColor: eq.isOperational ? 'rgba(0, 100, 0, 0.2)' : 'rgba(100, 0, 0, 0.2)',
},
}))
window.parent.postMessage(
{
event: 'updateTagStyles',
data: { tagStyles }, // No requestId = applies to all documents
},
'*'
)
}
Operation Search
FieldTwin Operation provides a global search interface (Google Maps style) that allows integrations to provide searchable content and handle actions when results are selected.
operationSearch
Direction: FieldTwin → Integration
This event is sent from the host to all active integrations when the user presses Enter in the operation search input, or when the search is cleared — either via the clear button or by pressing Enter on an empty input. Integrations should listen for this event and perform a search within their own domain, or drop their previous results when clear is true.
| Attribute | Description |
|---|---|
| event | is set to operationSearch |
| query | the search string typed by the user (empty string when cleared) |
| clear | true when the search was cleared/emptied, otherwise false |
operationSearchResults
Direction: Integration → FieldTwin
Integrations should reply with this message to provide search results to the host.
| Attribute | Description |
|---|---|
| event | must be set to operationSearchResults |
| results | an array of result objects |
| results.id | a stable ID, unique within this integration's current result tree. Required for interactive results. |
| results.category | a string identifying the category of the item (for grouping, used if no tags) |
| results.tags | (optional) an array of strings. Items sharing the same tags are grouped together. |
| results.html | the HTML string to display for the result (sanitized by host) |
| results.action | (legacy, optional) the direct event name sent to the integration on a normal row click |
| results.args | (optional) an object containing arguments for the row click and generic double-click messages |
| results.actions | (optional) an array of inline icon-button descriptors; keep this list short for the compact search UI |
| results.actions[].id | a stable action ID, unique within the result |
| results.actions[].label | the accessible label and tooltip for the icon button |
| results.actions[].icon | a Font Awesome icon name such as faLocationCrosshairs or fa-file-lines |
| results.actions[].action | the integration-defined action returned in operationSearchAction |
| results.actions[].args | (optional) object returned in operationSearchAction |
| results.doubleClickAction | (optional) descriptor with action and optional args, returned in operationSearchDoubleClick |
| results.target | (optional) core to execute the legacy row action in the host, otherwise sends it to the integration |
| results.noPanel | (optional) boolean. If true, clicking the item will not open/focus the integration panel |
| results.subItems | (optional) an array of child result objects. Child results support the same interaction fields as their parent. |
| results.subItems.id | stable ID for the child result |
| results.subItems.html | the HTML string to display for the child result |
| results.subItems.action | (legacy, optional) event sent to the integration on a normal child-row click |
| results.subItems.args | (optional) arguments for the child-row interaction |
| results.subItems.actions | (optional) inline icon-button descriptors using the same shape as results.actions |
| results.subItems.target | (optional) core to execute the legacy row action in the host |
| results.subItems.noPanel | (optional) boolean. If true, clicking does not open the integration panel |
| results.subItems.icon | (optional) child-row icon: file, cube, cloud, or circle (default). This is separate from action-button icons. |
Action descriptors with missing required fields or an unknown Font Awesome icon are ignored. The host sanitizes result HTML and never accepts icon HTML from an integration.
Example
window.parent.postMessage(
{
event: 'operationSearchResults',
data: {
results: [
{
id: 'asset-123',
category: 'Assets',
html: '<strong>Asset 001</strong> - <em>Active</em>',
args: { id: 'asset-123' },
actions: [
{
id: 'focus',
label: 'Focus on asset',
icon: 'faLocationCrosshairs',
action: 'focusOnAsset',
args: { id: 'asset-123' },
},
],
doubleClickAction: {
action: 'openAsset',
args: { id: 'asset-123' },
},
subItems: [
{
id: 'log-456',
html: 'Maintenance Log',
icon: 'file',
actions: [
{
id: 'open',
label: 'Open maintenance log',
icon: 'faFileLines',
action: 'openMaintenanceLog',
args: { id: 'log-456' },
},
],
},
{
id: 'tree-789',
html: 'Subsea Tree 3D',
icon: 'cube',
args: { id: 'tree-789' },
},
],
},
],
},
},
'*'
)
operationSearchProgress
Direction: Integration → FieldTwin
Integrations can use this message to communicate search progress or status to the host. If no update is received for 30 seconds, the progress indicator will be automatically removed.
| Attribute | Description |
|---|---|
| event | must be set to operationSearchProgress |
| status | a string describing the current progress state (e.g., "Scanning database...") |
| progress | (optional) a number from 0 to 100 representing completion percentage |
| isComplete | (optional) boolean. If true, the progress indicator for this integration is hidden |
Example
window.parent.postMessage(
{
event: 'operationSearchProgress',
data: {
status: 'Querying external API...',
progress: 50,
isComplete: false,
},
},
'*'
)
Visual Filtering (Operation Mode)
FieldTwin Operation allows integrations to provide dynamic visual filters displayed as persistent buttons next to the global search bar.
visualFilteringUpdate
Direction: Integration → FieldTwin
Integrations can send this message at any time to update the list of available filters. Filters are automatically grouped by integration and sorted by integrationId to ensure a consistent UI layout.
| Attribute | Description |
|---|---|
| event | must be set to visualFilteringUpdate |
| filters | an array of filter objects |
| filters.id | unique ID for the filter |
| filters.label | display name for the button |
| filters.state | boolean indicating if the filter is currently active |
| filters.subFilters | (optional) array of sub-filters. If present, the button opens a popup |
| filters.subFilters.id | unique ID for the sub-filter |
| filters.subFilters.label | display name for the sub-filter |
| filters.subFilters.state | boolean indicating if the sub-filter is active |
Example
window.parent.postMessage(
{
event: 'visualFilteringUpdate',
data: {
filters: [
{
id: 'safety',
label: 'Safety Hazards',
state: true,
subFilters: [
{ id: 'electrical', label: 'Electrical', state: true },
{ id: 'chemical', label: 'Chemical', state: false },
],
},
],
},
},
'*'
)
visualFilterToggle
Direction: FieldTwin → Integration
When a user interacts with a filter chip or a sub-filter checkbox, FieldTwin sends a message back to the originating integration.
| Attribute | Description |
|---|---|
| event | set to visualFilterToggle |
| data.id | the ID of the parent filter |
| data.state | the new desired state (boolean) |
| data.subFilterId | (optional) the ID of the specific sub-filter toggled |
Visual Legends
Integrations can publish a small legend overlay on top of the 3D canvas. The legend is grouped by integration and replaces the previous legend from the same integration each time it is sent.
visualLegendUpdate
Direction: Integration → FieldTwin
Send this message to create, replace, or clear the legend owned by the sending integration.
| Attribute | Description |
|---|---|
| event | must be set to visualLegendUpdate |
| data.title | optional legend title |
| data.position | optional position: top-left, top-right, bottom-left, or bottom-right; defaults to top-right |
| data.items | array of legend items |
| data.items[].id | optional unique ID for this row |
| data.items[].label | display text for this row |
| data.items[].color | CSS color shown as the row swatch |
| data.visible | set to false to clear the legend |
Sending data.visible: false or an empty data.items array removes the legend for that integration.
Example
window.parent.postMessage(
{
event: 'visualLegendUpdate',
data: {
title: 'Risk',
position: 'bottom-left',
items: [
{ id: 'high', label: 'High', color: '#ff3b30' },
{ id: 'medium', label: 'Medium', color: '#ffcc00' },
{ id: 'low', label: 'Low', color: '#34c759' },
],
},
},
'*'
)
Context Menu Entries (Operation Mode)
Integrations can publish custom context menu entries that appear in the viewport context menu in Operation mode.
contextMenuUpdate
Direction: Integration → FieldTwin
Use this message to register or replace context menu entries for the sending integration.
| Attribute | Description |
|---|---|
| event | must be set to contextMenuUpdate |
| data.entries | array of menu entries |
| data.entries[].id | unique ID for this entry within the integration |
| data.entries[].label | text shown in the context menu |
| data.entries[].tooltip | (optional) tooltip text |
| data.entries[].icon | (optional) Font Awesome icon name (for example faMapMarker, faWrench) |
| data.entries[].action | integration-defined action string returned in contextMenuAction |
| data.entries[].args | (optional) object payload returned in contextMenuAction |
| data.entries[].subItems | (optional) nested entries using the same structure |
Example
window.parent.postMessage(
{
event: 'contextMenuUpdate',
data: {
entries: [
{
id: 'asset-tools',
label: 'Asset Tools',
icon: 'faWrench',
subItems: [
{
id: 'open-integration-detail',
label: 'Open details',
action: 'openDetails',
args: { source: 'context-menu' },
},
],
},
],
},
},
'*'
)
Operation Search interaction events
All result interactions are sent only to the integration that supplied the result. A normal row click keeps the legacy behavior: FieldTwin selects the result, may open its integration panel, and sends the direct event named by results.action with results.args in data. Prefer the explicit events below for new focus, open, or secondary commands.
operationSearchAction
Direction: FieldTwin → Integration
FieldTwin sends this message when the user clicks a button from results.actions or results.subItems.actions. The button interaction does not also trigger the row click or open an integration panel.
| Attribute | Description |
|---|---|
| event | set to operationSearchAction |
| integrationId | ID of the integration that supplied the result |
| resultId | the result or child-result id |
| actionId | the clicked action descriptor's id |
| action | the integration-defined action string |
| args | the action descriptor's args object, or an empty object |
operationSearchDoubleClick
Direction: FieldTwin → Integration
FieldTwin sends this message when the user double-clicks a result or child-result row. If the result links to a FieldTwin graph resource, the host also selects and focuses that resource. The second ordinary browser click is ignored, so the legacy row action is not repeated.
| Attribute | Description |
|---|---|
| event | set to operationSearchDoubleClick |
| integrationId | ID of the integration that supplied the result |
| resultId | the result or child-result id |
| action | (optional) doubleClickAction.action from the result |
| args | doubleClickAction.args, or the result's args when no double-click action was declared |
window.addEventListener('message', (event) => {
const message = event.data
if (message?.event === 'operationSearchAction' && message.action === 'focusOnAsset') {
focusOnAsset(message.args.id)
}
if (message?.event === 'operationSearchDoubleClick' && message.action === 'openAsset') {
openAsset(message.args.id)
}
})
contextMenuAction
Direction: FieldTwin → Integration
When a user clicks a context menu item defined by contextMenuUpdate, FieldTwin sends a contextMenuAction message to the originating integration.
| Attribute | Description |
|---|---|
| event | set to contextMenuAction |
| action | the action string from the clicked entry |
| args | the args object from the clicked entry (if any) |
| integrationId | integration ID that registered the menu entry |
| cursorPosition | (optional) position of the clicked point. Omitted when no point could be resolved. |
The cursorPosition object describes where the context menu was opened:
| Attribute | Description |
|---|---|
| cursorPosition.x | X coordinate of the clicked point, in the project CRS |
| cursorPosition.y | Y coordinate of the clicked point, in the project CRS |
| cursorPosition.z | Z coordinate (elevation) of the clicked point |
| cursorPosition.latitude | (optional) WGS84 latitude, set only when reprojection succeeds |
| cursorPosition.longitude | (optional) WGS84 longitude, set only when reprojection succeeds |
Example
window.addEventListener('message', (event) => {
const msg = event.data
if (msg?.event !== 'contextMenuAction') {
return
}
if (msg.action === 'openDetails') {
const { latitude, longitude } = msg.cursorPosition || {}
openDetails(msg.args, { latitude, longitude })
}
})
Operation UI Navigation
openOperationPanel
Direction: Integration → FieldTwin
Integrations can use this message to request the FieldTwin UI to open or focus on a specific integration panel. This is particularly useful for opening Dynamic Pages from another integration or from a Global integration.
| Attribute | Description |
|---|---|
| event | must be set to openOperationPanel |
| data.path | (optional) the specific path of a Dynamic Page to open. This should match the page path returned by dynamicPagesUrl (or the generated fallback path such as page-1 if omitted). |
| data.integrationId | (optional) the ID of the integration to open. Defaults to the sending integration's ID. |
Example
window.parent.postMessage(
{
event: 'openOperationPanel',
data: {
integrationId: 'my-asset-manager',
path: '/details/asset-123',
},
},
'*'
)
operationPaneClosed
Direction: FieldTwin → Integration
Sent by FieldTwin to an integration when its operation panel is closed in the right-side operation HUD (the user clicks the panel/tab close button, or closes all tabs). Use it to release resources, stop polling, or reset state that only makes sense while the panel is visible. This message is delivered only to the integration whose panel was closed.
| Attribute | Description |
|---|---|
| event | is set to operationPaneClosed |
| customTabId | the ID of the integration whose panel was closed |
Example
window.addEventListener('message', (event) => {
if (event.data?.event !== 'operationPaneClosed') {
return
}
// The panel is no longer visible - stop work tied to being shown.
stopPolling()
})
Time Series
The Time Series Golden Layout panel allows integrations to publish multi-channel time-series data that engineers can inspect, zoom, measure, and export directly inside FieldTwin. This topic covers timeline updates, opening the panel, publishing series metadata, and requesting series data.
timelineTimeUpdate
Direction: FieldTwin → Integration
This event is sent when the current time in the Timeline Viewer changes.
| Attribute | Description |
|---|---|
| event | is set to timelineTimeUpdate |
| time | the current timeline time as an ISO 8601 timestamp |
displayTimeSeries
Direction: Integration → FieldTwin
Sent by the integration to open the Time Series panel in the right-side operation HUD.
All series already registered via timeSeriesInfo will be available in the panel immediately.
| Attribute | Description |
|---|---|
| event | must be set to displayTimeSeries |
Example
window.parent.postMessage({ event: 'displayTimeSeries' }, '*')
timeSeriesInfo
Direction: Integration → FieldTwin
Sent by the integration to publish its available time series. The host registers the metadata and the Time Series panel displays these series in its tree. Send this on startup and whenever your dataset changes.
| Attribute | Description |
|---|---|
| event | must be set to timeSeriesInfo |
| data.series | array of series descriptor objects (see below) |
| data.replaceExisting | (optional, default false) when true, all series previously registered by this integration are removed before the new list is applied; when false the lists merge |
Each series descriptor:
| Field | Description |
|---|---|
| id | Unique series identifier (scoped to this integration) |
| name | Display name shown in the series tree |
| unit | Y-axis unit string (e.g. bar, °C, m/s). Series with the same unit share a Y axis. |
| xMin | Minimum X value of the full dataset |
| xMax | Maximum X value of the full dataset |
| sampleCount | Total number of samples in the full dataset (used to compute the downsampling ratio) |
| xAxisTitle | Label for the X axis |
| yAxisTitle | Label for the Y axis |
| color | (optional) hex color string - auto-assigned if omitted |
Example
window.parent.postMessage(
{
event: 'timeSeriesInfo',
data: {
replaceExisting: true, // omit or set false to merge with existing series
series: [
{
id: 'pressure',
name: 'Wellhead Pressure',
unit: 'bar',
xMin: 0,
xMax: 86400,
sampleCount: 86400,
xAxisTitle: 'Time (s)',
yAxisTitle: 'Pressure',
},
{
id: 'temperature',
name: 'Fluid Temperature',
unit: '°C',
xMin: 0,
xMax: 86400,
sampleCount: 86400,
xAxisTitle: 'Time (s)',
yAxisTitle: 'Temperature',
},
],
},
},
'*'
)
getTimeSeriesData
Direction: FieldTwin → Integration
Sent by the Time Series panel to request downsampled data for a specific viewport window.
The host automatically calculates an appropriate sampleCount based on the viewport pixel width
(capped at 2 × pixel width or 4096) to avoid loading unnecessary data.
This message is sent only to the integration that owns the series (matched by customTabId).
| Attribute | Description |
|---|---|
| event | set to getTimeSeriesData |
| data.seriesId | the id of the series to fetch (as provided in timeSeriesInfo) |
| data.reqId | unique request correlation ID - must be echoed back in the reply |
| data.xMin | start of the requested data window |
| data.xMax | end of the requested data window |
| data.sampleCount | maximum number of samples to return |
Example (integration side)
window.addEventListener('message', (event) => {
if (event.data?.event !== 'getTimeSeriesData') return
const { seriesId, reqId, xMin, xMax, sampleCount } = event.data.data
const buffer = buildSampledBuffer(seriesId, xMin, xMax, sampleCount)
// Transfer the buffer to avoid copying
event.source.postMessage({ event: 'timeSeriesData', data: { reqId, buffer } }, event.origin, [buffer])
})
timeSeriesData
Direction: Integration → FieldTwin
Reply to getTimeSeriesData. Contains a binary ArrayBuffer of Float64 values and a
stride field that selects the encoding:
Stride 2 (legacy / simple): [x0, y0, x1, y1, …] - one mean value per sample.
Stride 4 (envelope): [x0, mean0, min0, max0, x1, mean1, min1, max1, …] - mean plus the
min/max spread for each sample, used to render a shaded confidence band in the chart.
Passing the buffer as a transferable (third argument to postMessage) avoids copying and is
strongly recommended.
| Attribute | Description |
|---|---|
| event | must be set to timeSeriesData |
| data.reqId | the reqId from the getTimeSeriesData request |
| data.buffer | ArrayBuffer - see stride encoding above |
| data.stride | 2 (default, legacy) or 4 (envelope with min/max). Omitting defaults to 2. |
Example (stride 4 - min/max envelope)
function buildSampledBuffer(seriesId, xMin, xMax, sampleCount) {
// Each sample produces 4 Float64 values: x, mean, min, max.
const arr = new Float64Array(sampleCount * 4)
const step = (xMax - xMin) / Math.max(1, sampleCount - 1)
for (let i = 0; i < sampleCount; i++) {
const x = xMin + i * step
const { mean, min, max } = sampleEnvelope(seriesId, x)
arr[i * 4] = x
arr[i * 4 + 1] = mean
arr[i * 4 + 2] = min
arr[i * 4 + 3] = max
}
return arr.buffer
}
window.addEventListener('message', (event) => {
if (event.data?.event !== 'getTimeSeriesData') return
const { seriesId, reqId, xMin, xMax, sampleCount } = event.data.data
const buffer = buildSampledBuffer(seriesId, xMin, xMax, sampleCount)
event.source.postMessage({ event: 'timeSeriesData', data: { reqId, buffer, stride: 4 } }, event.origin, [buffer])
})
User-defined messages
Direction: FieldTwin → Integration
For metadata of type "button", the administrator can define a custom message to be sent when the user
clicks on the button. The message contains project and related item information, and event will be
set to the value saved in the metadata definition.