---
title: "Extending LUSID's data model using custom entities"
slug: "extending-lusids-data-model-using-custom-entities"
updated: 2025-06-23T12:25:57Z
published: 2025-06-23T12:25:57Z
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 user-specified 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, [obtain an API token](/v1/docs/how-do-i-obtain-and-use-a-short-lived-api-access-token-from-okta) and call the [CreateCustomEntityType](https://www.lusid.com/docs/api#operation/CreateCustomEntityType) API for your LUSID domain, specifying a unique name for the type, values for the built-in data fields `displayName` and `description`, and defining user-specified data fields. Note LUSID automatically prefixes the entity type name with `~` to distinguish it from current or future built-in entity types.

User-specified 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, user-specified 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://<your-domain>.lusid.com/api/api/customentitytypes"
 -H "Authorization: Bearer <your-API-access-token>"
 -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 a ...** | **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://<your-domain>.lusid.com/api/api/search/propertydefinitions?filter=constraintStyle%20eq%20%27Identifier%27%20and%20domain%20eq%20%27CustomEntity%27"
  -H "Authorization: Bearer <your-API-access-token>"
```

### 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://<your-domain>.lusid.com/api/api/propertydefinitions"
 -H "Authorization: Bearer <your-API-access-token>"
 -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://<your-domain>.lusid.com/api/api/propertydefinitions"
 -H "Authorization: Bearer <your-API-access-token>"
 -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://<your-domain>.lusid.com/api/api/customentities/~Office"
 -H "Authorization: Bearer <your-API-access-token>"
 -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 user-specified data fields a 'valid from' date.

Providing the request is successful, the response confirms the date ranges that custom entity fields are valid between:

```json
{
    "href": "https://<your-domain>.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 user-specified 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 an existing custom entity type

You can optionally modify a custom entity type using the [UpdateCustomEntityType](https://www.lusid.com/docs/api#operation/UpdateCustomEntityType) API. Please note, however, 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 custom entities 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.

## A note on unsupported features

Custom entities are in the early phase of their development and do not yet support:

- Properties.
- Deleting a custom entity type.
- Identifier properties that are specific to the 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.
