Complete language reference

Logic Script

Ayami's native programming language — lightweight, safe, and built for Discord automations. Write scripts, react to events, send embeds, and manage server data with clean, direct code.

Lua-inspired Asynchronous Sandboxed .logic extension Integrated with Logic Builder
Quick reference Open editor
🌸

Introduction

Logic Script is an interpreted language built for automations inside Discord servers. Scripts are .logic files that react to Discord events — messages, members joining, button clicks — using the on block.

Every server has its own isolated environment: scripts, database, and settings are completely separate. Logic Script runs inside the bot in a secure sandbox — no access to the operating system or external resources.

💡

Logic Script doesn't replace the Logic Builder — it extends it. Scripts can be called from visual flows and can fire custom events between each other.

primeiro-script.logic
-- Meu primeiro script

on messageCreate(message)
  if message.content == "!ola" then
    message.reply("Olá! 👋")
  end
end
📦

Variables

Use let for mutable variables and const for constants. Scope is lexical — variables declared inside a block don't exist outside it.

.logic
let nome   = "Ayami"
let pontos = 0
let ativo  = true
const PREFIX = "!"

-- Reatribuição (só funciona com let)
pontos = pontos + 10
🔢

Data types

TypeExampleDescription
number42, 3.14Integers and decimals
string"texto", 'outro'Text in single or double quotes
booltrue, falseBoolean
nilnilAbsence of a value
array[1, 2, 3]Ordered list
object{ nome: "A" }Key → value map
functionfunction() endFunctions are first-class values
.logic
let lista  = ["maçã", "pera", "uva"]
let pessoa = { nome: "Ayami", pontos: 100 }

print(lista[0])       -- "maçã"
print(pessoa.nome)    -- "Ayami"
print(type(42))       -- "number"
⚙️

Operators

Math
Op.DescriptionExample
+Addition2 + 35
-Subtraction10 - 46
*Multiplication3 * 412
/Division10 / 25
%Modulo10 % 31
..Concatenation"Oi " .. "Ayami"
Comparison & Logical
Op.Description
==Equal
!=Not equal
< / >Less than / Greater than
<= / >=Less/greater than or equal to
andLogical AND
orLogical OR
notNegation
🔀

Control flow

if / elseif / else
if pontos >= 100 then
  print("Ouro")
elseif pontos >= 50 then
  print("Prata")
else
  print("Bronze")
end
while / for numérico / for in
-- while
let i = 1
while i <= 5 do
  print(i)
  i = i + 1
end

-- for numérico: for var = início, fim [, passo] do
for i = 1, 10 do
  print(i)
end

-- for in (iteração)
let frutas = ["maçã", "pera", "uva"]
for fruta in frutas do
  print(fruta)
end
💡

Use break to exit a loop and continue to skip to the next iteration.

🧩

Functions

.logic
-- Declaração
function somar(a, b)
  return a + b
end

print(somar(3, 7))   -- 10

-- Funções anônimas (valores de primeira classe)
let dobrar = function(x)
  return x * 2
end

print(dobrar(5))    -- 10
📁

Import / Export

Organize your code across multiple files. Use export to expose functions and import to use them in another script.

economia.logic
export function addCoins(user, qtd)
  db.user(user).add("coins", qtd)
  return db.user(user).get("coins")
end
trabalho.logic
import { addCoins } from "economia.logic"

on command("trabalhar", message, args)
  let ganho = random(10, 100)
  let total = addCoins(message.author, ganho)
  message.reply("💰 +" .. ganho .. " coins! Total: " .. total)
end

Events (on)

Scripts react to Discord events using the on block. For prefix commands, the first argument is a string with the command name.

sintaxe
on nomeDoEvento(param1, param2)
  -- código aqui
end

-- Comando por prefixo (ex: !ping)
on command("ping", message, args)
  message.reply("🏓 Pong!")
end
EventParametersFires when
messageCreatemessageA message is sent
messageDeletemessageA message is deleted
messageEditmessageA message is edited
memberJoinmemberA member joins the server
memberLeavememberA member leaves the server
buttonClickinteractionA button is clicked
selectMenuinteractionA menu option is selected
command"nome", message, argsA prefix command is run
reactionAddemoji, user, messageA reaction is added
reactionRemoveemoji, user, messageA reaction is removed
voiceJoinmemberA member joins a voice channel
voiceLeavememberA member leaves a voice channel
banAdduserA user is banned
banRemoveuserA ban is removed
channelCreatechannelA channel is created
channelDeletechannelA channel is deleted
🗂️

