Skip to content

Instantly share code, notes, and snippets.

@cornernote
Last active September 19, 2025 04:16
Show Gist options
  • Save cornernote/6661d10dd95d6802df8d1be92a4f3e70 to your computer and use it in GitHub Desktop.
Save cornernote/6661d10dd95d6802df8d1be92a4f3e70 to your computer and use it in GitHub Desktop.
This Lua module lets your Tabletop Simulator object auto-update its script directly from a GitHub repository.

Script AutoUpdate for Tabletop Simulator

This Lua module lets your Tabletop Simulator object auto-update its script directly from a GitHub repository.

✨ Features

  • Automatically checks GitHub for new versions
  • Compares semantic versions (1.2.3 style)
  • Fetches and replaces the script when an update is found
  • Reloads the object automatically
  • Provides a useful base class for your script
  • Optionally updates custom objects
  • Optional pre-commit hook to prevent infinite update loops

📦 Repository Structure

Your GitHub repo should contain at least two files:

  • yourscript.lua # Your actual TTS script
  • yourscript.ver # A text file with the current version number (e.g. 1.0.0)

⚙️ Setup in Your Script

  1. Copy the Base and AutoUpdate classes into the top of your script.
  2. Configure these values:
local Base = {
    name = "Your Script Name", -- used for output of messages
    ...
}

local AutoUpdate = {
    version = "1.0.0", -- current version of your script, must match the .ver file contents
    versionUrl = "https://raw.githubusercontent.com/yourname/yourrepo/refs/heads/main/yourscript.ver", -- text file with the version number, eg: 1.0.0
    scriptUrl = "https://raw.githubusercontent.com/yourname/yourrepo/refs/heads/main/yourscript.lua", -- latest version of your script
    ...
}
  1. Add a one-liner to your object’s onLoad:
function onLoad()
    AutoUpdate:run()
end

Optional Configuration for Custom Objects

If you want the script to update custom object, you can use the AutoUpdateCustomObject module.

  1. Copy the AutoUpdateCustomObject class into the top of your script (below AutoUpdate).
  2. Configure customObject with the required values (see Custom Game Objects):
local AutoUpdateCustomObject = {
    customObject = {
        image = "https://steamusercontent-a.akamaihd.net/ugc/ABC123/",
        image_secondary = "https://steamusercontent-a.akamaihd.net/ugc/ABC456/",
    },
    ...
}
  1. Add a one-liner to your object’s onLoad:
function onLoad()
    AutoUpdateCustomObject:run()
end

🚀 Publishing a New Version

When you want to release an update:

  1. Update your script file (yourscript.lua).
  2. Update the version file (yourscript.ver) with the new version string, e.g.: 1.0.1.
  3. Commit and push to GitHub.
  4. Next time the object is loaded in Tabletop Simulator, it will auto-update.

📝 Notes

Versions must be numeric (semantic versioning is recommended, e.g. 1.0.0).

Works best if each script has its own .lua and .ver file pair in the repo.


⚠️ Warning

If the version in your .lua file is less than the version in your .ver file, you will cause an infinite update loop.

To prevent this, set up a pre-commit hook that checks the versions match before allowing a commit.

Setup (requires Python 3)

  1. Save the git_hooks_pre-commit file below as .git/hooks/pre-commit.
  2. Make it executable (this step is for Mac/Linux only, Windows users can skip it) chmod +x .git/hooks/pre-commit.
  3. Test it: python3 .git/hooks/pre-commit.

Now your commit will fail if the .lua and .ver versions don't match.


✅ License

MIT License. Free to use and adapt.

