BDSC’s plugin system was designed for extending the player object in a modular, clean, and lifecycle-safe way. Whether internal to your core or part of a standalone release, plugins are the best way to inject logic, state, UI, and methods into your players.
This guide will walk you through building your first full plugin using the provided example plugin as a template.
What Is a Plugin?
In BDSC, a plugin is more than just a function hook. It’s a full-featured extension module that can:
Modular – Everything is self-contained, easy to read, and easy to reuse.
Composable – Multiple plugins can attach to the same player cleanly.
Integrated – Plugins run automatically at the correct points in the player lifecycle.
Portable – External plugins can be dropped into any resource or shared as paid modules.
Use internal plugins for logic that belongs in your core.
Use external plugins for marketplace drops, private releases, or premium add-ons.
Plugin Structure
Plugins live in the plugins/ folder of your BDSC-based resource.
Their internal structure is flexible and modular — you can add only the parts you need.
This file defines the plugin’s logic and lifecycle hooks.
local Example = {}
Example.__index = Example
--- Creates the extension and attaches it to a player.
--- This runs automatically when the player is created.
--- @param player table: The player object (provided by BDSC).
--- @return table: Your extension instance.
function Example.new(player)
local self = setmetatable({ player = player }, Example)
self:on_load() -- Run your setup logic immediately
return self
end
--- Called after the player finishes loading.
--- You can add data or register new methods on the player object here.
function Example:on_load()
-- Create a new synced data category called "example"
self.player:add_data("example", {
enabled = true,
value = 0
}, true) -- true|false: true = data will be replicated to client, false = server only
-- Add a method `example_add(amount)` to increase `value`
self.player:add_method("example_add", function(p, amount)
local data = p:get_data("example")
data.value = data.value + (amount or 1)
p:sync_data("example")
end)
-- Add a method `example_toggle()` to flip the enabled flag
self.player:add_method("example_toggle", function(p)
local data = p:get_data("example")
data.enabled = not data.enabled
p:sync_data("example")
end)
end
--- Called when the player is saved.
--- Add persistence logic here if needed (e.g. writing to the database).
function Example:on_save()
local data = self.player:get_data("example") or {}
bdsc.log("debug", "example", "example_save_player", json.encode(data))
end
--- Called when the player unloads.
--- Clean up or log anything you want here.
function Example:on_unload()
bdsc.log("debug", "example", "example_unload_player", self.player.source)
end
return Example
Step 2: Register the Plugin
plugins/example/init.lua
if not bdsc.is_server then return end
local manager = utils:get("core.player.manager", { bdsc = bdsc })
local class = utils:get("plugins.example.player.class", { bdsc = bdsc })
manager.register_hook("example", function(player)
local extension = class.new(player)
player:add_extension("example", extension)
end)
Step 3: Add Server + Client Logic (optional)
server/example.lua
--- Fired internally by BDSC when a player is created.
AddEventHandler("bdsc:sv:player_joined", function(player)
local player = bdsc.get_player(source)
if not player then return end
local data = player:get_data("example")
if data then
print(("[example][server] Player %s joined with example data: enabled = %s, value = %s"):format(source, tostring(data.enabled), data.value))
end
end)
client/example.lua
--- Fired internally by BDSC when a player is created.
AddEventHandler("bdsc:cl:player_joined", function()
local data = bdsc.p_data.example
if data then
print(("[example][client] Player data loaded. Enabled = %s, Value = %s"):format(tostring(data.enabled), data.value))
end
end)
Step 4: Add UI (Optional)
If your plugin includes frontend files, put them in the ui/ folder and register them in the global manifest: