Skip to content

Using Core Libraries

As shown in Writing Plugins, a plugin reaches NodeBB's internals through require.main.require('./src/...'). This page is a practical tour of the libraries plugins use most, plus a couple of common recipes that combine them.

Note

require.main.require('./src/...') works on all current versions, but it is being deprecated starting with NodeBB v4.12.0. The future-proof way to require core modules is nodebb.require('./src/user'). The examples below use require.main.require for compatibility; substitute nodebb.require if you are targeting v4.12.0 or newer.

Note

This is a curated starting point, not an exhaustive API reference. NodeBB's modules expose many more methods than are listed here, and signatures may change between major versions. When you need something that isn't covered, read the relevant file under src/ in the NodeBB source — that is the authoritative reference. Almost all of these methods are async and return Promises.

Commonly used modules

const db            = require.main.require('./src/database');
const user          = require.main.require('./src/user');
const groups        = require.main.require('./src/groups');
const topics        = require.main.require('./src/topics');
const privileges    = require.main.require('./src/privileges');
const notifications = require.main.require('./src/notifications');
const meta          = require.main.require('./src/meta');
const translator    = require.main.require('./src/translator');
const pagination    = require.main.require('./src/pagination');
const helpers       = require.main.require('./src/controllers/helpers');
const routeHelpers  = require.main.require('./src/routes/helpers');

database

The database module is an abstraction over the configured backend (Redis, MongoDB, or Postgres), exposing a single API so your plugin works on any of them. It deals in objects (hashes) and sorted sets, among other structures.

This is distinct from the Database Structure page, which documents the keys NodeBB core itself stores. The methods below are the API you call to read and write your own keys.

getObject(key) / getObjects([keys])         // read hash(es)
getObjectFields(key, [fields])              // read selected fields of a hash
setObject(key, obj)                         // write a hash
deleteObjectFields(key, [fields])           // remove fields from a hash
delete(key) / deleteAll([keys])             // delete key(s)
incrObjectField(key, field)                 // atomic increment (e.g. an id counter)
sortedSetAdd(key, score, member)            // add to a sorted set
sortedSetRemove(key, member)                // remove from a sorted set
sortedSetCard(key)                          // count members
getSortedSetRange(key, start, stop)         // members ascending by score
getSortedSetRevRange(key, start, stop)      // members descending by score
getSortedSetRevRangeWithScores(key, start, stop)  // [{ value, score }]

A common pattern is a per-entity hash plus a sorted set used as an ordered index:

const id = await db.incrObjectField('global', 'nextThingId');
await db.setObject(`thing:${id}`, { id, uid, content, timestamp: Date.now() });
await db.sortedSetAdd(`thing:byTopic:${tid}`, Date.now(), id);

user

getUserFields(uid, [fields])                // one user
getUsersFields([uids], [fields])            // many users
getSettings(uid)                            // per-user settings (e.g. topicsPerPage)
exists(uid)                                 // boolean
isAdministrator(uid)                        // boolean
isGlobalModerator(uid)                      // boolean
isModeratorOfAnyCategory(uid)               // boolean

Frequently requested fields: ['uid', 'username', 'userslug', 'picture'].

groups

search(query, { sort, filterHidden })       // search groups by name
getMembers(name, start, stop)               // member uids
getUserGroups([uids])                       // groups each uid belongs to
getGroupFields(name, [fields])              // group metadata
exists(name)                                // boolean
isPrivilegeGroup(name)                      // boolean — filter these out of UI lists

topics

getTopicFields(tid, [fields])               // selected fields of one topic
getTopicsByTids([tids], uid)                // full, render-ready topic objects
getFollowers(tid)                           // uids watching the topic
follow(tid, uid)                            // make a user watch a topic

privileges

Always filter content by the viewer's privileges before returning or rendering it:

const visibleTids = await privileges.topics.filterTids('read', tids, uid);

Recipe: sending a notification

const notifications = require.main.require('./src/notifications');

const notifObj = await notifications.create({
    type: 'myplugin-event',                          // an identifier for the event
    bodyShort: '[[myplugin:notif-text, ' + title + ']]',  // a translation key (see i18n)
    nid: 'myplugin:thing:' + tid + ':' + id,         // MUST be unique — it is the dedupe key
    from: fromUid,                                   // who triggered it
    path: '/topic/' + slug,                          // where the notification links to
});

// push() returns early if notifObj is null (e.g. when there is nothing to
// send), so there's no need to guard the call yourself.
await notifications.push(notifObj, recipientUids);

The bodyShort is a language string and may include positional arguments. Define a matching key in your language file, e.g. "notif-text": "New activity in %1".

Recipe: a custom list page

Page routes can render your own template, or reuse a core one by supplying the data that template expects. For a list of topics, the recent template is convenient. The helpers below assemble that data:

const user       = require.main.require('./src/user');
const topics     = require.main.require('./src/topics');
const privileges = require.main.require('./src/privileges');
const pagination = require.main.require('./src/pagination');
const helpers    = require.main.require('./src/controllers/helpers');

async function renderMyList(req, res) {
    const uid = req.uid;
    const page = Math.max(1, parseInt(req.query.page, 10) || 1);

    const settings = await user.getSettings(uid);                 // settings.topicsPerPage
    let tids = await getMyTids(uid);                              // your own data source
    tids = await privileges.topics.filterTids('read', tids, uid); // respect read access

    const start = (page - 1) * settings.topicsPerPage;
    const stop = start + settings.topicsPerPage - 1;
    const topicsData = await topics.getTopicsByTids(tids.slice(start, stop + 1), uid);

    const pageCount = Math.max(1, Math.ceil(tids.length / settings.topicsPerPage));
    res.render('recent', {
        topics: topicsData,
        title: '[[myplugin:list.title]]',
        breadcrumbs: helpers.buildBreadcrumbs([{ text: '[[myplugin:list.title]]' }]),
        pagination: pagination.create(page, pageCount),
    });
}