Brightspot CMS Developer Guide

Adding areas to the navigation menu


An area is an item in the navigation menu. You can add areas to the navigation menu, a useful feature if you want to make your custom widgets or plugins easily accessible to editors.

A role must have permission to access an area before that area appears in the role’s navigation menu.

However, access to an admin page (or any other content) is governed by the Page or Servlet that implements it; even if a role does not have access to an area, a user could paste the URL into the browser’s address bar and gain access to the area. To avoid this scenario, extend PageServlet as described in the snippet "Checking permissions to view an area."

To add an area to the Navigation menu you must supply a com.psddev.cms.tool.Area object. The following sections describe the proprties included in an Area object.

See also:


displayName represents the label appearing in the navigation menu. The label can be a string literal, but you should consider adding hooks for localization to ensure the label can be displayed in other languages via resource bundles. The following snippet is an example of using Localization to display an area’s localized label at run time.

String displayName = Localization.currentUserText(ExampleServlet.class, "displayName", "Example Servlet");

The string Example Servlet serves as the fallback if there are no resource bundles matching the user’s preferred locale.

See also:


internalName represents an area’s identifier that must be unique over all areas and plugins. Consider using a namespace prefix for your identifiers to prevent conflicts. You can check if your desired internalName is already in use by running the following code in the /_debug/code tool.

public class Code {

    public static Object main() throws Throwable {
        return com.psddev.cms.tool.Tool.Static.getPlugins().stream()
            .map(p -> p.getInternalName())
            .collect(java.util.stream.Collectors.toList());
    }

}

Administrators can disable plugins and areas for all users by listing the corresponding internalName in the Disabled Plugins field found in menu> Admin > Sites & Settings > Global > Debug. For that reason, and as a best practice, consider using internalNames that provide a cue for the corresponding area, such as adminAbout, adminUsers, and adminThemes.

See also:


hierarchy represents an area’s position in the navigation menu. The position can be at the top level (such as Admin) or at a second level (such as Admin > Sites & Settings). (Hierarchy below the second level is currently not supported.)

You use a slash / to indicate an area’s level in the hierarchy. For example, if you want to place an area with internalName of customImport under the Admin label, set hierarchy to admin/customImport.

At run time, Brightspot sorts the areas alphabetically level-by-level by their displayName with the following exceptions:

  • The top-level area dashboard/ and its children are always displayed first.
  • The top-level area admin/ and its children are always displayed last.

The value for hierarchy must be unique across all areas. You can check if your desired hierarchy is already in use by running the following code in the /_debug/code tool.

public class Code {

    public static Object main() throws Throwable {
        return com.psddev.cms.tool.Tool.Static.getPlugins()
            .stream()
            .filter(p -> p instanceof com.psddev.cms.tool.Area)
            .map(a -> (com.psddev.cms.tool.Area) a)
            .map(com.psddev.cms.tool.Area::getHierarchy)
            .sorted()
            .collect(Collectors.toList());
    }

}


url represents the URL the user’s browser loads when clicking on an area.

When a user clicks on an area that is a leaf in the hierarchy, Brightspot redirects the user’s browser to the corresponding URL. In contrast, when a user clicks on a parent area that has children, Brightspot opens the area to expose its children—regardless if the parent has a URL. To view the parent’s URL, users may right-click and attempt to open the URL in a new browser tab or window. To accommodate this scenario, assign to the parent a URL that is the same as one of its children. For example, right-clicking Admin can take the user to Sites & Settings.

Ensure that URLs pointing to a page in Brightspot do not include the domain, because the domain could change based on environment. For example, the URL for Users & Roles is /admin/users.jsp. Brightspot resolves the full URL based on your application context.

For examples showing how Brightspot resolves an area’s entire URL, see Creating areas.


  • Overriding Tool#getPlugins. This approach is slightly more involved, but is best when building a reusable plugin that may be shared across several projects.
  • Implementing AutoArea. This approach is simpler, and is best when you have a one-off, project-specific area that is not part of a larger plugin.

The following sections provide detailed explanation for each approach.

Tool#getPlugins

This approach requires that you first have a class that extends Tool. In your subclass override the getPlugins method which expects a return value of type List<Plugin>. (Area is a subclass of Plugin, so you can include areas in the return value.) The following snippet adds a new area under the Admin section named Docs Example Area that provides a link to the Brightspot documentation site.

import java.util.ArrayList;
import java.util.List;

import com.psddev.cms.tool.Plugin;
import com.psddev.cms.tool.Tool;

public class ExampleTool extends Tool {

    @Override
    public List<Plugin> getPlugins() {
        List<Plugin> plugins = new ArrayList<>();

        Area exampleArea = new Area();
        exampleArea.setDisplayName("Docs Example Area");
        exampleArea.setInternalName("docsExampleArea");
        exampleArea.setHierarchy("admin/docsExampleArea");
        exampleArea.setUrl("https://docs.brightspot.com");

        plugins.add(exampleArea);

        return plugins;
    }
}

The example above can be rewritten with the convenience method Tool#createArea2(String, String, String, String) as follows, in which the four arguments correspond to the four fields set in the previous snippet. Using this convenience method may future-proof your code in case the process for Area instantiation changes.

import java.util.ArrayList;
import java.util.List;

import com.psddev.cms.tool.Plugin;
import com.psddev.cms.tool.Tool;

public class ExampleTool extends Tool {

    @Override
    public List<Plugin> getPlugins() {
        List<Plugin> plugins = new ArrayList<>();

        plugins.add(createArea2(
            "Docs Example Area", // displayName
            "docsExampleArea", // internalName
            "admin/docsExampleArea", // hierarchy
            "https://docs.brightspot.com")); // url

        return plugins;
    }
}

