Ontology

The Ontology is the semantic layer in Palantir Foundry that maps raw datasets to meaningful business objects, relationships, and operations. It sits between the data pipeline layer (Transforms) and the application layer (Workshop, Functions, AIP), providing a unified, governed model of your enterprise domain. Every object type, link type, and action type you define becomes a reusable building block that any downstream application or function can consume without re-implementing domain logic.

Object Types

An Object Type declaration tells Foundry three things: which dataset backs it, which column is the primary key, and which columns are exposed as properties. Once registered, every downstream consumer — Workshop, Functions, OSDK, AIP Logic — can query, filter, and aggregate objects without touching raw SQL or Spark. Primary key selection is the single most consequential decision when defining an Object Type. The key must be unique per row, stable over the lifetime of the object, and ideally meaningful to business users. When a natural business identifier exists and satisfies those constraints, prefer it. When it does not, generate a synthetic key in the upstream transform (SHA-256 of stable columns, or a UUID seeded from the pipeline). Properties declare the full set of columns that consumers can read. You do not need to expose every column — projecting only what is useful keeps the Ontology surface clean and reduces the risk of leaking sensitive columns to broad audiences. Each property carries a type drawn from Foundry's property type vocabulary, a display name, and an optional description used in search and documentation.
Property Types
TypeExampleNotes
string"Acme Corp"UTF-8, arbitrary length; use for identifiers and free text
integer4232-bit signed integer; use for counts and discrete quantities
double3.1415964-bit IEEE 754 float; use for measurements and ratios
booleantrueTrue/false flag; backed by boolean column in dataset
date"2024-03-15"Calendar date without time; ISO 8601 format (YYYY-MM-DD)
timestamp"2024-03-15T14:30:00Z"UTC instant; ISO 8601 with timezone; use for events
geohash"9q8yy"Geohash string encoding a lat/lon bounding box; enables spatial queries
struct{ lat: 37.7, lon: -122.4 }Nested object with named fields; backed by struct column
array["tag1", "tag2"]Ordered list of a single element type; backed by array column
Object Type configuration (Ontology manager YAML export)
objectType:
  apiName: employee
  displayName: Employee
  primaryKey: employee_id
  datasource:
    datasetRid: ri.foundry.main.dataset.abc123
    branchName: master
  properties:
    - apiName: employeeId
      columnName: employee_id
      type: string
      displayName: Employee ID
      description: Unique HR system identifier for the employee
    - apiName: fullName
      columnName: full_name
      type: string
      displayName: Full Name
    - apiName: department
      columnName: department
      type: string
      displayName: Department
    - apiName: hireDate
      columnName: hire_date
      type: date
      displayName: Hire Date
    - apiName: baseSalary
      columnName: base_salary
      type: double
      displayName: Base Salary
    - apiName: isActive
      columnName: is_active
      type: boolean
      displayName: Is Active

⚠️ Primary key collisions

If two rows in the backing dataset share the same primary key value, Foundry does not raise an error at registration time — it silently retains one row and discards the other (last-write semantics per sync). This means your object count will be lower than your row count and queries will return stale or incomplete data with no obvious signal. Always deduplicate the backing dataset upstream in your Transform before registering it as an Object Type datasource. Add a uniqueness assertion to your pipeline to catch regressions.

📖 Related Module: Object Types

Action Types

An Action Type defines what mutation is allowed, what parameters the caller must supply, what validations must pass before the write executes, and which datasets or object storage backends receive the written data. Every invocation is recorded in the Foundry audit log with the caller identity, timestamp, and parameter values, making Action Types the preferred write path for regulated industries. Actions can be simple (create a row, edit a property) or complex (function-backed). A function-backed action delegates its logic to a Foundry Function written in TypeScript or Python, allowing arbitrary computation before committing writes. The function receives the action parameters, performs validation and business logic, and returns a set of object edits that Foundry commits atomically. Validation rules run before submission. They can check that required parameters are non-null, that property values fall within allowed ranges, or that referenced objects exist. Submission criteria are higher-level conditions — for example, "this action can only be submitted if the current user has the Approver role." Both layers prevent invalid state from reaching the backing dataset.
Action Operation Types
OperationDescriptionParameters
CREATEInserts a new object into the backing datasetAll required properties for the new object
EDITUpdates one or more properties on an existing objectPrimary key of the target object + properties to update
DELETERemoves an existing object from the backing datasetPrimary key of the target object
CREATE_LINKAdds an edge between two existing objects in a link datasetPrimary keys of both objects
DELETE_LINKRemoves an edge from a link datasetPrimary keys of both objects
Function-backed Action in TypeScript (Foundry Functions SDK)
import { Action, ActionEditResponse, Edits, Integer } from "@foundry/functions-api";
import { Employee } from "@foundry/ontology-api";

