Skip to content

NodeBB Plugins

Discussion regarding NodeBB Plugin development.

1.8k Topics 15.0k Posts
Most Voted Plugins

Subcategories


  • Have a question about building a plugin? Ask here
    426 Topics
    2k Posts
    starkorebaS

    Hello, first of' thanks for the amazing work !
    For the context: I'm a junior developer from France, so excuse my english (and my limited understanding of everything lol). I was looking for a software alternative to build a forum RPG. I just installed NodeBB on Ubuntu 20 (Focal) working on WSL 2. I'm using redis and want to write plugin using typescript.

    It would be great to have a guide version of the Quickstart plugin in typescript !
    I know its possible to write plugin in typescript, the emoji plugin does that. But it would save me so much time to have a guide that explain the extra step we need to do in order of compiler and make it work on NodeBB.

    Here's my questions tho:

    Is it in the roadmap team of NodeBB to document how to write plugin in typescript? Did the team plan to write a typescript version of NodeBB?

    Although it would be great to have some more advanced example right in the documentation on how to do something and what are the step to make it work.
    Like here : https://community.nodebb.org/topic/16994/how-do-i-add-custom-user-fields-to-my-theme

    Like "if I want to add/ change something, I have to think of the scope of this features and what's need to be modify in other file to be working properly". Or at least, say it in the documentation a little bit more straightforward. So that beginner will understand that first sight. But I think examples are more worthy than tons of words.

    Is it possible for beginner to contribute on the documentation to make it more specific and understandable for beginners, or is it a documentation for more experienced developers and a choice of the team (and don't want to change that)?

    Is it because from a plugin to another, things might be working so differently that it's not worthy to do a more detailed documentation ?

    Because people might be wanted to write plugin over time, mutualisation of knowledge would be great, so that might simplified the process of each developer trying to write plugin have to go throw investigations to understand how NodeBB works before even start to develop the actual plugin.

    For example: How to interact with the database explain in this answer: https://community.nodebb.org/topic/17657/best-way-to-store-plugin-data-in-database-in-context-of-compatibility/2?_=1714543873650 for me this should be in the documentation.

    In conclusion, I feel like in general people agree that it's difficult to start writting plugin without investigate and dive in the structure file of the source code. I'm willing to help on that make it easier to really understand how NodeBB works for developer. So that anyone not familiar with the source code can start almost immediately customization. :emo:

  • Need a plugin developed? Ask here!
    222 Topics
    1k Posts
    barisB

    Plugin doesn't show usernames if they set their status to offline AFAIK

  • Routing

    8
    0 Votes
    8 Posts
    5k Views
    S

    I commented string //categoryRoutes(router, middleware, controllers); in src/routes/index.js file and add all of this file in index.js my plugin:

    (function(Preparsed) { 'use strict'; var nconf = module.parent.require('nconf'), path = module.parent.require('path'), winston = module.parent.require('winston'), controllers = require.main.require('./src/controllers'), meta = require.main.require('./src/meta'), plugins = require.main.require('./src/plugins'), express = module.parent.require('express'), metaRoutes = require.main.require('./src/routes/meta'), apiRoutes = require.main.require('./src/routes/api'), adminRoutes = require.main.require('./src/routes/admin'), feedRoutes = require.main.require('./src/routes/feeds'), pluginRoutes = require.main.require('./src/routes/plugins'), authRoutes = require.main.require('./src/routes/authentication'), helpers = require.main.require('./src/routes/helpers'); var setupPageRoute = helpers.setupPageRoute; function mainRoutes(app, middleware, controllers) { setupPageRoute(app, '/', middleware, [], controllers.home); var loginRegisterMiddleware = [middleware.redirectToAccountIfLoggedIn]; setupPageRoute(app, '/login', middleware, loginRegisterMiddleware, controllers.login); setupPageRoute(app, '/register', middleware, loginRegisterMiddleware, controllers.register); setupPageRoute(app, '/confirm/:code', middleware, [], controllers.confirmEmail); setupPageRoute(app, '/outgoing', middleware, [], controllers.outgoing); setupPageRoute(app, '/search/:term?', middleware, [middleware.guestSearchingAllowed], controllers.search.search); setupPageRoute(app, '/reset/:code?', middleware, [], controllers.reset); setupPageRoute(app, '/tos', middleware, [], controllers.termsOfUse); } function staticRoutes(app, middleware, controllers) { setupPageRoute(app, '/404', middleware, [], controllers.static['404']); setupPageRoute(app, '/403', middleware, [], controllers.static['403']); setupPageRoute(app, '/500', middleware, [], controllers.static['500']); } function topicRoutes(app, middleware, controllers) { app.get('/api/topic/teaser/:topic_id', controllers.topics.teaser); setupPageRoute(app, '/topic/:topic_id/:slug/:post_index?', middleware, [], controllers.topics.get); setupPageRoute(app, '/topic/:topic_id/:slug?', middleware, [middleware.addSlug], controllers.topics.get); } function tagRoutes(app, middleware, controllers) { setupPageRoute(app, '/tags/:tag', middleware, [middleware.publicTagListing], controllers.tags.getTag); setupPageRoute(app, '/tags', middleware, [middleware.publicTagListing], controllers.tags.getTags); } function categoryRoutes(app, middleware, controllers) { setupPageRoute(app, '/categories', middleware, [], controllers.categories.list); setupPageRoute(app, '/popular/:term?', middleware, [], controllers.categories.popular); setupPageRoute(app, '/recent', middleware, [], controllers.categories.recent); setupPageRoute(app, '/unread', middleware, [middleware.authenticate], controllers.categories.unread); app.get('/api/unread/total', middleware.authenticate, controllers.categories.unreadTotal); setupPageRoute(app, '/category/:category_id/:slug/:topic_index', middleware, [], controllers.categories.get); setupPageRoute(app, '/category/:category_id/:slug?', middleware, [middleware.addSlug], controllers.categories.get); } function accountRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; var accountMiddlewares = [middleware.checkGlobalPrivacySettings, middleware.checkAccountPermissions]; setupPageRoute(app, '/user/:userslug', middleware, middlewares, controllers.accounts.getAccount); setupPageRoute(app, '/user/:userslug/following', middleware, middlewares, controllers.accounts.getFollowing); setupPageRoute(app, '/user/:userslug/followers', middleware, middlewares, controllers.accounts.getFollowers); setupPageRoute(app, '/user/:userslug/posts', middleware, middlewares, controllers.accounts.getPosts); setupPageRoute(app, '/user/:userslug/topics', middleware, middlewares, controllers.accounts.getTopics); setupPageRoute(app, '/user/:userslug/groups', middleware, middlewares, controllers.accounts.getGroups); setupPageRoute(app, '/user/:userslug/favourites', middleware, accountMiddlewares, controllers.accounts.getFavourites); setupPageRoute(app, '/user/:userslug/watched', middleware, accountMiddlewares, controllers.accounts.getWatchedTopics); setupPageRoute(app, '/user/:userslug/edit', middleware, accountMiddlewares, controllers.accounts.accountEdit); setupPageRoute(app, '/user/:userslug/settings', middleware, accountMiddlewares, controllers.accounts.accountSettings); setupPageRoute(app, '/notifications', middleware, [middleware.authenticate], controllers.accounts.getNotifications); setupPageRoute(app, '/chats/:userslug?', middleware, [middleware.redirectToLoginIfGuest], controllers.accounts.getChats); } function userRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings]; setupPageRoute(app, '/users', middleware, middlewares, controllers.users.getOnlineUsers); setupPageRoute(app, '/users/online', middleware, middlewares, controllers.users.getOnlineUsers); setupPageRoute(app, '/users/sort-posts', middleware, middlewares, controllers.users.getUsersSortedByPosts); setupPageRoute(app, '/users/sort-reputation', middleware, middlewares, controllers.users.getUsersSortedByReputation); setupPageRoute(app, '/users/latest', middleware, middlewares, controllers.users.getUsersSortedByJoinDate); setupPageRoute(app, '/users/search', middleware, middlewares, controllers.users.getUsersForSearch); } function groupRoutes(app, middleware, controllers) { var middlewares = [middleware.checkGlobalPrivacySettings, middleware.exposeGroupName]; setupPageRoute(app, '/groups', middleware, middlewares, controllers.groups.list); setupPageRoute(app, '/groups/:slug', middleware, middlewares, controllers.groups.details); setupPageRoute(app, '/groups/:slug/members', middleware, middlewares, controllers.groups.members); } Preparsed.init = function(params, callback){ var app = params.router, middleware = params.middleware, controllers = params.controllers; var router = express.Router(), pluginRouter = express.Router(), authRouter = express.Router(), relativePath = nconf.get('relative_path'); pluginRouter.render = function() { app.render.apply(app, arguments); }; // Set-up for hotswapping (when NodeBB reloads) pluginRouter.hotswapId = 'plugins'; authRouter.hotswapId = 'auth'; app.use(middleware.maintenanceMode); app.all(relativePath + '/api/?*', middleware.prepareAPI); app.all(relativePath + '/api/admin/?*', middleware.isAdmin); app.all(relativePath + '/admin/?*', middleware.ensureLoggedIn, middleware.applyCSRF, middleware.isAdmin); adminRoutes(router, middleware, controllers); metaRoutes(router, middleware, controllers); apiRoutes(router, middleware, controllers); feedRoutes(router, middleware, controllers); pluginRoutes(router, middleware, controllers); /** * Every view has an associated API route. * */ mainRoutes(router, middleware, controllers); staticRoutes(router, middleware, controllers); topicRoutes(router, middleware, controllers); tagRoutes(router, middleware, controllers); categoryRoutes(router, middleware, controllers); accountRoutes(router, middleware, controllers); userRoutes(router, middleware, controllers); groupRoutes(router, middleware, controllers); app.use(relativePath, pluginRouter); app.use(relativePath, router); app.use(relativePath, authRouter); if (process.env.NODE_ENV === 'development') { require('./debug')(app, middleware, controllers); } app.use(function(req, res, next) { if (req.user || parseInt(meta.config.privateUploads, 10) !== 1) { return next(); } if (req.path.indexOf('/uploads/files') === 0) { return res.status(403).json('not-allowed'); } next(); }); app.use(relativePath, express.static(path.join(__dirname, '../../', 'public'), { maxAge: app.enabled('cache') ? 5184000000 : 0 })); handle404(app, middleware); handleErrors(app, middleware); // Add plugin routes plugins.init(app, middleware); authRoutes.reloadRoutes(); callback(); }; function handle404(app, middleware) { app.use(function(req, res, next) { if (plugins.hasListeners('action:meta.override404')) { return plugins.fireHook('action:meta.override404', { req: req, res: res, error: {} }); } var relativePath = nconf.get('relative_path'); var isLanguage = new RegExp('^' + relativePath + '/language/[\\w]{2,}/.*.json'), isClientScript = new RegExp('^' + relativePath + '\\/src\\/.+\\.js'); if (isClientScript.test(req.url)) { res.type('text/javascript').status(200).send(''); } else if (isLanguage.test(req.url)) { res.status(200).json({}); } else if (req.accepts('html')) { if (process.env.NODE_ENV === 'development') { winston.warn('Route requested but not found: ' + req.url); } res.status(404); if (res.locals.isAPI) { return res.json({path: req.path, error: 'not-found'}); } middleware.buildHeader(req, res, function() { res.render('404', {path: req.path}); }); } else { res.status(404).type('txt').send('Not found'); } }); } function handleErrors(app, middleware) { app.use(function(err, req, res, next) { if (err.code === 'EBADCSRFTOKEN') { winston.error(req.path + '\n', err.message) return res.sendStatus(403); } winston.error(req.path + '\n', err.stack); if (parseInt(err.status, 10) === 302 && err.path) { return res.locals.isAPI ? res.status(302).json(err.path) : res.redirect(err.path); } res.status(err.status || 500); if (res.locals.isAPI) { return res.json({path: req.path, error: err.message}); } else { middleware.buildHeader(req, res, function() { res.render('500', {path: req.path, error: err.message}); }); } }); } }(module.exports));

    Changing accordingly all paths and replaced var app, middleware and controllers. But not work:
    Снимок экрана от 2015-03-17 00:30:46.png
    Tried on action:app.load and action:init.

  • 0 Votes
    9 Posts
    3k Views
    A

    Hey!!! Thanks for the reply.... We have completed our task.

  • [solved] Plugin not work

    3
    0 Votes
    3 Posts
    1k Views
    S

    All is well! I helped @psychobunny https://github.com/NodeBB/NodeBB/issues/2869#event-255546006

  • 1 Votes
    3 Posts
    3k Views
    OrotonO

    Is it even possible to integrate a plug in that posts to a 'group'

    I'm sure FB only allows posting to fan pages and walls.

    That being said, I know you can do 'Canvas' app for NodeBB on FB, but this only integrates with FB notifiactions
    On the Browser, where as you'll find most of the time they check FB via mobile. And FB mobile is not compatible with
    Canvas or any external FB notifications.

    So you are probably going to be disappointed with it's results.

    Also, on that note. I've found it susccefull for mobile users, using a push notification app. That notifies users on events.
    And when they are notified, it refers them back to your forum. via a webui Browser in the app.
    This works great with a Responsive design or a dedicated mobile browser.

  • 0 Votes
    4 Posts
    2k Views
    X

    how does this plugin work?
    I installed it but cannot find in anywhere in ACP

  • How to Get Domain Name.

    2
    0 Votes
    2 Posts
    1k Views
    julianJ
    var nconf = require.main.require('nconf'); console.log(nconf.get('url'));
  • Use Custom Template to send Email

    2
    0 Votes
    2 Posts
    1k Views
    julianJ

    No dependencies required, just have your plugin serve templates (add the template option into plugin.json), and in that template folder, have a folder called emails, and place your template in there.

    Also place a corresponding plaintext one with the suffix _plaintext.

  • Is there plugin to auto embed COUBs?

    8
    0 Votes
    8 Posts
    3k Views
    BobberB

    @kosyak it works, thanks!

  • What's the current theme's less path

    7
    0 Votes
    7 Posts
    2k Views
    PitaJP

    @julian but, how would I pass the less path of the theme into my plugin's less file without knowing what theme will be used?

    I'm trying to use mixins to style my plugin's pages the same as the rest of the site.

  • Error installing Mandrill emailer plugin

    24
    0 Votes
    24 Posts
    10k Views
    limkerL

    Fixed, i had to as suggested by several users to change my "base_url" or "url" (depends on your nodebb version). But i had to add the key "port" in the config.json as well.

    "url": "http://myforum.com",
    "port": "1234"

  • This topic is deleted!

    Locked
    1
    0 Votes
    1 Posts
    5 Views
  • Apply styles to only certain pages

    11
    0 Votes
    11 Posts
    4k Views
    T

    No, actually I wouldn't recommend adding it to the content as it requires a higher level for better customization such as body or even html so that you can totally customize it the way you want. For example, adding it to the #content would be a bit more work to customize because it contains padding and marginal properties to go around.

    @julian Oh yeah, just noticed that. Good stuff. 👍

    @pitaj said putting the template route on the html element would cover everything.

    +1 for html or body (since they usually contain 0 padding and margins).

  • 0 Votes
    3 Posts
    1k Views
    T

    @pitaj said:

    Link Preview Image GitHub - psychobunny/nodebb-plugin-badges: Badges: an achievement system for NodeBB

    Badges: an achievement system for NodeBB. Contribute to psychobunny/nodebb-plugin-badges development by creating an account on GitHub.

    favicon

    GitHub (github.com)

    Thanks a lot!

  • getting custom fonts in .less

    1
    0 Votes
    1 Posts
    859 Views
    belstgutB

    hi,

    I was just trying to write my first Plugin but failed when referencing custom fonts in my .less.

    my fonts are in ./static/fonts and my .less is ./static/style.less

    in my .less i use eg: url(fonts/font.woff)

    The font doesnt get loaded though.

    What is the easiest way to include fonts in my Plugin?

    edit: it seems to work if i use plugins/nodebb-plugin-pluginname/static/fonts as fonturl
  • ProfileLinks broken after upgrade 0.6.1

    7
    0 Votes
    7 Posts
    2k Views
    R

    Thanks @baris for the carification

  • Slideshare Embedding

    5
    3 Votes
    5 Posts
    2k Views
    W

    😆

  • 0 Votes
    6 Posts
    3k Views
    julianJ

    @akumbhare It will be available in v0.7.0.

  • 0 Votes
    4 Posts
    3k Views
    A

    I am writing like plug-in, which provides an like functionality for anonymous as well as registered user.
    1. Anonymous User should be able to like on any comment/reply and I have to restrict anonymous user via IP address.
    Yet, I can store IP values to redis database, I need to find out, The given IP value is present in "like:pid" hash set.
    I need to write query structure which finds out the given anonymous users IP address is not present in database for that particular post.

    Thanks!!

  • Emaze Plugin Help

    14
    0 Votes
    14 Posts
    10k Views
    JonDoe12J

    I was just going to let you take most of the credit. It's still your plugin, and you helped me make the modifications. I'm working on a new modification now, though. I'm trying to get the presentations to embed into an area of the user's profile.

    Right now, I'm testing it with the Fullname field on the profile, by entering the URL of the presentations (instead of a human name) and seeing if it parses into an embedded slide show.

    Would this be easy to accomplish? If not, are there any hints you could give me @a_5mith ?

    So far, I've edited the library.js file and replaced data.post and data.post.conent with data.fullname and data.fullname.content. I've also tried changing the hook in plugin.js from:

    { "hook": "filter:parse.post", "method": "parse", "callbacked": true } ]

    to

    { "hook": "action:user.set", "method": "parse", "callbacked": true } ]

    I feel like I'm missing something, though. If you're up to it, I'd like to collaborate with you further.

  • 0 Votes
    2 Posts
    1k Views
    MegaM

    related https://community.nodebb.org/topic/3876/nodebb-plugin-facebook-facebook-posts/11