How to add license keys to a Garry's Mod addon
Short answer: copy sunlicense.lua into your addon, read the server owner's key from a protected convar, check it server-side once the server has finished loading (InitPostEntity), and only add your hooks when SunLicense says yes. A network error is not a no: keep running and check again later.
-- lua/autorun/server/myaddon_license.lua
local SunLicense = include("myaddon/sunlicense.lua")
local licenseCvar = CreateConVar("myaddon_license", "", { FCVAR_ARCHIVE, FCVAR_PROTECTED }, "MyAddon license key")
local client = SunLicense.new({ baseUrl = "https://api.sunlicense.com", token = MYADDON_API_KEY }) -- from your build
hook.Add("InitPostEntity", "MyAddon.License", function()
client:validate(licenseCvar:GetString(), { host = MyAddon.ServerId() }, function(ok, status, body)
if status == 400 then -- EXPIRED, REVOKED, TOO_MANY_MACHINES...: body has the code
MsgC(Color(255, 80, 80), "[MyAddon] License rejected: ", body, "\n")
return
end
if not ok then MsgC(Color(255, 200, 0), "[MyAddon] SunLicense unreachable, starting anyway\n") end
MyAddon.Start() -- adds your hooks, net receivers and commands
end)
end)On Garry's Mod the SDK uses the game's HTTP function, so the callback runs later and the server never waits for it.
Or let your AI agent do it: paste the setup prompt into Claude Code, Cursor or Codex in your addon's folder.
Free plan: 3 products, 100 licenses, no credit card. Create a free account.
Step by step
In the dashboard: a product, a policy (a machine is a game server) and an API key, which can only validate.
The SDK is one file: copy
sunlicense.luafrom sunlicense-com/sunlicense-lua tolua/myaddon/sunlicense.lua. Load it server-side only and neverAddCSLuaFileit, so it isn't sent to players.Wait for the server.
HTTPrequests made while the map is still loading can fail;InitPostEntity(or atimer.Simpleafter it) is the first safe point.The server owner's key goes in
server.cfg(myaddon_license "AX8P-…").FCVAR_PROTECTEDkeeps its value from being sent to clients.Your API key is written into a generated file by your release script (
MYADDON_API_KEY), never in git.Server id: generate one once and keep it in the data folder, so restarts count as the same machine:
luafunction MyAddon.ServerId() local id = file.Read("myaddon/server_id.txt", "DATA") if not id or id == "" then id = ("gmod-%08x%08x"):format(math.random(0, 0x7fffffff), os.time()) file.CreateDir("myaddon") file.Write("myaddon/server_id.txt", id) end return id endResults: 200 starts the addon, 400 leaves it off with the reason in the console (mask the key), anything else starts it and retries (
timer.Create("MyAddon.Recheck", 600, 1, …)). Re-check every few hours with a repeating timer so a revoked key reaches running servers.
gmodstore's rules
gmodstore allows DRM and license checks, with conditions in its development guidelines: the submission reviewers see must contain all the code the addon runs locally (a private, readable version is fine, with the protected build on the stable branch), obfuscated code needs that readable copy too, and products that depend on an external service must follow gmodstore's sunsetting policy when you end support. Say in the listing that the addon checks its license online. Free Workshop addons are free downloads: don't put a license check in those.
Going further
- Obfuscation for Lua (Prometheus) and protection: seal the core of the addon so a copy with the check removed has nothing to run.
- Selling on Tebex for FiveM too? See the FiveM guide. More on licensing GMod addons.
