Workshop

Workshop is Palantir Foundry's no-code/low-code application builder that lets operators and analysts create interactive, data-driven apps directly on top of the Ontology without writing frontend code. It exposes a drag-and-drop canvas where builders compose layouts from pages, widgets, variables, and events to produce full operational applications. Workshop apps consume live Ontology data through object sets, surface action buttons for governed write-back, and update in real time as underlying pipeline data changes.

Building Blocks

Layouts are the structural containers of a Workshop application. Pages are the top-level navigable screens. Sections are horizontal bands within a page that group related content. Columns and rows provide fine-grained grid positioning within sections. Overlays are modal panels that render on top of the current page — used for detail views and confirmation dialogs. Loops repeat a layout template once per item in a list variable, enabling dynamic lists without manual widget duplication. Widgets are the visual elements placed inside layouts. Workshop ships with over 30 built-in widget types spanning data display (Object Table, Chart, Map, Metric Card), data input (Text Input, Dropdown, Date Picker, Toggle), navigation (Button, Link), and structural decoration (Divider, Text Block, Image). Every widget exposes a set of configuration bindings that connect it to variables, object sets, and events. Variables hold application state at runtime. Each variable has a declared type and an optional default value. Widgets read from variables to determine what to display and write to variables when the user interacts with them. Variables can also be computed from other variables through derived expressions, letting you build reactive data flows without event wiring. Events are the glue between user interactions and variable mutations. Each event is attached to a widget (e.g., the onClick of a Button) and carries a sequence of actions: set a variable, invoke an action type, navigate to a page, or open an overlay. Events execute in order and can be conditionally gated on variable values.
Variable Types
TypeUse CaseExample
stringStore text input values, filter keywords, status labels"active"
numberStore numeric thresholds, counts, slider values42
booleanToggle visibility, enable/disable widgets, flag statetrue
dateStore calendar date selections without time component"2024-06-15"
timestampStore precise point-in-time values including time zone"2024-06-15T09:30:00Z"
objectSetHold a filtered or full set of Ontology objects for table/chart inputObjectSet<Order>
objectHold a single selected Ontology object (e.g., from a row-click)Order { id: "ORD-001" }
structHold a composite value with named fields not backed by an object type{ lat: 37.7, lon: -122.4 }
stringListHold a list of string values for multi-select inputs or loop sources["red", "green", "blue"]
numberListHold a list of numeric values for chart series or batch inputs[10, 20, 30]
booleanListHold per-row toggle states in a loop layout[true, false, true]
Event configuration (JSON representation of a Workshop event)
{
  "eventId": "onSubmitResolution",
  "trigger": "onClick",
  "widgetId": "resolveButton",
  "condition": {
    "type": "variableEquals",
    "variable": "selectedStatus",
    "value": "open"
  },
  "actions": [
    {
      "type": "invokeActionType",
      "actionTypeApiName": "resolveIncident",
      "parameters": {
        "incidentId": { "fromVariable": "selectedIncident.id" },
        "resolutionNote": { "fromVariable": "resolutionNoteInput" },
        "resolvedAt": { "fromVariable": "currentTimestamp" }
      }
    },
    {
      "type": "setVariable",
      "variable": "selectedIncident",
      "value": null
    },
    {
      "type": "closeOverlay",
      "overlayId": "incidentDetailOverlay"
    }
  ]
}

⚠️ Too many variables on initial load

Every variable with a non-null default value that is backed by an object set query executes that query when the page loads, even if the widget consuming it is below the fold or hidden behind an overlay. On data-dense applications with 10+ object set variables, this causes a cascade of parallel Ontology queries at load time, making the page feel slow for all users regardless of what they actually need. Audit your variables and mark any that are not required for the initial page view as lazy-loaded — Workshop will defer their queries until the first widget that binds them becomes visible or their triggering event fires.

📖 Related Module: Workshop Fundamentals

Widget Patterns