Discord objects

Message
Returned by messageCreate, sendMessage(), and message.reply().
.idstringMessage ID
.contentstringText content
.authorobjectMessage author (id, username, isBot…)
.channelstringChannel ID
.edit(texto, opts?)functionEdits the message. Accepts an embed in opts
.delete()functionDeletes the message
.reply(texto, opts?)functionReplies to the message
.react(emoji)functionAdds a reaction
.pin()functionPins the message
User
Obtained via getUser() or message.author.
.idstringUser ID
.usernamestringUsername
.nicknamestringServer nickname
.rolesarrayRole IDs
.isBotboolWhether it's a bot
.addRole(id)functionAdds a role
.removeRole(id)functionRemoves a role
.timeout(ms)functionApplies a timeout in ms
.ban(motivo?)functionBans the user
.kick()functionKicks the user
.send(texto, opts?)functionSends a DM
Interaction
Obtained via getInteraction(). Used in buttonClick and selectMenu.
.customIdstringID of the clicked component
.valuesarraySelected values (SelectMenu)
.reply(texto, opts?)functionReplies. opts.ephemeral: true = private
.defer(efemero?)functionDefers the reply (loading)
.edit(texto, opts?)functionEdits the original reply
.followUp(texto, opts?)functionFollow-up message
.showModal(modal)functionOpens a Modal — must already have gone through .onSubmit(fn)
🎨

UI — Embed, Button, SelectMenu, Modal

All methods can be chained. Embed accepts every field Discord supports.

EmbedBuilder
let embed = new EmbedBuilder()
embed.setTitle("Título")
embed.setDescription("Descrição")
embed.setColor("#A9D6FF")
embed.setThumbnail("https://url.png")
embed.setFooter({ text: "Ayami" })
embed.addFields(
  { name: "Campo", value: "Valor", inline: true }
)
embed.setTimestamp(nil)  -- nil = agora

sendMessage(message.channel, "", { embed: embed })
Button & SelectMenu
let btn = Button()
btn.setLabel("Clique aqui")
btn.setStyle("primary")  -- primary | secondary | success | danger | link
btn.setCustomId("meu_botao")
btn.setEmoji("🎉")

let menu = SelectMenu()
menu.setCustomId("meu_menu")
menu.setPlaceholder("Escolha uma opção")
menu.addOptions(
  { label: "Opção 1", value: "op1" },
  { label: "Opção 2", value: "op2" }
)

sendMessage(message.channel, "Escolha:", {
  components: [btn, menu]
})
Capturando interações
on buttonClick(interaction)
  if interaction.customId == "meu_botao" then
    interaction.reply("✅ Clicou!", { ephemeral: true })
  end
end

on selectMenu(interaction)
  let valor = interaction.values[0]
  interaction.reply("Escolheu: " .. valor)
end
Modal
on buttonClick(interaction)
  if interaction.customId == "abrir_feedback" then
    let modal = Modal()
    modal.setTitle("Feedback")
    modal.addTextInput("nota", "Nota de 1 a 5", { style: "short" })
    modal.addTextInput("comentario", "Comentário", { style: "long", required: false })
    modal.onSubmit(function(interaction, fields)
      interaction.reply("Recebido! Nota: " .. fields.nota)
    end)

    interaction.showModal(modal)
  end
end