Brightspot resolves relative URLs relative to the Servlet Context and the Tool’s application path. Suppose you implement an area as follows:

  • Your application’s .war file is named my-app.war, and your Tool is also mapped to the path by the same application name.
  • Your Tool class implements the Tool#getApplicationName method to return my-tool.
  • The URL returned from AutoArea#getUrl is /my-servlet.

In this case, the URL Brightspot provides to the area is /my-app/my-tool/myservlet.

Alternatively, if your .war file is named ROOT.war, and your application name is the empty string (""), then the final link value is /my-servlet.

See also:

AutoArea

This is the quickest way to introduce a new area and requires minimal interaction with other components and concepts. Create a class that implements the AutoArea interface. This interface specifies four methods corresponding to displayName, internalName, hierarchy, and url. The following snippet adds a new area under Admin named Docs Example Area that provides a link to the Brightspot documentation site.

import com.psddev.cms.tool.AutoArea;

public class ExampleAutoArea implements AutoArea {

    @Override
    public String getDisplayName() {
        return "Docs Example Area";
    }

    @Override
    public String getInternalName() {
        return "docsExampleArea";
    }

    @Override
    public String getHierarchy() {
        return "admin/docsExampleArea";
    }

    @Override
    public String getUrl() {
        return "https://docs.brightspot.com";
    }
}

Brightspot uses ClassFinder to find all concrete classes that implement the AutoArea interface, and uses reflection to instantiate them so that the interface methods can be called. Make sure there is a default constructor or an explicit no-argument constructor present on your class, otherwise your area will not appear in the UI.

In this approach, Brightspot resolves relative URLs relative to the Servlet Context only. Suppose you implement an area as follows:

  • Your application’s .war file is named my-app.war.
  • You are linking to a Servlet with the URL pattern /my-servlet.

In this case, the URL returned from AutoArea#getUrl is /my-servlet. Brightspot prepends the .war file’s name to provide the area with a URL /my-app/myservlet.

Adding areas: a complete example

Below is an example using the approach Tool#getPlugins that leverages all of the best practices discussed in this section to create a custom plugin, page, and area.

import java.io.IOException;
import javax.servlet.ServletException;

import com.psddev.cms.tool.PageServlet;
import com.psddev.cms.tool.ToolPageContext;
import com.psddev.dari.util.RoutingFilter;

import static example.ExamplePage.PATH;
import static com.psddev.dari.html.Nodes.*;

@RoutingFilter.Path(application = "example-plugin", value = PATH)
public class ExamplePage extends PageServlet {

    static final String PATH = "/example-page.jsp";

    @Override
    protected String getPermissionId() {
        return "area/examplePlugin/examplePage";
    }

    @Override
    protected void doService(ToolPageContext page) throws IOException, ServletException {
        page.writeHeader();
        page.write(P.with("It works!"));
        page.writeFooter();
    }
}
import java.util.ArrayList;
import java.util.List;

import com.psddev.cms.db.Localization;
import com.psddev.cms.tool.Plugin;
import com.psddev.cms.tool.Tool;

public class ExampleTool extends Tool {

    @Override
    public String getApplicationName() {
        return "example-plugin";
    }

    @Override
    public List<Plugin> getPlugins() {
        List<Plugin> plugins = new ArrayList<>();

        // parent area
        plugins.add(createArea2(
            Localization.currentUserText(ExampleTool.class, "title", "Example Plugin"), // displayName
            "examplePlugin", // internalName
            "examplePlugin", // hierarchy
            null)); // url

        // child area
        plugins.add(createArea2(
            Localization.currentUserText(ExamplePage.class, "title", "Example Page"), // displayName
            "examplePage", // internalName
            "examplePlugin/examplePage", // hierarchy
            ExamplePage.PATH)); // url

        return plugins;
    }
}


Brightspot displays classes in almost every widget—the content edit form, the search panel, Edit Site, and many other places. You can conditionally show or hide a class or its instances depending depending on Brightspot’s state at run time by implementing the abstract method ClassDisplay#shouldHide.

For example, when doing a federated search, editors can select from external content providers appearing in the External Types section of content type filter.

External types External types

If your instance of Brightspot is not integrated with a particular service, you should prevent it from appearing in the External Types list by implementing ClassDisplay#shouldHide. If this method returns true, Brightspot hides the class from the display; if it returns false, Brightspot shows the class in the display.

import com.psddev.cms.tool.ClassDisplay;
import com.psddev.dari.util.ObjectUtils;

public class VimeoClassDisplay implements ClassDisplay {

    @Override
    public boolean shouldHide(Class<?> instanceClass) {

        /* Ensure the current class is a subclass of VimeoVideo */
        if (VimeoVideo.class.isAssignableFrom(instanceClass)) {
            VimeoSettings settings = VimeoUtils.getSettings();

            /* Ensure the API key and secret are configured for the Vimeo service */
            return ObjectUtils.isBlank(settings.getApiKey()) || ObjectUtils.isBlank(settings.getApiSecret());
        }
        return false;
    }
}

Previous Topic
Profile tabs
Next Topic
Time Series
Was this topic helpful?
Thanks for your feedback.
Our robust, flexible Design System provides hundreds of pre-built components you can use to build the presentation layer of your dreams.

Asset types
Module types
Page types
Brightspot is packaged with content types that get you up and running in a matter of days, including assets, modules and landing pages.

Content types
Modules
Landing pages
Everything you need to know when creating, managing, and administering content within Brightspot CMS.

Dashboards
Publishing
Workflows
Admin configurations
A guide for installing, supporting, extending, modifying and administering code on the Brightspot platform.

Field types
Content modeling
Rich-text elements
Images
A guide to configuring Brightspot's library of integrations, including pre-built options and developer-configured extensions.

Google Analytics
Shopify
Apple News