Skip to main content
Version: Current

Scripting

Introduction​

CasualOS is a powerful platform for creating interactive, collaborative experiences using bots. Bots are the fundamental building blocks in CasualOS - they're objects that contain data (stored in tags) and behavior (defined in listen tags).

This tutorial will teach you the basics of CasualOS scripting, including:

  • How to respond to events using listen tags
  • How to create and destroy bots
  • How to search for and modify bots
  • How to use common CasualOS functions

Listen Tags: Adding Behavior to Bots​

What are Listen Tags?​

Listen tags are special tags whose values start with the @ symbol. They contain JavaScript scripts that run in response to specific events. Think of them as event handlers that give your bots behavior.

For example:

@os.toast("Hello, world!");

When this listen tag is triggered, it will display a toast message to the user.

When viewing a listen tag in the CasualOS editor (either in the sheetPortal or systemPortal), the @ symbol is omitted from the editor and shown in the tag name, like this:

Image of CasualOS sheetPortal showing onClick tag in the editor

From now on, whenever we refer to a listen tag, we will include the @ symbol in the tag name instead of in the script value.

Common Listen Tags​

CasualOS provides many built-in listen tags. Here are some of the most commonly used ones:

@onClick​

documentation link

The @onClick tag runs when a user clicks on a bot.

@onClick
os.toast("You clicked me!");

Example: Create a counter bot that tracks how many times it's been clicked.

@onClick
tags.count = (tags.count || 0) + 1;
os.toast("Clicked " + tags.count + " times");

@onCreate​

documentation link

The @onCreate tag runs once when a bot is first created.

@onCreate
os.toast("Bot created!");
tags.createdAt = new Date().toISOString();

Use cases:

  • Initialize default values
  • Set up bot relationships
  • Log creation events

@onInstJoined​

documentation link

The @onInstJoined tag runs when a user first joins an inst (instance/workspace).

@onInstJoined
os.toast("Welcome to " + that.inst + "!");

Use cases:

  • Welcome messages
  • Initialize user-specific data
  • Set up the initial scene

@onKeyDown​

documentation link

The @onKeyDown tag runs when a user presses a keyboard key. The event provides information about which keys were pressed.

if (that.keys.includes('Enter')) {
os.toast("You pressed Enter!");
}

if (that.keys.includes('Escape')) {
os.toast("You pressed Escape!");
}

Useful properties:

  • that.keys: Array of all currently pressed keys

Creating and Destroying Bots​

Creating Bots with create()​

The create() function creates a new bot with the specified tags.

Basic syntax:

create(tags)

Examples:

// Create a simple bot
const bot = create({
name: "My Bot",
color: "#ff0000"
});
// Create a bot with behavior
const counter = create({
name: "Counter",
count: 0,
onClick: "@tags.count += 1; os.toast('Count: ' + tags.count);"
});
// Create a bot and position it in 3D space
const cube = create({
shape: "cube",
color: "#00ff00",
position: { x: 0, y: 0, z: 0 },
scale: 2
});

Destroying Bots with destroy()​

The destroy() function removes a bot from the inst.

Basic syntax:

destroy(bot)

Examples:

// Destroy the current bot
destroy(thisBot);
// Create and then destroy a bot after 5 seconds
const bot = create({
name: "Temporary Bot"
});

os.sleep(5000).then(() => {
destroy(bot);
os.toast("Bot destroyed!");
});
// Destroy all bots with a specific tag
const bots = getBots("type", "temporary");
for (let bot of bots) {
destroy(bot);
}

Finding Bots​

CasualOS provides several functions to search for bots in your inst.

getBot(tag, value)​

Returns the first bot that has the specified tag with the given value. Returns null if no bot is found.

Examples:

// Find a bot by name
const player = getBot("name", "Player");
if (player) {
os.toast("Found player: " + player.id);
}
// Find a bot by custom tag
const mainMenu = getBot("type", "menu");
if (mainMenu) {
mainMenu.tags.visible = true;
}

getBots(tag, value)​

Returns an array of all bots that have the specified tag with the given value.

Examples:

// Find all enemy bots
const enemies = getBots("type", "enemy");
os.toast("Found " + enemies.length + " enemies");
// Find and modify all red bots
const redBots = getBots("color", "#ff0000");
for (let bot of redBots) {
bot.tags.color = "#0000ff"; // Change to blue
}