export interface UpdateEmployeeSalaryParams {
  employee: Employee;
  newSalary: Double;
  effectiveDate: LocalDate;
  justification: string;
}

export class SalaryActions {
  @Action()
  @Edits(Employee)
  public async updateEmployeeSalary(
    params: UpdateEmployeeSalaryParams
  ): Promise<ActionEditResponse> {
    const { employee, newSalary, effectiveDate, justification } = params;

    if (newSalary <= 0) {
      throw new Error("Salary must be a positive value.");
    }

    if (newSalary > employee.baseSalary * 2) {
      throw new Error(
        "Salary increase exceeds 100% — requires executive approval."
      );
    }

    employee.baseSalary = newSalary;
    employee.lastSalaryUpdateDate = effectiveDate;

    return {
      edits: [employee],
    };
  }
}

⚠️ Action validation failure

When an Action Type fails silently or returns a generic validation error, check three things in order: (1) required properties — every property marked required in the Action definition must be supplied by the caller, even if it has a default in the dataset; (2) marking constraints — if a property uses an allowed-values list, the submitted value must exactly match one of the allowed values including case; (3) submission criteria — role-based or condition-based submission criteria are evaluated server-side and may reject the call even when all parameters are valid. Enable action audit logging to see the specific rule that fired.

📖 Related Module: Properties & Actions

Interfaces

An Interface is a declaration of named, typed properties with no backing dataset of its own. Each participating Object Type must supply a mapping from one of its concrete properties to each Interface property. At query time, Foundry fans out the interface query to all implementing Object Types and unions the results, returning objects of any implementing type that match the filter. The canonical use case is asset tracking: Aircraft, Vehicle, and Vessel are distinct Object Types with different schemas, but all share location, assetId, and status as meaningful properties. Defining an Asset interface with those three properties lets an AIP agent query all assets in a region without knowing the specific type. Interfaces are not inheritance. An Object Type that implements an interface is not a subtype — it simply agrees to expose the interface's properties under their interface-level API names. This means an Object Type can implement multiple unrelated interfaces simultaneously, and changing the interface does not automatically propagate to implementing types.
Interface vs Object Type
DimensionInterfaceObject Type
Backing dataNone — purely a contractRequired — a Foundry dataset
Can be queried directlyYes — fans out to all implementationsYes — queries one dataset
Has a primary keyNoYes — required
Can define actionsNoYes
Can have link typesNo (links are on Object Types)Yes
Supports polymorphismYes — core purposeNo
When to useWhen multiple types share the same properties semanticallyWhen modeling a distinct real-world entity
Interface definition and Object Type implementation
interface:
  apiName: asset
  displayName: Asset
  description: Any trackable physical asset in the enterprise
  properties:
    - apiName: assetId
      type: string
      displayName: Asset ID
    - apiName: currentLocation
      type: geohash
      displayName: Current Location
    - apiName: status
      type: string
      displayName: Status

---

objectType:
  apiName: vehicle
  displayName: Vehicle
  primaryKey: vehicle_vin
  implements:
    - interfaceApiName: asset
      propertyMappings:
        - interfaceProperty: assetId
          objectTypeProperty: vehicleVin
        - interfaceProperty: currentLocation
          objectTypeProperty: lastKnownGeohash
        - interfaceProperty: status
          objectTypeProperty: operationalStatus

⚠️ Over-using interfaces

Interfaces add cognitive overhead — every implementing Object Type must maintain property mappings, and adding a new interface property requires updating all implementations simultaneously or accepting partial coverage. Only create an interface when two or more Object Types genuinely share the same semantic properties: same meaning, same type, same unit. Do not create an interface just because two types happen to have a column with the same name but different semantics (e.g., "status" meaning order state on an Order vs. employment status on an Employee).

📖 Related Module: The Ontology

Best Practices

