Entity Extension
Some entities in DemandFlow are defined as extensions of other entities. An extension entity inherits the shape of a base entity and adds its own fields. Physically, an extended record is stored as two objects linked together: one base record and one extension record. This article explains how to detect the pattern, what the API returns, and how to create and navigate extended records.
The concept
A typical example from the shipped definitions: LEAD, CANDIDATE, and PATENTI (Inventor) all extend PPL (Contact). A Lead is a Contact plus lead-specific fields. A Candidate is a Contact plus candidate-specific fields. The same underlying person can appear as any combination of these. Each extension adds its own fields without duplicating the contact data.
Extensions are a distinct relationship from the four standard relationship types: they are a storage pattern, not a user-facing many-to-many or parent-child relationship.
How to detect an extension entity
An entity's definition tells you whether it extends another and whether it is extended by others:
extends: set to an entity code on the definition of the extension. For example,LEAD's definition carries"extends": "PPL".extendedBy: an array on the definition of the base entity listing every entity that extends it. For example,PPLcarries"extendedBy": ["LEAD", "PATENTI", "CANDIDATE"].
To read these programmatically, GET the DEFINITION object whose id matches the entity code.
How the two records link
Every extension record carries a field called df_extendObject holding the id of its base record. The base record has no field pointing back at the extension, and a single base record may be extended by any number of extension records.
// The base record (a PPL)
{
"id": "<base-id>",
"entity": "PPL",
"nameFirst": "Ada",
"nameLast": "Lovelace",
"email": "ada@example.com",
...
}
// The extension record (a LEAD for the same person)
{
"id": "<lead-id>",
"entity": "LEAD",
"df_extendObject": "<base-id>",
"leadStatus": "qualified",
"source": "webinar",
...
}
API behaviour
The API returns records exactly as stored. There is no server-side merge.
| Operation | Behaviour |
|---|---|
GET /v1/objects/{leadId} | Returns only the extension record, including its df_extendObject reference. Contact fields such as email and nameFirst are NOT merged in. To see them, GET the record whose id is df_extendObject. |
GET /v1/entities/LEAD/SUB | Returns every LEAD extension record in the tenant. Base PPL records are not included and must be fetched separately. |
POST /v1/objects for an extension | Creates only the extension record. You are responsible for setting df_extendObject to the id of an existing base record. Create the base first if it does not yet exist. |
PATCH /v1/objects/{leadId} | Updates only the extension record. Updating a contact field such as email must be done by PATCHing the base PPL directly. |
DELETE /v1/objects/{leadId} | Deletes only the extension. The base record is not touched, so you can remove a LEAD without deleting the underlying Contact. Delete the base separately if nothing else extends or references it. |
Creating an extended record
Creating a new LEAD for a person who is not yet in the system is two POSTs: first the base, then the extension.
// Step 1: create the base PPL
POST /v1/objects
{
"entity": "PPL",
"level": 210,
"comboKey": "SUB:<your-sub-id>|ENT:",
"nameFirst": "Ada",
"nameLast": "Lovelace",
"email": "ada@example.com"
}
// Response: { "id": "<base-id>", ... }
// Step 2: create the LEAD pointing at the PPL
POST /v1/objects
{
"entity": "LEAD",
"level": "<from LEAD definition>",
"comboKey": "SUB:<your-sub-id>|ENT:",
"df_extendObject": "<base-id>",
"leadStatus": "new",
"source": "webinar"
}
If a matching base record already exists (for example a known Contact), skip step 1 and reuse its id in step 2.
Reading an extended record in full
To show a caller every field of a LEAD including the underlying contact data, perform two reads:
// 1. Read the extension
const lead = await fetch('/v1/objects/<lead-id>', ...).then(r => r.json());
// 2. Read the base
const base = await fetch('/v1/objects/' + lead.df_extendObject, ...).then(r => r.json());
// 3. Merge in your client. Precedence rule is yours to decide.
const combined = { ...base, ...lead };
Most DemandFlow clients overlay the base onto the extension so extension fields take precedence when both records carry the same field name, but this merge happens entirely in the client, not in the API.
Multiple extensions of the same base
A single PPL record can be extended by a LEAD and a CANDIDATE at the same time. Each extension has its own id and df_extendObject, both pointing at the same base. Listing extensions for a given base requires querying each extension entity and filtering by df_extendObject, or using POST /v1/query with one query line per extension entity.
Dangling extensions
If a base record is deleted without first removing its extensions, those extensions become "dangling": the extension record still exists but df_extendObject points at a missing id. This is a data-integrity concern. Before deleting a base record, enumerate and remove (or relink) any extensions first.