Volto add-on concepts#

This guide describes Volto add-on concepts.

What is a Volto add-on?#

Volto add-ons are just CommonJS or ESM packages. Their main purpose is to encapsulate logic, configuration, components, customizations, and even themes in a reusable way.

Suppose you want to have more control and flexibility beyond the plain Volto project when building a site. You can build a Volto add-on and make it available as a generic JavaScript package. Then you can reuse and include it in any Volto project.

An add-on can configure or provide any of the following aspects of Volto.

  • Provide additional views and blocks.

  • Override or extend Volto's built-in views, blocks, and settings.

  • Shadow or customize Volto's, or another add-on's, modules.

  • Register custom routes.

  • Provide custom Redux actions and reducers.

  • Register custom Express middleware for Volto's server process.

  • Tweak Volto's webpack configuration, loading custom Razzle and webpack plugins.

  • Provide even a custom theme.

Volto registry#

Volto has a built-in extensible and pluggable system to enhance the Plone CMS user interface. It helps developers extend Volto in a pluggable way through add-ons. This system is implemented through Volto's registry.

For Volto 17 and earlier, the registry was integrated into Volto core.

From Volto 18 onward, the Volto registry is in its own package @plone/registry.

Add-on configuration pipeline#

A Volto app's configuration is determined through a pipeline starting with Volto's default configuration, then each of your app's add-ons' configuration. In Volto 17 and earlier, you can also use project configuration at the end of the pipeline after any add-ons.

Deprecated since version Volto: 18.0.0

The project configuration approach is deprecated and will be removed in Volto 19.

Add-ons are applied in the order they are declared in the addons key of package.json or programmatically via a provided configuration file. Add-ons can override configuration coming from other add-ons, providing a hierarchy of configuration stacks.

Add-ons can be chained, where each one can configure the app in some way. If needed, each add-on in the chain can override or extend the previous configuration that other add-ons set. Thus, the order in which you register add-ons matters.

Add-ons can define shadowed components. "Component shadowing" is a technique for overriding modules of other packages at build time. This technique builds upon the resolve.aliases facilities of bundlers, so modules can be replaced when the app is being built.

Volto will automatically provide aliases for your package. Once you've released it, you don't need to change import paths, since you can use the final ones from the very beginning. This means that you can use imports, such as import { Something } from '@plone/my-volto-add-on' without any extra configuration.

Note

By declaring a JavaScript package as a Volto add-on, Volto provides several integration features. These include JavaScript language features with transpilation by Babel, whole-process customization via razzle.extend.js, and integration with Volto's configuration registry.

Use cases#

In practice with the configuration pipeline, for example, you can create a "policy" core add-on for your project, and use another add-on for your project's theme. This way the project itself renders as a simple boilerplate, which you can extend or rebuild at any time.

You can also reuse add-ons across projects, and adjust them using other add-ons, depending on the other projects' requirements.

Add-on configuration#

The default export of your add-on's main index.js file should be a function with the signature config => config. That is, it should take the global configuration object and return it, possibly mutated or changed. An index.js file should contain the following code.

export default function applyConfig(config) {
  config.blocks.blocksConfig.faq_viewer = {
    id: 'faq_viewer',
    title: 'FAQ Viewer',
    edit: FAQBlockEdit,
    view: FAQBlockView,
    icon: chartIcon,
    group: 'common',
    restricted: false,
    mostUsed: true,
    sidebarTab: 1,
    security: {
      addPermission: [],
      view: [],
    },
  };
  return config;
}

And the package.json file of your add-on should contain the following code.

{
  "main": "src/index.js",
}

In effect, Volto does the equivalent of the following pseudocode:

import installMyVoltoAddon from 'my-volto-addon'

// ... in the configuration registry setup step:
const configRegistry = installMyVoltoAddon(defaultRegistry);

The Volto add-on needs to export a default function that receives the Volto configuration registry. Then it is free to change the registry. Finally, it must return that registry.

Volto will execute all the add-on configuration functions in a chain to compute the final configuration registry.

Note

An add-on's default configuration method will always be loaded.

See also

See @kitconcept/volto-button-block as an example.

Provide optional add-on configurations#

You can export additional configuration functions from your add-on's main index.js.

import applyConfig, {loadOptionalBlocks,overrideSomeDefaultBlock} from './config';

export { loadOptionalBlocks, overrideSomeDefaultBlock };
export default applyConfig;

Define your add-ons programmatically#

The addons key in the package.json file alone might not be flexible enough in complex scenarios. You can programmatically load your add-ons outside your package.json file using a volto.config.js file with the following content.

module.exports = {
    addons: ['@eeacms/volto-accordion-block']
}

This creates an "escape hatch", where you can use logic and environment conditions to define the add-ons to load in the current project, as in the next example. The add-ons that you define here will be added to the existing ones in package.json.

let addons = [];
if (process.env.MY_SPECIAL_ENV_VAR) { // Does not have to be RAZZLE_
  addons = ['volto-my-awesome-special-add-on'];
}

if (process.env.MARKER_FOR_MY_SECRET_PROJECT) { // Does not have to be RAZZLE_
  addons = [
    '@kitconcept/volto-heading-block',
    '@kitconcept/volto-slider-block',
    'volto-my-secret-project-add-on',
  ];
}

module.exports = {
  addons: addons,
};

Important

You must add the addons key with the value of your add-on package's name wherever you configure it. In Plone terminology, it is like including a Python egg in the zcml section of zc.buildout.

Add-on dependencies#

Add-ons can depend on any other JavaScript package, including other Volto add-ons. To do this, specify the name of your Volto add-on dependency in your dependencies key of your package.json file. Then create a new addons key in the package.json file of your add-on, where you specify the extra Volto add-on dependency. By doing this, the add-ons can "chain load" one another.

{
  "name": "volto-slate",

  "addons": ["@eeacms/volto-object-widget"]
}

Publish an add-on#

Volto add-ons should not be transpiled. They should be released as "source" packages.

Their primary entry point (the main key of their package.json) must point to a module that exports a default function, which acts as a default configuration loader for that package.

You can publish an add-on to an npm registry or to a remote repository host such as GitHub or GitLab, like any other package. If you publish your add-on to the npm Registry or make your repository public, as a bonus, you will benefit from collaborating on open source software.

By using mrs-developer, it's possible to have a workflow similar to zc.buildout's mr.developer, where you can "checkout" an add-on for development. Eric Brehault ported this amazing Python tool.