How to write hooks

Tutorials
  • This post is for plugin developers creating hooks to change functionality of NodeBB.

    NodeBB supported a hook system since the early days of it's development. This system was built long before async/await was part of nodejs. Because of this the plugin system was built using a callback system.

    When core fired a hook it would pass it a callback, and the callback would be called with the result once all the plugins processed the parameters. Here is a sample from the callback system.

    // core 
    plugins.hooks.fire('filter:topics.get', { topics: topics }, function (err, result) {
        console.log(results.topics);
        //...
    });
    
    // your plugin
    myPlugin.filterTopicsGet = function (hookData, callback) {
        // do something async with topics and call callback
        somethingAsync(hookData, function (err) {
           callback(err, hookData);
        });    
    }
    

    Once promises and async/await support was widely available we switched core to use async/await. Now the same hook is fired as follows.

    // core
    const result = await plugins.hooks.fire('filter:topics.get', { topics: topics });
    console.log(results.topics);
    

    For backwards compatibility plugin hooks written with callback style still work, but you can use async functions in your plugins as well. The above sample would look like this

    myPlugin.filterTopicsGet = async function (hookData) {
        // do something async with topics
        await somethingAsync(hookData);
        return hookData;
    }
    

    Starting with 1.17.0, you can also use synchronous functions if your hook isn't making any async calls. Below are 3 different ways to write hooks, they are all supported.

    // async (prefered style for async functions)
    myPlugin.myHook = async function (hookData) {
        // do some async call
        await somethingAsync(hookData);
        return hookData;
    };
    
    // callback-style (old style)
    myPlugin.myHook = function (hookData, callback) {
        // do some async call
        somethingAsync(hookData, function (err) {
           callback(err, hookData);
        });  
    }
    
    // sync function (new since 1.17.0)
    myPlugin.myHook = function (hookData) {
        // set some value on hookData
        hookData.foo = 1;
        return hookData;
    }
    

    You should not mix these styles together. For example do not return a value from the hook and call the callback at the same time.

    myPlugin.badHook1 = function (hookData, callback) {
        hookData.foo = 1;
        callback(null, hookData);
        return hookData;
    };
    // or
    myPlugin.badHook2 = function (hookData, callback) {
        hookData.foo = 1;
        return setImmediate(callback, null, hookData);
    };
    

    Let us know if you have any questions.

  • Hello!

    Can you explain the new standards for client-side hooks?

    Previously we used the $(window).on(..., ...) notation, and now we have the new hooks module that we can use in our "client" code (was added in 1.17.0).

    For example

    require(['hooks'], function (hooks) {
    	hooks.on('action:ajaxify.end', ({ tpl_url }) => {
            ...
            });
    });
    

    What the difference between these two ways and what should we use in our plugins?

  • The hooks module has the same functionality as $(window).on() but it also allows having async filter hooks client side which was not possible with $(window).trigger/on


Suggested Topics