You are viewing the documentation of TeamCity 9.x, which is not the most recently released version of TeamCity.
View this page in TeamCity 2018.x documentation or refer to the listing to choose the documentation corresponding to your TeamCity version.

Skip to end of metadata
Go to start of metadata

This section covers:


Hint: you can use source code of the existing plugins as a reference, for example:

Getting Started

The simplest way of adding your own custom tab is to derive from one of the classes:

This will add your tab to the project, build type (build configuration), build or administration page respectively.
Here's an example of the Diagnostics admin page:

There are a couple of things to note here:

  • it is important to call "register" method; ProjectTab, BuildTypeTab and ViewLogTab will do that for you automatically, AdminPage won't, that's why the call is there;
  • TeamCity might have difficulties with finding your resources (JSP, CSS, JS) if you don't refer to your resources through the PluginDescriptor.
  • the page above doesn't provide any model to the JSP. If you need one, just override the "fillModel" method.

Here's another example of the project tab:

That's it! Just specify your tab as a Spring bean, and you'll be able to see your tab in TeamCity.


We are using Spring MVC web framework.

Under the Hood

If you download and take a look at the TeamCity open API sources, you'll notice that all tabs above derive from the jetbrains.buildServer.web.openapi.SimpleCustomTab. And the only major difference between them all is a jetbrains.buildServer.web.openapi.PlaceId they specify in constructor.
Here's what they use:


Don't get confused by the variety of names, it's a long story. The main thing is there are more than 30 other place ids that you can hook into!

  • PlaceId.LOGIN_PAGE
  • ...

There is a convention that a place id named as a TAB can be used with the SimpleCustomTab. Others cannot, and to use them you will have to deal with low level jetbrains.buildServer.web.openapi.SimplePageExtension.
But that's pretty much the only change, take a look at the example:

This extension provides a custom HTML (usually a link) near the each file in the modification's list.
We use it to add "Open in IDE", "Open in External Change Viewer", etc links.
In this particular case the file itself is passed via "changedFile" attribute of the request, but this is different for different extensions.

A couple of useful notes:

  • isAvailable(HttpServletRequest) method is called to determine whether page extension content should be shown or not.
  • in case isAvailable(HttpServletRequest) is true, the fillModel(Map, HttpServletRequest) method will always be called and JSP will be rendered in UI. You cannot abort the process after isAvailable(HttpServletRequest) is done, that's why it's usually inconvenient to handle POST requests in extensions. Use a custom controller for that (see below).
  • One more case when you might need a custom controller is when you need to process HTTP response manually, e.g. stream a file content. fillModel(Map, HttpServletRequest) won't allow you to do that.

Developing a Custom Controller

Sometimes page extensions provide interaction with user and require communication with server. For example, your page extension can show a form with a "Submit" button. In this case in addition to writing your own page extension, you should provide a controller which will process requests from such forms, and use path to this controller in the form action attribute (the path is a part of URL without context path and query string).


To simplify things your controller can extend our jetbrains.buildServer.controllers.BaseController class and implement BaseController.doHandle(HttpServletRequest, HttpServletResponse) method.

With the custom controller you can provide completely new pages.

Obtaining paths to JSP files

Plugin resources are unpacked to <TeamCity web application>/plugins directory when server starts. However to construct paths to your JSP or images in Java it is recommended to use jetbrains.buildServer.web.openapi.PluginDescriptor. This descriptor can be obtained as any other Spring service.

In JSP files to construct paths to your resources you can use ${teamcityPluginResourcesPath}. This attribute is provided by TeamCity automatically, you can use it like this:

<img src="${teamcityPluginResourcesPath}your_image.gif" height="16" width="16" border="0">

Note: <c:url/> is required to construct correct URL in case if TeamCity is deployed under the non root context.

Classes and interfaces from TeamCity web open API

Class / Interface



A list of page place identifiers / extension points


A single page place associated with PlaceId, allows to add / remove extensions


Page extension interface


Base class for page extensions


Custom tab extension interface


Maintains a collection of page places and allows to locate PagePlace by PlaceId


Maintains a collection of custom controllers, allows to register custom controllers


Base class for controllers