Table of Contents
Lua scripting in Project Tinybot
Since April 9, 2025, Project Tinybot allows you to run custom scripts written in Lua in Twitch chats. The scripts are run in the sandbox. This is a safe environment that prevents malicious code from doing its bad things by removing access to certain libraries.
Also, there are execution limits for scripts - 2MB RAM and 500 milliseconds wait time. These limits are made for cases where you may have forgotten “while-true loop” and so it doesn't run indefinitely until the bot shuts down, the loop will stop after 500 milliseconds. Additionally, it's a test to see if you know how to write optimized code :)
The bot provides API calls for your scripts, which will be expanded in the future, and commands that allow you to run Lua scripts.
If you are interested in any examples, there are a mini-game about drinking 'not milk' and test counter.
Have fun!
Commands
Lua script in the chat - !lua
Syntax - !lua [Valid Lua code…]
This command acts as a (Read-eval-print loop). It's useful for small scripts, such as !lua 2+2 or !lua function hello(name) return “hello, ” .. name end return hello(“chat”). It must always return something, otherwise it will return Empty or unsupported response. As you can see, in the first example, we didn't explicitly use the return keyword; it will be automatically added by the backend. However, in the second example, the bot doesn't know what the script should return, so we need to add the return keyword at the end.
If the script has more than 1 line of code, then you'll probably like the !luaimport command.
Remote Lua script - !luaimport
Syntax - !luaimport [URL]
This command downloads a Lua script from the specified URL. Make sure the URL returns one of the allowed content types: text/plain, text/plain; charset=utf-8, text/x-lua or text/x-lua; charset=utf-8. The script must use the return keyword, which won't be added automatically.
By the way, scripts executed within !luaimport have access to the Storage API.
In previous versions, you had to use something like !luaimport pastebin:Ejdrqmb7 or !luaimport pastea:testE. As mentioned, the full URL must be specified. So, if you're using Pastebin, your command would look like !luaimport https://pastebin.com/raw/Ejdrqmb7. If you're using Pastea, the command will look like this: !luaimport https://tnd.quest/~/testE.lua.
API calls
Not all APIs are available to user scripts. Chat/remote Lua scripts can only use the base APIs: Bot metadata API, Time API, JSON API, Network API, Localization API, String API, Array API. From the built-in Lua library, you can use type, pairs, table, math, string (without dump function), table, tonumber, tostring.
Bot metadata API
bot_get_compiler_version()
Returns Python X.X.X.
bot_get_uptime()
Returns the bot uptime in seconds.
bot_get_memory_usage()
Returns the bot memory usage in bytes.
bot_get_compile_time()
Returns the bot compile time in seconds.
bot_get_version()
Returns the bot version. If bot doesn't have active tag, then it will return short commit SHA hash.
bot_config()
Returns bot configuration. See Bot configuration structure.
Time API
time_current()
Get current UTC time in seconds.
time_humanize(number)
Converts UNIX timestamp into humanized timestamp, e.g. 1488 will be 25m48s (25 minutes and 48 seconds)
time_format(timestamp: Number, format: String)
Converts timestamp (in seconds) into a beautiful, formatted human-readable datetime. For example, timestamp = 1758462814 and format = “%Y-%m-%d %H:%M:%S” will return 2025-09-21 13:53:34.
time_parse(datetime: String, format: String)
This function is a reverse for time_format(). You specify the datetime (e.g. 2025-09-21 13:53:34) and its format (e.g. %Y-%m-%d %H:%M:%S) and it will return the timestamp in seconds (e.g. 1758462814).
JSON API
json_parse(String)
Convert stringified JSON to valid Lua value.
json_stringify(value)
Convert Lua value to stringified JSON.
json_get_value(body: LuaTable, path: String)
Retrieves a value from body based on the path. Path should be formatted like X.Y.Z (e.g. data.keys.cert_key).
Network API
net_get(url)
Sends a HTTP GET request to the specified URL and returns a network response structure.
net_get_with_headers(url, headers)
Sends a HTTP GET request with headers to the specified URL and returns a network response structure. Headers must be a key-value table (e.g. { “Header-Name” = “Header-Value”, “Accept” = “application/json” }).
net_post_multipart_with_headers(url, body, headers)
Sends a HTTP POST multipart/form-data request with headers to the specified URL and returns a network response structure. Headers must be a key-value table (e.g. { “Header-Name” = “Header-Value”, “Accept” = “application/json” }). Also, request body must be a key-value table. Files are not supported!
Localization API
l10n_line_request(request, lines, line_id, parameters)
Returns a string from the specified lines table. Firstly, it looks for the localization table based on the channel language. If it doesn't exist, then the function will use the first localization table. Secondly, the function searches for line ID, and then substitute {} with the parameters. Also, there are defined tokens: {sender.alias_name}, {source.alias_name}, {default.prefix}, {channel.prefix}. See localization table structure.
l10n_get_localization_names()
Returns an array of available language IDs.
String API
str_split(string, separator)
Split a string by the separator.
str_make_parts(base, values, prefix, separator, max_length)
Returns an array of strings that have the following structure: [base] · [prefix][value 1][separator][prefix][value 2][separator]…. If the message exceeds the maximum length, it is wrapped to the next line. Used in massping and events.
event_type_to_str(val)
Converts the EventType into a string.
str_to_event_type(val)
Converts the string into an EventType.
Array API
array_contains(arr, val)
Returns true if the array has the value, otherwise false.
Storage API
storage_get()
Get user's storage cell. Returns an empty string if it was just created.
storage_put(String)
Put string to user's storage cell. Returns true if it was successful.
storage_channel_get()
Get channel's storage cell. Returns an empty string if it was just created.
storage_channel_put(String)
Put string to channel's storage cell. Returns true if it was successful.
Structures
Bot configuration structure
{ "twitch" = { ["username"] = "String" }, "commands" = { ["join_allowed"] = "Boolean", ["join_allow_from_other_chats"] = "Boolean", ["rpost_path"] = "String", ["rpost_url"] = "String", ["paste_path"] = "String", ["paste_body_name"] = "String", ["paste_title_name"] = "String", ["paste_url"] = "String", ["help_url"] = "String" }, "owner" = { ["id"] = "Integer", ["name"] = "String" }, }
Network response structure
{ "code" = "Integer", "text" = "String" }
Localization table structure
{ "language_id" = { ["line_id"] = "String" }, "english" = { ["line_id"] = "Hi {sender.alias_name}! This is an example. This is the first parameter -> {} and this is the second parameter -> {}." }, "russian" = { ["line_id"] = "Привет {sender.alias_name}! Это пример. Это первый параметр -> {}, а это второй параметр -> {}." } }