getBots(...filters)​

Returns an array of bots that match the given filter criteria. This is the most flexible search method.

List of filters

Common filters:

  • byTag(tag, value): Match bots with a specific tag value
  • inDimension(dimension): Match bots in a specific dimension
  • byMod(bot): Match bots that have the given bot as a mod
  • Custom filter functions

Examples:

// Find bots using multiple criteria
const bots = getBots(
byTag("type", "enemy"),
byTag("active", true)
);
// Find bots in a specific dimension
const menuBots = getBots(inDimension("menu"));
// Use a custom filter function
const largeBots = getBots(bot => {
return bot.tags.scale && bot.tags.scale > 5;
});
// Combine multiple filters
const activePlayers = getBots(
byTag("type", "player"),
bot => bot.tags.health > 0
);

Modifying Bot Data​

Using bot.tags​

The tags property provides access to a bot's data. You can read and write tag values directly.

Reading tags:

const name = thisBot.tags.name;
const position = thisBot.tags.position;
const health = thisBot.tags.health || 100; // Default value if tag doesn't exist

Writing tags:

thisBot.tags.name = "New Name";
thisBot.tags.color = "#ff0000";
thisBot.tags.score = 100;

Deleting tags:

delete thisBot.tags.temporaryData;

// or set to null
thisBot.tags.temporaryData = null;

Example - Update health system:

// On hit
tags.health = (tags.health || 100) - 10;

if (tags.health <= 0) {
os.toast("Bot destroyed!");
destroy(thisBot);
} else {
os.toast("Health: " + tags.health);
}

Using bot.masks​

Masks are temporary tag values that override the bot's actual tag values without permanently changing them. They're useful for:

  • Temporary visual changes
  • User-specific views
  • Testing different values

Mask spaces:

  • tempLocal: Visible only to the current user, cleared on refresh (default space)
  • local: Visible only to the current user, persists across refresh
  • tempShared: Visible to all users, cleared on refresh

Examples:

// Apply a temporary color change
thisBot.masks.color = "#ff0000";
// Set a user-specific mask
setTagMask(thisBot, 'customData', "Only I can see this", 'tempLocal');
// Clear a mask
delete thisBot.masks.color;
// Highlight bots temporarily for current user
const bots = getBots("type", "target");
for (let bot of bots) {
bot.masks.color = "#ffff00"; // Yellow highlight
}

// Clear after 3 seconds
os.sleep(3000).then(() => {
for (let bot of bots) {
delete bot.masks.color;
}
});

Common CasualOS Functions​

os.toast(message)​

Displays a temporary message to the user at the bottom of the screen.

os.toast("Hello, CasualOS!");
os.toast("Score: " + tags.score);

shout(eventName, arg?)​

Sends an event to all bots that have a listener tag for that event. This is a broadcast mechanism that allows bots to communicate with each other.

Basic usage:

shout("myEvent");

With arguments:

shout("onPlayerScored", { points: 10, player: thisBot });

Examples:

// Bot A - Trigger event on click
shout("onGameStarted");
// Bot B - Listen for the event
// Add this to a bot's onGameStarted tag
os.toast("The game has started!");
tags.gameActive = true;
// Advanced - Pass data with the event
// In a player bot's onClick
shout("onPlayerClicked", {
playerId: thisBot.id,
playerName: tags.name,
timestamp: Date.now()
});
// Any bot can listen for onPlayerClicked
// The event data is available in 'that'
os.toast(that.playerName + " was clicked!");

Use cases:

  • Triggering game events (start, pause, reset)
  • Broadcasting state changes to all bots
  • Coordinating multiple bots simultaneously
  • Implementing game mechanics (explosions, power-ups, etc.)

whisper(bot, eventName, arg?)​

Sends an event to a specific bot or array of bots. Unlike shout(), which broadcasts to all bots, whisper() targets specific bots.

Basic syntax:

whisper(bot, eventName)
whisper(bot, eventName, argument)
whisper([bot1, bot2, bot3], eventName)

Examples:

// Send event to a specific bot
const player = getBot("type", "player");
if (player) {
whisper(player, "onTakeDamage", { amount: 10 });
}
// Bot receiving the whisper (in onTakeDamage tag)
tags.health = (tags.health || 100) - that.amount;
os.toast("Took " + that.amount + " damage. Health: " + tags.health);

