Skip to content

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
-- 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 ​

  1. In the dashboard: a product, a policy (a machine is a game server) and an API key, which can only validate.

  2. The SDK is one file: copy sunlicense.lua from sunlicense-com/sunlicense-lua to lua/myaddon/sunlicense.lua. Load it server-side only and never AddCSLuaFile it, so it isn't sent to players.

  3. Wait for the server. HTTP requests made while the map is still loading can fail; InitPostEntity (or a timer.Simple after it) is the first safe point.

  4. The server owner's key goes in server.cfg (myaddon_license "AX8P-…"). FCVAR_PROTECTED keeps its value from being sent to clients.

  5. Your API key is written into a generated file by your release script (MYADDON_API_KEY), never in git.

  6. Server id: generate one once and keep it in the data folder, so restarts count as the same machine:

    lua
    function 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
    end
  7. Results: 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 ​

Send feedback

Found something broken, or have an idea? It goes straight to our board.

The first line becomes the title.
Images (optional, up to 5: paste, drop or capture)
Check each image before sending, and remove any that show something private.

We also receive this page's address and your browser version.

Ctrl Enter to send