The Object Table is the workhorse of most operational Workshop apps. It binds to an objectSet variable and renders one row per object, with each column mapping to a property, a link traversal result, or a function-backed computation. Row-click events expose the selected object as an object variable, enabling master-detail layouts where the table drives a detail panel or overlay. Object Set Filter widgets sit above a table and write filter predicates back into the objectSet variable. They are pre-built filter controls — dropdowns, text searches, date range pickers — that abstract away the ObjectSet filter API. Stacking multiple filter widgets on the same objectSet variable produces an AND-combined filter, narrowing the set with each applied condition. Chart widgets (bar, line, pie, scatter, histogram) bind to an objectSet and aggregate its objects by property. They are declarative — you specify the X axis property, the Y axis aggregation (count, sum, average, min, max), and optional grouping. Charts re-render automatically when the bound objectSet variable changes, making them reactive to filter interactions without explicit event wiring. Map widgets render objects that carry geospatial properties (geohash, latitude/longitude pairs) as markers or heatmaps on a base map. Clicking a map marker fires an onSelect event exposing the underlying object, enabling the same master-detail pattern as the Object Table. Button widgets are the primary way to invoke Action Types. The button's onClick event calls an invokeActionType action, passing current variable values as parameters. After a successful invocation, Workshop automatically refreshes object sets that could be affected, keeping the UI consistent with the written state.
Common Widget Types
WidgetPurposeKey Config
Object TableDisplay and select from a list of Ontology objectsobjectSet binding, column definitions, onRowClick event
Object Set FilterLet users filter the bound objectSet by property valuestarget objectSet variable, filter property, filter UI type
Chart (bar/line/pie)Aggregate and visualize object properties as chartsobjectSet binding, X axis property, Y axis aggregation function
MapRender objects with geospatial properties on an interactive mapobjectSet binding, geohash or lat/lon property, marker style
ButtonTrigger events including Action Type invocationslabel, onClick event chain, visibility condition
Text InputCapture free-text from the user and store in a string variablebound string variable, placeholder, onChange event
Metric CardDisplay a single aggregated KPI value with optional trendobjectSet binding, aggregation (count/sum/avg), display format
Widget binding configuration (Object Table column definitions)
{
  "widgetId": "incidentTable",
  "type": "objectTable",
  "bindings": {
    "objectSet": { "fromVariable": "filteredIncidents" },
    "onRowClick": {
      "actions": [
        {
          "type": "setVariable",
          "variable": "selectedIncident",
          "value": { "fromEvent": "clickedObject" }
        },
        {
          "type": "openOverlay",
          "overlayId": "incidentDetailOverlay"
        }
      ]
    }
  },
  "columns": [
    { "property": "incidentId", "displayName": "ID", "width": 100 },
    { "property": "title", "displayName": "Title", "width": 300 },
    { "property": "severity", "displayName": "Severity", "width": 120 },
    { "property": "status", "displayName": "Status", "width": 120 },
    { "property": "assignee", "displayName": "Assignee", "width": 160 },
    {
      "type": "functionBacked",
      "functionApiName": "computeResolutionEta",
      "displayName": "ETA",
      "width": 140,
      "parameters": { "incidentId": { "fromProperty": "incidentId" } }
    }
  ]
}

⚠️ Function-backed columns

Function-backed columns in an Object Table invoke a Foundry Function once per row on every render cycle. On a table displaying 500 rows, that is 500 separate function executions fired in parallel on each page load and each filter change. Function latency is multiplied by row count — a 200ms function becomes a 200ms-per-row cost paid 500 times. For large tables, move computed properties into the upstream Transform pipeline as pre-computed dataset columns, expose them as standard Object Type properties, and use property columns instead. Reserve function-backed columns only for values that genuinely cannot be computed at pipeline time (e.g., real-time external lookups).

📖 Related Module: Workshop Fundamentals

Event System

