Cross-project search
With cross-project search (CPS), users in your organization can search across multiple Elastic Cloud Serverless projects at once, instead of searching each project individually. When your data is split across projects to organize ownership, use cases, or environments, cross-project search lets you query all the data from a single place.
From the origin project, you can run queries, build dashboards, and configure alerting rules that include data from all linked projects. Results are filtered by each user's permissions across projects.
Cross-project search relies on linking projects within your Elastic Cloud organization. After you link projects together, searches from the origin project automatically run across all linked projects.
This overview explains how cross-project search works, including project linking and security. For prerequisites, compatibility requirements, architecture planning, and scope defaults, refer to Configure cross-project search.
For details on how search, tags, and project routing work in CPS, refer to the following pages:
- Search in CPS: Learn how search expressions, search options, and index resolution work.
- Tags in CPS: Learn about predefined and custom project tags and how to use them in queries.
- Project routing in CPS: Learn how to route searches to specific projects based on tag values.
- Manage CPS scope in your project apps: Control which projects are searched as you work in Discover, Dashboards, and other Kibana apps.
Projects are intended to act as logical namespaces for data, not hard boundaries for querying it. You can split data into projects to organize ownership, use cases, or environments, while still expecting to search and analyze that data from a single place.
After you link projects, searches from the origin project run across the origin and all linked projects by default. This default behavior provides a consistent experience for querying, analysis, and insights across linked projects. Restricting search scope is always possible, by explicitly scoping the search request using qualified expressions or project routing parameters.
In Serverless, projects can be linked together.
Cross-project search runs across origin and linked projects within your Elastic Cloud organization:
- Origin project: The base project where you link projects and run cross-project searches.
- Linked projects: The projects you connect to the origin project. Data in the linked projects becomes searchable from the origin project.
After you link projects, searches that you run from the origin project are no longer scoped to the origin project by default. Any search initiated on the origin project automatically runs across the origin project and all its linked projects (cross-project search).
When you search from an origin project, the query runs against its linked projects automatically unless you explicitly change the query scope by using project routing expressions or qualified index expressions.
Project linking is not bidirectional. Searches initiated from a linked project do not run against the origin project. If you need bidirectional search, link the projects twice, in both directions.
You can link projects by using the Elastic Cloud UI. For step-by-step instructions, refer to Link projects for cross-project search.
Each project has a unique project ID and a project alias. The project alias is derived from the project name and can be modified.
The project ID uniquely identifies a project and is system-generated.
The project alias is a human-readable identifier derived from the project's connection alias. If you want to change the project alias, you must update the connection alias of the linked project.
While both the project ID and project alias uniquely identify a project, cross-project search uses project aliases in index expressions. Project aliases are intended to be user-friendly and descriptive, making search expressions easier to read and maintain.
In addition to using a project alias, CPS provides a reserved identifier, _origin, that always refers to the origin project of the search.
You can use _origin in search expressions to explicitly target the origin project, without having to reference its specific project alias. Refer to Qualified and unqualified search expressions for detailed examples and to learn more.
You can exclude specific indices or projects from a cross-project search by prefixing a pattern with a dash (-).
This enables you to start with a broad search scope and narrow it down by removing specific indices or projects from the results.
Exclusion follows these rules:
- A leading
-on a pattern signals exclusion. The dash can be placed on the index part or on the project part of an expression, each with different requirements. Placing the dash on the index part (for example,linked-project-1:-my-indexorlinked-project-1:-*) works for any index pattern and can be used on its own. Placing the dash on the project part (for example,*,-linked-project-1:*) requires a preceding inclusion pattern and only works when the index part is the*wildcard. For example,*,-linked-project-1:*is valid, but*,-linked-project-1:my-indexis not. You cannot prefix both the project and the index with a dash in the same expression (for example,-linked-project-1:-*is invalid). - An exclusion pattern only affects patterns that appear before it in the expression.
Patterns listed after the exclusion are not affected by it (for example, in
*,-*,my-index, the exclusion-*removes everything matched by the first*, butmy-indexcomes after the exclusion and is still included). - You can use multiple exclusion patterns in a single expression.
The following examples assume an origin project with two linked projects: linked-project-1 and linked-project-2.
*,-linked-project-1:*- Searches everything across all projects, then excludes all indices on the
linked-project-1project. The search runs on the origin project andlinked-project-2only. *,linked-project-1:-my-index- Searches everything across all projects, then excludes only the
my-indexindex on thelinked-project-1project. All other indices onlinked-project-1and all indices on the origin project andlinked-project-2are still included. *,-my-index*,-logs- Searches everything, then applies two exclusion patterns. Indices matching
my-index*and thelogsindex are excluded from the results from all projects. *,linked-project-1:-*- Excludes all indices on the
linked-project-1project. This is functionally equivalent to*,-linked-project-1:*. *,-*- Matches all indices across all projects, then excludes all of them. The result is an empty scope.
*,-*,my-index- Matches all indices, then excludes all indices. Because the exclusion only affects patterns before it, the
my-indexpattern that follows is unaffected andmy-indexis still included in the search.
This section gives you a high-level overview of how security works in cross-project search.
From within Kibana: Searches you run from the origin project use your Elastic Cloud user role assignments on each project that participates in the search. Each role assignment must include Cloud Console, Elasticsearch, and Kibana access to those projects to return project data.
Programmatically: Requests authenticated with an Elastic Cloud API key use that key’s role assignments on each project. Each role assignment must include Cloud, Elasticsearch, and Kibana API access to those projects to return project data.
Alternatively, a user or key can be granted organization-level roles that grant access to all projects in the organization.
Permissions are always evaluated per project. It does not matter whether you query that project from its own endpoint or from an origin project linked through CPS: the same role assignments apply.
For cross-project search, you must use Elastic Cloud API keys, which can authenticate across project boundaries.
Cross-project search is not available when performing programmatic searches using Elasticsearch API keys, because they're scoped to a single project. These keys return results from the origin project only.
Access control operates in two stages:
- Authentication verifies the identity associated with a request (for example, a Cloud user or API key) and retrieves that identity's role assignments in each project.
- Authorization evaluates those roles to determine which actions and resources the request can access within each project.
For example, if you have a viewer role in project 1, an admin role in project 2, and a custom role in project 3, you can access all three projects through cross-project search. Each project enforces the permissions associated with the role you have in that project.
When a cross-project search query targets a linked project that you have access to, authorization checks are performed locally in that project to determine whether you have the required privileges to access the requested resources.
Example
You have read access to the logs index in project 1, but no access to the logs index in project 2.
If you run GET logs/_search:
- documents from the
logsindex in project 1 are returned - the
logsindex in project 2 is not accessible and is excluded from the results. No error is returned. The query succeeds, but results only include data from projects where your role grants access.
The following APIs support cross-project search:
- Async search
- Count and CAT count
- ES|QL query and ES|QL async query
- EQL search
- Field capabilities
- Multi search
- Multi search template
- PIT (point in time) close, open
- Reindex
- Resolve Index API
- SQL
- Search
- Search a vector tile
- Search scroll clear, run
- Search template
The Painless execute API (POST _scripts/painless/_execute) does not search across linked projects. Unlike the search APIs listed above, the execute API resolves index names against the origin project only.
When testing scripts with the execute API in a cross-project search environment:
- To target a specific linked project, prefix the index with the project alias:
projectAlias:myindex. - To explicitly target the origin project, use
_origin:myindex.- An unqualified index name like
logsis equivalent to_origin:logs— it targets the origin project only.
- An unqualified index name like
- Only a single index is accepted. Wildcards and project routing are not supported.
- Requests to linked projects are subject to the same security model as other cross-project search requests.
For additional information, refer to the Painless execute API reference.
Project routing: _project_routing
- Create or update project routing expressions
- Get a project routing expression
- Delete a project routing expression
Project tags: _project/tags
To determine whether a document comes from the origin project or a linked project, examine the _index field.
Documents from linked projects include the linked project's alias as a prefix, separated by a colon:
my-linked-project-abc123:.ds-logs-generic.otel-default-2026.03.02-000001
Origin documents have no prefix:
.ds-logs-generic.otel-default-2026.03.02-000001
In ES|QL, the _index field is not returned by default. To include it, use the METADATA keyword:
FROM logs-* METADATA _index
| WHERE @timestamp > "2026-03-16T15:15:00Z"
| KEEP @timestamp, _index, message
- Maximum of 20 linked projects: Each origin project can have up to 20 linked projects. A linked project can be associated with any number of origin projects.
- System indices: Indices such as
.securityand.fleet-*are excluded from cross-project search results by design. - New projects only: During technical preview, only newly created projects can function as origin projects.
- Anomaly detection and transforms: During technical preview, ML anomaly detection jobs and transforms are not supported with CPS. They continue to run on origin project data only.
- Data frame analytics analytics jobs: data frame analytics analytics jobs are not supported with CPS. They continue to run on origin project data only.
- For ES|QL limitations specific to CPS, refer to ES|QL with cross-project search.
For a complete list of limitations, including restrictions for Elastic Observability and Elastic Security projects, as well as administrator-focused details including compatibility, architecture patterns, and feature impacts, refer to Configure cross-project search.
To check whether cross-project search is available in a specific Kibana app, refer to the availability table.
The following examples show how cross-project search resolves index names and routes queries when you use unqualified expressions, qualified expressions, and project routing.
In the following example, an origin project and a linked project both contain an index named my-index.
GET /my-index/_search
{
"size": 2,
"query": {
"match_all": {}
}
}
The request will return a response similar to this:
{
"took": 34,
"timed_out": false,
"num_reduce_phases": 3,
"_shards": {
"total": 12,
"successful": 12,
"skipped": 0,
"failed": 0
},
"_clusters": {
"total": 2,
"successful": 2,
"skipped": 0,
"running": 0,
"partial": 0,
"failed": 0,
"details": {
"_origin": {
"status": "successful",
"indices": "my-index",
"took": 21,
"timed_out": false,
"_shards": {
"total": 6,
"successful": 6,
"skipped": 0,
"failed": 0
}
},
"linked_project": {
"status": "successful",
"indices": "my-index",
"took": 5,
"timed_out": false,
"_shards": {
"total": 6,
"successful": 6,
"skipped": 0,
"failed": 0
}
}
}
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "linked_project:my-index",
"_id": "IH-mupwBMZyy2F9u2IQz",
"_score": 1.0,
"_source": {
"project": "linked"
}
},
{
"_index": "my-index",
"_id": "u0SnupwBaOrMOsBImb7G",
"_score": 1.0,
"_source": {
"project": "origin"
}
}
]
}
}
In this example, both the origin project and a linked project contain an index named my-index:
POST /_query
{
"query": "FROM my-index",
"include_execution_metadata": true
}
The query will return a response similar to this:
{
"took": 39,
"is_partial": false,
"completion_time_in_millis": 1772659251830,
"documents_found": 2,
"values_loaded": 4,
"start_time_in_millis": 1772659251791,
"expiration_time_in_millis": 1773091251753,
"columns": [
{
"name": "project",
"type": "text"
},
{
"name": "project.keyword",
"type": "keyword"
}
],
"values": [
[
"origin",
"origin"
],
[
"linked",
"linked"
]
],
"_clusters": {
"total": 2,
"successful": 2,
"running": 0,
"skipped": 0,
"partial": 0,
"failed": 0,
"details": {
"_origin": {
"status": "successful",
"indices": "my-index",
"took": 39,
"_shards": {
"total": 6,
"successful": 6,
"skipped": 0,
"failed": 0
}
},
"linked_project": {
"status": "successful",
"indices": "my-index",
"took": 23,
"_shards": {
"total": 6,
"successful": 6,
"skipped": 0,
"failed": 0
}
}
}
}
}
These requests don’t include a project prefix. The my-index index is searched in the origin project and in the linked project.
Search limited to the origin project:
GET _origin:my-index/_search
POST /_query
{
"query": "FROM _origin:my-index | LIMIT 10"
}
The requests include the _origin prefix. Only the origin project is searched.
Search across all projects using a wildcard expression:
GET *:my-index/_search
POST /_query
{
"query": "FROM *:my-index | LIMIT 10"
}
The requests explicitly target all projects using the *: prefix.
The my-index index is evaluated separately in each project.
The index my-index must exist in every project, otherwise the search returns an error.
In the following example, there is an origin project and a linked project. The origin project contains one index, my-index. The linked project contains two indices: my-index and logs.
The following request searches all indices on projects whose alias starts with "lin".
GET /*/_search
{
"project_routing":"_alias:lin*",
"query": {
"match_all": {}
}
}
GET /_query
{
"query": "SET project_routing=\"_alias:lin*\"; FROM * METADATA _index",
"include_execution_metadata":true
}
The request will return a response similar to this:
{
"took": 60,
"timed_out": false,
"_shards": {
"total": 12,
"successful": 12,
"skipped": 0,
"failed": 0
},
"_clusters": {
"total": 1,
"successful": 1,
"skipped": 0,
"running": 0,
"partial": 0,
"failed": 0,
"details": {
"linked_project": {
"status": "successful",
"indices": "*",
"took": 11,
"timed_out": false,
"_shards": {
"total": 12,
"successful": 12,
"skipped": 0,
"failed": 0
}
}
}
},
"hits": {
"total": {
"value": 2,
"relation": "eq"
},
"max_score": 1.0,
"hits": [
{
"_index": "linked_project:my-index",
"_id": "ytm_v5wB1c8L_6vBSeM6",
"_score": 1.0,
"_source": {
"project": "linked"
}
},
{
"_index": "linked_project:logs",
"_id": "y9m_v5wB1c8L_6vBW-Mu",
"_score": 1.0,
"_source": {
"project": "linked-logs-data"
}
}
]
}
}
{
"took": 54,
"is_partial": false,
"completion_time_in_millis": 1772740419771,
"documents_found": 2,
"values_loaded": 6,
"start_time_in_millis": 1772740419717,
"expiration_time_in_millis": 1773172419734,
"columns": [
{
"name": "project",
"type": "text"
},
{
"name": "project.keyword",
"type": "keyword"
},
{
"name": "_index",
"type": "keyword"
}
],
"values": [
[
"linked-logs-data",
"linked-logs-data",
"linked_project:logs"
],
[
"linked",
"linked",
"linked_project:my-index"
]
],
"_clusters": {
"total": 1,
"successful": 1,
"running": 0,
"skipped": 0,
"partial": 0,
"failed": 0,
"details": {
"linked_project": {
"status": "successful",
"indices": "*",
"took": 35,
"_shards": {
"total": 12,
"successful": 12,
"skipped": 0,
"failed": 0
}
}
}
}
}
First, create the named expression:
PUT /_project_routing/origin-only
{
"expression": "_alias:_origin"
}
Then, query it:
GET /my*/_search
{
"project_routing": "@origin-only",
"query": {
"match_all": {}
}
}
GET /_query
{
"project_routing": "@origin-only",
"query": "FROM *",
"include_execution_metadata": true
}
In the first example, both the project routing rule and the qualified index expression limit the search to the linked project:
GET /linked_project:my*/_search
{
"project_routing": "_alias:lin*",
"query": {
"match_all": {}
}
}
In the next example, the project routing rule and the qualified index expression target different projects which causes a conflict:
GET /_origin:*,linked_project:*/_search
{
"project_routing": "@origin-only",
"query": {
"match_all": {}
}
}
This request returns an error:
{
"error": {
"root_cause": [
{
"type": "no_matching_project_exception",
"reason": "No such project: [linked_project] with project routing [@origin-only]"
}
],
"type": "no_matching_project_exception",
"reason": "No such project: [linked_project] with project routing [@origin-only]"
},
"status": 404
}