Skip to content

Variables

Read and write local, global, and chat variables. All three namespaces share storage with the built-in macro engine, so values set by your extension are immediately visible in prompt assembly and vice versa.

No permission is required — variables are free tier.

Namespace Macro family Storage Persists?
local {{getvar}} / {{setvar}} (no prefix) chat.metadata.macro_variables.local Per-chat scratchpad. Persisted via the spindle API; cleared by in-prompt mutations.
chat {{getchatvar}} / {{setchatvar}} (@ prefix) chat.metadata.chat_variables Persisted across generations within the same chat. Use this for state you want to survive turns (HP, counters, flags).
global {{getgvar}} / {{setgvar}} ($ prefix) macro_variables_global setting Persisted user-wide, across all chats.

Which one should I use?

Reach for chat whenever you want game-state-style values — turn counters, hit points, flags — that should survive across regens, swipes, and message edits. local is best understood as a per-evaluation scratchpad that the spindle API can also write to. global is for cross-chat user preferences.

Local Variables (per-chat scratchpad)

Local variables are stored per-chat in chat.metadata.macro_variables.local. They correspond to the unprefixed {{getvar}} / {{setvar}} macro family. Mutations made through this API are persisted to the chat record, but in-prompt macro mutations ({{setvar::x::5}} inside a prompt block) are evaluation-scoped only.

const chatId = '...' // the chat to operate on

// Set a variable
await spindle.variables.local.set(chatId, 'mood', 'happy')

// Get a variable (returns empty string if not set)
const mood = await spindle.variables.local.get(chatId, 'mood')

// Check existence
const exists = await spindle.variables.local.has(chatId, 'mood')

// List all local variables for a chat
const allVars = await spindle.variables.local.list(chatId)
// { mood: 'happy', score: '42', ... }

// Delete a variable
await spindle.variables.local.delete(chatId, 'mood')

Chat Variables (persisted per-chat)

Chat variables live in chat.metadata.chat_variables and persist across generations within the same chat. They correspond to the @-prefixed macro family — {{@hp}}, {{@turn++}}, {{setchatvar::flag::true}}, {{getchatvar::flag}}, {{incchatvar::turn}}, etc. When a generation runs and the assembled prompt mutates a chat variable, the new value is flushed back to chat metadata after assembly so subsequent turns see it.

This is the right namespace for game-loop state: HP, turn counters, inventory flags, story milestones — anything you want to survive regens, swipes, and message edits.

const chatId = '...'

// Initialize state for a new chat
await spindle.variables.chat.set(chatId, 'hp', '100')
await spindle.variables.chat.set(chatId, 'turn', '0')

// Read current state (returns "" if unset)
const hp = await spindle.variables.chat.get(chatId, 'hp')

// Check existence
const hasInventory = await spindle.variables.chat.has(chatId, 'inventory')

// Snapshot all chat variables
const state = await spindle.variables.chat.list(chatId)
// { hp: '100', turn: '7', flag_met_villain: 'true' }

// Clear a single key
await spindle.variables.chat.delete(chatId, 'temporary_buff')

These values are visible to prompt assembly via {{@key}} / {{getchatvar::key}}, so you can write a single variable from your extension and have it appear inside character cards, world books, or system prompts on the very next turn.

Global Variables (cross-chat)

Global variables are stored in the user's settings under the macro_variables_global key. They persist across all chats.

// Set a global variable
await spindle.variables.global.set('user_level', '5')

// Get a global variable
const level = await spindle.variables.global.get('user_level')

// Check existence
const exists = await spindle.variables.global.has('user_level')

// List all global variables
const allGlobals = await spindle.variables.global.list()
// { user_level: '5', theme_preference: 'dark', ... }

// Delete a global variable
await spindle.variables.global.delete('user_level')

Methods

spindle.variables.local

Method Returns Description
get(chatId, key) Promise<string> Get a local variable value. Returns "" if not set.
set(chatId, key, value) Promise<void> Set a local variable.
delete(chatId, key) Promise<void> Delete a local variable.
list(chatId) Promise<Record<string, string>> Get all local variables for a chat.
has(chatId, key) Promise<boolean> Check if a local variable exists.

spindle.variables.chat

Method Returns Description
get(chatId, key) Promise<string> Get a chat-persisted variable. Returns "" if not set.
set(chatId, key, value) Promise<void> Set a chat-persisted variable.
delete(chatId, key) Promise<void> Delete a chat-persisted variable.
list(chatId) Promise<Record<string, string>> Get all chat-persisted variables for a chat.
has(chatId, key) Promise<boolean> Check if a chat-persisted variable exists.

spindle.variables.global

Method Returns Description
get(key) Promise<string> Get a global variable value. Returns "" if not set.
set(key, value) Promise<void> Set a global variable.
delete(key) Promise<void> Delete a global variable.
list() Promise<Record<string, string>> Get all global variables.
has(key) Promise<boolean> Check if a global variable exists.

Macro Compatibility

All values are strings, matching the macro system's behavior:

  • spindle.variables.local.set(chatId, 'score', '42'){{setvar::score::42}} (unprefixed)
  • spindle.variables.local.get(chatId, 'score'){{getvar::score}}
  • spindle.variables.chat.set(chatId, 'hp', '100'){{setchatvar::hp::100}} or {{@hp = 100}} (@ prefix)
  • spindle.variables.chat.get(chatId, 'hp'){{getchatvar::hp}} or {{@hp}}
  • spindle.variables.global.set('theme', 'dark'){{setgvar::theme::dark}} or {{$theme = dark}} ($ prefix)

The full chat-variable macro family also includes {{incchatvar::key}}, {{decchatvar::key}}, {{addchatvar::key::value}}, {{haschatvar::key}}, and {{deletechatvar::key}}, along with the inline {{@key++}} / {{@key--}} / {{@key += n}} shorthand.

Note

For user-scoped extensions, the user context is inferred automatically. For operator-scoped extensions, global variable methods use the userId resolved from the extension context. Chat-scoped methods always derive their owner from the chat record.