How to properly define a JS file for a plugins ACP page

NodeBB Development
  • If I want to get some data other than the plugins settings on a plugin's ACP page (e.g., get members of a group and display the list of members). What's the proper way of defining this script file so that it runs when you open the plugin's ACP page?

  • Not sure if it is proper, but I use define call with the page's route. I put this in my client.js.

    define('admin/plugins/plugin-id', function () { // 'admin/plugins/plugin-id' is the route for the admin page
        return { init: function(){
            // do stuff here, like call socket.emit, and setup settings as normal.

    I usually require a separate module, so that the full script isn't loaded on every page load.

    define('admin/plugins/plugin-id', function () {
    	return { init: function () {
    		require(['/route/to/plugin/js/acp.js'], function (acp) {


    define(['settings', 'translator'], function (settings, translator) {
    	var acp = { };
    	acp.doThings = function () {
    		// This is where I would call socket.emit('mySocketEvent') so that I can get the additional data I need.
    	return acp;

    I'm certain this is not the best way to do it, and the additional require is not necessary now that we have acpScripts now. There was some other reason I separated the scripts too, i don't remember at the moment.

  • @yariplus how do I require modules (groups, users...) so I can use their methods?

  • You can't call them directly from the client. I usually use, you define an event in your library file where you get the data, and then call it in the acp script.


    var socketAdmin = module.parent.require('./');
    var Groups = module.parent.require('./groups');
    var User = module.parent.require('./user');
    socketAdmin.yourplugin = {
      // This function is now exposed as the event 'admin.yourplugin.someevent'
      someevent: function (socket, data, callback) {
        // Use the data from the client to grab member objects.
        Groups.getMembers(data.groupName, data.start, data.stop, function (err, uids) {
            if (err) return console.log(err);
            User.getUsersData(uids, function (err, members){
                callback(null, {members: members});


    // Send the group name with the amount of members we want to the server.
    socket.emit('admin.yourplugin.someevent', {groupName: "GroupName", start: 0, stop: 200}, function (err, data) {
        // Log the response, should be an array of user objects.

Suggested Topics

  • 1 Votes
    2 Posts

    oof... maybe, but not yet, I don't think.

    We can use LESS insomuch that what is entered is parsed back out into CSS, but because we don't rerun "variables.less" from bootstrap, then you can't really use variables.

    I'd recommend opening up an issue in our bug tracker so we can get this in if it's something you want 🙂

  • 0 Votes
    6 Posts



    Plugin = module.exports; var categories = require.main.require('./src/categories'); // init hook Plugin.init = function (data, callback) { var router = data.router; var middleware = data.middleware; // Create route to render the template to. router.get('/example-categories', middleware.buildHeader, renderExampleCategories); router.get('/api/example-categories', renderExampleCategories); function renderExampleCategories(req, res, next) { // Get all the visible categories. categories.getCategoriesByPrivilege('cid:0:children', req.uid, 'find', function(err, categoryData) { if (err) return next(err); // Put the categories in a tree format. categories.flattenCategories([], categoryData); // Send the data to the template. `example-categories.tpl` res.render('example-categories', {categories: categoryData}); }); } };


    <!-- BEGIN categories --> <a href="{config.relative_path}/category/{categories.slug}" itemprop="url">{}</a> <br> <!-- BEGIN categories.children --> - <a href="{config.relative_path}/category/{categories.children.slug}" itemprop="url">{}</a> <br> <!-- END categories.children --> <!-- END categories -->

    Should give you a list like this

  • 3 Votes
    31 Posts

    Composer is uncomposed....

    When clicking into the text box I'm seeing this on safari iOS:

    Comes back into view when I start typing

  • 0 Votes
    8 Posts

    Hello @sadmulwar -- I haven't had a chance to look at this yet, it will have to wait until after Wednesday.

  • 0 Votes
    6 Posts

    @a_5mith @psychobunny Thank you both. 🙂 So the error is removed and cache is cleared, so now it works like it should. ^^