Skip to content

Syntax Cheatsheet

Every construct in the language, at a glance. All snippets on this page compile with swoftc check.

File structure

A .sw file is a sequence of top-level declarations, in any order:

DeclarationPurpose
import "name" / import "./file.sw"Pull in a module (libraries)
var name = exprModule-level variable, private to the file
export <declaration>Make a symbol importable (libraries)
command "name" { ... }Chat command (aliases share one body)
event Name { ... }Server event handler
function name(...) { ... }Reusable function
async function name(...) { ... }Function that may wait
item "id" { ... }Custom item (reference)
mob "id" { ... }Custom mob (reference)
gui "name" { ... }Declarative inventory GUI (reference)
scoreboard "name" { ... }Sidebar (reference)
tablist "name" { ... }Player-list columns (reference)
bossbar "name" { ... }Boss bar (reference)
api "/path/:param" { ... }HTTP route (reference)
every <duration> { ... }Boot-started repeating task (reference)
on packet "Name" { ... }Inbound packet listener (reference)
server { ... }Server bootstrap config (reference)
storage { ... }Persistence backend (reference)
persistent name: Type = defaultPersistent variable (guide)

Comments: // line and /* block */. Statements are newline-insensitive — there are no semicolons, and you can split expressions across lines freely.

Commands

swoftlang
command "tp",
command "teleport" {                          // aliases share one body
    permission: "swoftlang.teleport"
    description: "Teleport a player"

    arguments {
        player: Player = sender               // one-token default
        target: either<Player|Location>       // union type, no default
    }

    execute {
        if args.player is not a Player {
            send "<red>You can only teleport players" to sender
            halt
        }
        teleport args.player to args.target
        send "<lime>Teleported ${sender} to ${args.target}"
    }
}
  • Alias groups: command "tp", command "teleport" { ... } or the short form command "tp", "teleport" { ... }.
  • Arguments are readable as args.name and also bound unprefixed (player, target).
  • execute async { ... } makes the whole handler async.
  • sender is always bound to the executing Player.

Events

swoftlang
event PlayerChat {
    priority: 0

    execute {
        set event.message to "[Filtered] ${event.message}"
        if event.message contains "badword" {
            cancel event
        }
    }
}

Known events (the compiler rejects unknown names):

EventCancellableProperties on eventShortcut aliases
PlayerChatyesplayer (ro), message (rw), cancelled (rw)player, message
PlayerJoinnoplayer (ro), first_spawn (ro), world (ro)player, name
PlayerUseItemyesplayer (ro), item (ro), custom_id (ro, optional<String>), cancelled (rw)player, item
BlockBreakyesplayer (ro), block (ro), location (ro), cancelled (rw)player, block, location
BlockPlaceyesplayer (ro), block (ro), location (ro), cancelled (rw)player, block, location
MobSpawnnomob (ro)mob
MobDeathnomob (ro), killer (ro, optional<Player>)mob, killer
MobDamageyesmob (ro), damage (rw), cancelled (rw)mob, damage
TpsChangenopast (ro), current (ro)past, current
ServerPingnomotd (rw), online (rw), max (rw)motd

cancel event on a non-cancellable event is a compile error:

e_cancel.sw:3:9: error: event 'PlayerJoin' is not cancellable
        cancel event
        ^

Types

TypeAlso writtenValues
String"text", with ${...} interpolation
Integerint42, -3
Doubledouble2.5
Booleanbooltrue, false
Playeran online player
Locationfrom location(x, y, z[, yaw, pitch])
Worldfrom world(name)
Itemfrom item(material[, amount]), custom_item(id)
Mobfrom spawn mob ... as m, all_mobs() (mobs)
Displayfrom spawn_text_display(...) etc. (displays)
Songfrom song(file) (songs)
Skinfrom skin(t, s), fetch_skin(name) (skins)
Canvasfrom map_canvas() (maps)
Schedulefrom a schedule expression (schedulers)
WorldLoaderfrom anvil_loader(...) etc. (worlds)
either<A|B>union; narrow with is a
optional<T>maybe-missing; narrow with exists / otherwise
list<T>[1, 2, 3], all_players()

There is no null. none is the missing value of an optional<T>, and the typechecker forces you to prove presence before use — see Options. Number in is a Number checks accepts both Integer and Double. Unknown type names in argument lists are accepted with a compile warning (unknown type 'Thing', treating as UNKNOWN).

Variables and assignment

swoftlang
command "vars" {
    execute {
        set x to 10                    // declare/assign a local
        set x to x + 1                 // reassign
        set sender.health to 20.0      // property write (see Properties)
        set held to sender.held_item   // property read into a local
    }
}