local Base = {
name = "Your Script Name", -- used for output messages
debug = true, -- true to show error messages
host = self,
print = function(self, message)
print(self.name .. ": " .. message)
end,
error = function(self, message)
if self.debug then
error(self.name .. ": " .. message)
end
end,
reload = function(self)
Wait.condition(function()
return not self.host or self.host.reload()
end, function()
return not self.host or self.host.resting
end)
end
}
local AutoUpdate = setmetatable({
version = "1.0.0", -- current version of your script, must match the .ver file contents
versionUrl = "https://raw.githubusercontent.com/yourname/yourrepo/refs/heads/main/yourscript.ver", -- text file with the version number, eg: 1.0.0
scriptUrl = "https://raw.githubusercontent.com/yourname/yourrepo/refs/heads/main/yourscript.lua", -- latest version of your script
run = function(self)
WebRequest.get(self.versionUrl, function(request)
if request.response_code ~= 200 then
self:error("Failed to check version (" .. request.response_code .. ": " .. request.error .. ")")
return
end
local remoteVersion = request.text:match("[^\r\n]+") or ""
if self:isNewerVersion(remoteVersion) then
self:fetchNewScript(remoteVersion)
end
end)
end,
isNewerVersion = function(self, remoteVersion)
local function split(v)
return { v:match("^(%d+)%.?(%d*)%.?(%d*)") or 0 }
end
local r, l = split(remoteVersion), split(self.version)
for i = 1, math.max(#r, #l) do
local rv, lv = tonumber(r[i]) or 0, tonumber(l[i]) or 0
if rv ~= lv then
return rv > lv
end
end
return false
end,
fetchNewScript = function(self, newVersion)
WebRequest.get(self.scriptUrl, function(request)
if request.response_code ~= 200 then
self:error("Failed to fetch new script (" .. request.response_code .. ": " .. request.error .. ")")
return
end
if request.text and #request.text > 0 then
self.host.setLuaScript(request.text)
self:print("Updated to version " .. newVersion)
self:reload()
else
self:error("New script is empty")
end
end)
end,
}, { __index = Base })
--function onLoad()
-- AutoUpdate:run()
--end
-- OPTIONAL - update custom object if incorrect
local AutoUpdateCustomObject = setmetatable({
customObject = {
image = "https://steamusercontent-a.akamaihd.net/ugc/ABC123/",
image_secondary = "https://steamusercontent-a.akamaihd.net/ugc/ABC456/",
},
run = function(self)
if self:needsCustomObjectUpdate() then
self.host.setCustomObject(self.customObject)
self:print("Updated custom object")
self:reload()
end
end,
needsCustomObjectUpdate = function(self)
if not self.customObject or not next(self.customObject) then
return
end
local currentCustomObject = self.host.getCustomObject()
for key, value in pairs(self.customObject) do
if not currentCustomObject[key] or currentCustomObject[key] ~= value then
return true
end
end
end,
}, { __index = Base })
--function onLoad()
-- AutoUpdateCustomObject:run()
--end
#!/usr/bin/env python3
import re
import sys
from pathlib import Path
LUA_FILE = Path("yourscript.lua")
VER_FILE = Path("yourscript.ver")
lua_text = LUA_FILE.read_text(encoding="utf-8")
match = re.search(r'version\s*=\s*"(\d+\.\d+\.\d+)"', lua_text)
if not match:
print("Could not find version string in", LUA_FILE)
sys.exit(1)
lua_version = match.group(1)
ver_version = VER_FILE.read_text(encoding="utf-8").strip()
if lua_version != ver_version:
print("Version mismatch!")
print(f" AutoUpdater.version = {lua_version}")
print(f" {VER_FILE.name} = {ver_version}")
sys.exit(1)
print(f"Version check passed ({lua_version})")
@stom66
Copy link

stom66 commented Sep 16, 2025

Very nice! Could be worth handling non-200 responses in fetchNewScript as well but this looks very comprehensive.

@cornernote
Copy link
Author

Thanks for the feedback @stom66 !
I have added some error output for non-200 responses so they don't fail silently.

@cornernote
Copy link
Author

cornernote commented Sep 17, 2025

I also added a debug option (default false), so when you deploy and share with others, if it a link breaks it won't spam games where the object was used.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment