Search API Basics
Generally Available in Liferay DXP 2025.Q4+/Portal 2026.Q1+
You can search for content from a Liferay search page, but you can also use the /search API endpoints. If you’re running Liferay locally, while logged in you can visit http://localhost:8080/o/api?endpoint=http://localhost:8080/o/search/v1.0/openapi.json to explore the API.
Use the included commands to generate and search for sample content. The commands below work with basic authentication and assume that you are running Liferay locally at http://localhost:8080 and authenticating as an administrator with the email test@liferay.com and the password learn.
Run the following command to generate a blog post:
curl \
-H "Content-Type: application/json" \
-X POST \
"http://localhost:8080/o/headless-delivery/v1.0/sites/L_GUEST/blog-postings" \
--data-raw '{"articleBody": "Foo", "headline": "Able"}' \
-u "test@liferay.com:learn"
The default site in a Liferay bundle or Docker container uses the external reference code (ERC) L_GUEST. If your instance does not have a site with the L_GUEST ERC, replace it in the above call with the site ID or ERC you’re targeting.
The /search API contains GET and POST endpoints for returning a search results page. Use the GET endpoint when query string parameters are sufficient to express the search. Use the POST endpoint when the search requires request body features, such as facetConfigurations for faceted aggregations or search blueprint context attributes (search.experiences.*).
Simple Query
Here is a simple GET query for the keyword able:
curl \
"http://localhost:8080/o/search/v1.0/search?search=able" \
--user "test@liferay.com:learn"
Here is a simple POST query for the keyword able:
curl \
-H "Content-Type: application/json" \
-X POST \
"http://localhost:8080/o/search/v1.0/search?search=able" \
-d "{}" \
-u "test@liferay.com:learn"
In both cases the response returns a search result with information about the blog post.
{
"items" : [ {
"dateModified" : "2023-07-20T17:15:32Z",
"description" : "Foo",
"itemURL" : "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/35384",
"score" : 318.95966,
"title" : "Able"
} ],
"lastPage" : 1,
"page" : 1,
"pageSize" : 20,
"totalCount" : 1
}
Newly created or changed content appears in /search results only after Liferay finishes indexing it, shortly after the change commits to the database. To retrieve a specific entity immediately after creating it, request it from its own endpoint by ID or external reference code rather than searching. See Retrieving Data: Search Index Versus Database.
Simple Query with Embedded Items
To return not only the search result, but the returned entity’s fields according to its own API schema, set the nestedFields parameter to embedded. This applies to both GET and POST searches. For example, this POST query for the keyword able also requests embedded items:
curl \
-H "Content-Type: application/json" \
-X POST \
"http://localhost:8080/o/search/v1.0/search?nestedFields=embedded&search=able" \
-d "{}" \
-u "test@liferay.com:learn"
The response now returns many more details on the blog post:
{
"items" : [ {
"dateModified" : "2023-07-20T17:15:32Z",
"description" : "Foo",
"embedded" : {
"actions": {
"get-rendered-content-by-display-page": {
"method": "GET",
"href": "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/38975/rendered-content-by-display-page/{displayPageKey}"
},
"get": {
"method": "GET",
"href": "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/38975"
},
"replace": {
"method": "PUT",
"href": "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/38975"
},
"update": {
"method": "PATCH",
"href": "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/38975"
},
"delete": {
"method": "DELETE",
"href": "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/38975"
}
},
"alternativeHeadline" : "",
"articleBody" : "Foo",
"creator" : {
"additionalName" : "",
"contentType" : "UserAccount",
"familyName" : "Test",
"givenName" : "Test",
"id" : 20123,
"name" : "Test Test"
},
"customFields" : [ ],
"dateCreated" : "2023-07-20T17:15:32Z",
"dateModified" : "2023-07-20T17:15:32Z",
"datePublished" : "2023-07-20T17:15:00Z",
"description" : "Foo",
"encodingFormat" : "text/html",
"externalReferenceCode" : "f73109ce-8db6-36e3-f2c7-4505c6454ed8",
"friendlyUrlPath" : "able",
"headline" : "Able",
"id" : 35384,
"keywords" : [ ],
"numberOfComments" : 0,
"relatedContents" : [ ],
"renderedContents" : [ ],
"siteId" : 20119,
"taxonomyCategoryBriefs" : [ ]
},
"itemURL" : "http://localhost:8080/o/headless-delivery/v1.0/blog-postings/35384",
"score" : 318.95966,
"title" : "Able"
} ],
"lastPage" : 1,
"page" : 1,
"pageSize" : 20,
"totalCount" : 1
}
You can build more complex requests using the supported parameters and request body properties.
Search Result Fields
Each result in the response’s items array can carry these fields. Which ones appear depends on the entity and your request parameters:
| Field | Description |
|---|---|
dateCreated, dateModified | The item’s creation and modification timestamps, in ISO 8601 UTC (for example, 2025-09-20T09:18:45Z). |
dateReview | The item’s scheduled editorial review date in the same format (for example, 2026-08-15T14:32:00Z), populated by content types with a review workflow and null when unset. |
description, title | The item’s description and title, localized per the request’s Accept-Language header where the entity supports it. |
embedded | The entity’s own payload, present only when the request sets nestedFields=embedded, shaped by the entity’s headless API schema and varying by entryClassName. |
entryClassName | The indexed entity’s class name (for example, com.liferay.blogs.model.BlogsEntry), which you can pass back in entryClassNames to narrow a follow-up search. |
itemURL | The headless REST API URL for fetching the full entity (for example, /o/headless-delivery/v1.0/blog-posting/12345). |
score | The search engine’s relevance score as a decimal number (for example, 1.42), where higher scores mean more relevant results, useful for threshold filtering or custom ranking. |
Search Parameters
Query parameters can be used to control the results further.
| Parameter | Notes | Method |
|---|---|---|
entryClassNames | Specify a comma-separated list of entry class names to search. Defaults to all searchable types. See Searchable Content Types for the out-of-the-box class names and how to discover them at runtime. | GET POST |
fields | The fields parameter requests only specific fields to be enumerated in each of the elements in the response. When the search spans multiple types (entryClassNames is empty or lists multiple class names), only fields common to all types work. Requesting other fields returns an empty object for each result item. | GET POST |
nestedFields | Supports embedded to get back nested data. | GET POST |
restrictFields | Specify a comma-separated list of fields to exclude from each result. This is the inverse of fields; use one or the other, not both. | GET POST |
filter | Filters across different fields. Supported fields appear in the table below. Supported operators are eq, ne, gt, ge, lt, and le. Multivalue collections use any() or all() (for example, groupIds/any(g:g eq 20121)). Date fields use the OData Edm.Date format YYYY-MM-DD. Combine expressions with the logical operators and, or, and not. For more robust filtering options, use a search blueprint (DXP subscription). | GET POST |
page | Specify which page to return. Defaults to 1. | GET POST |
pageSize | Specify how many items you want per page. Defaults to 20. | GET POST |
scope | Specify a list of site, asset library, or space IDs or ERCs to search. You can mix IDs and ERCs in the same request (for example, scope=20121,L_GUEST). | GET POST |
search | Search by keyword(s). | GET POST |
sort | Sort by ascending or descending order using the format field:asc or field:desc (for example, dateModified:desc). Supported fields appear in the table below. By default, results sort by relevance score, descending. | GET POST |
blueprintExternalReferenceCode | Specify a search blueprint to customize the search request. POST requests must instead use a request attribute. | GET |
emptySearch | Set this to true to return results when no search keywords are entered in the search parameter. | GET |
Search Blueprints can also add sort configurations to the search page. Make sure you understand how these blueprint-added sorts interact with the sort parameter if you plan to use both.
See API Query Parameters for more information.
Searchable Content Types
When entryClassNames is empty, the search covers all searchable types. These out-of-the-box types and class names are available:
| Type | Class Name |
|---|---|
| Basic Document (CMS) | com.liferay.object.model.ObjectDefinition#Z7P5 |
| Basic Web Content (CMS) | com.liferay.object.model.ObjectDefinition#H4T4 |
| Blog (CMS) | com.liferay.object.model.ObjectDefinition#Y8H4 |
| Blogs Entry | com.liferay.blogs.model.BlogsEntry |
| Bookmarks Entry | com.liferay.bookmarks.model.BookmarksEntry |
| Bookmarks Folder | com.liferay.bookmarks.model.BookmarksFolder |
| Calendar Event | com.liferay.calendar.model.CalendarBooking |
| Commerce Product | com.liferay.commerce.product.model.CPDefinition |
| Document | com.liferay.document.library.kernel.model.DLFileEntry |
| Documents Folder | com.liferay.document.library.kernel.model.DLFolder |
| Dynamic Data Lists Record | com.liferay.dynamic.data.lists.model.DDLRecord |
| External Video (CMS) | com.liferay.object.model.ObjectDefinition#S1T1 |
| Form Record | com.liferay.dynamic.data.mapping.model.DDMFormInstanceRecord |
| Knowledge Base Article | com.liferay.knowledge.base.model.KBArticle |
| Message Boards Message | com.liferay.message.boards.model.MBMessage |
| Object Entry Folder | com.liferay.object.model.ObjectEntryFolder |
| Page | com.liferay.portal.kernel.model.Layout |
| Sharing Entry | com.liferay.sharing.model.SharingEntry |
| User | com.liferay.portal.kernel.model.User |
| Web Content Article | com.liferay.journal.model.JournalArticle |
| Web Content Folder | com.liferay.journal.model.JournalFolder |
| Wiki Page | com.liferay.wiki.model.WikiPage |
The types marked (CMS) are available only on instances with Liferay CMS, using the class names shown above.
Custom objects and custom CMS content structures follow the same com.liferay.object.model.ObjectDefinition#[identifier] format, but Liferay generates each identifier when you create the object definition, so the identifiers on your instance differ from the values above. An identifier stays the same on the instance where it was created, so resolve it at runtime rather than hard-coding it in code you deploy to more than one environment. To target a type by a value that’s stable across instances, filter on objectDefinitionExternalReferenceCode instead (for example, filter=objectDefinitionExternalReferenceCode eq 'L_CMS_BASIC_WEB_CONTENT'). See Filter Fields.
The Wiki Page type is deprecated as of Liferay DXP 2024.Q4/Portal GA129, and the Wiki deprecation feature flag (LPD-35013) is disabled on new and upgraded instances. While the flag is disabled, Wiki Page is absent from searchable-asset-names and from searches that leave entryClassNames empty. A request that names com.liferay.wiki.model.WikiPage explicitly still returns indexed wiki pages.
To discover the class names available on your instance at runtime, call GET /o/search-experiences-rest/v1.0/searchable-asset-names/[language-id] (for example, /searchable-asset-names/en-US). Each entry pairs a class name with its display name, so you can match a custom object to its identifier. Each search result also carries its entryClassName, which you can pass back in entryClassNames to narrow a follow-up search.
Filter Fields
If they’re present in the headless entity’s response, you can filter and/or sort by these fields:
| API Field | Corresponding Search Index Field | Parameter |
|---|---|---|
creatorId | userId | filter |
folderId | folderId | filter |
groupIds | groupId | filter |
objectDefinitionId | objectDefinitionId | filter |
objectDefinitionExternalReferenceCode | objectDefinitionExternalReferenceCode | filter |
objectFolderExternalReferenceCode | objectFolderExternalReferenceCode | filter |
status | status | filter |
taxonomyCategoryIds | assetCategoryIds | filter |
dateCreated | createDatecreateDate_sortable | filter sort |
dateDisplay | displayDatedisplayDate_sortable | filter sort |
dateExpiration | expirationDateexpirationDate_sortable | filter sort |
dateModified | modifiedmodified_sortable | filter sort |
datePublish | publishDatepublishDate_sortable | filter sort |
dateReview | reviewDatereviewDate_sortable | filter sort |
extension | extension | filter |
keywords | assetTagNames.lowercase | filter sort |
scopeGroupId | scopeGroupId | filter |
title | localized_title_[locale].keyword_lowercase | filter sort |
This reference reflects the latest quarterly release. Field availability may vary between versions and entities; verify specific field support within your environment’s API schema.
- The corresponding search index field names appear for convenience. They can change without notice between versions. See Exploring Indexed Fields to learn more about the indexed fields.
- The headless API field
keywordrefers to the entity’s tag names.
Filtering by Status
DXP 2025.Q3+
By default only approved items are returned when searching (i.e., items with a status of 0). Items with other workflow statuses can be returned by filtering on the status field. For example, a filter value for returning both approved and pending items is status eq 0 or status eq 1. Pass the expression in the filter parameter, URL-encoding it in GET requests:
curl \
"http://localhost:8080/o/search/v1.0/search?filter=status%20eq%200%20or%20status%20eq%201&search=able" \
--user "test@liferay.com:learn"
These status values are available:
| Value | Status |
|---|---|
-1 | Any |
0 | Approved |
1 | Pending |
2 | Draft |
3 | Expired |
4 | Denied |
5 | Inactive |
6 | Incomplete |
7 | Scheduled |
8 | In Trash |
9 | Empty |
You cannot filter web content articles by status. There’s a built-in filter that ensures only head versions (the latest approved content) can be returned by a search. To limit web content results, use the scope parameter or filter by groupIds instead; only approved items are returned by default.
Search Request Body
GET searches do not include a request body. For POST searches, empty requests are allowed (e.g., specify {} as the request body), but there are two properties available for influencing the response:
| Property | Description |
|---|---|
attributes | Set available search context attributes to configure a search blueprint or enable empty search. See Available Search Request Attributes for details. |
facetConfigurations | Set facet configurations to return facets in the response. See Adding Facet Configurations to the Request. |
Adding Attributes to the Search Request
To search with a blueprint, use this request body syntax:
{
"attributes": {
"search.experiences.blueprint.external.reference.code": ""
}
}
Custom Search Request Attributes
You can add a custom attribute to the API request, then consume its value in a search blueprint. With custom attributes, the search request can receive dynamic contextual data and pass it to a blueprint. In the blueprint you may want to filter by the value, use it to configure the query, or add it to a condition. This allows for creating personalized search experiences using contextual data.
Prefix custom attributes with search.experiences. This example adds the freshnessInteger and its value to the search request:
{
"attributes": {
"search.experiences.freshnessInteger": 30
}
}
Declare the attribute as a parameter in the blueprint, with its type:
{
"parameters": {
"search.experiences.freshnessInteger": {
"type": "Integer"
}
}
}
Use the value in an element. For example, query for content that was published in the last 30 days, using a range query with the createDate field:
"query": {
"range": {
"createDate": {
"gte": "now-"${search.experiences.freshnessInteger}d/d",
"lte": "now/d"
}
}
}
Available Search Request Attributes
You can set these attributes into the request:
| Property | Description |
|---|---|
search.empty.search | Set this to true to return results even if the search parameter is omitted from the request. |
DXP Onlysearch.experiences.blueprint.external.reference.code | (Preferred) Set a search blueprint to control the search query and configuration. |
DXP Onlysearch.experiences.blueprint.id | Set a search blueprint to control the search query and configuration. |
DXP Onlysearch.experiences.ip.address | Set automatically. Only use this to test blueprints with geolocation configured, to simulate different locations. |
DXP Onlysearch.experiences.scope.group.id | Set this when your blueprint uses an element that requires it: Limit Search to the Current Site, Boost Contents in a Category for a User Segment, or Staging Aware. |
DXP Onlysearch.experiences.* | You can pass dynamic data to blueprints-backed headless search requests using custom attributes. Prefix these attributes with search.experiences. and declare them in the blueprint’s parameter configuration. |
Adding Facet Configurations to the Request
To search with a facet configuration, use this request body syntax:
{
"facetConfigurations": [
{
"aggregationName": "",
"attributes": {},
"frequencyThreshold": "",
"maxTerms": "",
"name": "",
"values": []
}
]
}
A facet configuration can have several properties:
| Property | Description |
|---|---|
aggregationName | Choose a unique name for the aggregation. This is required to distinguish between instances of the same type (i.e., with the same name property). |
attributes | Some facets require additional attributes.field: The custom, date-range, and nested facets require a String attribute called field to set the field to aggregate results by.format and ranges: The date-range facet also requires a format String to specify the date format (e.g., yyyyMMddHHmmss) and a ranges object array to provide the ranges.filterField, filterValue, and path: The nested facet requires a filterField String, a filterValue String, and a path String.vocabularyIds: The vocabulary facet requires a String array of vocabularyIds. Discover vocabulary IDs with GET /o/headless-admin-taxonomy/v1.0/sites/{siteId}/taxonomy-vocabularies. |
frequencyThreshold | Set the minimum frequency required for terms to appear in the list of facet terms. Defaults to 1. |
maxTerms | Set the maximum number of facet terms to display, regardless of how many matching terms are found for the facet. Defaults to 10. |
name | Set the type of facet:categorycustomdate-rangefoldernestedsitetagtypevocabularyuser |
values | Post filter the results by selecting values. This is like clicking facet terms in the facet widget. |
If a custom, date-range, or nested facet configuration omits a required attribute, the request still succeeds, but the response contains no aggregation for that facet. The same applies to any facet configuration with an unrecognized name value.
The custom facet recognizes top level fields. Objects and web content structure fields are indexed as nested fields, so you must choose the nested facet for these entities.
For example, here’s a date-range facet’s ranges attribute:
{
"ranges": [
{
"label": "range-1",
"range": "[20220411085757 TO 20230413075757]"
},
{
"label": "range-2",
"range": "[20230409085757 TO 20230413075757]"
}
]
}
Aggregations and Search Facets in the Response
GET searches do not return facets. For POST searches, you can see aggregations and search facets in the API response. To see aggregations,
-
Set the attribute
search.experiences.blueprint.external.reference.codein your search request.
Search facets are returned if you add a facet configuration to the request. For example, this request body asks for a tag facet:
{
"facetConfigurations": [
{
"name": "tag"
}
]
}
In the response, the returned search facet looks like this:
"searchFacets": {
"tag": [
{
"displayName": "business",
"term": "business",
"frequency": 26
},
{
"displayName": "fun",
"term": "fun",
"frequency": 1
}
]
}
The searchFacets map is keyed by each facet configuration’s aggregationName. When you don’t set one, the key defaults to the facet’s name, which is why this example’s key is tag. The map appears in the response only when the request includes facetConfigurations.
Enabling Guest Access to the Search API
To enable guest access to the API, create a new service access policy as follows:
| Field | Entry |
|---|---|
| Name | SEARCH |
| Enabled | Checked |
| Default | Checked |
| Title | Public Access to Search API |
| Service Class Name | com.liferay.portal.search.rest.internal.resource.v1_0.SearchResultResourceImpl |
| Method Name | [postSearchPage OR getSearchPage] |