Each event category maps to a specific interaction point on a widget. onClick fires when a user clicks a button, row, or map marker. onSelect fires when a user picks an item from a dropdown or multi-select list. onChange fires continuously as a user types in a text input or moves a slider. onSubmit fires when a user submits a form container. onLoad fires once when a page or overlay first renders, typically used to initialize variables. onFilter fires when an Object Set Filter widget applies or removes a filter condition. An event action sequence is an ordered list of steps executed left-to-right. Available step types include: setVariable (write a value to a variable), invokeActionType (call an Ontology Action Type), navigateTo (change the active page), openOverlay / closeOverlay (show or hide a modal panel), and refreshObjectSet (explicitly re-fetch an object set from the Ontology). Most sequences need only setVariable steps — Workshop refreshes bound widgets automatically when variables change. Event chains emerge when one event produces a variable change that triggers a second event on another widget that responds to that variable. Chains are the primary composition mechanism: an onChange on a search input sets a filter variable, which updates an objectSet variable, which causes a table to re-render and fires the table's onLoad event to reset the row selection. Chains are powerful but require careful mapping to avoid cycles. Conditional event logic gates individual event action steps on the current value of a variable. A condition takes the form of a boolean expression evaluated at the time the event fires. If the condition evaluates to false, the step is skipped and execution continues with the next step. This lets a single Button's onClick event behave differently based on application state without duplicating buttons.
Event Categories
CategoryTriggers WhenCommon Use
onClickUser clicks a button, table row, map marker, or any clickable widgetInvoke an Action Type, open an overlay, navigate to a page
onSelectUser selects an option from a dropdown, radio group, or multi-selectSet a filter variable, update a chart grouping, populate a form field
onChangeUser modifies a text input, slider, date picker, or toggleUpdate a search filter variable, recompute a derived variable in real time
onSubmitUser submits a form container widgetValidate inputs then invoke a CREATE or EDIT Action Type
onLoadA page or overlay finishes rendering for the first timeInitialize variable defaults, load user-context data, log a page view
onFilterAn Object Set Filter widget applies or removes a filter predicateSync the filtered objectSet variable to downstream tables and charts
Event chain configuration (search input driving table and chart)
{
  "eventId": "onSearchInputChange",
  "trigger": "onChange",
  "widgetId": "searchInput",
  "actions": [
    {
      "type": "setVariable",
      "variable": "searchKeyword",
      "value": { "fromEvent": "currentValue" }
    },
    {
      "type": "setVariable",
      "variable": "filteredIncidents",
      "value": {
        "type": "objectSetFilter",
        "baseObjectSet": "allIncidents",
        "filter": {
          "property": "title",
          "operator": "contains",
          "value": { "fromVariable": "searchKeyword" }
        }
      }
    }
  ]
},
{
  "widgetId": "incidentTable",
  "bindings": {
    "objectSet": { "fromVariable": "filteredIncidents" }
  }
},
{
  "widgetId": "severityChart",
  "bindings": {
    "objectSet": { "fromVariable": "filteredIncidents" }
  }
}

⚠️ Circular event chains

Circular event chains occur when Event A sets Variable X, a widget bound to Variable X fires Event B which sets Variable Y, and a widget bound to Variable Y fires Event A again. Workshop does not automatically detect or break these cycles — the application enters an infinite re-render loop that degrades to an unresponsive page and generates an unbounded number of Ontology queries. The fix is to identify the cycle in the event graph and break it with a guard condition: add a condition to one of the event steps that checks whether the target variable already holds the value about to be written, and skips the setVariable if so. Alternatively, restructure the data flow so the downstream variable derives its value reactively rather than through a manual event chain.

📖 Related Module: Advanced Workshop Patterns

Common Application Patterns

The Inbox / Task Management pattern addresses use cases where users must triage, assign, and resolve a queue of items. The primary surface is an Object Table filtered by status and optionally by assignee. Clicking a row opens a detail overlay showing full item context. The overlay contains an Action button that invokes a resolution or assignment Action Type. After invocation, the table refreshes and the resolved item disappears from the filtered view. This pattern works well for incident management, work order queues, approval workflows, and support ticket systems. The Common Operational Picture (COP) pattern provides situational awareness across a geographic or organizational domain. The layout places a Map widget as the dominant element, flanked by Metric Cards showing aggregate KPIs and Chart widgets showing distributions over time or category. Object Set Filter widgets at the top narrow the viewed domain by region, time range, or status. COP apps are read-heavy — write-back is rare. When geographic context is irrelevant, the Map is replaced by a dashboard of charts and metrics, producing a dashboard-style COP. The Investigation Workflow pattern supports deep-dive analysis starting from a list and drilling progressively into detail. A left-panel Object Table drives a right-panel detail section that shows properties and linked objects of the selected item. The detail panel may itself contain a secondary table of linked objects, and clicking those drives a tertiary panel. Navigation breadcrumbs implemented as text labels derived from selected-object variables communicate the current drill-down path. This pattern suits supply chain tracing, fraud investigation, and entity relationship exploration. The What-If Scenario Comparison pattern lets analysts model alternative futures by adjusting input parameters and comparing model outputs side by side. It uses Scenario widgets backed by ML model functions, with parameter inputs on one side and output metrics or charts on the other. The layout typically uses a two-column or tabbed structure. This pattern is common in logistics optimization, demand planning, and resource allocation.
Application Patterns
PatternComponentsBest For
Inbox / Task ManagementObject Table (filtered by status) + detail overlay + Action buttonIncident response, work order queues, approval workflows, support tickets
Common Operational Picture (Map-centric)Map widget + Metric Cards + Charts + Object Set FiltersFleet tracking, field operations, geographic asset monitoring
Common Operational Picture (Dashboard)Metric Cards + Charts + Object Set Filters + tabbed layoutExecutive dashboards, KPI monitoring, operational health views without geo context
Investigation WorkflowLeft-panel Object Table + right-panel detail section + linked object sub-tablesSupply chain tracing, fraud investigation, entity relationship exploration
What-If Scenario ComparisonScenario widgets + parameter inputs + side-by-side output charts and metricsLogistics optimization, demand planning, resource allocation modeling

