User-defined custom entity APIs - use cases, sample requests
For an overview of custom entities:
Use cases for custom entities:
Postman collection with sample requests
The linked Postman collections contains example requests to the available joins endpoint for the Eureka snapshot environment. These can be used to test this endpoint on Eureka snapshot.
Environment: https://folio-etesting-snapshot-diku.ci.folio.org/ (Eureka snapshot)
Note: in order to use this collection, you’ll need to get a valid token and include it in the header for the
x-okapi-token
High level overview of endpoints
API documentation: https://s3.amazonaws.com/foliodocs/api/folio-query-tool-metadata/s/queryTool.html#tag/custom/operation/createCustomEntityType
Custom ET (Create Read Update Delete) CRUD endpoints
These endpoints are to create, view, edit and delete custom ETs in FQM: an entity defines the data is available in a given view, also referred to as an entity type (or abbreviated to ET). Entities display as the “Record type” in the Lists app, and are queryable through FQM APIs. The Create custom ET API allows authorized users to combine existing data/record types into custom entities.
Create a custom entity: POST /entity-types/custom
View a custom entity: GET /entity-types/custom/{entityTypeId}
Update a custom entity: PUT /entity-types/custom/{entityTypeId}
Delete a custom entity: DEL /entity-types/custom/{entityTypeId}
Available joins API:
The Available joins endpoint will help you populate sources that are used in custom entity definitions
Use case 1: create a custom entity for Instances with holdings
I want to build a list of instances that returns holdings data
Steps and example request to create a Custom ET:
POST to /entity-types/custom
the request body:
the
idcan be left blank in the initial POST; it is provided in the responseprovide a
namefor the ET; when testing in snapshot, please include TEST or something to the name to indicate it’s experimentaloptionally provide a
descriptionshared: true/false, determines if other users can access this ETprivate: true/false, determines if it appears in the Lists appsources:you can use the source definitions from the getAvailableJoins endpoint to help populate request the body
at least one source needs to have
useIdColumns : true
Example body of a request to create a custom ET:
{ "id": "", //the id can be left blank in the initial POST; it is provided in the response "name": "Test Instance with holdings", //Provide name here "description": "Returns instances with holdings data, if it's available", //Provide a description here "labelAlias": null, "shared": false, //determines if other users can access this ET "private": false, //false means it appears in the Lists app "sources": [ { "type": "entity-type", "alias": "simple_instance", //used to identify fields from this source "name": "Instance", //user facing name for this source; fields from this ET display with "name" as prefix "targetId": "8fc4a9d2-7ccf-4233-afb8-796911839862", //UUID for the base source, in this case simple instance "essentialOnly": true, "useIdColumns": true }, { "type": "entity-type", "alias": "simple_holdings", //used to identify fields from this source "name": "Holdings", //user facing name for this source; fields from this ET display with "name" as prefix "targetId": "30a5cfad-1868-4f46-86b9-a6ef67e2d9bf", //UUID for the source, in this case simple holdings "sourceField": "simple_instance.id", //getAvailableJoins endpoint helps identify the correct sourceField name "targetField": "instance_id", //getAvailableJoins endpoint helps identify the correct targetField name "essentialOnly": true, "useIdColumns": true } ] }To define sources, use the available joins API
Follow the use cases 1 - 3 to define complete sources https://folio-org.atlassian.net/wiki/spaces/FOLIOtips/pages/1515061315/Available+joins+API+-+use+cases+and+sample+requests#Use-case-1%3A-all-sources
For additional help/info, see: https://folio-org.atlassian.net/wiki/spaces/FOLIOtips/pages/1558413333/User-defined+custom+entity+APIs+-+use+cases+sample+requests#Additional-details-%2F-example-of-defining-sources
Response: a valid request returns a 201 Created. the body of the response includes the newly created custom ET, including the
idand additional properties added by defaulttake note of the
idas that how you’ll be able to view or modify your newly created entityexample response:
{ "id": "60f48cd8-9e9e-492b-aa6d-9fd3b87ac604", "name": "Test Instance with holdings", "description": "Custom ET that returns Instances with holdings data, when available", "labelAlias": null, "customFieldEntityTypeId": null, "columns": [], "crossTenantQueriesEnabled": false, "defaultSort": null, "fromClause": null, "idView": null, "groupByFields": null, "sourceView": null, "sourceViewExtractor": null, "sources": [ { "type": "entity-type", "alias": "simple_instance", "name": "Instance", "joinedViaEntityType": null, "order": 100, "sourceField": null, "targetId": "8fc4a9d2-7ccf-4233-afb8-796911839862", "targetField": null, "overrideJoinDirection": null, "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true }, { "type": "entity-type", "alias": "simple_holdings", "name": "Holdings", "joinedViaEntityType": null, "order": 200, "sourceField": "simple_instance.id", "targetId": "30a5cfad-1868-4f46-86b9-a6ef67e2d9bf", "targetField": "instance_id", "overrideJoinDirection": null, "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true } ], "requiredPermissions": [], "filterConditions": [], "additionalEcsConditions": [], "deleted": false, "usedBy": [], "owner": "76edcb72-77b3-4922-952a-6a9d99c69e8c", "shared": false, "createdAt": "2025-12-31T17:05:52.676+00:00", "updatedAt": "2025-12-31T17:05:52.676+00:00", "isCustom": true, "private": false }
if the ET is set to
private: falseyou'll be able to access and test it in the Lists app at this point!
Steps to view the custom ET in the lists app
Preconditions:
a custom ET has been created, with
private: falsethe user has the appropriate permissions:
view/create permissions in the Lists app: https://folio-org.atlassian.net/wiki/spaces/FOLIOtips/pages/572948576/Lists+App+permissions#App-permissions
content permissions to for the content of the custom ET (i.e., can they access inventory records) https://folio-org.atlassian.net/wiki/spaces/FOLIOtips/pages/572948576/Lists+App+permissions#Content-permissions
Open the Lists app
Click “New” - provide a list name
In the “Record type” dropdown, an option should be available that matches the
namein the request to create a custom ET - select that nameClick “Build query” > the Query Builder opens
When you click on the “Select field” dropdown, you should see a list of available fields that can be used to query the new ET
the
namefor eachsourceshould prepend available field names (e.g., “Instance – Administrative notes” or “Holdings – Created date”)build a query with your desired parameters
Click “Test query”
See short recording to see how things look in the Lists app at this point
note the second query returned no results - indicating there are no instances without holdings, or, that there’s something potentially wrong with this custom ET.
in this case the custom ET is only returning Instances that have associated holdings, which isn’t the desired behavior.
- Viewing a custom ET in the Lists app - returns instances that have holdings
Use case 2: update custom entity for Instances with holdings
I want to build a list of instances that returns holdings data when it’s available. I want to be able to query instance records that don’t have associated holdings records.
Steps to update the custom ET definition
PUT to /entity-types/custom/{entityTypeId}
{entityTypeId} should be replaced with the
idfrom the response when you created the custom ET; in this example it would be60f48cd8-9e9e-492b-aa6d-9fd3b87ac604the request body:
includes the ET definition
we want to update this custom ET so that it will return all instances, and will include holdings data if it’s available
this can be done by updating the
overrideJoinDirectionfor the holdingssourcetoleft(row 39 below)
example request body:
{ "id": "60f48cd8-9e9e-492b-aa6d-9fd3b87ac604", "name": "Test Instance with holdings", "description": "Custom ET that returns Instances with holdings data, when available", "labelAlias": null, "customFieldEntityTypeId": null, "columns": [], "crossTenantQueriesEnabled": false, "defaultSort": null, "fromClause": null, "idView": null, "groupByFields": null, "sourceView": null, "sourceViewExtractor": null, "sources": [ { "type": "entity-type", "alias": "simple_instance", "name": "Instance", "joinedViaEntityType": null, "order": 100, "sourceField": null, "targetId": "8fc4a9d2-7ccf-4233-afb8-796911839862", "targetField": null, "overrideJoinDirection": null, "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true }, { "type": "entity-type", "alias": "simple_holdings", "name": "Holdings", "joinedViaEntityType": null, "order": 200, "sourceField": "simple_instance.id", "targetId": "30a5cfad-1868-4f46-86b9-a6ef67e2d9bf", "targetField": "instance_id", "overrideJoinDirection": "left", "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true } ], "requiredPermissions": [], "filterConditions": [], "additionalEcsConditions": [], "deleted": false, "usedBy": [], "owner": "76edcb72-77b3-4922-952a-6a9d99c69e8c", "shared": false, "createdAt": "2025-12-31T17:05:52.676+00:00", "updatedAt": "2025-12-31T17:05:52.676+00:00", "isCustom": true, "private": false }
Response:
200 OK and the custom ET is updated
View the updated custom ET in the lists app
As you did in a previous step, create a list with the custom entity type
Build a query
Instance – Source = MARC
AND Holdings – UUID is null/empty true
Test the query
Expected results:
Instances with source MARC but without associated holdings appear in the results
- Viewing a custom ET in the Lists app - returns Instances with holdings data, when available
Use case 3: view custom ET definition
I want to see what sources/settings are used for a specific custom ET. I can then use the response to update the custom ET if needed.
Steps to view the custom ET definition
GET to /entity-types/custom/{entityTypeId}
{entityTypeId} should be replaced with the
idfrom the response when you created the custom ET; in this example it would be60f48cd8-9e9e-492b-aa6d-9fd3b87ac604the request body is not needed for this request
Response:
{ "id": "60f48cd8-9e9e-492b-aa6d-9fd3b87ac604", "name": "Test Instance with holdings", "description": "A custom ET that will return Instances with Holdings data, if it's available", "labelAlias": null, "customFieldEntityTypeId": null, "columns": [], "crossTenantQueriesEnabled": false, "defaultSort": null, "fromClause": null, "idView": null, "groupByFields": null, "sourceView": null, "sourceViewExtractor": null, "sources": [ { "type": "entity-type", "alias": "simple_instance", "name": "Instance", "joinedViaEntityType": null, "order": 100, "sourceField": null, "targetId": "8fc4a9d2-7ccf-4233-afb8-796911839862", "targetField": null, "overrideJoinDirection": null, "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true }, { "type": "entity-type", "alias": "simple_holdings", "name": "Holdings", "joinedViaEntityType": null, "order": 200, "sourceField": "simple_instance.id", "targetId": "30a5cfad-1868-4f46-86b9-a6ef67e2d9bf", "targetField": "instance_id", "overrideJoinDirection": null, "useIdColumns": true, "essentialOnly": true, "inheritCustomFields": true } ], "requiredPermissions": [], "filterConditions": [], "additionalEcsConditions": [], "deleted": false, "usedBy": [], "owner": "25f4732d-9999-4f20-9ebe-509c69c1437a", "shared": false, "createdAt": "2026-01-15T19:43:24.592+00:00", "updatedAt": "2026-01-15T19:52:23.666+00:00", "isCustom": true, "private": false }
Use case 4: delete a custom ET
There’s a custom ET available to my institution that doesn’t work, or that needs to be deleted for another reason.
Steps to delete a custom ET definition
note: this is a soft delete
DELETE to /entity-types/custom/{entityTypeId}
{entityTypeId} should be replaced with the
idfrom the response when you created the custom ET; in this example it would be60f48cd8-9e9e-492b-aa6d-9fd3b87ac604the request body is not needed for this request
Response:
a successful delete returns a 204 No Content
if the ET is being used by a List or another ET, it will not allow you to delete the ET
it will return a 409 conflict
to restore a deleted custom ET:
use the updateCustomEntityType endpoint
as part of the body of the request
"deleted": false