Skip to main content

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 NameDescriptionData TypeRequiredExample
nameUnique identifier for the command.stringyes"createIssue"
labelDisplay text for the command in the UI.stringyes"Create Issue"
iconBundled icon shown on the command — a lucide icon name, or a path/URL to an image. See Icons.stringno"FileOutput"
ds-icon3DEXPERIENCE icon-font classes, used in preference to icon when the shell's icon font is loaded. See Icons.stringno"fonticon fonticon-export-to-csv"
icon-colorCSS colour applied to whichever icon is rendered. See Icons.stringno"#c0392b"
typeCommand type (e.g., form 1) launchFormAsModal this is used to popup form with data.genericService without form perform action behind service call trigger.stringno"launchFormAsModal"
settingsConfiguration specific to the command type.objectno{"config": {"type": "form", "id": "createNewIssue"}} or {"relatedConfig": {"name": "CreateChangeRequest"}}
isMenuDisplays as a menu if true. Defaults to false if omitted.booleannofalse

Command settings properties​

Below sub elements define how command settings should be configured

Property NameDescriptionData TypeRequiredExample
refreshBehaviorDefines 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.stringno"refreshRow"
relatedConfigDefines the service to call and how to refresh the row after execution.objectno
responseMappingMaps 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 arrayno{ "expression": "results[0].maturityState", "attribute": "state" }
serviceControls if to select data from nodes or edges in dataset.booleannotrue
useSavedContextIdRuns 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.booleannotrue
downloadSettingsControls how files are named and organised inside ZIP archives produced by file/derived-output download commands (e.g. downloadDerivedOutputFilesFromSelection). See downloadSettings below.objectno{ "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"
}
]
}
}
}
PropertyDescriptionData TypeRequiredExample
folderNameFormatTemplate 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 objectno"$title_$enterpriseItemNumber_$revision"
fileNameControls how each file inside the ZIP is named (prefix + original filename, or prefix-only).objectnosee example above
fileName.defaultDefault 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.stringno"$enterpriseItemNumber_$title_$revision"
fileName.excludeOriginalNameWhen true, the original filename is dropped and only prefix + extension is written. When false (default), the file is named prefix_originalName.booleannotrue
fileName.conditionsPer-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>nosee example above
narrowByFilterFiltered 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.booleannotrue
selectAllWhenNoneSelectedFiltered 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.booleannotrue
groupByDocumentFiltered 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.booleannofalse

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:

FieldDescriptionData TypeRequired
typeRow type to match against the row's type cell (case-insensitive).stringyes
fileNamePrefixPrefix template used when this condition matches.stringyes
excludeOriginalNameOptional per-condition override for the parent fileName.excludeOriginalName flag.booleanno

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 sourceAll filesOnly filtered files
Related documents (actions column)downloadRelatedDocsFilesFromSelectiondownloadFilteredRelatedDocsFilesFromSelection
Derived outputs (locate service)downloadDerivedOutputFilesFromSelectiondownloadFilteredDerivedOutputFilesFromSelection

All four are export-type commands wired through onClick.

warning

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 downloadRelatedDocsFilesFromSelection still 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 setting downloadSettings.groupByDocument to true. 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 = DWG on 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.

Troubleshooting

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" }
]
}
FieldDescriptionTypeRequired
expressionJSONata expression evaluated against the raw HTTP response body.stringyes
attributeGraph node attribute to write the result to. Must match the data field of the target column definition.stringyes

When to use responseMapping vs refreshBehavior

ScenarioUse
The service response contains the updated attribute valuesresponseMapping — instant update, no extra network call
The service response does not include the updated datarefreshBehavior: "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:

  1. useSavedContextId: true — the first object of the widget's saved context (what was dropped on the widget).
  2. The selected rows — one service call per selected row.
  3. 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 that type — widen the $mask if 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.

note

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.

KeyDescription
iconA lucide icon name ("Focus", "ListTree"), or a path/URL to an image file. Bundled with the widget, so it renders anywhere.
ds-iconSpace-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-colorOptional 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​

  1. ds-icon, if its classes resolve to a real glyph. The widget probes the class at runtime — it reads the ::before content the icon font injects — so a class that styles nothing counts as absent.
  2. icon as an image, when the value looks like a path or URL (contains / or ., or starts with http). Rendered as an <img>.
  3. icon as a lucide glyph — the bundled fallback.
  4. 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"
}
Image icons are not recoloured

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.

tip
Set icon as well

ds-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:

LocationApplies to
A definition in the top-level commands mapThe command wherever it is used
A reference inside toolbars[].commandsThat toolbar's button only
A definition in the top-level menus mapThe submenu's parent entry
An entry in a contextMenus arrayThat context-menu item only
An app entry in an openWith command's openAppsThat 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​

SettingTypeRequiredDescription
panelIdstringyesLayout key of the panel slot (e.g. "assetSidePanel").
formIdstringyesForm definition name to render in the panel.
useSelectedRowbooleannoTable toolbar only. Bind the form to the table's selected row. Requires exactly one selected row.
useRowContextbooleannoTable / column icons only. Same as useSelectedRow, for icon handlers that pass objectId.
collapsePanelIdsarray<string>noOptional 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" }
}
}
}