# Polymorphic relationships (/docs/framework/guides/polymorphism)



Some Salesforce references can point to more than one object type. `Event.What` is a common example.

Kysoql enables its polymorphic helpers only when the generated Salesforce metadata marks the reference as polymorphic.

## Filter by the runtime object type [#filter-by-the-runtime-object-type]

The virtual `.Type` qualifier can be selected and filtered:

```ts
import { db } from "./db";

const query = db
  .selectFrom("Event")
  .select(["Id", "What.Type"])
  .where("What.Type", "in", ["Account", "Opportunity"])
  .where("What.Type", "like", "Acc%");
```

Equality and list values are constrained to the generated reference targets. `.Type` is not treated as sortable, groupable, or aggregatable.

If `.Type` is all you need, you do not have to generate every possible target object.

## Select different fields with `TYPEOF` [#select-different-fields-with-typeof]

Generate `Event`, `Account`, and `Opportunity` for this example:

```ts
import { db } from "./db";

const events = await db
  .selectFrom("Event")
  .select(["Id", "Subject"])
  .selectTypeOf("What", (typeOf) =>
    typeOf
      .when("Account", ["Phone", "NumberOfEmployees"])
      .when("Opportunity", ["Amount", "CloseDate"]),
  )
  .limit(25)
  .execute();

type RelatedRecord = NonNullable<(typeof events)[number]["What"]>;

function isObjectType<T extends RelatedRecord["attributes"]["type"]>(
  record: RelatedRecord | null,
  objectType: T,
): record is Extract<RelatedRecord, { readonly attributes: { readonly type: T } }> {
  return record?.attributes.type === objectType;
}

for (const event of events) {
  const related = event.What;

  if (isObjectType(related, "Account")) {
    console.log(related.Phone, related.NumberOfEmployees);
  } else if (isObjectType(related, "Opportunity")) {
    console.log(related.Amount, related.CloseDate);
  }
}
```

Use the relationship name such as `What`, not the foreign-key field `WhatId`.

Each `when()` selection is checked against its target object. The result union is discriminated by Salesforce's returned `attributes.type`. Nullable lookups stay nullable.

## Add an `ELSE` branch when the remaining targets share fields [#add-an-else-branch-when-the-remaining-targets-share-fields]

A final `.else(["Name"])` is available when the field is valid across every generated target that is not already covered by a `when()` branch.

Kysoql is conservative here. If a field is not safe across the remaining generated targets, add explicit branches or generate the missing target metadata instead of forcing the type with a cast.

`TYPEOF` cannot be mixed with aggregate queries or SELECT-function expressions. Do not also select a normal field path through the same polymorphic relationship.

Filtering on `What.Type` is still fine, which lets you narrow the runtime types without duplicating selections.

Nested polymorphic paths keep the nullability of their parent relationships. Normal [relationship limits](/docs/framework/guides/relationships#query-wide-relationship-limits) still apply.
