--- title: "Extending LUSID's data model using custom entities" slug: "extending-lusids-data-model-using-custom-entities" tags: ["custom entity", "identifier type", "identifier scope", "extensible data model"] updated: 2026-09-04T09:26:41Z published: 2026-09-04T09:26:41Z canonical: "support.lusid.com/extending-lusids-data-model-using-custom-entities" --- > ## Documentation Index > Fetch the complete documentation index at: https://support.lusid.com/llms.txt > Use this file to discover all available pages before exploring further. # Extending LUSID's data model using custom entities LUSID has a set of [built-in entities](/v1/docs/what-is-an-entity) that represent real-world objects central to the task of managing investment data, such as portfolios, instruments, quotes and so on. You can create a *custom entity* to model a real-world object or concept that LUSID does not natively represent, for example corporate office location. Note the following: - A custom entity must have a *custom entity type* that specifies values for two built-in data fields, `displayName` and `description`, and defines custom (that is, user-defined) data fields. Once created, you can modify a custom entity type, but only [with caveats](/v1/docs/extending-lusids-data-model-using-custom-entities#modifying-an-existing-custom-entity-type). - A custom entity must have at least one user-defined identifier. Under the hood, each identifier is specified as a property with a `constraintStyle` of `Identifier` and a 3-stage property key in the `CustomEntity` domain. - You can create [relationships](/v1/docs/creating-relationships-between-certain-types-of-entity) between a custom entity and certain other types of entity, for example between a corporate office location and the [person entities](/v1/docs/representing-people-in-lusid-using-person-entities) allowed to work there. - You can apply [access metadata](/v1/docs/understanding-access-metadata-amd) to a custom entity. This may be a more effective way of restricting access to custom entity data stored in LUSID. - LUSID stores custom entity data [bitemporally](/v1/docs/what-is-bitemporal-data) and you can retrieve historical information by rolling back the *as at* timeline in the normal way. However, LUSID does not know how to *interpret* your custom entity data. It cannot be used to inform holding calculations or valuation operations. - Custom entities are not [feature-complete](/v1/docs/extending-lusids-data-model-using-custom-entities#a-note-on-unsupported-features) yet. Providing you have appropriate [access control permissions](/v1/docs/identity-management-and-access-control-iam), you can interact with a custom entity (and any relationships it might have): - Using the **Data Management > Custom Entities** dashboard in the LUSID web app. - Using the LUSID API endpoints in the [Custom Entity](https://www.lusid.com/docs/api#tag/CustomEntities) and [Custom Entity Types](https://www.lusid.com/docs/api#tag/Custom-Entity-Types) collections. - If you have a Luminesce license, using [read and write providers](/v1/docs/how-do-i-use-luminesce-to-readwrite-custom-entities) you create yourself. ## Defining a custom entity type The first task is to create a custom entity type defining core characteristics. You can then create as many custom entities of this type as you need. To do this, call the [CreateCustomEntityType](https://www.lusid.com/docs/api#operation/CreateCustomEntityType) API and specify a unique name for the type, values for the built-in data fields `displayName` and `description`, and custom data fields. Note LUSID automatically prefixes the entity type name with `~` to distinguish it from current or future built-in entity types. Custom data fields are analogous to, though not the same as, [properties](/v1/docs/introduction-to-properties). Each can: - Be `required` or not. - Have a `lifeTime` of `Perpetual` or `TimeVariant` (that is, expected to vary during different time periods). [More information](/v1/docs/what-is-bitemporal-data#understanding-perpetual-and-time-variant-data-items). - Have a data `type` of either `String`, `Boolean`, `DateTime` or `Decimal`. - Have a `collectionType` of `Array` to store multiple values (note that array values are ordered and may contain duplicates). > **Note**: Unlike properties, custom data fields cannot be scoped (that is, entitled) independently of parent custom entities. For example, to create a custom entity type to represent the concept of corporate office location: ```json curl -X POST "https://.lusid.com/api/api/customentitytypes" -H "Authorization: Bearer " -H "Content-Type: application/json-patch+json" -d '{  "entityTypeName": "Office",  "displayName": "Office location",  "description": "An office or branch location",  "fieldSchema": [    {      "name": "address",      "lifetime": "Perpetual",      "type": "String",      "required": false,      "description": "The address of the location"    },    {      "name": "seatingCapacity",      "lifetime": "TimeVariant",      "type": "Decimal",      "required": false,      "description": "The seating capacity of the location"    },    {      "name": "isHeadOffice",      "lifetime": "TimeVariant",      "type": "Boolean",      "required": true,      "description": "Whether or not the location is a head office"    },    {      "name": "Amenities",      "lifetime": "TimeVariant",      "type": "String",      "collectionType": "Array",      "required": false,      "description": "A list of facilities for staff"    }  ] }' ``` Providing the request is successful, the response identifies the entity type as `~Office`: ```json {    "entityTypeName": "Office",    "displayName": "Office location",    "description": "An office or branch location",    "entityType": "~Office",    "fieldSchema": [        {            "name": "isHeadOffice",            "lifetime": "TimeVariant",            "type": "Boolean",            "required": true,            "description": "Whether or not the location is a head office"        },        {            "name": "seatingCapacity",            "lifetime": "TimeVariant",            "type": "Decimal",            "required": false,            "description": "The seating capacity of the location"        },        {            "name": "address",            "lifetime": "Perpetual",            "type": "String",            "required": false,            "description": "The address of the location"        },        {           "name": "Amenities",           "lifetime": "TimeVariant",           "type": "String",           "collectionType": "Array",           "required": false,           "description": "A list of facilities for staff"        }    ] } ``` ## Understanding user-defined identifiers A custom entity must have at least one [user-defined identifier](/v1/docs/what-is-an-identifier). Consider the following JSON fragment defining One Carter Lane, a corporate location that is both FINBOURNE Technology's London office and European headquarters, and so has an identifier for each context: ```json "displayName": "One Carter Lane", "description": "FINBOURNE office and headquarters", "identifiers": [  {    "identifierScope": "Location",    "identifierType": "OfficeId",    "identifierValue": "London"  },  {    "identifierScope": "Location",    "identifierType": "HeadquartersId",    "identifierValue": "Europe"  } ], ... ``` An identifier consists of three components: an `identifierScope`, `identifierType` and `identifierValue`. The values you assign to these components combine to uniquely identify One Carter Lane in each of the contexts in which it operates. In this example: | **To uniquely identify One Carter Lane as...** | **identifierScope** | **identifierType** | **IdentifierValue** | | --- | --- | --- | --- | | Office location | `Location` | `OfficeId` | `London` | | Headquarters | `Location` | `HeadquartersId` | `Europe` | For each identifier you intend to give a custom entity, you must first call the [CreatePropertyDefinition](https://www.lusid.com/docs/api#operation/CreatePropertyDefinition) API to create a [property type](/v1/docs/how-do-i-create-a-property-type) with the following mandatory characteristics: - A `domain` of `CustomEntity`. - A `scope` with the `identifierScope` value, for example `Location`. - A `code` with the `identifierType` value, for example `OfficeId` or `HeadquartersId`. - A `constraintStyle` of `Identifier`. - A `lifeTime` of `Perpetual`. Note you can call the [SearchProperties](https://www.lusid.com/docs/api#operation/SearchProperties) API with a suitable filter to find all the property types that have been created as identifiers for custom entities, in case a suitable definition already exists: ```json curl -X GET "https://.lusid.com/api/api/search/propertydefinitions?filter=constraintStyle%20eq%20%27Identifier%27%20and%20domain%20eq%20%27CustomEntity%27"  -H "Authorization: Bearer " ``` ### Creating a property type to constitute an 'Office location' identifier The following call creates a property type with a 3-stage property key of `CustomEntity/Location/OfficeId`: ```json curl -X POST "https://.lusid.com/api/api/propertydefinitions" -H "Authorization: Bearer " -H "Content-Type: application/json-patch+json" -d '{  "domain": "CustomEntity",  "scope": "Location",  "code": "OfficeId",  "displayName": "Office ID",  "dataTypeId": {"scope": "system", "code": "string"},  "lifeTime": "Perpetual",  "constraintStyle": "Identifier",  "description": "Identifier property used to identify custom entities that are office locations" }' ``` ### Creating a property type to constitute a 'Headquarters' identifier The following call creates a property type with a 3-stage property key of `CustomEntity/Location/HeadquartersId`: ```json curl -X POST "https://.lusid.com/api/api/propertydefinitions" -H "Authorization: Bearer " -H "Content-Type: application/json-patch+json" -d '{  "domain": "CustomEntity",  "scope": "Location",  "code": "HeadquartersId",  "displayName": "Headquarters ID",  "dataTypeId": {"scope": "system", "code": "string"},  "lifeTime": "Perpetual",  "constraintStyle": "Identifier",  "description": "Identifier property used to identify custom entities that are headquarters" }' ``` ## Creating a custom entity with unique identifier values You can now call the [UpsertCustomEntity](https://www.lusid.com/docs/api#operation/UpsertCustomEntity) API to create a custom entity belonging to a custom entity type. Note the following: - The underlying custom entity type is identified by its type name in the URL of the `UpsertCustomEntity` API, in this case `/api/customentities/~Office` (note the `~` prefix). - The `identifierScope` of each identifier must be the `scope` of its underlying property type, in this case `Location`. - The `identifierType` of each identifier must be the `code` of its underlying property type, in this case `OfficeId` or `HeadquartersId`. - The `identifierValue` of each identifier must be unique among all identifiers with the same `identifierScope` and `identifierCode`, in this case `London` or `Europe`. > **Note**: You can call the [UpsertCustomEntities](https://www.lusid.com/docs/api#operation/UpsertCustomEntities) API to batch upsert multiple custom entities, and decide whether to fail the entire operation if one fails validation. For example, to create a custom entity representing One Carter Lane: ```json curl -X POST "https://.lusid.com/api/api/customentities/~Office" -H "Authorization: Bearer " -H "Content-Type: application/json-patch+json" -d '{  "displayName": "One Carter Lane",  "description": "FINBOURNE office and regional headquarters",  "identifiers": [    {      "identifierScope": "Location",      "identifierType": "OfficeId",      "identifierValue": "London"    },    {      "identifierScope": "Location",      "identifierType": "HeadquartersId",      "identifierValue": "Europe"    }  ],  "fields": [    {      "name": "address",      "value": "One Carter Lane, London, EC4V 5ER"    },    {      "name": "seatingCapacity",      "value": 150,      "effectiveFrom": "2021-08-01T00:00:00.00Z"    },    {      "name": "isHeadOffice",      "value": true,      "effectiveFrom": "2021-08-01T00:00:00.00Z"    },    {      "name": "Amenities",      "value": ["Bike parking", "Kitchenette", "Table tennis"],      "effectiveFrom": "2021-08-01T00:00:00.00Z"    }  ] }' ``` Note the use of the `effectiveFrom` field to give time-variant custom data fields a 'valid from' date. Providing the request is successful, the response confirms the date ranges that custom data fields are valid between: ```json {    "href": "https://.lusid.com/api/api/customentities/~Office",    "entityType": "~Office",    "version": {        "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",        "asAtDate": "2022-09-20T14:47:45.0500820+00:00"    },    "displayName": "One Carter Lane",    "description": "FINBOURNE office and regional headquarters",    "identifiers": [        {            "identifierScope": "Location",            "identifierType": "OfficeId",            "identifierValue": "London",            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        },        {            "identifierScope": "Location",            "identifierType": "HeadquartersId",            "identifierValue": "Europe",            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        }    ],    "fields": [        {            "name": "address",            "value": "One Carter Lane, London, EC4V 5ER",            "effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        },        {            "name": "Amenities",            "value": [              "Bike parking",              "Kitchenette",              "Table tennis"            ],            "effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        },        {            "name": "isHeadOffice",            "value": true,            "effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        },        {            "name": "seatingCapacity",            "value": 150.0,            "effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",            "effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"        }    ],   ... } ``` ## Retrieving custom entities and their relationships You can retrieve a particular custom entity using the [GetCustomEntity](https://www.lusid.com/docs/api#operation/GetCustomEntity) API. You can retrieve all the custom entities of a particular type using the [ListCustomEntities](https://www.lusid.com/docs/api#operation/ListCustomEntities) API, or [perform a filter operation](/v1/docs/filtering-information-retrieved-from-lusid) to only retrieve a matching set. Custom entities support filtering on custom data field values using the `fields` keyword, for example: ```json fields[streetAddress] startswith 'One' ``` Note if you [create relationships](/v1/docs/creating-relationships-between-certain-types-of-entity) between a custom entity and other entities you can retrieve them using the [GetCustomEntityRelationships](https://www.lusid.com/docs/api#operation/GetCustomEntityRelationships) API. ## Modifying a custom entity type You can optionally modify an existing custom entity type using the [UpdateCustomEntityType](https://www.lusid.com/docs/api#operation/UpdateCustomEntityType) API. Please note this is a sensitive operation if custom entity instances of this type already exist: - If you modify the data `type` of a field (for example, from `String` to `DateTime`), values of instances that do not match the new type cannot be retrieved. If you revert the change, original values are returned even if they have been updated in the meantime (so your updates are lost). - If you modify the `lifeTime` of a field (for example, from `TimeVariant` to `Perpetual`), values cannot be retrieved. If you revert the change, original values are returned even if they have been updated in the meantime. - If you modify the `required` status (for example, from `True` to `False`), values *can* be retrieved, but note that the new validation applies to all future updates to values. ## Deleting a custom entity type You can call the `DeleteCustomEntityDefinition` API (coming soon) to delete a custom entity type. Please note this is a sensitive operation if custom entity instances of this type already exist: - You cannot retrieve instances by calling (for example) the `ListCustomEntities` API with the latest `asAt` (the default behavior). You can still retrieve instances by explicitly specifying an `asAt` datetime prior to the deletion of the custom entity type. - If you create a new custom entity type with the same name it is considered a completely new type. Existing instances are not resurrected, and nor are relationships to other entities. To delete a custom entity *instance*, call the [DeleteCustomEntity](https://www.lusid.com/docs/api/lusid/endpoints/custom-entities/DeleteCustomEntity) API. ## A note on unsupported features Custom entities are in the early phase of their development and do not yet support: - Identifier properties that are specific to a custom entity type (identifier properties in the `CustomEntity` domain can currently be applied to custom entity instances of any type). - [Filtering on identifier properties](/v1/docs/filtering-information-retrieved-from-lusid) when retrieving custom entities.