Commands
Commands
Commands are bound to toolbars, defining menu actions and their execution logic. Each toolbar menu item specifies which service to execute or what action to perform, such as opening a form in a modal, adding a new object, or deleting a selected row. Commands include a settings configuration where you can define the related service and refresh behavior based on the use case.
"commands": {
"csvExport" : {
"type": "export",
"label": "CSV Export",
"settings": {
"columns": ["title", "description"],
"excludeHiddenColumn": true
}
},
"createNewCR" : {
"type": "genericService",
"label": "Create Change Request",
"isMenu": false,
"settings": {
"relatedConfig": {
"services": [
{
"name": "CreateChangeRequest"
}
],
"useSavedContextId":true,
"refreshBehavior": "removeRow"
}
}
},
}
Command properties
| Prop Name | Description | Data Type | Required | Example |
|---|---|---|---|---|
name | Unique identifier for the command. | string | yes | "createIssue" |
label | Display text for the command in the UI. | string | yes | "Create Issue" |
icon | Bundled icon shown on the command — a lucide icon name, or a path/URL to an image. See Icons. | string | no | "FileOutput" |
ds-icon | 3DEXPERIENCE icon-font classes, used in preference to icon when the shell's icon font is loaded. See Icons. | string | no | "fonticon fonticon-export-to-csv" |
icon-color | CSS colour applied to whichever icon is rendered. See Icons. | string | no | "#c0392b" |
type | Command type (e.g., form 1) launchFormAsModal this is used to popup form with data.genericService without form perform action behind service call trigger. | string | no | "launchFormAsModal" |
settings | Configuration specific to the command type. | object | no | {"config": {"type": "form", "id": "createNewIssue"}} or {"relatedConfig": {"name": "CreateChangeRequest"}} |
isMenu | Displays as a menu if true. Defaults to false if omitted. | boolean | no | false |
Command settings properties
Below sub elements define how command settings should be configured
| Property Name | Description | Data Type | Required | Example |
|---|---|---|---|---|
refreshBehavior | Defines how the UI refreshes after execution. Possible values: refreshRow — refreshes the selected row from graph data. refreshParents — reserved; currently does nothing. refreshRowWithColumnService — refreshes the row using a column-specific service (network call). expandRow — expands the selected row from the graph datasets alone, with no service call. expandRowWithService — expands the row, calling the expand service to fetch children that are not in the graph yet. removeRow — removes the selected row. syncRows — table scoped: re-runs the table's datasets against the graph, inserting the rows that are new (and dropping the ones that are gone); needs no selected row, so it is the one to use after a service that creates an object. Ignored when responseMapping is defined. | string | no | "refreshRow" |
relatedConfig | Defines the service to call and how to refresh the row after execution. | object | no | |
responseMapping | Maps fields from the service response directly to graph node attributes, triggering an instant row re-render without an extra network call. Single object { expression, attribute } or an array for multiple mappings. See responseMapping below. | object or array | no | { "expression": "results[0].maturityState", "attribute": "state" } |
service | Controls if to select data from nodes or edges in dataset. | boolean | no | true |
useSavedContextId | Runs the service on the widget's saved context — the object dropped on the widget — instead of the selected row. Ignored when the widget has no saved context; the service is then called without a context. See Which context a genericService runs on. | boolean | no | true |
downloadSettings | Controls how files are named and organised inside ZIP archives produced by file/derived-output download commands (e.g. downloadDerivedOutputFilesFromSelection). See downloadSettings below. | object | no | { "folderNameFormat": "$title_$revision", "fileName": { "default": "$enterpriseItemNumber_$title", "excludeOriginalName": true } } |
downloadSettings
downloadSettings configures the folder hierarchy and file naming for ZIP archives built by selection-download commands (related docs, derived output files, etc.). All token names prefixed with $ resolve to the corresponding row cell value (e.g. $title, $enterpriseItemNumber, $revision, $name, $type). Tokens without a $ prefix are treated as literal text. It also carries the behaviour toggles for the filtered download commands - narrowByFilter, selectAllWhenNoneSelected, and groupByDocument.
"settings": {
"downloadSettings": {
"folderNameFormat": "$title_$enterpriseItemNumber_$revision",
"fileName": {
"default": "$enterpriseItemNumber_$title_$revision",
"excludeOriginalName": true,
"conditions": [
{
"type": "Drawing",
"fileNamePrefix": "$title_$name_$revision"
}
]
}
}
}
| Property | Description | Data Type | Required | Example |
|---|---|---|---|---|
folderNameFormat | Template used for every folder segment in the ZIP path. Built from the selected row plus its ancestors (each ancestor contributes one segment). Empty resolved tokens are kept as empty segments so the separator layout is preserved (e.g. Doc__A when the middle field is missing). Accepts a string or a conditional object (see Conditional formats). Defaults to $title. | string or object | no | "$title_$enterpriseItemNumber_$revision" |
fileName | Controls how each file inside the ZIP is named (prefix + original filename, or prefix-only). | object | no | see example above |
fileName.default | Default prefix template applied to every downloaded file. Empty resolved tokens are dropped from the prefix (e.g. EIN__REV becomes EIN_REV). When omitted, files keep their original name as returned by the download ticket. | string | no | "$enterpriseItemNumber_$title_$revision" |
fileName.excludeOriginalName | When true, the original filename is dropped and only prefix + extension is written. When false (default), the file is named prefix_originalName. | boolean | no | true |
fileName.conditions | Per-row-type overrides for fileNamePrefix (and optionally excludeOriginalName). The first condition whose type matches the row's type cell (case-insensitive) wins; otherwise fileName.default is used. | array<object> | no | see example above |
narrowByFilter | Filtered download commands only. When true (default) only the files left by the active column filter are included; set false to always include every file regardless of the filter. | boolean | no | true |
selectAllWhenNoneSelected | Filtered download commands only. When true (default) the command acts on every row currently shown when nothing is selected; set false to require an explicit row selection. | boolean | no | true |
groupByDocument | Filtered download commands only. When true, each file is nested in an extra folder named after its parent document/derived output (on top of the folderNameFormat hierarchy). Defaults to false, so files sit directly in the row folder. The extra folder is skipped for any parent that has no resolvable title. | boolean | no | false |
Conditional formats
Both folderNameFormat and fileName.conditions[] can override the default by row type. Useful when one row type needs a different naming scheme (e.g. Drawings using $name rather than $enterpriseItemNumber).
fileName.conditions[] entry shape:
| Field | Description | Data Type | Required |
|---|---|---|---|
type | Row type to match against the row's type cell (case-insensitive). | string | yes |
fileNamePrefix | Prefix template used when this condition matches. | string | yes |
excludeOriginalName | Optional per-condition override for the parent fileName.excludeOriginalName flag. | boolean | no |
folderNameFormat may also be expressed as a conditional object:
"folderNameFormat": {
"default": "$title_$enterpriseItemNumber_$revision",
"conditions": [
{ "type": "Drawing", "format": "$title_$revision" }
]
}
The matching format is applied per row — including ancestor segments — so a Drawing ancestor uses the Drawing format while sibling segments fall back to default.
Downloading only the filtered files
downloadRelatedDocsFilesFromSelection downloads every related file of the selected rows. Its counterpart downloadFilteredRelatedDocsFilesFromSelection downloads only the files still visible after the active column filter has narrowed a multi-value Documents/Files column — useful when a user filters a large file list down to a handful and wants just those.
There are two command families, one per data source, and each has an "all files" and an "only filtered files" variant:
| Data source | All files | Only filtered files |
|---|---|---|
| Related documents (actions column) | downloadRelatedDocsFilesFromSelection | downloadFilteredRelatedDocsFilesFromSelection |
| Derived outputs (locate service) | downloadDerivedOutputFilesFromSelection | downloadFilteredDerivedOutputFilesFromSelection |
All four are export-type commands wired through onClick.
The two families are not interchangeable. The related-docs commands read the actions column; the derived-output commands call locateServiceName and ignore it. Pointing a derived-output table at a related-docs command finds no actions column and reports No downloadable … files were found, even though every service in the settings is correct.
Example — document files
From app-docs-by-ups.json, where the files hang off a related-documents actions column:
"downloadRelatedDocumentsFilesCommand": {
"label": "Download Selected Files",
"icon": "FileStack",
"type": "export",
"onClick": "downloadRelatedDocsFilesFromSelection",
"settings": {
"downloadServiceName": "dsapi-documents-files-DownloadTicket",
"relationMode": "both",
"actionsColumnField": "relatedDocumentFilesActions"
}
},
"downloadRelatedDocumentsFilteredFilesCommand": {
"label": "Download filtered files",
"icon": "FileStack",
"type": "export",
"onClick": "downloadFilteredRelatedDocsFilesFromSelection",
"settings": {
"downloadServiceName": "downloadDocumentFile",
"actionsColumnField": "relatedDocumentFilesActions"
}
}
downloadServiceName resolves the per-file ticket service — here dsapi-documents-files-DownloadTicket, inherited through extends, which calls PUT /resources/v1/modeler/documents/{{parentContextId}}/files/{{contextId}}/DownloadTicket. {{parentContextId}} is the document, {{contextId}} the file.
Example — derived outputs
From app-derived-by-ups.json. The files are discovered by a locate service instead of a column, so locateServiceName is required and actionsColumnField names the column carrying the ids (derivedOutputAction, whose data.select is "id"):
"downloadFilteredDerivedOutputFilesCommand": {
"label": "Download filtered files",
"icon": "FileStack",
"type": "export",
"onClick": "downloadFilteredDerivedOutputFilesFromSelection",
"settings": {
"actionsColumnField": "derivedOutputAction",
"downloadServiceName": "dsdo-derivedOutputFiles-downloadTicket",
"locateServiceName": "dsdo-derivedOutputs-locate",
"downloadService": {
"uri": "/resources/v1/modeler/dsdo/dsdo:DerivedOutputs/{{parentContextId}}/dsdo:DerivedOutputFiles/{{contextId}}/DownloadTicket",
"method": "POST",
"payload": {},
"addCSRFToken": true,
"securityContextHeaderPattern": "ctx::{{securityContext}}"
},
"locateService": {
"uri": "/resources/v1/modeler/dsdo/dsdo:DerivedOutputs/Locate",
"method": "POST",
"addCSRFToken": true,
"securityContextHeaderPattern": "ctx::{{securityContext}}",
"uriParams": { "$mask": "dsmvdo:DerivedOutputsMask.Details" }
},
"includeDownloadableOnly": true,
"referencedObjectSource": "3DSpace",
"defaultReferencedObjectType": "VPMReference"
}
}
Swap onClick to downloadDerivedOutputFilesFromSelection for the unfiltered variant; the settings are otherwise identical.
downloadService and locateService are inline fallbacks used when the named service is not found in the merged config — handy while a config is still being wired up. downloadServiceName is still required as the lookup key even when the inline definition is supplied.
Behaviour notes:
- With no rows selected it acts on every row currently shown — the same result as selecting them all by hand. Under an active filter that means only the rows that survived it; rows the filter removed are never downloaded. (The standard
downloadRelatedDocsFilesFromSelectionstill requires an explicit selection.) - Each file is fetched through the same per-file download-ticket service as the standard command (
PUT /{docId}/files/{fileId}/DownloadTicket), then all files are combined into a single ZIP client-side. - Files can be nested one folder per document inside the ZIP, on top of the row folder hierarchy from
downloadSettings, by settingdownloadSettings.groupByDocumenttotrue. The folder is named after the document's title; when a parent has no resolvable title the extra folder is skipped (rather than falling back to the raw id). Name collisions are de-duplicated with a numeric suffix. Grouping is off by default. - A file reachable from more than one row (e.g. a parent and its child) is archived once, since acting on every row would otherwise pick it up repeatedly.
- A row keeps all of its files when the column filter did not narrow it, so with no filter active the filtered command produces the same files as its unfiltered counterpart.
- A parent row that is shown only because one of its children matched (the tree keeps ancestors so the structure stays readable) contributes no files: its own files did not pass the filter. Filtering
Format = DWGon a derived-outputs table therefore downloads the drawing's DWG files but not the PDF/STEP files of the part above it. This holds even when that parent row is selected explicitly: selecting rows by hand gives the same result as acting on every shown row, so the filter is never bypassed. - Narrowing is matched per row on either the file id or its parent id, so filtering on a document keeps that document's files.
- While a download runs, a progress toast reports each phase (
Downloading file 3 of 12…,Creating ZIP archive…) so a long run does not look like an unresponsive button.
Choosing actionsColumnField
actionsColumnField must name the column whose cell values are the file (or parent) ids — that is what the kept ids are matched against. In the examples above that is relatedDocumentFilesActions for documents and derivedOutputAction for derived outputs.
It does not have to be the column the user filtered: when one multi-value column is narrowed, its siblings of equal length are narrowed at the same positions, so pointing at the id-bearing column works whichever column the filter was applied to.
If it names a column that does not exist on the table, no files are found and the command reports No downloadable … files were found.
No downloadable … files were found almost always means the command cannot see the files at all, rather than that the filter excluded them. Check, in order: the command belongs to the right family for the table's data source; actionsColumnField names a column that exists; and the utility name in onClick has no stray whitespace — the lookup is an exact match, so "…FromSelection " silently resolves to nothing.
responseMapping
responseMapping is a single object or an array of objects applied to the raw HTTP response body after a genericService call. Each entry evaluates a JSONata expression against the response and writes the result to a graph node attribute, which is immediately reflected in the table row — no second network call needed.
"promote": {
"type": "genericService",
"label": "Promote",
"settings": {
"relatedConfig": {
"services": ["promoteItemService"],
"responseMapping": { "expression": "results[0].maturityState", "attribute": "state" }
}
}
}
For multiple attributes, pass an array:
"relatedConfig": {
"services": ["promoteItemService"],
"responseMapping": [
{ "expression": "results[0].maturityState", "attribute": "state" },
{ "expression": "results[0].owner", "attribute": "owner" }
]
}
| Field | Description | Type | Required |
|---|---|---|---|
expression | JSONata expression evaluated against the raw HTTP response body. | string | yes |
attribute | Graph node attribute to write the result to. Must match the data field of the target column definition. | string | yes |
When to use responseMapping vs refreshBehavior
| Scenario | Use |
|---|---|
| The service response contains the updated attribute values | responseMapping — instant update, no extra network call |
| The service response does not include the updated data | refreshBehavior: "refreshRowWithColumnService" — re-fetches column services |
Which context a genericService runs on
A genericService command resolves the context id it passes to the service in this order:
useSavedContextId: true— the first object of the widget's saved context (what was dropped on the widget).- The selected rows — one service call per selected row.
- The first root row of the table.
All three can be empty: a widget with requiresContext: false that lists objects from a nodes dataset has no saved context, and nothing needs to be selected. That is not an error — the command then calls the service once, without a context id, which is exactly what services that create an object need:
"createRequirementSpecification": {
"label": "Create Requirement Specification",
"type": "genericService",
"settings": {
"relatedConfig": {
"services": [{ "name": "createRequirementSpecification" }]
}
}
}
If the service does need a context — its uri or payload contains {{contextId}} — and none is available, the command reports Context ID is missing but required for this operation instead of silently doing nothing.
Showing the created object in the table
A create service returns the object it created, and its response is persisted into the graph like any other service response. Add refreshBehavior: "syncRows" and the table re-runs its own datasets against the graph, so the new object appears as a row — no second search call:
"settings": {
"relatedConfig": {
"services": [{ "name": "createRequirementSpecification" }],
"refreshBehavior": "syncRows"
}
}
Rows that appear this way are highlighted for a few seconds by the table itself — nothing to configure. Two things to check when the row does not show up:
- The created node must match the table's dataset statement.
"statement": "[type = 'Requirement Specification']"only matches if the create response carries thattype— widen the$maskif it does not. - New rows are appended, so with a long list the row lands at the bottom (and on the last page when pagination is on). Sort the table on a creation-ordered column to bring it into view.
Several services on one command
relatedConfig.services is a candidate list, not a sequence: the command calls the first entry whose condition matches the row it runs on, and stops there. That is how one toolbar button serves several row types — order the entries most specific first, and leave an unconditioned fallback last:
"settings": {
"relatedConfig": {
"services": [
{ "name": "promotePart" },
{ "name": "promoteDocument" },
{ "name": "promoteGeneric" }
],
"refreshBehavior": "refreshRowWithColumnService"
}
}
With promotePart conditioned on [type='Part'] and promoteDocument on [type='Document'], a Part row calls the first, a Document row the second, and anything else falls through to promoteGeneric. When every entry is conditioned and none matches, the command reports No service on this command applies to the selected row. rather than doing nothing.
The condition is evaluated against the row the command runs on, so a command with no context — a create service — always takes the first entry.
Only one service is called per row. To call a second service after the first, chain it with the service's own services property rather than by listing it here.
Icons
A command, submenu or toolbar entry can carry three icon keys. icon and ds-icon are not alternatives — set both and the widget renders whichever one the runtime can actually produce.
| Key | Description |
|---|---|
icon | A lucide icon name ("Focus", "ListTree"), or a path/URL to an image file. Bundled with the widget, so it renders anywhere. |
ds-icon | Space-separated CSS classes for a 3DEXPERIENCE icon-font glyph, e.g. "fonticon fonticon-group-add". Renders only inside the 3DDashboard shell, which is what loads the icon font (/Core/wux-3ds-fonticons.css). |
icon-color | Optional CSS colour for whichever of the two ends up rendered — any value CSS accepts ("#c0392b", "tomato", "rgb(192 57 43)"). Omit it and the icon takes its colour from the stylesheet, as before. |
Resolution order
ds-icon, if its classes resolve to a real glyph. The widget probes the class at runtime — it reads the::beforecontent the icon font injects — so a class that styles nothing counts as absent.iconas an image, when the value looks like a path or URL (contains/or., or starts withhttp). Rendered as an<img>.iconas a lucide glyph — the bundled fallback.- Nothing, when neither key is set. The icon slot collapses so labels in a list stay flush left.
ds-icon needs its own key rather than being detected from icon: icon-font classes contain neither / nor ., so the image heuristic in step 2 could never tell the two apart.
Colouring the icon
icon-color is applied to whatever step 1–3 above selected, so you set it once and it works whether the glyph or the fallback ends up on screen:
"promote": {
"label": "Promote",
"icon": "ArrowUp",
"ds-icon": "fonticon fonticon-arrow-up",
"icon-color": "#2e7d32"
}
An icon that resolves to an image (step 2) is drawn as an <img>, and a CSS colour cannot repaint a picture. icon-color is silently ignored there. It applies to the icon-font glyph and the lucide icon only.
The colour lands as an inline style, so it beats the stylesheet — including the widget shell's own toolbar styling. That also means a themed toolbar will not restyle a command you have coloured; leave icon-color off wherever you want the icon to follow the theme.
icon as wellds-icon is inert wherever the icon font is not loaded — Storybook, standalone hosts, any consumer outside the DS widget shell. Pairing it with icon keeps a recognisable glyph in those places. A command resolved through a toolbar, menu or context-menu reference falls back to a generic Square when icon is omitted; a submenu's own parent entry has no default and simply shows no icon.
Where it is accepted
ds-icon and icon-color are valid at every level icon is:
| Location | Applies to |
|---|---|
A definition in the top-level commands map | The command wherever it is used |
A reference inside toolbars[].commands | That toolbar's button only |
A definition in the top-level menus map | The submenu's parent entry |
An entry in a contextMenus array | That context-menu item only |
An app entry in an openWith command's openApps | That app's row in the Open With submenu |
When a command carries ds-icon or icon-color in both its definition and the reference that uses it, the reference wins — the same override rule label and settings already follow. That lets one toolbar tint a shared command differently from another.
Example
"commands": {
"csvExport": {
"type": "export",
"label": "CSV Export",
"icon": "FileOutput",
"ds-icon": "fonticon fonticon-export-to-csv"
}
},
"toolbars": [
{
"name": "toolbar",
"commands": {
"csvExport": {
"isMenu": false,
"icon": "FileDown",
"ds-icon": "fonticon fonticon-export-to-csv",
"icon-color": "#1a73e8"
}
}
}
]
Inside 3DDashboard this shows the fonticon-export-to-csv glyph in #1a73e8. Elsewhere it falls back to the lucide FileDown from the toolbar reference — which overrides the FileOutput on the command definition — in the same colour.
A submenu parent uses the same three keys:
"menus": [
{
"name": "stateActions",
"label": "State Actions",
"icon": "Workflow",
"ds-icon": "fonticon fonticon-collaborative-lifecycle-management",
"icon-color": "#6a1b9a",
"commands": [{ "name": "promote" }, { "name": "demote" }]
}
]
Finding class names
The glyph classes come from the platform's icon font, loaded by the widget shell from /Core/wux-3ds-fonticons.css. The class is always the pair fonticon fonticon-<glyph-name>. Read the available <glyph-name> values from that stylesheet in your 3DEXPERIENCE environment, or inspect an existing 3DDashboard button in the browser dev tools and copy its classes. A name that does not exist in the font is not an error — the widget silently falls back to icon.
Panel commands
Panel commands use type: "panel". Clicking the command opens the panel and mounts the configured form. The panel closes from form button actions configured on the form (settings.closePanelOn), not from a separate close command.
See Layout — Panel slots and Forms — Panel lifecycle.
panel
| Setting | Type | Required | Description |
|---|---|---|---|
panelId | string | yes | Layout key of the panel slot (e.g. "assetSidePanel"). |
formId | string | yes | Form definition name to render in the panel. |
useSelectedRow | boolean | no | Table toolbar only. Bind the form to the table's selected row. Requires exactly one selected row. |
useRowContext | boolean | no | Table / column icons only. Same as useSelectedRow, for icon handlers that pass objectId. |
collapsePanelIds | array<string> | no | Optional panel ids to collapse when this command opens a panel (e.g. close edit panel when opening create). |
Form toolbars ignore useSelectedRow. Panel commands on a form always bind the target form using the source form's context (selected rows, context ids, or current bean row).
Table toolbar example (uses selected table row):
"openEditForm": {
"type": "panel",
"label": "Edit Asset",
"settings": {
"panelId": "assetSidePanel",
"formId": "editForm",
"useSelectedRow": true
}
}
Form toolbar example (uses source form context — no useSelectedRow):
"openEditForm": {
"type": "panel",
"label": "Edit Asset",
"settings": {
"panelId": "assetSidePanel",
"formId": "editForm"
}
}
Column icons
Column icon mappings can open a panel with actionName: "panel":
"callback": {
"clickHandler": true,
"actionName": "panel",
"panelId": "assetSidePanel",
"formId": "editForm",
"useRowContext": true
}
Edit command
The built-in edit command toggles the host form between read and edit mode. It is bound on form toolbars only (ignored on table toolbars). Define it under commands with label and icon only — no type.
"commands": {
"edit": {
"label": "Edit",
"icon": "Pencil"
}
},
"toolbars": {
"droppedEditFormToolbar": {
"commands": {
"edit": { "name": "edit" }
}
}
}