---
title: Cross-project search
description: Learn how cross-project search (CPS) enables you to search across multiple Serverless projects from a single request.
url: https://www.elastic.co/docs/explore-analyze/cross-project-search
products:
  - Elasticsearch
applies_to:
  - Elastic Cloud Serverless: Preview
  - Elastic Stack: Unavailable
---

# 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](https://www.elastic.co/docs/deploy-manage/cross-project-search-config).
For details on how search, tags, and project routing work in CPS, refer to the following pages:
- [Search in CPS](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-search): Learn how search expressions, search options, and index resolution work.
- [Tags in CPS](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-tags): Learn about predefined and custom project tags and how to use them in queries.
- [Project routing in CPS](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-project-routing): Learn how to route searches to specific projects based on tag values.
- [Manage CPS scope in your project apps](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-manage-scope): Control which projects are searched as you work in Discover, Dashboards, and other Kibana apps.


## Cross-project search as the default behavior for linked projects

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](/docs/explore-analyze/cross-project-search/cross-project-search-search#search-expressions) or [project routing parameters](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-project-routing).

## Project linking

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](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-project-routing) or [qualified index expressions](/docs/explore-analyze/cross-project-search/cross-project-search-search#search-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](https://www.elastic.co/docs/deploy-manage/cross-project-search-config/cps-config-link-and-manage).

### Project IDs and aliases

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**](/docs/deploy-manage/deploy/elastic-cloud/project-settings#elasticsearch-manage-project-connection-aliases) 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.

#### Referencing the origin project

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](/docs/explore-analyze/cross-project-search/cross-project-search-search#search-expressions) for detailed examples and to learn more.

## Excluding indices and projects

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.

### How exclusion works

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-index` or `linked-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-index` is 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 `*`, but `my-index` comes after the exclusion and is still included).
- You can use multiple exclusion patterns in a single expression.


### Exclusion examples

The following examples assume an origin project with two linked projects: `linked-project-1` and `linked-project-2`.
<definitions>
  <definition term="*,-linked-project-1:*">
    Searches everything across all projects, then excludes all indices on the `linked-project-1` project. The search runs on the origin project and `linked-project-2` only.
  </definition>
  <definition term="*,linked-project-1:-my-index">
    Searches everything across all projects, then excludes only the `my-index` index on the `linked-project-1` project. All other indices on `linked-project-1` and all indices on the origin project and `linked-project-2` are still included.
  </definition>
  <definition term="*,-my-index*,-logs">
    Searches everything, then applies two exclusion patterns. Indices matching `my-index*` and the `logs` index are excluded from the results from all projects.
  </definition>
  <definition term="*,linked-project-1:-*">
    Excludes all indices on the `linked-project-1` project. This is functionally equivalent to `*,-linked-project-1:*`.
  </definition>
  <definition term="*,-*">
    Matches all indices across all projects, then excludes all of them. The result is an empty scope.
  </definition>
  <definition term="*,-*,my-index">
    Matches all indices, then excludes all indices. Because the exclusion only affects patterns before it, the `my-index` pattern that follows is unaffected and `my-index` is still included in the search.
  </definition>
</definitions>


## Security

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](https://www.elastic.co/docs/deploy-manage/users-roles/cloud-organization/user-roles) on each project that participates in the search. Each role assignment must include [Cloud Console, Elasticsearch, and Kibana access](/docs/deploy-manage/users-roles/cloud-organization/user-roles#access) to those projects to return project data.
- **Programmatically:** Requests authenticated with an [Elastic Cloud API key](https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys) use that key’s role assignments on each project. Each role assignment must include [Cloud, Elasticsearch, and Kibana API access](/docs/deploy-manage/api-keys/elastic-cloud-api-keys#project-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.
<admonition title="Use Elastic Cloud API keys for CPS">
  For cross-project search, you must use [Elastic Cloud API keys](https://www.elastic.co/docs/deploy-manage/api-keys/elastic-cloud-api-keys), which can authenticate across project boundaries.Cross-project search is not available when performing programmatic searches using [Elasticsearch API keys](https://www.elastic.co/docs/deploy-manage/api-keys/serverless-project-api-keys), because they're scoped to a single project. These keys return results from the origin project only.
</admonition>


### How access is evaluated

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 `logs` index in project 1 are returned
- the `logs` index 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.


## Supported APIs

The following APIs support cross-project search:
- [Async search](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-async-search-submit)
- [Count](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-count) and [CAT count](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-cat-count)
- [ES|QL query](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-esql-queryv) and [ES|QL async query](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-esql-async-query)
- [EQL search](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-eql-search)
- [Field capabilities](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-field-caps)
- [Multi search](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch)
- [Multi search template](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-msearch-template)
- PIT (point in time) [close](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-close-point-in-time), [open](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-open-point-in-time)
- [Reindex](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-reindex)
- [Resolve Index API](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-indices-resolve-index)
- [SQL](https://www.elastic.co/docs/api/doc/elasticsearch/v9/group/endpoint-sql)
- [Search](https://www.elastic.co/docs/api/doc/elasticsearch/operation/operation-search)
- [Search a vector tile](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-search-mvt)
- Search scroll [clear](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-clear-scroll), [run](https://www.elastic.co/docs/api/doc/elasticsearch/v9/operation/operation-scroll)
- [Search template](https://www.elastic.co/docs/solutions/search/search-templates)


### Painless scripting

The [Painless execute API](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-api-examples) (`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 `logs` is equivalent to `_origin:logs` — it targets the origin project only.
- Only a single index is accepted. Wildcards and [project routing](https://www.elastic.co/docs/explore-analyze/cross-project-search/cross-project-search-project-routing) are not supported.
- Requests to linked projects are subject to the same [security model](#security) as other cross-project search requests.

For additional information, refer to the [Painless execute API reference](https://www.elastic.co/docs/reference/scripting-languages/painless/painless-api-examples).

### Cross-project search specific APIs

**Project routing**: `_project_routing`
- [Create or update project routing expressions](https://www.elastic.co/docs/api/doc/elasticsearch-serverless/operation/operation-project-create-many-routing)
- [Get a project routing expression](https://www.elastic.co/docs/api/doc/elasticsearch-serverless/operation/operation-project-get-routing)
- [Delete a project routing expression](https://www.elastic.co/docs/api/doc/elasticsearch-serverless/operation/operation-project-delete-routing)

**Project tags**: `_project/tags`
- [Get tags](https://www.elastic.co/docs/api/doc/elasticsearch-serverless/operation/operation-project-tags)


## Identifying the location of a document

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:
```esql
FROM logs-* METADATA _index
| WHERE @timestamp > "2026-03-16T15:15:00Z"
| KEEP @timestamp, _index, message
```


## Limitations

- **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 `.security` and `.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](https://www.elastic.co/docs/reference/query-languages/esql/esql-cross-serverless-projects#limitations).

For a [complete list of limitations](/docs/deploy-manage/cross-project-search-config#cps-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](https://www.elastic.co/docs/deploy-manage/cross-project-search-config).
To check whether cross-project search is available in a specific Kibana app, refer to the [availability table](/docs/explore-analyze/cross-project-search/cross-project-search-manage-scope#cps-availability).

## Cross-project search examples

The following examples show how cross-project search resolves index names and routes queries when you use unqualified expressions, qualified expressions, and project routing.

### Unqualified search expressions

In the following example, an origin project and a linked project both contain an index named `my-index`.
```json

{
  "size": 2,
  "query": {
    "match_all": {}
  }
}
```

The request will return a response similar to this:
```json

{
  "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`:
```json

{
 "query": "FROM my-index",
  "include_execution_metadata": true
}
```

The query will return a response similar to this:
```json
{
  "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.

### Qualified search expressions

Search limited to the `origin` project:
<tab-set>
  <tab-item title="_search">
    ```json
    ```
  </tab-item>

  <tab-item title="ES|QL">
    ```json

    {
      "query": "FROM _origin:my-index | LIMIT 10"
    }
    ```
  </tab-item>
</tab-set>

The requests include the `_origin` prefix. Only the origin project is searched.
Search across all projects using a wildcard expression:
<tab-set>
  <tab-item title="_search">
    ```json
    ```
  </tab-item>

  <tab-item title="ES|QL">
    ```json

    {
      "query": "FROM *:my-index | LIMIT 10"
    }
    ```
  </tab-item>
</tab-set>

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](/docs/explore-analyze/cross-project-search/cross-project-search-search#search-expressions).

### Project routing examples

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".
<tab-set>
  <tab-item title="_search">
    ```json

    {
      "project_routing":"_alias:lin*",
      "query": {
        "match_all": {}
      }
    }
    ```
  </tab-item>

  <tab-item title="ES|QL">
    ```json

    {
      "query": "SET project_routing=\"_alias:lin*\"; FROM * METADATA _index",
      "include_execution_metadata":true
    }
    ```
  </tab-item>
</tab-set>

The request will return a response similar to this:
<tab-set>
  <tab-item title="_search">
    ```json
    {
      "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"
            }
          }
        ]
      }
    }
    ```
  </tab-item>

  <tab-item title="ES|QL">
    ```json
    {
      "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
            }
          }
        }
      }
    }
    ```
  </tab-item>
</tab-set>


#### Project routing with named project routing expressions

First, create the named expression:
```json

{
  "expression": "_alias:_origin"
}
```

Then, query it:
<tab-set>
  <tab-item title="_search">
    ```json

    {
      "project_routing": "@origin-only",
      "query": {
        "match_all": {}
      }
    }
    ```
  </tab-item>

  <tab-item title="ES|QL">
    ```json

    {
      "project_routing": "@origin-only",
      "query": "FROM *",
      "include_execution_metadata": true
    }
    ```
  </tab-item>
</tab-set>


#### Project routing and qualified expressions

In the first example, both the project routing rule and the qualified index expression limit the search to the linked project:
```json

{
  "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:
```json

{
  "project_routing": "@origin-only",
  "query": {
    "match_all": {}
  }
}
```

This request returns an error:
```json
{
  "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
}
```