⚠️ Overloading a single page

It is tempting to build all application functionality into a single Workshop page, using overlays and conditional visibility to reveal and hide sections. This approach quickly becomes unmanageable: the variable model grows large, event chains become difficult to trace, and page load time increases as every object set variable queries on render. Split complex workflows across multiple pages with a clear navigational model. Use shared variables sparingly — only for state that genuinely needs to survive page transitions (e.g., a selected entity that the user is working on across pages). State that is local to a workflow step should live on the page where that step occurs, not in global variables.

📖 Related Module: Advanced Workshop Patterns

Performance

Workshop ships a built-in Performance Profiler accessible from the builder toolbar. The Profiler shows a waterfall of all queries fired during a page load or user interaction, with timing breakdowns by widget and variable. Use it to identify which object set queries are slowest, which function-backed columns are most expensive, and whether multiple widgets are independently querying the same data that could be shared through a single variable. Lazy loading is the most impactful single change for pages with many variables. By default, every objectSet variable queries on page load. Variables bound only to widgets in overlays, tabs, or below-the-fold sections should be configured to load on demand — either triggered by the event that opens the overlay or by a user scroll event. Workshop supports this natively through the variable's load trigger configuration. Object set size has a direct linear relationship with table render time, chart aggregation time, and function-backed column execution time. Always pre-filter the object set to the minimum set the user actually needs before passing it to a widget. Use Object Set Filter widgets and variable-driven filter expressions to narrow sets before binding. Never pass an unfiltered base object set of thousands of objects to a chart widget that will perform client-side aggregation. Pagination on Object Tables limits the number of rows rendered and fetched per page. Enable server-side pagination for tables that may display more than 100 rows. Without pagination, Workshop fetches all matching objects in the object set to the client, which for large sets produces slow initial loads and sluggish scroll behavior. Workshop caches objectSet query results for a short TTL (typically 30 seconds for read-only views). After an Action Type invocation, Workshop automatically invalidates caches for object types that the action could have modified. If you observe stale data after an action, check whether the affected object type is declared in the action's output schema — missing output declarations prevent cache invalidation and leave the UI showing pre-action state.
Performance Optimization Checklist
IssueSymptomFix
Unfiltered object set passed to a widgetPage loads slowly; chart or table takes 5+ seconds to render on initial openAdd filter expressions or Object Set Filter widgets to narrow the set before it reaches the widget
Function-backed columns on large tablesTable renders slowly; Performance Profiler shows function calls per rowPre-compute the value in the upstream Transform pipeline and expose it as an Object Type property
Too many variables loading on initial renderProfiler waterfall shows 10+ parallel object set queries on page loadSet non-critical variables to lazy load; trigger them from the event that reveals their consuming widget
Pagination disabled on large tablesTable with 1000+ rows takes a long time to display; browser memory usage is highEnable server-side pagination; set a reasonable page size (25–100 rows)
Stale data after Action Type invocationTable still shows the old value after a successful actionEnsure the modified object type is listed in the action's output schema so Workshop invalidates the cache
Duplicate object set queries across widgetsProfiler shows identical queries fired by multiple widgets independentlyBind both widgets to a single shared objectSet variable instead of each computing their own

⚠️ Passing unfiltered object sets to charts

Chart widgets in Workshop perform aggregation at query time against the bound objectSet. When the bound objectSet is a base set with no filters — for example, all Orders across all time — the Ontology must scan and aggregate every object in that type, which can be tens of millions of rows. This produces chart load times measured in tens of seconds and places heavy load on the Ontology query engine for every user who views the page. Always pre-filter the objectSet to the minimum required scope before binding it to a chart: filter by time range, region, status, or any other dimension that is logically relevant to the chart's purpose. If the chart genuinely requires an all-time aggregate, pre-compute it as a pipeline-built dataset metric and surface it through a Metric Card instead.

📖 Related Module: Workshop Fundamentals

Decision Trees

Which Workshop app pattern fits your use case?

What is the primary user action?

Knowledge Check

Test your understanding with 3 questions. You need 2/3 to pass.