doc/developers/README.add_new_pref.md
This document explains how to add a new preference to ntopng. There are two patterns depending on whether the preference needs to be read by the C++ backend:
Prefs.cpp, exposed back to Lua via ntop.getPrefs(). Use this for settings that the C++ core needs at runtime.Both patterns share the same first three steps.
Add title and description strings to scripts/locales/en.lua inside the ["prefs"] table:
["my_feature_url_title"] = "API URL",
["my_feature_url_description"] = "Base URL of the remote API endpoint",
["my_feature_token_title"] = "API Token",
["my_feature_token_description"] = "Authentication token for the remote API",
prefs_menu.luascripts/lua/modules/prefs_menu.lua drives the tab list and controls access (pro-only, enterprise-only, hidden, etc.).
Adding a new tab:
},{
id = "my_feature", -- must match the tab= GET param and the if-block in prefs.lua
label = i18n("prefs.my_feature"),
advanced = false,
pro_only = true, -- set true to restrict to Pro/Enterprise
hidden = false,
entries = {
my_feature_url = {
title = i18n("prefs.my_feature_url_title"),
description = i18n("prefs.my_feature_url_description")
},
my_feature_token = {
title = i18n("prefs.my_feature_token_title"),
description = i18n("prefs.my_feature_token_description")
}
}
}}
Adding entries to an existing tab — just extend the entries table of the relevant subpage.
prefs.luascripts/lua/admin/prefs.lua contains one render function per tab. Use the helpers at the top of the file:
| Helper | Purpose |
|---|---|
create_table() | Opens <form> + <table> |
add_section(title) | Adds a blue <thead> section divider |
end_table() | Adds Save button, CSRF token, closes form + table |
prefsToggleButton(subpage_active, opts) | On/off toggle |
prefsInputFieldPrefs(label, desc, prekey, key, default, type, ...) | Text / number / password input |
Dispatch block — near the bottom of prefs.lua, add:
if (tab == "my_feature") then
printMyFeature()
end
Render function skeleton:
function printMyFeature()
create_table()
add_section(i18n("prefs.my_feature"))
prefsInputFieldPrefs(
subpage_active.entries["my_feature_url"].title,
subpage_active.entries["my_feature_url"].description,
"ntopng.prefs.my_feature", "url",
"https://api.example.com", -- default
"text", -- input type
true, -- show
true, -- disable autocomplete
true, -- allow URLs
{ attributes = { spellcheck = "false", maxlength = 255 } }
)
prefsInputFieldPrefs(
subpage_active.entries["my_feature_token"].title,
subpage_active.entries["my_feature_token"].description,
"ntopng.prefs.my_feature", "token",
"",
"password", -- renders as dots
true, true, false,
{ attributes = { spellcheck = "false", maxlength = 255 } }
)
end_table()
end
Redis key is assembled as prekey .. "." .. key, so the above produces ntopng.prefs.my_feature.url and ntopng.prefs.my_feature.token. Read them anywhere in Lua with ntop.getPref("ntopng.prefs.my_feature.url").
Input types:
| Value | Renders as |
|---|---|
"text" | Plain text box |
"number" | Numeric input (supports min, max, step) |
"password" | Masked dots; also sets autocomplete="new-password" |
Multiple sections on one tab — call add_section() multiple times between field groups:
add_section(i18n("prefs.section_a"))
-- fields for section A ...
add_section(i18n("prefs.section_b"))
-- fields for section B ...
http_lint.luaEvery key that can appear in _POST must be whitelisted in scripts/lua/modules/http_lint.lua. Find the relevant block and add entries:
-- MY FEATURE
["url"] = validateUnquoted,
["token"] = {passwordCleanup, validatePassword},
Common validators:
| Validator | Use for |
|---|---|
validateUnquoted | Free text, URLs, hostnames |
validateSingleWord | Tokens without spaces |
validateNumber | Numeric values |
validatePassword + passwordCleanup | Secret / password fields |
validateIpAddress | IP addresses |
Skip this section if the preference is only consumed from Lua.
In include/ntop_defines.h:
#define CONST_PREFS_MY_FEATURE_TOKEN \
NTOPNG_PREFS_PREFIX ".my_feature.token"
In include/Prefs.h:
#ifdef NTOPNG_PRO
bool my_feature_enabled;
#endif
Use #ifdef NTOPNG_PRO (or NTOPNG_ENTERPRISE) for version-gated features.
In Prefs::Prefs() constructor in src/Prefs.cpp:
#ifdef NTOPNG_PRO
my_feature_enabled = false;
#endif
In Prefs::reloadPrefsFromRedis():
my_feature_enabled = getDefaultBoolPrefsValue(
CONST_PREFS_MY_FEATURE_TOKEN, false
);
In Prefs::lua():
lua_push_bool_table_entry(vm, "my_feature_enabled", my_feature_enabled);
The value is then accessible in Lua via ntop.getPrefs().my_feature_enabled.
| File | Change |
|---|---|
scripts/locales/en.lua | Add i18n strings |
scripts/lua/modules/prefs_menu.lua | Add subpage / entries |
scripts/lua/admin/prefs.lua | Add render function + dispatch block |
scripts/lua/modules/http_lint.lua | Whitelist POST keys |
| File | Change |
|---|---|
include/ntop_defines.h | Add constant |
include/Prefs.h | Declare variable |
src/Prefs.cpp | Initialize, reload, expose to Lua |
ntopng.prefs.<feature>.<key> to avoid collisions.pro_only = true for any feature that should be restricted to Pro/Enterprise builds.http_lint.lua — unregistered POST keys are silently dropped, which can cause confusing save failures."password" as the input type and {passwordCleanup, validatePassword} in http_lint; never store tokens in plain "text" fields.