Adding a field to CQL
This tutorial applies to Confluence 5.9 or higher
Level of experience:
This is an advanced tutorial. You should have completed at least one intermediate tutorial before working through this tutorial.
It should take you approximately 1 hour to complete this tutorial.
On this page:
This tutorial shows you how to add a field to the Confluence Query Language (CQL). For more detailed reference documentation please see: CQL Field Module
To complete this tutorial, you must already understand:
- The basics of Java development: classes, interfaces, methods, how to use the compiler, and so on.
- How to create an Atlassian plugin project using the Atlassian Plugin SDK.
We encourage you to work through this tutorial. If you want to skip ahead or check your work when you are done, you can find the plugin source code on Atlassian Bitbucket. To clone the repository, use the following command:
Alternatively, you can download the source using the get source option here: https://bitbucket.org/atlassian/confluence-query-lang-add-field-tutorial/downloads
Step 1. Add the field declaration to your atlassian-plugin.xml
First, let's add a dependency on the SPI to the
Now to add a field, we'll need to make this addition to the
cql-query-field declaration is what defines a new CQL field:
|fieldName||Name of the field which will be used in CQL statements|
|key||Unique id of this CQL field declaration in the plugin|
|name||A human readable name for the field|
|class||The implementation of the CQL field, this should extend the
ui-support declaration, allows the new field to appear in the UI:
|value-type||Type of values which can be stored against this field (see supported UI value types in CQL Field Module)|
|default-operator||CQL operator used in conjunction with the contents of the UI field when it's converted to a statement|
|i18n-key||The i18n key in the i18n properties file for the text that will be displayed as a label for the field in the UI|
|data-uri||REST endpoint which provides field values|
For this example, we'll implement a status field, using the generic string UI value type backed by a data-uri which provides the user with a set of values to select from in the front-end.
Our data-uri points to a simple REST resource, responding with a set of status values.
The response from the /status-field/status-values endpoint is:
Finally, we need to add the i18n field name to our i18n properties file:
Step 2. Add an EventListener and Extractor
This step is not specifically related to the process of adding fields to CQL, but is required for the field to function correctly. In the example repository, an EventListener has been setup that listens for page create events and sets the default 'status' of 'pending' as a content property on the page. Setting a content property to a piece of content could be done in a variety of ways, such as via a REST resource but we have used the EventListener in this case for simplicity. An Extractor class is then used which runs after the EventListener and page has been created, extracting the status information from a content property, then adding the information to the index and allowing it to searched.
Below is an extractor implementation used in the example repository and adapted from the Extractor Module documentation.
Step 3. Add a FieldHandler
In order to complete the addition of our new CQL field, we need to provide the implementation of the class which was referenced in our cql-query-field declaration.
We extend the BaseFieldHandler, which provides much of the default implementation required and is the class recommended as a base for all CQL FieldHandler implementations. The class must also implement at least one typed FieldHandler interface, EqualityFieldHandler in our case, which will allow for support of "=, !=, IN and NOT IN" CQL operators for querying the status of a page. See CQL Field Module for a full list of available field types.
The EqualityFieldHandler interface requires that we implement two build methods. One will support SetExpressions, "status IN ('pending', 'review')", and the other EqualityExpressions, "status = 'pending'".
These methods usually look quite similar, and we have provided a number of helper functions via the CQL SPI for performing common operations found in these build method implementations.
validateSupportedOp - is used to check whether the operator used in the CQL statement being executed is supported by our FieldHandler. Using the ExpressionData.getOperator method as a first argument and providing a set of the operations we have decided to support will perform this validation for us, throwing an exception with a meaningful error message to the user when a CQL statement with an unsupported operator has attempted to execute. Using this helper is recommended to allow existing FieldHandlers to continue working by providing the appropriate response in the event that we add new operators to CQL.
wrapV2Search - Used to combine a V2SeachQuery with the ExpressionData object provided to the build method to return a V2SearchQueryWrapper object, expected as the return type of the the build method. This V2SearchQueryWrapper object contains all the necessary information to execute the CQL and handles negation based on the ExpressionData operator to retrieve the required data from the index.
joinSingleValueQueries - A helper method used in SetExpressionData build method implementations, allowing a single V2SearchQuery to be transformed into a single QueryObject which can then be passed to wrapV2Search.
Step 4. Build, install and run the plugin
Follow these steps to build and install your plugin, so that you can test your code.
- Make sure you have saved all your code changes to this point.
- Open a terminal window and navigate to the plugin root folder (where the
Run the following command:
This command builds your plugin code, starts a Confluence instance, and installs your plugin. This may take several seconds. When the process is complete, you'll see many status lines on your screen concluding with something like:
- Open your browser and navigate to the local Confluence instance started by
If you used the settings in the instructions, the default port is 1990.
- Navigate to your local Confluence, enter http://localhost:1990/confluence/ in your browser.
- At the Confluence login, enter the username
- Navigate to the Confluence search page (http://localhost:1990/confluence/) which is backed by CQL.
- Hit Add a filter. Your new field should appear in the list of CQL fields available to filter the search.
Screenshot: Confluence Search page showing the newly added Status CQL field.