if (tags.health <= 0) {
destroy(thisBot);
}
// Send event to multiple bots
const enemies = getBots("type", "enemy");
whisper(enemies, "onAlert", { threat: thisBot });
// Enemies respond (in onAlert tag)
tags.target = that.threat;
tags.state = "attacking";
os.toast("Enemy alerted!");
// Call a bot's own event
whisper(thisBot, "initialize");

Use cases:

  • Direct communication between specific bots
  • Triggering private events
  • Sending targeted notifications
  • Implementing bot-to-bot interactions (trading, messaging, etc.)
  • Reusing event handlers (e.g., calling onCreate again to reinitialize)

Difference between shout() and whisper():

  • shout() broadcasts to all bots with the listener tag
  • whisper() sends to specific bot(s) you choose

ai.chat(options)​

Sends a message to an AI assistant and receives a response. Supports various AI providers.

Basic usage:

const response = await ai.chat([
{ role: "user", content: "What is the capital of France?" }
]);

os.toast("AI says: " + response.content);

With context:

const response = await ai.chat([
{ role: "system", content: "You are a helpful game master." },
{ role: "user", content: "Describe a mysterious forest." }
]);

const description = response.content;
os.toast(description);

Example - AI-powered NPC:

// Store conversation history on the bot
tags.conversation = tags.conversation || [];

// Add user input
tags.conversation.push({
role: "user",
content: that.message
});

// Get AI response
const response = await ai.chat([
{ role: "system", content: "You are a wise wizard in a fantasy world." },
...tags.conversation
]);

const aiMessage = response.content;

// Store AI response
tags.conversation.push({
role: "assistant",
content: aiMessage
});

// Display response
os.toast(aiMessage);

os.focusOn(botOrPosition)​

Moves the camera to focus on a specific bot or position.

Focus on a bot:

const target = getBot("name", "Target");
if (target) {
os.focusOn(target);
}

Focus on a position:

os.focusOn({ x: 0, y: 0, z: 0 });

Focus with camera rotation:

os.focusOn(target, {
rotation: { x: 0, y: 45, z: 0 },
zoom: 2
});

Putting It All Together​

Here's a complete example that demonstrates all the concepts covered in this tutorial:

// Create a game manager bot
const gameManager = create({
name: "Game Manager",
score: 0,

// Initialize game when inst is joined
onInstJoined: `@
os.toast("Welcome to the game!");

// Create some target bots
for (let i = 0; i < 5; i++) {
create({
type: "target",
color: "#ff0000",
home: true,
homePosition: new Vector3(
Math.random() * 10 - 5,
Math.random() * 10 - 5,
0
),
onClick: "@shout('onTargetClicked', thisBot);"
});
}

os.focusOn({ x: 5, y: 5, z: 0 });
`,

// Handle target clicks
onTargetClicked: `@
const target = that;

// Update score
thisBot.tags.score += 10;

// Visual feedback
target.masks.color = "#00ff00";
os.toast("Hit! Score: " + thisBot.tags.score);

// Destroy target after delay
os.sleep(500).then(() => {
destroy(target);

// Check if all targets destroyed
const remaining = getBots("type", "target");
if (remaining.length === 0) {
os.toast("Game complete! Final score: " + thisBot.tags.score);
}
});
`,

// Reset game on key press
onKeyDown: `@
if (that.key === 'r') {
// Destroy all targets
const targets = getBots("type", "target");
destroy(targets);

// Reset score
tags.score = 0;

// Recreate targets
whisper(thisBot, "onInstJoined");
}
`
});

Next Steps​

Now that you understand the basics of CasualOS scripting, you can:

Tips and Best Practices​

  1. Use meaningful tag names: Choose descriptive names for your tags to make your code easier to understand.

  2. Initialize with default values: Use || to provide default values:

    tags.count = tags.count || 0;
  3. Clean up after yourself: Destroy temporary bots when they're no longer needed.

  4. Use masks for temporary changes: Use bot.masks instead of bot.tags when you want temporary modifications.

  5. Handle missing bots gracefully: Always check if getBot() returns null:

    const bot = getBot("name", "Player");
    if (bot) {
    // Safe to use bot here
    }
  6. Use comments: Add comments to explain complex logic in your listen tags.

  7. Test incrementally: Build your scripts step by step, testing each part as you go.

Happy scripting!