February 23, 2022

Migration Guide for v2

assorted color plastic tools on gray wooden table

v2 Migration Guide

In advance of the release of version 2, we are releasing this guide in order to give third-party developers a chance to bring their plugins and themes up-to-date. In the successive sections below, we will outline the breaking change or new best-practice, and the steps to migrate, along with a live example.

This blog post is the third in a series of posts related to the release of NodeBB v2

We tried to plan out this release in such a way that any breaking changes would be minimized. There is a chance that your theme or plugin will just work out-of-the-box, or with minimal changes.

We've tried to list any gotchas and necessary changes in this article, but please do let us know if you encounter one we haven't seen yet.

Migrate AMD modules from scripts and acpScripts to modules (breaking change 🚨)

Standard practice in v1.x was to write a page-specific client-side script or admin script as an AMD module, and bundle them with the minified files served to the browser. We now recommend that the files be defined in the modules property in package.json so that they can be bundled as necessary, or dynamically loaded otherwise.

Note:These scripts can continue to be written in AMD-style, as it is supported by Webpack.

Live example (nodebb-theme-persona)

The persona theme contains two page-specific scripts, one loaded on the user settings page, and another loaded in the ACP settings page.

Migrate third-party modules to use npm dependencies, if available (best practice)

Any third-party modules are no longer recommended to be used in the modules section of plugin.json. Instead, install them directly from npm  if available, and just import them into your client-side code.

There are some savings to be had by no longer appending third-party code into the main bundle. Webpack can choose to either bundle it in, or dynamically load it as-needed.

Additionally, having a dependency maintained in your package.json allows for better version control and updates, without needing to modify copy-pasted code directly in your repository.

Live example (nodebb-plugin-markdown)

The markdown plugin relies on highlightjs as a dependency in order to highlight code written in code blocks.

Live example (nodebb-plugin-2factor)

The 2factor plugin relies on @github/webauthn-json to properly encode and verify key registrations and challenges.

Require.js dependency removed (breaking change 🚨)

While modules and client-side scripts can continue to be written in AMD-style (with define and require calls), you will no longer be able to use the AMD-style require in widgets or the browser console to get modules. By and large, this will not affect most installations/plugins.

A helper method app.require is added to require modules dynamically. app.require can be used in widgets or custom javascript tab in the ACP to require modules.


require(['alerts'], function (alerts) {}); // single module
require(['alerts', 'translator'], function (alerts, translator) {}); // multiple modules


const alerts = await app.require('alerts'); // single module
const [alerts, translator] = await app.require(['alerts', 'translator']); // multiple modules

Update try_files directive in nginx config (best practice)

Power users may opt to have a reverse proxy serve the built assets directly, instead of going through NodeBB.

Several paths are included in this configuration, but you no longer need to have your reverse proxy handle the /plugins path prefix.

Remove this block

location /plugins/ {
root /path/to/nodebb/build/public/;
try_files $uri @nodebb;

Update nbbpm.compatibility in third-party themes and plugins (best practice)

In order to ensure that plugins and themes updated for NodeBB v2.x are not accidentally installed in v1.x installations, you should update the nbbpm.compatibility string in your package.json to at least ^2.0.0.

Keep in mind that after updating your plugin, it might still actually be compatible with NodeBB v1.x. Whether your plugin continues to be backwards compatible does not depend on whether you needed to apply the breaking changes in this guide. A breaking change just means that change is needed to be forwards compatible.


  "nbbpm": {
-    "compatibility": "^1.17.4"
+    "compatibility": "^2.0.0"

translator, utils, and helpers library restrictions (breaking change 🚨)

The following three libraries were used on both the server and client side:

  • utils (basic utility functions)
  • translator (localisation methods)
  • helpers (NodeBB’s set of built-in Benchpress helpers)

In NodeBB v1.x, you were able to require() them from the server-side, even though they were contained in the client-side directories.

As of NodeBB v2.x, you will need to require() from the server-side src/ directory instead:

  • src/utils.js
  • src/translator.js
  • src/helpers.js


-    const utils = require.main.require('./public/src/utils');
+    const utils = require.main.require('./src/utils');
-    const translator = require.main.require('./public/src/modules/translator');
+    const translator = require.main.require('./src/translator');
-    const helpers = require.main.require('./public/src/modules/helpers');
+    const helpers = require.main.require('./src/helpers');

ACP Privileges table template change (breaking change 🚨)

If you maintain a theme that modifies one of the following files:

  • admin/partials/privileges/global.tpl
  • admin/partials/privileges/category.tpl

The format of the privilege labels has changed from an array of objects to a simpler array of language keys (strings).

Apply the changeset to the right.


-    {privileges.labels.users.name}
+    {@value}

Live Example

Core maintains templates for the ACP privilege page, the diff for this particular changeset can be found here.

Sorted Lists client-side hook renamed

A client-side hook used in the sorted-lists module has been renamed from action:settings.sorted-list.loaded to action:settings.sorted-list.itemLoaded.

A new action hook by the same name is still called, but only once at the very end, when all sorted list items have been parsed and inserted into the DOM.


hook.on('action:settings.sorted-list.loadedItemLoaded', ...

Top and bottom padding removed from body element (breaking change 🚨)

In v1.19.x and earlier, the body element had a 70px top padding, which was missing on mobile. This discrepancy meant that styles for mobile needed to account for a missing 70px in their positioning.

The padding is now removed, and the #panel element now contains a top padding corresponding to the dynamically generated height of the header bar(s).

Live Example

The Persona theme contains the logic for calculating the panel-offset. As part of this change, some of the styles depending on the old 70px offset needed to be adjusted. Some additional styles needed to be removed as they were no longer required as a single common style for both desktop and mobile was applicable.

utils.params() refactored

The common utility method .params() has been refactored in order to use browser and node built-ins, instead of executing its own logic.
  • The skipToType and disableToType options have now been removed
  • A new option — full — has been added, which will cause the method to return an URLSearchParams object instead of a plain Object

No Example Available

There are no pre-existing usages of the .params() method — that we know of — that use either of the deprecated options.

This commit shows how to use the new full option to return something other than a plain Object.


© 2014 – 2023 NodeBB, Inc. — Made in Canada.