Passing custom data into a template?

Moved Technical Support
  • So to get data into a page there are obviously two methods, server side (pass into template then compile) or client side (ajax). I am very familiar with express, but I don't see a possible way of passing additional data to a template after res.render has been called, I am saying this because I know the home page is already being rendered somewhere in the core, so there is no way for me to inject data before it gets called is there?

    If not do I need to replicate all the json being passed in to the default render, add my custom data, then call res.render myself?

    Or is the only method rendering the data via ajax call, which would be my least preferable method.

    I know things like this have been documented but it is not very clear to me how to pass in custom data to a template. My case for this is to pass in categories to a sub-header inside header.tpl here is what I have so far.

    (function(module) {
    	"use strict";
    	var theme = {};
    	var catLength = [1,2,3,4,5,6,7];
    	var Categories = module.parent.require("./Categories");
    	theme.init = function(params, callback) {
    		var app = params.router;
    	    var hostMiddleware = params.middleware;
    		var hostControllers = params.controllers;
    		var categories = {};
    		Categories.getCategoriesData(catLength, function(err, data) {
    			categories = data;
            // TODO: make this a more nodebb like route
    		// app.get('/api', function(req,res) {
    		// 	res.render('/', {test:true});
    		// });
    	module.exports = theme;

    Also I looked through ./Categories and getCategoriesData() seemed like the only method to get categories, my question is why do I have to pass an array of how many categories I want, thats not fun. Why isn't there a method to get all without specifying anything?

    Thanks guys!

  • You can do what you want with the filter:middleware.renderHeader hook.

    In your plugin add the data you want into it like so

    myPlugin.onRenderHeader(data, callback) {
        Categories.getAllCategories(data.req.uid, function(err, categories){
             if (err) {
                  return callback(err);
             data.templateValues.categories = categories;
             callback(null, data);

    Now in your header.tpl you can access the categories it will be in an array called categories, To print out their names.

    <!-- BEGIN categories -->
    <!-- END categories -->
  • @baris

    WOW thanks man.

    One thing though myPlugin.onRenderHeader = function() {}

    This works, thanks! So I am guessing callback() is where the data is sent to the core then that adds it before rendering?

    Great job!

  • Yes, the hook is fired right before the header is rendered here. You add the categories into the templateValues which is then passed to app.render and rendered.

  • @baris I am not sure my question is related to this topic, but I have searched a lot about how to get the specific post data from a specific category to put them on the customize home page for my current site. For example, I have a category called "News", and what I want is each time when I post a new post in this category, I want the post also to show up on the home page. Since I am using custom home page plugin, so all I can do is insert HTML and those data like categories, recent post or popular post. I am a bit of confusion about how to exactly get those data and linked them to the home page? Thank you.

  • @baris said in Passing custom data into a template?:

    You can do what you want with the filter:middleware.renderHeader hook.

    old topic, new developer

    thanks.. that is very helpful

    I have one question
    I have a filter:post.shouldQueue plugin, it needs to check a list of data items on each post.
    I load the data at plugin init time using meta...

    	meta.settings.get(my_data_key, function(err, settings) {

    my plugin registers the routes for the admin pages, which causes the static admin,js to run and the template to load, which loads or saves the data (separate from plugin operation code)
    // copied from old category-queue plugin, admin.js

    	ACP.init = function () {
    		Settings.load(my_data_key, $('.key_class_settings'));
    		$('#save').on('click', function () {, $('.key_class_settings'), function () {

    how do I inform the plugin that the data has changed on save? cause init should only run once.

    is there some data/key 'onchange' (hook) I can register for to reload it so that my operational data is accurate?

    seems bad to get it on every post, as it will almost never change (except when admin/save is checked

    i haven't figured out template code to add/delete items from the list yet.. (more js event handlers I presume)

  • 'action:settings.set' this hook fires when you save plugin settings. It will receive the name of the settings object.'action:settings.set', {
    		plugin: hash,
    		settings: { ...values, ...sortedListData }, 

    So in your plugin you can update your settings in this hook like so.

    myPlugin.actionSettingsSet = async function (hookData) {
      if (hookData.plugin === my_data_key) {
          my_local_settings = await meta.settings.get(my_data_key);
  • @baris so, I am SO close.. just adding admin pages...

    repo here

    the postqueue function works
    the link shows for admin

    Screenshot at 2022-08-30 15-06-58.png

    the admin template shows when u click the link,

    Screenshot at 2022-08-30 15-06-27.png

    but no data, the header hook for data doesn't fire

    and I get that error

    the static/lib/admin.js doesn't fire

    its the '.' I am sure, but why and from where.. the log shows status 200 for loading content.. no 404..

    npm i the repo,
    rebuild and relaunch
    go to admin, plugins, click the link post_link_list

    npmi the

  • I think you are missing the plugin.json entry for your admin page javascript, take a look at how persona adds the admin page javascript.

    You need an try in your plugin.json like that.

  • @baris thanks.. that is a completely different plugin.json layout.

    maybe I started with an old module format..

    it WAS working yesterday.. so I'll go back to that commit and see if I can find the difference

  • @baris this page shows the format required by the 2.4.5 version I am on..
    it is similar to mine

    also, my admin.js IS loaded and run

    as the messages from the run are shown at the client
    (browser dev winbow console)

    /plugins/post_link_list admin define running
    /plugins/post_link_list admin define ending returning acp keys= ['init']

    but the actual init() method is not called.

    adding the module entry to plugin.json solved the error and the init is now called...

  • Yeah you need your admin.js file in the modules section, acpScripts is for scripts that will be run whenever the admin site is loaded, think of them as global javascript that you want to include on the ACP site. modules section is for new pages for plugins or custom js modules. They are loaded on demand when the user navigates to your page.

  • @baris ok, thanks. but none of that makes sense.. sorry.. 'user' is only admin in my case.

    added on the ACP site (for what purpose if not for when the user navigates)?

    i don't want it to run twice.. so it sounds like do NOT use acpScripts..

    but. back on the custom data to the template

    I see two different approaches used.. (neither work for me)

    some modules use admin to use the settings api to load the values into a css class using jquery $('.someclassname')
    which the template uses somehow, but unclear ${'../name'}

    Settings.load('category-queue', $('.category-queue-settings'));
    			<form role="form" class="category-queue-settings">
    				<!-- BEGIN categories -->
    				<div class="form-group col-sm-4 col-xs-6">
    					<label for="{../cid}">{../name}</label>

    and the other passes data in the call to res.render(page, {data structure})
    in lib/controllers/renderAdmin()
    then the template uses the data structure elements clearly.

    my data is an array.. no internal named elements (presumably that is the ${../name} thing...accessing the object elements
    and there is no for loop construct in the template, so I don't see how it generates 4 instances in the output...

    sorry, one more question on templates.. is there any way to test them outside trying to load the plugin.. cause they are not generating the content I expect, but don't see any errors during build or run

  • In your plugin's case you would need to move your admin js file to the modules section like so

    "modules": {
      "../admin/plugins/post_link_list.js": "static/lib/admin.js",

    And remove it from acpScripts.

    When you do this static/lib/admin.js will be loaded and its init method will be called when the user(admin) navigates to your plugin page.

    Now settings.load uses some client side code to load the settings and fill in the input elements on the plugin page. So when that is called from the init function of the admin page. It will populate the forum fields. If you are storing a list of urls I suggest storing them as a string and displaying them in a text area. You can inspect it does something similar.

  • @baris thanks.. I want to have a field for each url string. (from the array)

    have an input field to add another with a click
    and some selector (minus sign, checkbox...) and click to remove..

    but the templating engine is killing me.. I don't know what the syntax is for non structure objects..

    {{{each post_link_list}}}

    gets me a line for each.. good.. what is its value?

    hm... now it works ok?
    @value says object.. (oops, put in {}), now compiler says use {} as required in 3.x)

    hm.. could use its array index tho...

    this post_link_list comes from the render..

    can I use meta from admin (as module) ? (button click handler) ..
    hm.. can't import meta in admin..

  • @baris well, got almost all of it working, list, add, delete, save button

    now to update the database

    I have the hook on save, so I can reload the operational list and use it to inform the admin page

    it will be loaded via meta.get in the index.js (it doesn't return any data now, as there isn't any)
    but meta isn't usable in admin..

    settings is, but I don't see a save or (or load) method in the src/settings.js
    but from quickstart admin.js
    settings.load('quickstart', $('.quickstart-settings'), function () {

    i want to save from a JS variable. but that causes some error not in the log, and I get the spinning circle saying my plugin is taking a long time.,

    so close..
    the admin code that gets the save click to save

    			var selected = [];
    			$('#urls').find('input[type="text"]').each(function() {
    			console.log("urls to save=",selected)
    		try {,selected, (err,sss)=>{

    and the dev window console log

    post_link_list save  clicked 
    urls to save= (4) ['', '', '', '']
    post_link_list settings save executed
    settings save completed

    the node bb log capturing from index
    this is the output of setting save(), have the right 'hash', well its the 'key' I sent in
    similar to all the other plugins I looked at

    but no data

    io: 1 on [
        type: 2,
        nsp: '/',
        id: 8,
        data: [ 'admin.settings.set', { hash: 'post_link_list', values: {} } ]

    in index , the hook

    settings saved for  { plugin: 'post_link_list', settings: {}, caller: { uid: 1 } }
  • @baris debugging why my settings don't save

    I see the value in the hook now.. the 'value' MUST be a keyed object... not flat

    but still no persistence.

    I see the action hook is called before informing meta to reload..

    but even when I stop and restart nodebb the data is not current

  • @sdetweil I suggest you take a look at It has an admin page that saves a list of strings. You can compare it to yours and see why your data isn't being saved.

  • @baris thanks. altho the typical plugin doesn't do it, the settings apis are promisified. you don't wait, u get junk..

    adding .then() in the non async using functions solves the problem

  • julianJ julian moved this topic from NodeBB Development on

Suggested Topics

  • 0 Votes
    7 Posts

    @jim-bridger said in How do I update my custom theme?:

    Does that mean my theme would be listed in everyone elses plugin list? It's a custom theme for my site, not something I want to publish

    Yes, that is correct. Because you've published it to npm, our package manager automatically picks it up as an available theme and offers it for download to any other NodeBB.

    You can set nbbpm.index to false in your package.json, but this would also remove the theme from your plugins list.

    The easiest thing to do would be to publish the theme under your own namespace (agian, with nbbpm.index set to false), and then install it via the command line, as @pitaj suggested.

    e.g. npm i @jimbridger/nodebb-theme-mytheme

  • 0 Votes
    2 Posts

    What do you mean by "getting data by email"? It is possible to disable sending of emails, if your users don't want that...

  • 0 Votes
    1 Posts

    Im trying to add a list of categories to my custom homepage template using:

    <h1>homepage header</h1>
    <!-- IMPORT categories.tpl -->

    however - instead of a list, it just outputs:

    No new posts.

    Im using a custom theme based on persona. The h1 tag renders fine..

    Any help would be appreciated.
    I suspect I need to add in the categories data in somewhere.. but can't find any documentation on this..


  • Custom api router?

    Moved Technical Support
    0 Votes
    6 Posts

    Also, you'll want to add new routes to params.router, which is a special router just for plugins, and doesn't interfere with existing routes 😄

  • 3 Votes
    1 Posts

    I believe you should have as many partials in your main template as efficiently as possible to cut down on redundant code. Here I'll write the standard for a good rule of thumb on how you should organize your templates and all the code within in them.

    Any redundant JS scripts should be in their own partial to be reused in other templates if need be.

    Take full advantage of the NBB API so that you extend your template even further. For example using <!-- IF loggedIn --> condition can greatly improve UX and conversions rates on your site. Having some type of call to action or banner within these will let the user know that they aren't logged. OR when they are logged in, show something else. You can come up with some pretty crafty ideas. But yeah, do what you can with the existing API.

    You can only do so much with the templates but is that good enough? Maybe, maybe not, but it does work when you plan out the logical flow and hopefully not get lost in the div's and such. For example on the Majestic Theme, the topic covers are pretty complex and one can easily get lost in the flow of what's going on. Here's a screenshot of the topics UI.

    Screen Shot 2015-03-17 at 8.12.29 AM.png

    Now lets take a little peek at whats under the hood;

    Screen Shot 2015-03-17 at 8.16.46 AM.png

    Screen Shot 2015-03-17 at 8.14.33 AM.png

    Now there's way more to this, but this is just a little example of how I control the topic covers. The only reason why I'm posting this is because I'm quite certain it would take a while to replicate without all of the other partials in this one template (not shown for obvious reasons).

    You can also do stuff like this to take control over pluralization of your template: post<!-- IF !topics.unreplied -->s<!-- ENDIF !topics.unreplied -->
    Which would mean, if you don't have any posts, remove the s in post, otherwise its posts. Neat huh?

    Anyone else have any cool things they've done with the template system so far in their existing theme? Share some of your ideas here. 🙂