set <lvalue> to <expr> is the only assignment form. An lvalue is a plain variable or a dotted property path.

Operators and precedence

Lowest to highest:

LevelOperators
1or, ||
2otherwise (optional fallback)
3and, &&
4not <expr>
5=, ==, !=, <, >, <=, >=, contains, is, is not, is a / is an, is not a, exists, is missing
6+, -
7*, /, %
8unary -
9. property access, calls, literals, ( )

English-y aliases: is=; is not!=; is a Player / is an Item is a type check; x exists / x is missing test optionals. + on strings concatenates.

swoftlang
command "ops" {
    execute {
        set x to 1 + 2 * 3                       // 7 — precedence
        set y to (1 + 2) * 3                     // 9 — parens group
        set ok to x > 6 and not (y < 5)
        if "SwoftLang" contains "swoft" {        // case-insensitive substring
            send "yes" to sender
        }
        if x is a Number and ok is not a String {
            send "typed" to sender
        }
    }
}

String interpolation

${...} inside any string literal:

swoftlang
command "interp" {
    execute {
        set who to sender.name
        send "Hello ${who}, health ${sender.health}"          // simple paths
        send "Sum: ${1 + 2 * 3}, upper: ${uppercase(who)}"    // full expressions
    }
}

Simple dotted paths (${a.b.c}) stay in the string and are resolved at runtime through the property registry. Anything more complex is desugared at compile time into a concatenation chain of real expressions. Both forms are fully type-checked — a typo like ${sender.latencey} is a compile error with a suggestion.

Control flow

swoftlang
command "flow" {
    execute {
        // if / else if / else
        set x to 7
        if x = 5 {
            send "five" to sender
        } else if x = 7 {
            send "seven" to sender
        } else {
            send "other" to sender
        }

        // counted loop; optional counter runs 1..N
        loop 3 times as i {
            send "iteration ${i}" to sender
        }
        loop 3 times {
            send "again" to sender
        }

        // while (runtime guard stops runaway loops at 100,000 iterations)
        set n to 3
        while n > 0 {
            set n to n - 1
        }

        // iterate a list
        loop all_players() as p {
            send "hi ${p.name}" to p
        }
        loop all players as p {                   // sugar for all_players()
            send "hey ${p.name}" to p
        }
        loop first 5 of all_players() as p {      // cap the iteration count
            send "you're early" to p
        }

        halt                                       // stop this script run
        send "never reached" to sender
    }
}

Brace-free bodies

Every body position — if/else, loops, function bodies, execute, async blocks, GUI handlers — also accepts exactly one statement with no braces:

swoftlang
command "terse" {
    execute {
        // any body can be exactly one brace-free statement
        set x to 7
        if x > 5 send "big" to sender
        else if x = 5 send "exact" to sender
        else halt

        loop 3 times as i send "i = ${i}" to sender
        async send "from a task" to sender

        // a dangling else binds to the nearest if
        if x > 0 if x > 100 send "huge" to sender
        else send "positive, not huge" to sender
    }
}

Scoreboard lines { } and tablist column { } bodies still require braces.

Functions

swoftlang
function factorial(n: int) {
    if n <= 1 {
        return 1
    }
    return n * factorial(n - 1)               // recursion (depth cap 256)
}

function greet(player: Player) {
    send "Welcome, ${player}!" to player
}

command "demo" {
    execute {
        set f to factorial(5)                 // call as expression
        greet(sender)                         // call as statement
        call greet(sender)                    // optional 'call' keyword
        return                                // bare return = stop, like halt
    }
}

Parameter types are optional (untyped params are Any). A function whose paths sometimes return a value gets return type optional<T> — callers must narrow it.

Inline functions (lambdas)

function(...) without a name is an expression — a first-class function value:

swoftlang
function double(x: Integer) return x * 2      // brace-free body

command "lambda" {
    execute {
        set triple to function(x: Integer) return x * 3
        send "${triple(4)}" to sender         // 12: call through the variable

        set count to 0
        set inc to function() set count to count + 1
        inc()                                 // closures capture by reference
        send "${count}" to sender             // 1

        set task to async function(p: Player) {
            wait 1 seconds
            send "later" to p
        }
        spawn task(sender)                    // spawn works on async lambdas
    }
}

Call names resolve in order: local variable holding a callable → declared function → builtin. Calling a value the checker knows is not a function is a compile error ('x' is an Integer, not a function). Details on the Functions page.

Options (no more null)

