Themes
vMenu can draw its menus in a few different looks, called themes. The server owner picks one for everybody with a convar, and a plugin can put one player in a different one:
plugin.Themes.Changed += () =>{ foreach (var theme in plugin.Themes.Available) { // theme.Id is what you send back, theme.Name is what a player reads, // theme.IsCurrent says whether it is the one on screen. }};
plugin.Themes.Set("dark"); // this player sees the dark theme from now onplugin.Themes.Reset(); // back to the theme the server pickedplugin.Themes:OnChanged(function() for _, theme in ipairs(plugin.Themes:GetAvailable()) do -- theme.Id is what you send back, theme.Name is what a player reads, -- theme.IsCurrent says whether it is the one on screen. endend)
plugin.Themes:Set('dark') -- this player sees the dark theme from now onplugin.Themes:Reset() -- back to the theme the server pickedplugin.themes.onChanged(() => { for (const theme of plugin.themes.available) { // theme.id is what you send back, theme.name is what a player reads, // theme.isCurrent says whether it is the one on screen. }});
plugin.themes.set('dark'); // this player sees the dark theme from now onplugin.themes.reset(); // back to the theme the server pickedplugin.themes.onChanged(() => { for (const theme of plugin.themes.available) { // theme.id is what you send back, theme.name is what a player reads, // theme.isCurrent says whether it is the one on screen. }});
plugin.themes.set('dark'); // this player sees the dark theme from now onplugin.themes.reset(); // back to the theme the server pickedvMenu sends the list right after your plugin registers and again on every change, whoever caused it, so build your rows from the Changed event rather than straight after connecting. Available is empty until that first message arrives, and stays empty against a vMenu too old to know about themes.
A theme set this way beats the server’s convar, and it belongs to that one player. vMenu saves nothing, so reconnecting puts the server’s choice back, and Reset does the same without the wait. Remembering a choice is your plugin’s job: keep the id in your own resource’s key value store and set it again on the first list vMenu sends you, which is what the Theme Picker plugin does. No permission and no setting gates any of this, because it changes nothing except what that player sees.
Adding themes of your own
Section titled “Adding themes of your own”A resource can also hand vMenu new themes, so a server does not have to edit vMenu’s own files to get a look of its own. This one needs no plugin API and no plugin registration at all, it is a single event that any resource can send:
emit( "vMenu.Enhanced:Plugins:RegisterThemes", JSON.stringify({ themes: [ { id: "reddead", name: "Red Dead", css: "themes/red-dead.css", banner: "default", }, ], }),);- id is what a convar or a plugin names the theme by. Letters, digits, dashes, underscores and dots, and never one of vMenu’s own names.
- name is what a player reads, falling back to the id.
- css is a path inside your own resource, or a full
https://cfx-nui-<resource>/url naming another one. vMenu’s menu page loads it from there, so nothing is copied into vMenu. Addresses out on the internet are refused. - banner is the picture on top of the menu. One of vMenu’s own,
default,gold,green,red,dark,cartoon, ornonefor the plain Grand Theft Auto one, or a.png,.jpgor.webpof your own, given the same way as the stylesheet. An image needs a MenuAPI new enough to load banners out of another resource, and falls back to vMenu’s default banner otherwise.
Every file the stylesheet uses has to be listed in your fxmanifest.lua under files, or the game has no copy of it to serve. Fonts and images inside the stylesheet are resolved relative to the stylesheet itself.
Send the whole set in one go: a second registration replaces everything that resource registered before, and its themes are dropped the moment it stops. vMenu answers on vMenu.Enhanced:Plugins:<your resource>:ThemesRegistered with accepted, errors and warnings, which is where a refused theme tells you why. Send it once when your resource starts, and again whenever vMenu says vMenu.Enhanced:Plugins:Ready, which is what it says when it restarts.
Once registered, a theme is a theme like any other. It shows up for plugins reading Themes.Available, and the vMenu.Enhanced.MenuAppearance.Skin convar accepts its id. If you would rather not write any of this yourself, the Custom Themes plugin is exactly this, driven by a JSON file you edit.
