This guide takes you from zero to a running Disc project in about 10 minutes. By the end, you will have a schema, a running database with data in it, generated TypeScript types, and an open admin UI.
Deno (latest stable) is the only requirement. Disc runs on Deno and manages its own PostgreSQL instance automatically -- you do not need to install PostgreSQL.
Install Deno if you have not already:
curl -fsSL https://deno.land/install.sh | sh
Verify the installation:
deno --version
Pick whichever path matches your environment. Each option puts the disc binary on your PATH so the rest of this guide can run disc init/serve/etc. directly.
The quickest path — downloads the prebuilt disc binary for your platform and adds it to your PATH:
curl -fsSL https://disc.sh/install | sh
This installs disc into ~/.disc/bin. Pin a specific release by passing it through to the script:
curl -fsSL https://disc.sh/install | sh -s -- v2026.06.13
Prebuilt binaries are published for macOS (arm64), Linux (x64/arm64), and Windows (x64), and verified against a published SHA-256 checksum on download. On Intel macOS there is no prebuilt binary — use Homebrew or build from source (the Apple Silicon binary also runs under Rosetta 2). Pass --no-modify-path to skip the PATH edit.
The Homebrew Formula builds Disc from source via deno compile and installs the binary into $(brew --prefix)/bin. deno and bun are pulled in automatically.
# Cutting-edge (primary branch HEAD):
brew install --HEAD https://raw.githubusercontent.com/systemsoft/disc/primary/homebrew/disc.rb
# Stable (latest tagged release):
brew install https://raw.githubusercontent.com/systemsoft/disc/primary/homebrew/disc.rb
The brew-built binary doesn’t bundle PostgreSQL — the runtime downloads it on first disc init or disc serve. If you want a bundled binary instead, build from source with deno task build (see "From source" below).
A standalone tap repo (brew tap systemsoft/disc && brew install disc) is planned; until it’s published, the Formula URL above is the canonical install path. See homebrew/README.md for the full rationale.
Clone the repo and use the existing deno task build:
git clone https://github.com/systemsoft/disc.git
cd disc
deno task build
sudo install ./disc /usr/local/bin/disc # or wherever your PATH points
This produces a binary that bundles whatever PG version is in <DISC_HOME>/postgres/<version>/ if one is cached, otherwise the binary downloads PG on first run. For cross-compilation to a different platform, see disc build --platform.
Create a new Disc project:
disc init my-app
Or, if you have not installed the disc binary globally, run via Deno:
deno run -A jsr:@disc/cli init my-app
This creates the following structure:
my-app/
.env # Database configuration
.gitignore
deno.json # Deno configuration with Disc tasks
disc.toml # Project config (resolved by lib/project-context.ts)
migrations/ # Migration files (auto-generated)
mod.ts # Application entry point
README.md
dbschema/
default.disc # Your database schema
Move into the project directory:
cd my-app
On first run, disc init downloads a PostgreSQL binary for your platform and creates a managed instance. This happens once and is cached at ~/.disc/postgres/.
| Flag | Description |
|---|---|
--template <type> |
Schema template: minimal, basic (default), or full |
--backend-dsn <url> |
Use an external PostgreSQL instead of bundled |
--skip-postgres |
Skip PostgreSQL setup entirely |
--force |
Overwrite an existing directory |
--directory <path> |
Parent directory for the project |
Example with an external database:
disc init my-app --backend-dsn "postgres://user:pass@host:5432/mydb"
Open dbschema/default.disc in your editor. If you used the basic template, it already contains a User type. Replace it with a more complete schema:
module default {
type User {
created_at: datetime {
default := datetime_current();
readonly := true;
};
required email: str {
constraint exclusive;
};
required name: str;
multi posts: Post;
};
type Post {
required author: User;
required body: str;
created_at: datetime {
default := datetime_current();
};
published: bool {
default := false;
};
required title: str;
};
};
This defines two object types -- User and Post -- with a link between them. The required keyword means the property must always have a value. The multi keyword on posts means a user can have many posts. Constraints like exclusive enforce uniqueness.
For full schema syntax, see the Schema (SDL) reference.
Start the bundled PostgreSQL instance:
disc start
Check that it is running:
disc status
If you used --backend-dsn during init, skip this step -- your external PostgreSQL is used directly.
Generate and apply migrations from your schema:
disc migrate
Disc compares your .disc schema against the current database state, generates DDL statements (CREATE TABLE, etc.), and applies them. The migration is tracked in a disc_migrations table so it is never applied twice.
To preview what would happen without changing the database:
disc migrate --dry-run
To create a migration without applying it:
disc migrate --create
For more on migrations, see the Migrations guide.
Start the Disc server, which provides an HTTP API for running EdgeQL queries:
disc serve
The server starts on http://localhost:5656 by default. It automatically starts the bundled PostgreSQL instance if it is not already running.
| Flag | Description |
|---|---|
--port <port> |
HTTP port (default: 5656) |
--host <host> |
Bind address (default: localhost) |
--jwt-secret <key> |
Enable authentication with this signing key |
--enable-auth |
Enable the auth subsystem |
--enable-access-policies |
Enable object-level access policies |
--tls-cert <path> |
Path to TLS certificate for HTTPS |
--tls-key <path> |
Path to TLS private key |
Open the interactive EdgeQL shell:
disc shell
You will see a disc> prompt. Try these queries:
disc> insert User { email := "ada@example.com", name := "Ada" };
disc> insert Post {
... author := (select User filter .email = "ada@example.com"),
... body := "This is my first post.",
... title := "Hello World"
... };
disc> select User {
... email,
... name,
... posts: {
... created_at,
... title
... }
... };
The result is a nested JSON structure -- no joins, no ORMs:
[
{
"email": "ada@example.com",
"name": "Ada",
"posts": [
{
"created_at": "2026-03-20T12:00:00Z",
"title": "Hello World"
}
]
}
]
disc> select Post {
... author: { name },
... title
... } filter .published = false
... order by .created_at desc;
disc> update Post
... filter .title = "Hello World"
... set { published := true };
disc> delete Post filter .title = "Hello World";
Type \q to exit the shell. For full query language documentation, see the EdgeQL reference.
The shell supports several backslash commands:
| Command | Description |
|---|---|
\? or \help |
Show help |
\q or \quit |
Quit shell |
\d |
List tables |
\dt |
List tables with details (size, description) |
\c <database> |
Connect to a different database |
\i <file> |
Execute queries from a file |
\timing |
Toggle query timing display |
\history |
Show command history |
\clear |
Clear the screen |
The same table appears in
docs/cli.md. Keep them in sync — column widths included — so agit diffinstantly flags drift.
Generate TypeScript types and a client from your schema:
disc codegen
This reads all .disc files in ./dbschema/ (or schema.disc in the project root) and writes generated code to ./dbschema/disc-client/:
dbschema/disc-client/
index.ts # Re-exports and client factory
client.ts # DiscClient class
queries.ts # Type-safe query builder functions
types.ts # TypeScript interfaces for each type
| Flag | Description |
|---|---|
--schema <file> |
Single schema file path |
--schema-dir <dir> |
Schema directory for multi-file discovery (default: ./dbschema) |
--output <dir> |
Output directory (default: ./dbschema/disc-client) |
--target <type> |
Target: client, server, or both |
--no-queries |
Skip query builder generation |
--no-mutations |
Skip mutation method generation |
--no-client |
Skip client library generation |
For more details, see the Codegen guide.
Import the SDK and run queries programmatically:
import { createClient } from "disc/sdk/mod.ts";
const client = createClient({
baseUrl: "http://localhost:5656"
});
// Run an EdgeQL query
const users = await client.query(`
select User {
email,
name,
posts: { title }
}
`);
console.log(users);
const user = await client.query(
`select User { email, name }
filter .email = <str>$email`,
{ email: "ada@example.com" }
);
await client.query(
`
insert User {
email := <str>$email,
name := <str>$name
}
`,
{ email: "billie@example.com", name: "Billie" }
);
const result = await client.transaction(async tx => {
const user = await tx.query(`
insert User { email := "cher@example.com", name := "Cher" }
`);
await tx.query(`
insert Post {
author := (select User filter .email = "cher@example.com"),
body := "Written in a transaction.",
title := "First Post"
}
`);
return user;
});
const alive = await client.isAlive(); // true/false
const ready = await client.isReady(); // true/false
const health = await client.health(); // full status object
For the full SDK reference, see the Client SDK documentation.
Disc ships with a web-based admin interface:
disc ui
This opens your browser to the admin UI, which provides:
For more on the admin UI, see the Admin UI guide.
For day-to-day development, use the watcher to auto-apply schema changes:
disc watch
This watches your .disc files and automatically runs migrations and regenerates types when you save changes.
The typical workflow is:
dbschema/default.discdisc watch detects the change and runs migrationsYou now have a working Disc project with a schema, data, generated types, and a running server. Here is where to go from here:
with blocks, and more.disc start, disc stop, disc status, disc build, disc deploy, disc db, disc pg.