Build addons
with imLib
A modular, object-oriented library for Garry's Mod. Themes, networking, database, UI elements, state sync, HTTP — everything you need to ship professional addons without reinventing the wheel.
📦 Installation
Drop the imlib folder into your server's addons/ directory. imLib auto-loads via autorun/imlib.lua and is available on both client and server as the global imLib table. When imLib finishes loading, it fires the imLib:Loaded hook.
addons/
└── imlib/
└── lua/
├── autorun/
│ └── imlib.lua -- Entry point
└── imlib/
├── sh_init.lua -- Load orchestrator
├── modules/ -- Core modules
├── elements/ -- VGUI elements
├── themes/ -- Built-in themes
├── languages/ -- Translation files
└── utilities/ -- Drawing, scale, etc.🚀 Addon Bootstrap
imLib.Addon(id, globalName) is the single entry point for addon authors. It creates your global table, enables subsystems, queues files, and boots everything in one clean chain.
-- addons/myaddon/lua/autorun/myaddon.lua local addon = imLib.Addon("myaddon", "MYADDON") addon:SetName("My Addon") addon:SetAuthor("YourName") addon:SetVersion("1.0.0") addon:SetDescription("A cool addon built with imLib") -- Enable subsystems (order doesn't matter) addon:UseLogger() addon:UseDatabase() addon:UseConfig() addon:UseLanguages() addon:UseThemes() -- Overrides imLib element themes! addon:UseStateSync() addon:UseHTTP({ base_url = "https://api.example.com" }) -- Queue files & folders addon:AddFolder("modules", imLib.FileLoader.REALM_SHARED, true) addon:AddFolder("themes", imLib.FileLoader.REALM_SHARED) addon:AddFolder("languages", imLib.FileLoader.REALM_SHARED) addon:AddFile("sh_config", imLib.FileLoader.REALM_SHARED) -- Queue a raw function to execute at this point in the load order addon:AddFunction(function() print("Loaded up to this point!") end) -- Boot everything addon:Load()
UseThemes(), it registers as a theme provider. All imLib UI elements (Frame, Button, Checkbox, etc.) automatically use that addon's active theme. The last-loaded addon with UseThemes() wins.
What :Load() Creates
| Subsystem | Creates on Global | Description |
|---|---|---|
| UseLogger() | MYADDON.Logger | ConsoleLogger instance |
| UseDatabase() | MYADDON.Database | SQLite / MySQLOO instance |
| UseConfig() | MYADDON.Config | Settings instance |
| UseLanguages() | MYADDON.L(), .Languages, .SetLanguage(), .GetLanguage(), .GetLanguages(), .RegisterLanguage() | Translation system |
| UseThemes() | MYADDON.Themes, .SetTheme(), .GetTheme(), .GetActiveTheme(), .GetThemes() | Theme registry + provider |
| UseStateSync() | MYADDON.StateSync | Isolated state manager |
| UseHTTP() | MYADDON.HTTP | HTTP client with defaults |
Addon Instance Methods
| Method | Description |
|---|---|
| :GetID() | Returns the addon's string identifier |
| :GetGlobalName() | Returns the global table name (e.g. "MYADDON") |
| :GetTable() | Returns the global table reference (_G[globalName]) |
| :GetName() | Returns the display name |
| :GetAuthor() | Returns the author string |
| :GetVersion() | Returns the version string |
| :GetDescription() | Returns the description string |
| :AddFunction(fn) | Queue a raw function in the load order |
📂 File Loader
The FileLoader handles include() and AddCSLuaFile() automatically based on realm and filename prefix conventions.
local loader = imLib.FileLoader() :setDirectory("myaddon") :setLogger(imLib.Logger) -- Realm constants -- REALM_AUTO = detect from filename (sv_, cl_, sh_) -- REALM_SERVER = server only -- REALM_CLIENT = client only (auto AddCSLuaFile) -- REALM_SHARED = both realms :addFolder("modules", imLib.FileLoader.REALM_SHARED, true) :addFile("config", imLib.FileLoader.REALM_SHARED) :run()
| Method | Description |
|---|---|
| :setDirectory(dir) | Set the base directory for file resolution |
| :setLogger(logger) | Attach a ConsoleLogger for file-load messages |
| :addFile(path, realm, opts) | Queue a single Lua file. Extension is auto-appended. |
| :addFolder(path, realm, recursive, opts, ignore) | Queue all .lua files in a folder. ignore is a table of filenames to skip. |
| :addFunction(fn) | Queue a raw function to execute at this point in the load chain |
| :run() | Execute all queued files/functions in order |
📋 Logger
Colored console output with configurable log levels. Each addon gets its own prefixed logger.
local log = imLib.ConsoleLogger("MyAddon", { log = true, debug = true, error = true, warning = true, success = true, }) log:log("Player connected") log:debug("Processing inventory...") log:success("Data saved") log:warning("Rate limit approaching") log:error("Database connection failed")
⚙️ Settings
Typed, validated configuration system with serialize/deserialize for DB persistence. Supports bool, int, float, string, model, select, and table types.
local cfg = imLib.Settings("myaddon") cfg:Register("max_items", { type = "int", default = 50, min = 1, max = 500, desc = "Maximum inventory slots", }) cfg:Register("vip_enabled", { type = "bool", default = false, }) cfg:Register("difficulty", { type = "select", default = "normal", options = { "easy", "normal", "hard" }, }) -- Get / Set local max = cfg:Get("max_items") -- 50 local ok, err = cfg:Set("max_items", 200) -- true local ok, err = cfg:Set("max_items", 9999) -- false, MAX_NUMBER -- Export / Import (for saving) local data = cfg:Export() -- { max_items = 200, ... } cfg:Import(data)
🌍 Languages
Per-addon translation system with phrase lookup, format arguments, and fallback to a default language.
-- languages/en.lua local lang = imLib.Language("en") lang:SetName("English") lang:SetPhrases({ greeting = "Hello, %s!", balance = "Balance: %s%d", shop_title = "Item Shop", }) MYADDON.RegisterLanguage(lang) -- Usage anywhere local text = MYADDON.L("greeting", "Player") -- "Hello, Player!" local bal = MYADDON.L("balance", "$", 5000) -- "Balance: $5000" -- Switch language MYADDON.SetLanguage("pl")
UseLanguages("en")). If still missing, the raw key is returned.
🎨 Themes
Color, font, and material theming. Themes can be registered per-addon or globally. All imLib elements resolve their colors via imLib.GetTheme().
Built-in Themes
Creating a Custom Theme
local theme = imLib.Theme("my_dark") theme:SetName("My Dark Theme") theme:SetColors({ background = Color(20, 20, 25), primary = Color(40, 40, 48), accent = Color(90, 200, 160), text = Color(230, 230, 235), textdark = Color(140, 140, 150), -- ... all color keys }) theme:SetBoxRoundness(8) theme:Register() -- Register globally on imLib -- Or register on your addon MYADDON.RegisterTheme(theme) MYADDON.SetTheme("my_dark")
Static Color Helpers
local c = imLib.Theme.LerpColor(0.5, colorA, colorB) local d = imLib.Theme.Darken(color, 30) local l = imLib.Theme.Lighten(color, 20) local a = imLib.Theme.Alpha(color, 128)
🗃️ Database
Fluent query builder supporting both SQLite and MySQLOO. Chainable select, insert, update, delete, upsert, and DDL operations.
-- SQLite (default) local db = imLib.Database() db:Connect() -- MySQLOO local db = imLib.Database({ driver = "mysqloo", host = "127.0.0.1", port = 3306, database = "gmod", username = "root", password = "", }) db:Connect(function(success, err) end)
db:Select("players") :Columns("steamid", "name", "balance") :Where("balance", ">", 100) :OrderBy("balance", "DESC") :Limit(10) :Execute(function(rows) PrintTable(rows) end)
db:Insert("players") :Set("steamid", ply:SteamID64()) :Set("name", ply:Nick()) :Set("balance", 1000) :Execute()
db:CreateTable("players") :Column("id", "INTEGER", { primary = true, autoincrement = true }) :Column("steamid", "VARCHAR(32)", { unique = true, notnull = true }) :Column("balance", "INTEGER", { default = 0 }) :IfNotExists() :Execute()
⏱️ Timers
OO timer manager with cooldowns, debounce, throttle, grouping, pause/resume, and error handling. Wraps GMod's timer library with a clean instance-based API.
Basic Timers
-- One-shot delayed callback imLib.Timers:Simple("greet", 5, function(tmr) print("Hello after 5 seconds!") end) -- Repeating timer (10 times, every 1 second) imLib.Timers:Create("heartbeat") :SetDelay(1) :SetRepetitions(10) :SetCallback(function(tmr) print("Beat #" .. tmr:GetElapsedReps()) end) :SetOnComplete(function(tmr) print("All 10 beats complete!") end) :Start() -- Infinite repeat (0 = forever) imLib.Timers:Create("autosave") :SetDelay(300) :SetRepetitions(0) :SetCallback(function() SaveData() end) :Start()
Cooldowns
-- After firing, cannot be restarted for 10 seconds imLib.Timers:Create("ability") :SetDelay(0) :SetCooldown(10) :SetCallback(function(tmr) print("Ability used!") end) :Start() -- Later... local tmr = imLib.Timers:Get("ability") if tmr:IsOnCooldown() then print("On cooldown for " .. tmr:GetCooldownRemaining() .. "s") end
Debounce & Throttle
-- Debounce: fires AFTER 0.3s of inactivity (search input) hook.Add("OnTextChanged", "Search", function() imLib.Timers:Debounce("search", 0.3, function() RunSearch(GetSearchText()) end) end) -- Throttle: fires AT MOST once every 5 seconds imLib.Timers:Throttle("save", 5, function() AutoSave() end)
Groups & Lifecycle
-- Assign timers to groups imLib.Timers:Create("anim_1"):SetGroup("ui"):SetDelay(1):SetCallback(fn):Start() imLib.Timers:Create("anim_2"):SetGroup("ui"):SetDelay(2):SetCallback(fn):Start() -- Bulk operations imLib.Timers:PauseGroup("ui") imLib.Timers:ResumeGroup("ui") imLib.Timers:RemoveGroup("ui") -- Individual control local t = imLib.Timers:Get("heartbeat") t:Pause() t:Resume() t:Stop() t:Destroy()
📡 Network Messages
Typed network message system that replaces net.WriteTable / net.ReadTable. Adds compressed table serialization, per-player cooldowns, rate limiting, debounce, middleware hooks, and field-level validation.
imLib.Network:Register("Shop:BuyItem") :Field("item_id", "uint", { bits = 16 }) :Field("quantity", "uint", { bits = 8, min = 1, max = 64 }) :Field("gift_to", "entity", { optional = true }) :SetCooldown(1) -- 1s per player :SetRateLimit(10, 30) -- Max 10 per 30s :SetDebounce(0.5) -- 0.5s debounce on send :SetValidation(function(data, ply) return data.quantity > 0 end) :OnReceive(function(data, ply) print(ply:Nick(), "buys", data.item_id, "x", data.quantity) end)
-- Client → Server imLib.Network:Send("Shop:BuyItem", { item_id = 42, quantity = 3, })
-- Server → specific client(s) imLib.Network:Send("Shop:BuyItem", { item_id = 42, quantity = 3, }, ply) -- Server → all clients imLib.Network:Broadcast("Shop:BuyItem", { item_id = 42, quantity = 3, })
-- Define a message with a "table" field imLib.Network:Register("Sync:Inventory") :Field("inventory", "table") :SetCooldown(5) :OnReceive(function(data, ply) -- data.inventory is auto-decompressed PrintTable(data.inventory) end) -- Tables are: JSON-encoded → util.Compress → net.WriteData -- Far more efficient than net.WriteTable
-- BeforeSend: runs before data is written to the net message imLib.Network:Register("Chat:Send") :Field("message", "string") :BeforeSend(function(data) data.message = string.Trim(data.message) return data end) -- BeforeReceive: runs after reading but before OnReceive callback :BeforeReceive(function(data, ply) data.message = string.sub(data.message, 1, 256) return data end) :OnReceive(function(data, ply) print(ply:Nick() .. ": " .. data.message) end)
BeforeSend → net write → net read → BeforeReceive → validation → OnReceive. Use these hooks for data transformation, sanitization, or logging.
Supported Field Types
| Type | Writes / Reads | Options |
|---|---|---|
| "string" | net.WriteString / ReadString | — |
| "int" | net.WriteInt / ReadInt | bits (default 32), min, max |
| "uint" | net.WriteUInt / ReadUInt | bits (default 32), min, max |
| "float" | net.WriteFloat / ReadFloat | — |
| "double" | net.WriteDouble / ReadDouble | — |
| "bool" | net.WriteBool / ReadBool | — |
| "entity" | net.WriteEntity / ReadEntity | — |
| "vector" | net.WriteVector / ReadVector | — |
| "angle" | net.WriteAngle / ReadAngle | — |
| "color" | net.WriteColor / ReadColor | — |
| "table" | JSON → Compress → WriteData | — |
Network Utility Methods
| Method | Description |
|---|---|
| :Get(id) | Retrieve a registered message definition by ID |
| :IsRegistered(id) | Check whether a message ID has been registered |
| :IsOnCooldown(id, ply) | Check if a message is on cooldown for a player |
| :GetAll() | Returns a table of all registered message definitions |
| :CompressTable(tbl) | Manually JSON-encode and compress a table |
| :DecompressTable(data, len) | Manually decompress and decode table data |
🔄 State Sync
Server-authoritative reactive state. Define a schema, set values on the server, clients receive compressed delta updates automatically. Eliminates 80% of boilerplate net messages.
Defining Stores
-- Global state (broadcast to everyone) imLib.StateSync:Define("economy", { scope = "global", schema = { tax_rate = "float", bonus_active = "bool", motd = "string", }, defaults = { tax_rate = 0.1, bonus_active = false, motd = "Welcome!", }, }) -- Per-player private data (only that player sees it) imLib.StateSync:Define("wallet", { scope = "player", schema = { balance = "int", vip = "bool", items = "table", }, defaults = { balance = 1000, vip = false, items = {}, }, }) -- Shared per-player (everyone sees everyone's data) imLib.StateSync:Define("scoreboard", { scope = "shared", schema = { kills = "int", deaths = "int", }, defaults = { kills = 0, deaths = 0 }, })
"global" — one copy, everyone gets it. "player" — per-player, private to that player. "shared" — per-player, but broadcast to all (scoreboards, player stats).
Setting & Reading Values
-- Single field imLib.StateSync:Set("economy", nil, "tax_rate", 0.15) -- Multiple fields at once (one compressed update) imLib.StateSync:Set("wallet", ply, { balance = 5000, vip = true, items = { "sword", "shield" }, }) -- Reset to defaults imLib.StateSync:Reset("wallet", ply, "balance")
-- Reading values (both realms) local tax = imLib.StateSync:Get("economy", nil, "tax_rate") -- 0.15 local bal = imLib.StateSync:Get("wallet", nil, "balance") -- client reads own local all = imLib.StateSync:GetAll("wallet", ply) -- full snapshot
Client Subscriptions
-- Subscribe to all changes on a store local subID = imLib.StateSync:Subscribe("wallet", function(storeName, owner, key, newVal, oldVal) print(key, "changed:", oldVal, "→", newVal) end ) -- Subscribe to a specific key only imLib.StateSync:Subscribe("economy", function(_, _, _, newMotd) ShowMOTD(newMotd) end, "motd") -- Unsubscribe imLib.StateSync:Unsubscribe(subID)
🌐 HTTP Client
Promise-style HTTP client with retry, exponential backoff, response caching, base URLs, and concurrent request patterns. Wraps GMod's HTTP() with a builder API.
-- Simple GET imLib.HTTP:Get("https://api.example.com/users") :Then(function(res) print(res.code) -- 200 PrintTable(res.json) -- auto-parsed JSON print(res.elapsed) -- 0.152 (seconds) end) :Catch(function(err) print("Failed:", err) end) -- Global Defaults & Base URL imLib.HTTP:SetBaseURL("https://api.example.com") imLib.HTTP:SetDefaultHeader("X-Addon", "MyAddon") imLib.HTTP:SetDefaultTimeout(15) imLib.HTTP:SetDefaultRetry(2, 0.5) -- Now use relative paths imLib.HTTP:Get("/users/123"):Then(fn)
imLib.HTTP:Post("https://api.example.com/items") :JSON({ name = "Sword", damage = 50 }) :Header("Authorization", "Bearer " .. token) :Timeout(15) :Then(function(res) print("Created:", res.json.id) end) :Catch(function(err) print("Error:", err) end) :Finally(function(res, err) print("Request complete") end)
-- Retry 3 times on failure, 1s base delay (doubles each retry) imLib.HTTP:Get("https://api.example.com/config") :Retry(3, 1) :Cache(120) -- Cache for 2 minutes :Tag("config") -- Tag for bulk invalidation :Then(onSuccess) -- Invalidate cache by tag or URL imLib.HTTP:InvalidateTag("config") imLib.HTTP:InvalidateURL("https://api.example.com/config") imLib.HTTP:ClearCache()
-- All: wait for all to complete imLib.HTTP:All({ imLib.HTTP:Get("/users"), imLib.HTTP:Get("/items"), imLib.HTTP:Get("/config"), }, function(results) local users = results[1].json local items = results[2].json end, function(err) print("One failed:", err) end) -- Race: first to succeed wins imLib.HTTP:Race({ imLib.HTTP:Get("https://cdn1.example.com/data"), imLib.HTTP:Get("https://cdn2.example.com/data"), }, function(res, index) print("Winner: CDN" .. index) end)
Response Object
| Field | Type | Description |
|---|---|---|
| res.code | number | HTTP status code |
| res.body | string | Raw response body |
| res.headers | table | Response headers |
| res.json | table|nil | Auto-parsed JSON (nil if not JSON) |
| res.cached | bool | true if served from cache |
| res.url | string | The requested URL |
| res.method | string | HTTP method used |
| res.elapsed | number | Request duration in seconds |
🖼️ UI Elements
All elements are VGUI panels registered under the imLib. prefix. They automatically use the active theme (including addon overrides). All elements support smooth hover/press animations out of the box.
imLib.Frame
Draggable frame with themed header, close button, and drop shadow.
local frame = vgui.Create("imLib.Frame") frame:SetSize(600, 400) frame:Center() frame:SetTitle("My Panel") frame:SetDraggable(true) frame:SetRemoveOnClose(true) frame:MakePopup() -- Optional branding icon frame:SetBrandingMaterial(Material("icon16/star.png"))
imLib.Checkbox
Animated toggle checkbox with label.
local cb = vgui.Create("imLib.Checkbox", parent) cb:Dock(TOP) cb:SetLabel("Enable notifications") cb:SetChecked(true) cb:SetCallback(function(checked) print("Notifications:", checked) end) -- Toggle programmatically cb:Toggle()
imLib.Dropdown
Animated dropdown selector with simple or key-value options, popup overlay, and scrollbar.
local dd = vgui.Create("imLib.Dropdown", parent) dd:Dock(TOP) dd:SetTall(imLib.Scale(36)) -- Simple string options dd:SetOptions({ "Option A", "Option B", "Option C" }) dd:SetSelected("Option A") -- Or key-value options dd:SetOptionsKV({ { label = "English", value = "en" }, { label = "Français", value = "fr" }, { label = "Polski", value = "pl" }, }) dd:SetSelectedValue("en") dd:SetCallback(function(value, index) print("Selected:", value) -- "en" end)
imLib.Toast
Slide-in notification toasts anchored to a parent frame. Auto-stack, auto-dismiss, progress bar countdown.
-- Simple toast (5 second default) imLib.AddToast(frame, "Settings saved!") -- Custom duration + icon imLib.AddToast(frame, "Language changed to English.", 5, "https://example.com/globe.png") -- Short error toast imLib.AddToast(frame, "Error: invalid value.", 3)
Images
imLib.Images:Get(url) fetches and caches remote images as Materials for use in VGUI panels.
local mat = imLib.Images:Get("https://example.com/icon.png") surface.SetMaterial(mat) surface.SetDrawColor(color_white) surface.DrawTexturedRect(0, 0, 32, 32)
Scale
Resolution-independent scaling based on a reference resolution.
local h = imLib.Scale(40) -- Scales 40px relative to reference res
Drawing Utilities
Convenience wrappers for rounded boxes, text sizing, and font generation.
-- Rounded box (uses theme roundness) imLib:RoundedBox(x, y, w, h, color) imLib:RoundedBox(x, y, w, h, color, true, true, false, false) -- per-corner -- Text size local tw, th = imLib:GetTextSize("Hello", "imLib.Frame.Title") -- Font generation imLib:GenerateFont("MyFont", nil, 18)
💡 Full Addon Example
A complete example showing how all systems work together in a real addon.
-- addons/myshop/lua/autorun/myshop.lua local addon = imLib.Addon("myshop", "MYSHOP") addon:SetName("My Shop") addon:SetAuthor("Developer") addon:SetVersion("1.0.0") addon:UseLogger() addon:UseDatabase() addon:UseConfig() addon:UseLanguages() addon:UseThemes() addon:UseStateSync() addon:UseNetwork() addon:UseHTTP({ base_url = "https://api.myshop.com", timeout = 10 }) addon:AddFolder("modules", imLib.FileLoader.REALM_SHARED, true) addon:AddFolder("themes", imLib.FileLoader.REALM_SHARED) addon:AddFolder("languages", imLib.FileLoader.REALM_SHARED) addon:AddFile("sh_config", imLib.FileLoader.REALM_SHARED) addon:AddFile("cl_ui", imLib.FileLoader.REALM_CLIENT) addon:Load()
-- addons/myshop/lua/myshop/modules/sv_economy.lua -- Define player wallet state MYSHOP.StateSync:Define("wallet", { scope = "player", schema = { balance = "int", items = "table" }, defaults = { balance = 1000, items = {} }, }) -- Register purchase message imLib.Network:Register("MyShop:Buy") :Field("item_id", "uint", { bits = 16 }) :SetCooldown(0.5) :SetRateLimit(20, 60) :OnReceive(function(data, ply) local bal = MYSHOP.StateSync:Get("wallet", ply, "balance") local price = GetItemPrice(data.item_id) if bal < price then MYSHOP.Logger:warning(ply:Nick() .. " can't afford item") return end -- Deduct and give item local items = MYSHOP.StateSync:Get("wallet", ply, "items") table.insert(items, data.item_id) MYSHOP.StateSync:Set("wallet", ply, { balance = bal - price, items = items, }) MYSHOP.Logger:success(ply:Nick() .. " bought item " .. data.item_id) end)
-- addons/myshop/lua/myshop/cl_ui.lua -- Subscribe to wallet changes for live HUD MYSHOP.StateSync:Subscribe("wallet", function(_, _, key, val) if key == "balance" then UpdateBalanceHUD(val) end end) -- Build the shop UI local function OpenShop() local frame = vgui.Create("imLib.Frame") frame:SetSize(700, 500) frame:Center() frame:SetTitle(MYSHOP.L("shop_title")) frame:MakePopup() local sidebar = vgui.Create("imLib.Sidebar", frame) sidebar:Dock(LEFT) sidebar:SetWide(imLib.Scale(200)) sidebar:AddItem("weapons", "Weapons", "https://cdn.ex.com/sword.png") sidebar:AddItem("armor", "Armor", "https://cdn.ex.com/shield.png") local buyBtn = vgui.Create("imLib.Button", frame) buyBtn:Dock(BOTTOM) buyBtn:DockMargin(210, 8, 8, 8) buyBtn:SetText("Buy Selected") buyBtn:SetCallback(function() imLib.Network:Send("MyShop:Buy", { item_id = selectedItem }) imLib.AddToast(frame, "Purchasing...", 3) end) end