Odin is a general-purpose programming language with distinct typing, built for high performance, modern systems and built-in data-oriented data types.
In LiveCodes, Odin is compiled and run entirely in the browser by Odin's compiler, compiled to WebAssembly. Nothing runs on a server.
Usage
Demo:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Preact
- Web Components
import { createPlayground } from 'livecodes';
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import LiveCodes from 'livecodes/svelte';
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
</script>
<LiveCodes {...options} />
import LiveCodes from 'livecodes/solid';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
import LiveCodes from 'livecodes/preact';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
<live-codes></live-codes>
<script type="module">
import "livecodes/web-components";
const playground = document.querySelector("live-codes");
playground.config = {
"mode": "simple",
"activeEditor": "script",
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\n\nmain :: proc() {\n\tprogram := \"+ + * - /\"\n\taccumulator := 0\n\n\tfor token in program {\n\t\tswitch token {\n\t\tcase '+': accumulator += 1\n\t\tcase '-': accumulator -= 1\n\t\tcase '*': accumulator *= 2\n\t\tcase '/': accumulator /= 2\n\t\tcase: // ignore everything else\n\t\t}\n\t}\n\n\tfmt.printfln(\"The program \\\"%s\\\" calculates the value %d\", program, accumulator)\n}\n"
},
"tools": {
"status": "full"
}
};
</script>
Communication with JavaScript
The code is compiled in a web worker. Where it then runs depends on the target it is built for, which
is set in custom settings:
| Target | Runs on | What it reaches |
|---|
wasi_wasm32 (default) | a WASI host, in the worker | core:fmt, standard input, arguments, exit codes |
js_wasm32 | this page, on Odin's own JS runtime | the DOM and a <canvas>, through core:sys/wasm/js |
Custom Settings
{
"odin-wasm": { "target": "js_wasm32" }
}
Either way the following helpers are available in the browser global livecodes.odinWasm object:
livecodes.odinWasm.input: The standard input passed to the program.
livecodes.odinWasm.loaded: A promise that resolves when the compiler is loaded and the program has run once.
livecodes.odinWasm.output: The program's output.
livecodes.odinWasm.error: The compiler diagnostics, if the code failed to compile.
livecodes.odinWasm.exitCode: The program's exit code.
livecodes.odinWasm.run: A function that compiles and runs the code with new input. It takes a string as input and returns a promise that resolves with an object containing output, error and exitCode.
Example:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Preact
- Web Components
import { createPlayground } from 'livecodes';
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
return (<LiveCodes {...options} />);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import LiveCodes from 'livecodes/svelte';
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
</script>
<LiveCodes {...options} />
import LiveCodes from 'livecodes/solid';
export default function App() {
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
return (<LiveCodes {...options} />);
}
import LiveCodes from 'livecodes/preact';
export default function App() {
const options = {
"template": "odin-wasm",
"params": {
"activeEditor": "markup"
}
};
return (<LiveCodes {...options} />);
}
<live-codes
template="odin-wasm"></live-codes>
<script type="module">
import "livecodes/web-components";
const playground = document.querySelector("live-codes");
playground.params = {
"activeEditor": "markup"
};
</script>
JavaScript interop, the DOM and canvas
With target: "js_wasm32" the program runs on the page, on Odin's own JavaScript runtime, so
core:sys/wasm/js reaches what the page has: the
document, elements, events, WebGL and a canvas.
js.evaluate(str) evaluates JavaScript in the page — the shortest path to anything the bindings do
not wrap.
- The DOM bindings:
js.get_element_value_string, js.set_element_value_string, js.set_element_style,
js.set_document_title, js.get_bounding_client_rect.
- The event listeners in
core:sys/wasm/js/events, and the webgl / webgl2 bindings for a canvas.
- A program that exports
step :: proc(dt: f64, ctx: rawptr) -> bool is driven once per animation
frame, as Odin's runtime does it, and the canvas is yours to draw on. Such a program still needs a
main: Odin requires an entry point.
A canvas, drawn from Odin through js.evaluate:
show code
- JS
- TS
- React
- Vue
- Svelte
- Solid
- Preact
- Web Components
import { createPlayground } from 'livecodes';
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
createPlayground('#container', options);
import { createPlayground, type EmbedOptions } from 'livecodes';
const options: EmbedOptions = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
createPlayground('#container', options);
import LiveCodes from 'livecodes/react';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
<script setup>
import LiveCodes from "livecodes/vue";
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
</script>
<template>
<LiveCodes v-bind="options" />
</template>
<script>
import LiveCodes from 'livecodes/svelte';
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
</script>
<LiveCodes {...options} />
import LiveCodes from 'livecodes/solid';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
import LiveCodes from 'livecodes/preact';
export default function App() {
const options = {
"config": {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
}
};
return (<LiveCodes {...options} />);
}
<live-codes></live-codes>
<script type="module">
import "livecodes/web-components";
const playground = document.querySelector("live-codes");
playground.config = {
"mode": "simple",
"activeEditor": "script",
"markup": {
"language": "html",
"content": "<canvas id=\"canvas\" width=\"480\" height=\"240\"></canvas>\n<script>\n window.drawDot = () => {\n const c = document.getElementById('canvas');\n const ctx = c.getContext('2d');\n const t = performance.now() / 1000;\n ctx.fillStyle = '#0f1116';\n ctx.fillRect(0, 0, c.width, c.height);\n ctx.fillStyle = '#3b82f6';\n ctx.beginPath();\n ctx.arc(c.width / 2 + Math.cos(t) * 90, c.height / 2 + Math.sin(t) * 55, 16, 0, Math.PI * 2);\n ctx.fill();\n };\n</script>"
},
"script": {
"language": "odin-wasm",
"content": "package main\n\nimport \"core:fmt\"\nimport \"core:sys/wasm/js\"\n\nmain :: proc() {\n\tfmt.println(\"drawing on the canvas\")\n}\n\n@(export)\nstep :: proc(dt: f64, _: rawptr) -> bool {\n\tjs.evaluate(\"drawDot()\")\n\treturn true\n}\n"
},
"customSettings": {
"odin-wasm": {
"target": "js_wasm32"
}
},
"tools": {
"status": "full"
}
};
</script>
package main
import "core:fmt"
import "core:sys/wasm/js"
main :: proc() {
fmt.println("drawing on the canvas")
}
@(export)
step :: proc(dt: f64, _: rawptr) -> bool {
js.evaluate("drawDot()")
return true
}
The page's markup provides the canvas and the drawDot function; the JavaScript runtime, not the
program, owns the DOM, so the two meet through js.evaluate.
The input (livecodes.odinWasm.input, or whatever is passed to run) reaches the program byte for
byte — nothing appends a newline to it. That matters for a line-based program, because Odin's
bufio.reader_read_string returns the data together with .EOF when the last line has no trailing
newline (the same as Go's ReadString). Read the data first, then stop on the error:
for {
line, err := bufio.reader_read_string(&reader, '\n')
if len(line) > 0 {
// use the line
}
if err != nil {
break
}
}
Language Info
Name
odin-wasm
Aliases and Extensions
odin
Editor
script
Compiler
Odin's compiler, compiled to WebAssembly by wodin.
Version
Odin Compiler Version: dev-2026-09
Custom Settings
Custom settings added to the property odin-wasm are passed as a JSON object to the compiler during compile. Please check the documentation for full reference.
Please note that custom settings should be valid JSON (i.e. functions are not allowed).
Example:
args is passed to the program as its command-line arguments, compileArgs is added to the odin build command line, and target selects the target the program is built for (wasi_wasm32, the default, which runs on a WASI host, or js_wasm32, which runs on the browser's JavaScript runtime).
Example:
Custom Settings
{
"odin-wasm": {
"args": ["--flag", "value"],
"compileArgs": ["-o:speed"]
}
}
Live Reload
By default, new code changes are sent to the result page for re-evaluation without a full page reload, avoiding the need to reinitialize the Odin environment. This behavior can be disabled by adding the code comment // __livecodes_reload__ to the Odin code, which forces a full page reload.
This comment can be added in the hiddenContent property of the editor for embedded playgrounds.
Limitations
- The
vendor collection is not shipped: a program that imports a vendor: package will not find it.
- No threads, files, sockets or subprocesses: WASI preview 1 has none, and a program is given an empty filesystem.
- A program that never returns blocks the tab; both targets run synchronously.
Example Usage
package main
import "core:fmt"
main :: proc() {
fmt.println("hello from Odin, compiled in your browser")
for n in 1..=5 {
fmt.printfln("%d squared is %d", n, n * n)
}
}
Starter Template
https://livecodes.io/?template=odin-wasm
Links