Model the domain, not the data. Your Ontology should reflect how your business talks about entities, not how they are stored in source systems. A source database might split a customer across three normalized tables; the Ontology should present a single Customer object with the joined properties that users care about. Use Transforms to prepare the backing dataset, not to work around a poorly designed Object Type. Meaningful API names are critical because they cannot be changed without breaking consumers. Use lowerCamelCase for property API names and Object Type API names. Choose names that describe the business concept, not the column name. A column called emp_sal_usd_ann should become annualSalaryUsd. API names are permanent — treat them like a public API. Prefer link types over denormalization. When an Order needs to reference its Customer, do not copy customer_name onto the Order object. Model a Link Type between Order and Customer. This ensures that when a customer's name changes, all orders automatically reflect the update. Denormalized copies create eventual consistency problems that are hard to debug. Use interfaces for polymorphism. When Workshop or AIP needs to display or query multiple object types in the same panel, an interface is the right tool. Without it, every consumer must handle each type separately. Version your Ontology incrementally. Add new properties and object types freely — they are additive and non-breaking. Remove or rename only when all downstream consumers have migrated. Use the Ontology usage panel to identify consumers before deprecating anything. Keep primary keys stable. A primary key that changes causes the old object to be deleted and a new one to be created in Foundry's index. Any Links pointing to the old key become dangling edges. Favorites, comments, and audit records tied to the old object RID are lost. Never use a mutable business attribute (e.g., email address) as a primary key.
Naming Conventions
ElementConventionExample
Object Type API namelowerCamelCase singular nounsalesOpportunity
Object Type display nameTitle Case singular nounSales Opportunity
Property API namelowerCamelCase, descriptiveannualSalaryUsd
Property display nameTitle Case, human-readableAnnual Salary (USD)
Link Type API namelowerCamelCase, sourceToDestinationopportunityToAccount
Action Type API namelowerCamelCase verb phrasecloseOpportunity
Interface API namelowerCamelCase noun, often abstracttrackableAsset

⚠️ Storing computed values as properties

Computed values — totals, averages, scores, derived statuses — should not be stored as properties on an Object Type. When you bake computation into the backing dataset, the value is stale the moment the inputs change and you pay for recomputation on every pipeline run even when no consumer has requested the value. Instead, implement computed properties as Foundry Functions. A Function marked as a computed property is evaluated on-demand, always reflects the current state of its inputs, and can be composed with other Functions. Reserve stored properties for raw, sourced facts that do not change without a source system update.

📖 Related Module: Ontology Design Patterns

Anti-Patterns

Creating an Object Type for every source table is the most common mistake made by teams migrating from database-centric thinking. Not every table represents a meaningful business entity. Junction tables, audit log tables, configuration tables, and staging tables rarely belong in the Ontology. Ask: "Would a business user recognize this as a real thing they care about?" If not, it belongs in the data pipeline layer, not the Ontology. Embedding foreign keys as string properties is a subtle form of the same mistake. If an Order has a customer_id property of type string, you have recreated a relational join in string form. Every consumer must manually resolve the ID to a Customer object, duplicating logic and coupling consumers to the raw identifier scheme. Use Link Types instead. Over-indexing properties hurts write performance. Foundry maintains search indexes for every indexed property. Each sync that changes those properties must update the index. On large datasets with high-churn columns, this adds significant latency and cost to pipeline runs. Only index properties that users or applications actually filter or search on. Audit your indexes regularly. Using string properties for structured data prevents type-safe queries and spatial operations. GPS coordinates stored as "37.774929,-122.419416" cannot be used in geospatial filters. Use geohash or struct property types. Similarly, storing JSON blobs as strings prevents Foundry from indexing or filtering on individual fields — use struct types with named fields instead.
Anti-Pattern to Correct Pattern
Anti-PatternProblemCorrect Pattern
Object Type for every source tablePollutes Ontology with non-entities; confuses consumersOnly model entities a business user would recognize; join tables in Transforms
Foreign key as string propertyConsumers must manually resolve IDs; logic duplicated everywhereModel a Link Type between the two Object Types
Over-indexing all propertiesSlow pipeline syncs; high index storage costIndex only properties actively used in filters and search
String property for GPS coordinatesCannot use spatial query operators; no geohash clusteringUse geohash property type for spatial indexing and queries
JSON blob as string propertyCannot filter or index individual fields; type-unsafeUse struct property type with named, typed sub-fields
Mutable attribute as primary keyKey changes cause object re-creation; dangling links; lost historyUse a stable natural key or a synthetic generated UUID/SHA-256
Computed value as stored propertyStale data between pipeline runs; wasted recomputationImplement as a Foundry Function computed property evaluated on demand

⚠️ Over-indexing properties

Foundry indexes every property you mark as searchable or filterable. Each index entry must be written on every sync that touches that property, even if no downstream query has ever used the index. On datasets with millions of rows and dozens of indexed properties, this multiplies sync duration and increases storage costs with no user-visible benefit. Audit your Object Type indexes by checking the Ontology property panel — if a property has no active filters or search usage in Workshop or Functions, remove its index. You can always re-add it later if requirements change.

📖 Related Module: Ontology Design Patterns

Decision Trees

Which primary key strategy?

Is there a natural business identifier?

Knowledge Check

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