SDK
Use the Flora TypeScript SDK to build bot scripts that run inside the Flora runtime. In the runtime, SDK functions are available globally (no imports needed).
Quickstart
ts
const ping = prefix({
name: 'ping',
description: 'Respond with pong',
run: async (ctx) => {
const extra = ctx.args.join(' ') || 'none'
await ctx.reply(`pong! args: ${extra}`)
}
})
createBot({
prefix: '!',
commands: [ping]
})Event handlers
ts
on('ready', async (ctx) => {
console.log('bot ready')
})
on('messageCreate', async (ctx) => {
if (ctx.msg.author?.bot) return
if (ctx.msg.content === '!hello') {
await ctx.reply('hello!')
}
})
on('messageUpdate', async (ctx) => {
console.log('message updated', ctx.msg.id)
})
on('messageDelete', async (ctx) => {
console.log('message deleted', ctx.msg.id)
})
on('messageDeleteBulk', async (ctx) => {
console.log('bulk delete', ctx.msg.ids.length)
})
on('interactionCreate', async (ctx) => {
console.log('interaction', ctx.msg.commandName)
})Prefix commands
ts
const greet = prefix({
name: 'greet',
description: 'Greet someone',
run: async (ctx) => {
const name = ctx.args[0] || 'world'
await ctx.reply(`Hello, ${name}!`)
}
})
createBot({
prefix: '!',
commands: [greet]
})Slash commands
ts
const echo = slash({
name: 'echo',
description: 'Echo your input',
options: [
{ name: 'text', description: 'Text to echo', type: 'string', required: true }
],
run: async (ctx) => {
const text = ctx.options.text as string
await ctx.reply({ content: text, ephemeral: true })
}
})
createBot({
slashCommands: [echo]
})Slash command subcommands
ts
const settings = slash({
name: 'settings',
description: 'Manage settings',
subcommands: [
{
name: 'get',
description: 'Get a setting',
options: [{ name: 'key', description: 'Setting key', type: 'string', required: true }],
run: async (ctx) => {
const key = ctx.options.key as string
await ctx.reply(`Setting ${key}: ...`)
}
},
{
name: 'set',
description: 'Set a setting',
options: [
{ name: 'key', description: 'Setting key', type: 'string', required: true },
{ name: 'value', description: 'Setting value', type: 'string', required: true }
],
run: async (ctx) => {
const { key, value } = ctx.options as { key: string; value: string }
await ctx.reply(`Set ${key} = ${value}`)
}
}
]
})
createBot({ slashCommands: [settings] })Replies and edits
ts
await ctx.reply('simple reply')
await ctx.reply({
content: 'rich reply',
ephemeral: true,
allowedMentions: { parse: ['users'] }
})
await ctx.edit('edited content')Embeds
ts
const status = embed()
.setTitle('Status')
.setDescription('All systems nominal')
.setColor(0x00ff00)
.addField('Uptime', '99.9%', true)
.addField('Latency', '42ms', true)
.setFooter('Flora')
.toJSON()
await ctx.reply({ embeds: [status] })Components
ts
const row = actionRow().addComponents(
button().setCustomId('ping').setLabel('Ping').setStyle(ButtonStyle.Primary),
button().setUrl('https://example.com').setLabel('Docs')
)
await ctx.reply({ content: 'Pick one', components: [row.toJSON()] })V2 components require a message flag:
ts
const card = container()
.setAccentColor(0x3366ff)
.addComponents(
section()
.addComponents(textDisplay('Flora runtime'))
.setAccessory(thumbnail('https://example.com/logo.png'))
)
await ctx.reply({
components: [card.toJSON()],
flags: MessageFlags.IS_COMPONENTS_V2
})KV store
ts
const store = kv.store('settings')
await store.set('prefix', '!')
const prefixValue = await store.get('prefix')
await store.set('session', JSON.stringify({ userId: '123' }), {
expiration: Math.floor(Date.now() / 1000) + 3600,
metadata: { source: 'login' }
})
const { value, metadata } = await store.getWithMetadata('session')
const page = await store.list({ prefix: 'user:', limit: 100 })
if (!page.list_complete) {
await store.list({ cursor: page.cursor })
}Cron jobs
Schedule tasks to run at fixed times or intervals using cron expressions. All times are evaluated in UTC.
ts
// Run every minute
cron('heartbeat', '* * * * *', (ctx) => {
console.log(`Heartbeat at ${ctx.scheduledAt}`)
})
// Run daily at midnight UTC
cron('daily-cleanup', '0 0 * * *', async (ctx) => {
console.log(`Running ${ctx.name}`)
// cleanup logic here
})
// Run every hour at minute 30
cron('hourly-report', '30 * * * *', (ctx) => {
console.log('Generating hourly report')
})
// Run at 9am on weekdays
cron('weekday-reminder', '0 9 * * 1-5', (ctx) => {
console.log('Good morning!')
})The cron context provides:
name: The cron job name you specifiedscheduledAt: ISO 8601 timestamp of when the job was scheduled to run
Options
ts
// Skip execution if previous run is still active
cron('long-task', '*/5 * * * *', async (ctx) => {
await someLongRunningTask()
}, { skipIfRunning: true })skipIfRunning: Iftrue, the job won't start a new execution if the previous one is still running. Default:false.
Cron expressions follow standard 5-field format: minute hour day-of-month month day-of-week.
Limits:
- Max cron jobs per guild: 32 (configurable via
max_cron_jobs) - Handler timeout: 5 seconds (configurable via
cron_timeout_secs)
Utilities
ts
if (hasRole(ctx, '123456789')) {
await ctx.reply('role ok')
}
const sub = getSubcommand(ctx)
const group = getSubcommandGroup(ctx)Types
Types are generated from the Rust runtime and available in the SDK. Use these when authoring bot scripts and tests outside of the runtime bundle.