Start here · ~10 minutes
Your first Glyph program
Install it, scaffold a project, run it, then break it on purpose and read the error the compiler gives you. No prior Glyph knowledge assumed. If you've written a little TypeScript, you'll follow every line.
1 · Install
Glyph ships as a single prebuilt binary. You also need Node with tsx and
typescript, which Glyph uses to run and type-check.
npm install -g @glyphlang/glyph npm install -g tsx typescript # run & type-check toolchain glyph doctor # confirms your toolchain is ready
2 · Scaffold a project
glyph init creates a runnable starter: src/main.glyph, src/.types/README.md explaining where ambient declarations go, a package.json, a .gitignore, and two files coding agents read on their own: AGENTS.md, which points at glyph llms for the full language reference, and .mcp.json, which registers glyph mcp so an agent can ask the compiler for types and references instead of searching for them. In a project you already have, glyph agents writes those two. The package.json pins the compiler, typescript and tsx in devDependencies, so anyone who clones the project can build it with npm install and no global install.
glyph init hello cd hello
Open src/main.glyph. It's a hello-world:
module main
import std/io
fn main(argv: Array<string>) -> number {
io.println("hello from glyph")
return 0
}
Two things to notice: a program runs its main(argv), and main
returns a number, the process exit code. Returning sets that code without
forcing the process to stop, so a program that starts a server keeps serving.
3 · Run it
glyph run
hello from glyph
glyph run type-checked it, compiled it to TypeScript, and executed it. That's
the whole loop.
4 · Add something real
Now replace the body with a tiny bit of logic: a task status and a function that
labels it. This introduces the two things you'll use constantly: a tagged union
(type Status = ...) and match, which is Glyph's only conditional.
module main
type Status = Todo | Doing | Done
fn label(s: Status) -> string {
return match s {
Todo => "not started",
Doing => "in progress",
Done => "finished",
}
}
fn main(argv: Array<string>) -> number {
print(label(Done))
return 0
}
glyph run
finished
5 · Break it on purpose
Here's the part that shows you what Glyph is for. Delete the Done arm
from the match (the kind of thing an agent does when it adds a case
somewhere and forgets to handle it), and run again.
fn label(s: Status) -> string {
return match s {
Todo => "not started",
Doing => "in progress",
}
}
[E0200] Error: typecheck: non-exhaustive match on `Status`: missing variants `Done`
╭─[main:6:10]
│
6 │ ╭─▶ return match s {
┆ ┆
9 │ ├─▶ }
│ ╰───────── missing variants `Done`
│
│ Help: Add an arm for each missing variant, or an `else` arm to catch the rest.
───╯
It didn't run. In TypeScript the equivalent switch would compile clean and
return undefined at runtime; here the missing case is a compile error with a
stable code (E0200), the exact spot, and a one-line fix. Run
glyph --explain E0200 any time you want the long version.
6 · Fix it
Put the arm back (or add an else), and it runs again.
glyph run
finished
That's the core loop: write, run, and when something's wrong the compiler tells you exactly what and how, before your code ships, not after. From here:
- The five-minute tour: the whole language, quickly.
- The answers: straight replies to the questions engineers actually ask (React, typed APIs, why not tooled TS).
- The playground: try Glyph in your browser, no install.