swoftlang
command "find" {
    arguments {
        who: optional<Player>                 // optional command argument
    }
    execute {
        set found to player("Notch")          // player() : optional<Player>

        if found exists {
            send "hi ${found.name}" to found  // narrowed to Player here
        } else {
            send "offline" to sender
        }
        if found is missing {
            send "still missing" to sender
        }

        set target to args.who otherwise sender   // optional<T> otherwise T -> T
        set label to none                         // the missing value
        set title to label otherwise "guest"
        send "${title}" to target
    }
}

Using a possibly-missing value where a concrete one is needed is a compile error:

e_optional.sw:4:22: error: the send target is optional<Player> and may be missing; check it with 'if ... exists' or provide a fallback with 'otherwise'

Properties

Dotted paths read and write real game state through a compile-time-checked whitelist (no reflection). Read-only writes and typos are compile errors.

OwnerRead/writeRead-only
Playerhealth, max_health, food, food_saturation, level, exp, gamemode, location, world, held_item, held_slot, display_name, flying, allow_flying, flying_speed, skinname, uuid, latency, online
Locationx, y, z, yaw, pitchblock_x, block_y, block_z
Itemmaterial, amount, name, lorestats.*, tags.* (tags writable — items)
Mobhealth, name, locationmax_health, type, custom_id (mobs)
Worldtime, time_rate— (worlds)
Displaytext, item, block, scale, translation, rotation, billboard, glow_color, background, alignment, line_width, see_through, view_range— (displays)
Songtitle, author, length, speed (songs)
Skintexture, signature
Canvaswidth, height
servermotdtps, average_tps, mspt (TPS)
requestmethod, path, query, body, params.*api handlers only
swoftlang
command "props" {
    execute {
        set sender.health to sender.max_health
        set sender.location.y to sender.location.y + 10   // wither + one teleport
        set sender.held_item.amount to 32
        set sender.gamemode to "creative"                 // enum-validated string
    }
}

Values with immutable Java representations (Location, Item) are written copy-on-write: the runtime reads the deepest settable anchor (location, held_item, or a local variable), applies withers, and stores once. A stored Location is a snapshot — mutating a local copy never moves the player.

Statements reference

StatementForm
assignset x to <expr> / set a.b.c to <expr>
sendsend <expr> [to <player | list | all>]
broadcastbroadcast <expr>
teleportteleport <player> to <player | location>
halthalt — end this script task
cancelcancel event — sync event handlers only
ifif <cond> { } [else if <cond> { }] [else { }]
looploop <n> times [as i] { }
foreachloop [first <n> of] <list> as x { }
whilewhile <cond> { }
callf(args) / call f(args)
returnreturn [<expr>]
waitwait <expr> ticks|seconds|millis — async only
spawnspawn f(args) — fire-and-forget task
async blockasync { ... }
GUI navopen gui "g" to <p> [with { k: v }], replace gui "g" to <p>, close gui for <p>, go back for <p>
scoreboardshow scoreboard "s" to <p>, hide scoreboard from <p>, update scoreboard for <p>
tablistshow tablist "t" to <p>, hide tablist from <p>, set tablist header|footer to <expr> for <p>
bossbarshow bossbar "b" to <p>, hide bossbar "b" from <p>, set bossbar "b" progress|text to <expr> for <p>
titletitle <expr> [subtitle <expr>] to <p> [fade in <dur>] [stay <dur>] [fade out <dur>], clear title for <p>
actionbaractionbar <expr> to <p> [for <dur>]
belownamebelowname <expr> for <p>, set belowname score to <expr> for <p>, clear belowname for <p>
itemsgive item "id" to <p> [amount <n>]
mobsspawn mob "id" at <loc> [as <var>], despawn <mob>
viewersshow <entity> to <p|all>, hide <entity> from <p|all>, set name of <entity> to <expr> for <p>; viewers of <entity> (expr → list<Player>)
nametagsset nametag [prefix|suffix|color] of <p> to <expr> [for <viewer|all>], reset nametag of <p> [for ...]
packetssend packet "Name" { field: expr, ... } to <target>, cancel packet
displaysshow|hide display <d> to|from <target>, mount display <d> on <entity>, teleport display <d> to <loc>, destroy display <d>
worldscreate|load|unload|save|delete world ..., clone world "a" to "b" with <loader>, import anvil world ...
blocksset block at <loc> to "STONE", place <block> at <loc>, remove block at <loc>, fill blocks from <loc> to <loc> with "X"
httpreply [code <n>] with <expr> — api handlers only
songsplay|pause|resume|stop song ..., broadcast song <file>, set song volume ..., fade song ...
soundsplay sound <key> to <target> [at <loc>] [volume <v>] [pitch <p>], stop sound [<key>] for <target>
particlesspawn particle <name> at <loc> [count <n>] [offset <x>,<y>,<z>] [speed <s>] [to <viewer|all>]
toastsshow toast <title> [description <s>] [icon <mat>] [frame task|goal|challenge] to <target>
mapsdraw pixel|rect|text on <canvas> ..., give map of <canvas> to <p>
motdset server motd to <expr>
schedulerscancel schedule <handle> (creation is the schedule expression)

Targets accept a Player, a list<Player>, or the keyword all (broadcast).

Durations

Declaration positions (update:, refresh:, cooldown:, title fades, actionbar ... for, every <dur>, schedule after/every <dur>, fade song ... over <dur>) take an integer literal plus a unit and are converted to ticks at compile time:

WrittenTicks
10 ticks / 1 tickas-is
3 seconds / 1 second× 20
250 millis÷ 50 (minimum 1)

The wait statement is more general — its amount is any numeric expression, and the unit is kept for the runtime (seconds/millis sleep the task's virtual thread; ticks aligns to tick end).

Async

swoftlang
async function countdown(target: Player, from: Integer) {
    loop from times as i {
        send "<yellow>${from - i + 1}..." to target
        wait 1 seconds
    }
    send "<lime>Go!" to target
}

async function double_it(x: Integer) {
    wait 1 ticks
    return x * 2
}

command "start" {
    execute {
        spawn countdown(sender, 3)      // fire-and-forget from sync code
        async {                          // anonymous fire-and-forget block
            wait 10 ticks
            send "ready" to sender
        }
        send "started" to sender         // runs immediately, no blocking
    }
}

command "race" {
    execute async {                      // whole handler is async
        wait 5 ticks
        set doubled to double_it(21)     // direct async call: sequential
        send "result ${doubled}" to sender
    }
}

Coloring rules the compiler enforces:

  • wait only in async color (async function, execute async, async { }).
  • Direct calls to async functions only from async color; from sync code use spawn.
  • spawn and async { } are legal in both colors and never return a value.
  • cancel event is banned in async regions — cancel in the sync prologue, then detach.
  • Spawned tasks get a shallow snapshot of variables: reassignments don't leak back, object mutations are shared.
e_wait.sw:3:9: error: 'wait' is only allowed in async functions, 'execute async', or 'async { }' blocks

Persistence

swoftlang
storage {
  backend: files "data/swoftlang"     // or: sqlite "path.db" | mysql { ... } | mongodb "uri"
  flush: every 30 seconds             // write-behind cadence (default 30s)
}

persistent total_joins: Integer = 0            // global scalar, default required
persistent kills for Player: Integer = 0       // keyed by subject

event PlayerJoin {
    execute {
        set total_joins to total_joins + 1
    }
}

Persistent types are limited to String | Integer | Double | Boolean. Defaults are mandatory, so reads are total — never optional. Keyed access without for is a compile error (kills is keyed by Player — use kills for <player>). See the server config page for backend configuration.

Modules

swoftlang
import "music"                           // stdlib/addon by name (addon path)
                                         // import "./lib/util.sw" — relative form

var greeting_count = 0                   // module-level var: private, shared state

export function greet(target: Player) {  // 'export' makes it importable
    set greeting_count to greeting_count + 1
    send "hello (#${greeting_count})" to target
}

function helper() return 1               // un-exported = private to this file

export legally prefixes function, item, mob, gui, scoreboard, tablist, and bossbar. Commands, events, api routes, schedulers, and packet listeners in an imported module always register — they're effects, not symbols. Full rules, collision errors, and the addon search path live in the Libraries section.

What the compiler catches

A non-exhaustive sampler of compile errors (each shown with real swoftc output on its reference page): unknown/read-only properties, typos with did-you-mean suggestions, optional misuse, wrong builtin arity or argument types, wait in sync code, cancel event on non-cancellable events, sidebar line and tablist column caps, invalid enum values (gamemode, bossbar color/style, click filters, tablist skins, rarities, stats, attributes, entity types, nametag and glow colors, toast frames, HTTP methods), GUI slot ranges, duplicate server blocks, unknown item/mob ids in give item / spawn mob / drops, reply outside api handlers, cancel packet outside packet listeners, blocking builtins in sync code, import cycles, calls to un-exported module functions, and use of never-assigned variables.

Named runtime objects are checked against their declarations, too. Referring to a scoreboard, tablist, bossbar, gui, hologram, npc, or named schedule that no script declares is a compile error naming the declaration you're missing:

unknown_gui.sw:3:9: error: unknown gui 'nope'; declare it with 'gui "nope" { }'

And the target of a directed statement is type-checked: show, send, title, subtitle, actionbar, and friends want a Player, a list of players, or all — handing one a String (or any other type) is caught before it ships:

send_to_string.sw:4:22: error: cannot send a message to a String; expected a Player, a list of players, or 'all'