Only works in response to an existing interaction (buttonClick/selectMenu, or inside a temporary Button/SelectMenu's .onClick()) — it can't be opened from on(command).

🗄️

Database

Persistent, per-server database. Data survives across runs and restarts.

db — global / user / guild
-- Global (por servidor)
db.set("chave", "valor")
db.get("chave")
db.has("chave")      -- → bool
db.delete("chave")

-- Por usuário
db.user(user).set("coins", 100)
db.user(user).get("coins")
db.user(user).add("coins", 50)   -- soma
db.user(user).sub("coins", 10)   -- subtrai

-- Por servidor (guild)
db.guild().set("config", { prefix: "!" })
db.guild().get("config")
🛠️

Built-in functions

String
FunctionDescription
length(s)String length
contains(s, sub)Whether it contains a substring
replace(s, de, para)Replace text
split(s, sep)Split into an array
join(arr, sep)Join array into a string
lower(s) / upper(s)Lowercase / uppercase
trim(s)Removes whitespace
tostring(v)Converts to string
tonumber(s)Converts to number
Math
FunctionDescription
random(min, max)Random integer between min and max
round(n)Rounds
floor(n) / ceil(n)Floor / ceiling
abs(n)Absolute value
max(a, b, …) / min(a, b, …)Largest / smallest value
Array & Object
FunctionDescription
push(arr, v)Adds to the end
pop(arr)Removes and returns the last item
insert(arr, i, v)Inserts at position i
remove(arr, i)Removes at position i
size(v)Size of the array or object
keys(obj)Object's keys
values(obj)Object's values
Utilities
FunctionDescription
print(…)Prints to the dashboard console
type(v)Value's type as a string
wait(ms)Waits N ms (max 30,000ms/run)
json.encode(v)Object → JSON string
json.decode(s)JSON string → object
Time
FunctionDescription
schedule(tempo, canal, texto, opts?)Schedules a message — time: ms, "1h", or "9:00". Repeats with {recurring:true}
cancelSchedule(id)Cancels a pending schedule
listSchedules()Lists the server's pending schedules
cooldown(chave, tempo)true if it can act now (and marks it); false if on cooldown
cooldownRestante(chave)Remaining cooldown in ms
🔒

Sandbox & Limits

🚫

What Logic Script doesn't have: filesystem access, Node.js modules (require), external network access outside Discord's APIs, access to other servers, or external libraries.

LimitValueDescription
Steps per run50 000Prevents infinite loops
Recursion depth200Prevents stack overflow
Total wait() per run30 000msAccumulated wait limit
Concurrent scripts per guild20Concurrency limit
Log TTL7 diasLogs expire automatically

Complete examples

👋
Welcome with embed
Sends a custom embed when a member joins
boas-vindas.logic
on memberJoin(member)
  let canal = getChannel("ID_DO_CANAL")
  let guild = getGuild()

  let embed = new EmbedBuilder()
  embed.setTitle("Bem-vindo(a)! 🌸")
  embed.setDescription("Olá, <@" .. member.id .. ">!\nVocê é o membro nº " .. guild.memberCount)
  embed.setColor("#A9D6FF")
  embed.setFooter({ text: "Ayami Hoshiori" })
  embed.setTimestamp(nil)

  canal.send("", { embed: embed })
end
💰
Economy system
!work, !balance, and !transfer commands
economia.logic
on command("trabalhar", message, args)
  let ganho = random(10, 100)
  db.user(message.author).add("coins", ganho)
  let total = db.user(message.author).get("coins")
  message.reply("💰 +" .. ganho .. " coins! Saldo: " .. total)
end

on command("saldo", message, args)
  let raw   = db.user(message.author).get("coins")
  let total = raw or 0
  message.reply("💳 Saldo: **" .. total .. " coins**")
end

on command("transferir", message, args)
  let valor = tonumber(args[0])
  let alvo  = args[1]

  if valor <= 0 or alvo == nil then
    message.reply("❌ Use: !transferir <valor> <@usuário>")
    return
  end

  let saldo = db.user(message.author).get("coins") or 0

  if saldo < valor then
    message.reply("❌ Saldo insuficiente.")
    return
  end

  db.user(message.author).sub("coins", valor)
  db.user(alvo).add("coins", valor)
  message.reply("✅ Transferência de **" .. valor .. " coins** realizada!")
end
🎭
Role panel with buttons
Command that sends buttons; clicking one adds the role
cargos.logic
on command("painel", message, args)
  let btn1 = Button()
  btn1.setLabel("🎨 Designer")
  btn1.setStyle("primary")
  btn1.setCustomId("cargo_designer")

  let btn2 = Button()
  btn2.setLabel("💻 Dev")
  btn2.setStyle("secondary")
  btn2.setCustomId("cargo_dev")

  let embed = new EmbedBuilder()
  embed.setTitle("🎭 Selecione seu cargo")
  embed.setDescription("Clique no botão da sua área.")
  embed.setColor("#7C8FFF")

  message.reply("", { embed: embed, components: [btn1, btn2] })
end

on buttonClick(interaction)
  let user = getUser()
  let id   = interaction.customId

  if id == "cargo_designer" then
    user.addRole("ID_CARGO_DESIGNER")
    interaction.reply("✅ Cargo Designer adicionado!", { ephemeral: true })
  elseif id == "cargo_dev" then
    user.addRole("ID_CARGO_DEV")
    interaction.reply("✅ Cargo Dev adicionado!", { ephemeral: true })
  end
end

Ready to get started?

Add Ayami to your server and open the editor in the Dashboard to write your first scripts.