Last updated Feb 21, 2024

Using the v2 Search API

Applicable:This tutorial applies to Confluence 7.0 or higher.
Level of experience:Advanced. You should complete at least one intermediate tutorial before working through this tutorial.


The v2 search API is a low level search API that is available to third-party app developers. Because it's low level, we only recommended you use it when you can't implement your use case using CQL, which uses Confluence's high level search API.

The API is search engine agnostic, so your app is not likely to be affected if Confluence's search engine changes in the future. This tutorial will show you how to create a reusable custom query, sort it, and use it to implement a REST resource. By defining a custom query we can make the business logic reusable, easy to use, and robust to changes in the physical structure of the document in the search index.

In this tutorial we will create a plugin that defines a v2 custom query and use it to implement a REST service. The tutorial consists of 3 main elements:

  1. A custom v2 query that searches title of content objects (for example a page or attachment) in a non-standard way.
  2. A custom v2 sort that orders the search results.
  3. A REST service that returns a list of content objects satisfying the custom query ordered by the custom sort.

After completion you should be able to use the v2 search API to query data by title via REST.

Before you begin

To complete this tutorial, you'll need to be familiar with:

Source code

You can find the source code for this tutorial on Atlassian Bitbucket.

To clone the repository, run the following command:

git clone

Alternatively, you can download the source as a ZIP archive.

This tutorial was last tested with Confluence 7.0 using Atlassian SDK 8.0.2.

Key elements


The TitleQuery implements the SearchQuery interface. It takes an input string and uses it to construct a query against the targeted search index. The method SearchQuery#expand expresses the logic using Confluence's built-in v2 queries.

In this case we want to return content with a title matching the given input or that has a prefix of the unstemmed title itself, or its parent, that matches the input.

public class TitleQuery implements SearchQuery {
    private static final String KEY = "titleQuery";
    private final String query;
    public TitleQuery(String query) {
        this.query = query;
    public SearchQuery expand() {
        return BooleanQuery.builder()
                .addShould(new QueryStringQuery(singletonList(SearchFieldNames.TITLE), query, BooleanOperator.OR))
                .addShould(new PrefixQuery(SearchFieldNames.CONTENT_NAME_UNSTEMMED_FIELD, query))
                .addShould(new PrefixQuery(SearchFieldNames.PARENT_TITLE_UNSTEMMED_FIELD, query))


SortBySpaceKey implements the SearchSort interface. The method SearchSort#expand specifies that we want to sort the result by space key, then content type, using Confluence's built-in v2 sort.

public class SortBySpaceKey implements SearchSort {
    private static final String KEY = "sortBySpaceKey";
    private final Order order;
    public SortBySpaceKey(Order order) {
        this.order = order;
    public Order getOrder() {
        return order;
    public SearchSort expand() {
        return new FieldSort(SearchFieldNames.SPACE_KEY, Type.STRING, order);


This is a REST module that performs a search using the above query and sort. We execute a search using the v2 SearchManager which is injected into the REST module using the @ComponentImport annotation. Note that we are passing an instance of ContentSearch to the SearchManager, so the search will target the content index.

@Consumes(value = MediaType.APPLICATION_JSON)
@Produces(value = MediaType.APPLICATION_JSON)
public class SearchByTitleResource {
    private final SearchManager searchManager;
    public SearchByTitleResource(@ComponentImport SearchManager searchManager) {
        this.searchManager = requireNonNull(searchManager);
    public List<Map<String, String>> search(@QueryParam("query") String query) {
        SearchResults result;
        try {
            result = ContentSearch(
                    new TitleQuery(query),
                    new SortBySpaceKey(SearchSort.Order.ASCENDING),
                    0, 10));
        } catch (InvalidSearchException e) {
            throw new RuntimeException(e);
        List<Map<String, String>> response = new ArrayList<>();
        result.forEach(x ->
                response.add(ImmutableMap.of("title", x.getDisplayTitle(), "url", x.getUrlPath())));
        return response;

We specify the root path for all REST modules of the plugin in atlassian-plugin.xml.

<rest key="titleSearch" path="/v2/search/query/tutorial" version="none">

See how it works

Start a local Confluence instance

  1. Make sure you have saved all your code changes to this point.

  2. Open a Terminal and navigate to the plugin root folder (where the pom.xml file is stored).

  3. Run the following command:


    This command builds your plugin code, starts a Confluence instance, and installs your plugin. This may take a while. When the process is complete, you’ll see many status lines on your screen concluding with something like this:

    [INFO] Confluence started successfully in 71s at http://localhost:1990/confluence
    [INFO] Type CTRL-D to shutdown gracefully
    [INFO] Type CTRL-C to exit

You can also build the plugin and deploy it into an existing Confluence instance.

Check the Rest API

Run the following command:

curl -u admin:admin -G "http://localhost:1990/confluence/v2/search/query/tutorial/title" \
--data-urlencode "query=confluence"

This will return any content with a title matching "confluence", sorted by space key.

Next steps

Learn more about extending Confluence's search capabilities with these tutorials:

Rate this page: