Libraries
The standard library ships written in SwoftLang. An addon is a .sw file that exports functions; importing one is one line.
import "music"
command "anthem" {
execute {
play_song_for_all("anthem.nbs")
}
}Shipped addons
each ends with how it's builtAuthoring
build your ownThe addons double as the reference implementations for writing your own. The rest of this page is the import system itself — resolution, exports, and module state.
The import system
import "<spec>" at the top level, where the spec is either a module name or a relative path:
| Form | Resolves to |
|---|---|
import "music" | music.sw in the importing script's directory, else in the addon path |
import "./lib/util.sw" | exactly that file, relative to the importing script |
The addon path defaults to an addons/ directory next to your scripts and is overridable with swoftc --addon-path <dir>. A failed resolution tells you everywhere it looked:
e_impbad.sw:1:1: error: cannot resolve import "warp_utils" (looked for warp_utils.sw and addons/warp_utils.sw)Imports are resolved at compile time: the entry script and every transitively imported module compile as one unit, typechecked together — types, optionals, and async coloring all flow across module boundaries. The output is a single bundle JSON. Import graphs must be acyclic:
b.sw:1:1: error: import cycle: a -> b -> aImporting the same module twice (directly or through two paths) is a no-op — one copy compiles, one copy loads, one copy of its state exists.
Exports
Symbols are private by default. export in front of a declaration makes it visible to importers:
export function greet(target: Player) {
send "<green>hello from a library" to target
}
export async function delayed_greet(target: Player) {
wait 1 seconds
greet(target)
}
function internal_detail() return 42 // invisible to importersexport works on function (sync or async), item, mob, gui, scoreboard, tablist, and bossbar declarations. Lambdas are values, so first-class functions cross module boundaries through exported functions — the abilities addon's on_item_use / with_cooldown handler API is exactly that.
Calling a private function from outside is a compile error that names the module:
e_private.sw:5:9: error: function 'ability_put' is private to module 'abilities' (addons/abilities.sw); add 'export' to its declaration to call it from hereCommands and events are effects, not symbols. A command, event, api route, every scheduler, or on packet listener declared in a module registers the moment the module is imported — that's how the abilities addon installs its use-event listener. export on them is rejected:
e_expcmd.sw:1:8: error: commands and events always register when their module is imported (they are effects, not symbols); remove 'export'Two modules exporting the same symbol name is a load error naming both modules — there is no shadowing and no aliasing (v1).
Module-level variables
var name = expr at the top level declares module state: private to the file, initialized at load, alive for the server session, not persistent (use persistent for data that must survive restarts).
var opens = 0
export function count_open() {
set opens to opens + 1
return opens
}Module-level vars are shared, live state: every call into the module — from any command, event, api handler, or schedule — reads and writes the same variable. Reads and writes are individually safe across concurrent async tasks, but a compound update such as set opens to opens + 1 is a read-then-write and is not atomic: two async tasks updating the same module variable at the same time can lose one of the updates. If a counter must be exact under concurrency, funnel the updates through a single schedule or a persistent variable instead of racing async tasks over one module var.
Checking scripts that import
swoftc check follows imports, so CI stays one line — point --addon-path at your addons directory:
swoftc check scripts/lobby.sw --addon-path addonsReady to write one? The tutorial builds a titles.sw addon from an empty file to first-class-function tricks, every step compiled.