CLI Reference
Axel CLI
Section titled “Axel CLI”Axel is invoked as axel <command>. Commands split into two groups:
- Schema commands — compile ASL and manage PostgreSQL migrations (require a DB connection)
- Query commands — parse, compile, and generate code from AQL (no DB connection required)
Global flags
Section titled “Global flags”These flags are accepted by all commands.
| Flag | Short | Description |
|---|---|---|
--dir |
-d |
Project directory — auto-discovers axel.yaml, schema.asl, default.asl, or schema/ |
--config |
-c |
Explicit config file path (overrides --dir) |
--url |
-u |
PostgreSQL connection URL |
--schema-path |
Explicit schema: a file, a directory, or a glob such as schema/*.asl (overrides --dir) |
|
--migrations-dir |
Migrations directory (overrides --dir) |
Project directory (--dir)
Section titled “Project directory (--dir)”The simplest way to configure Axel. Point it at your project folder and it figures out the rest:
axel -d ./myproject validateaxel -d ./myproject compile --aql 'select User { id, email };'axel -d ./myproject upaxel -d ./myproject codegen -g go -o ./genDiscovery order inside --dir:
axel.yaml— if found, loaded as the full configschema.asl— used as the schema if noaxel.yamldefault.asl— fallback schema nameschema/— a directory of.aslfiles, merged into one schema
Config file (--config)
Section titled “Config file (--config)”For explicit control, or when the config lives outside the project directory:
# yaml-language-server: $schema=https://raw.githubusercontent.com/struckchure/axel/main/schema.jsondatabase-url: postgres://user:pass@localhost:5432/mydbschema-path: ./schema/main.asl # or a glob: ./schema/*.aslmigrations-dir: ./migrationsrel-load-strategy: query # query | join
codegen: generator: go # go | ts out-dir: ./db/generated queries: - ./queries/*.aql options: package: generatedaxel --config axel.yaml <command>Project initialization
Section titled “Project initialization”axel init
Section titled “axel init”Scaffolds a new Axel project by creating an axel.yaml config file, a starter axel/schema.asl schema with a Base type, and a migrations directory.
axel initaxel init --dir ./myproject --url postgres://localhost:5432/mydb| Flag | Short | Description |
|---|---|---|
--dir |
-d |
Target directory to initialize in |
--url |
-u |
Database connection URL to put in config |
--schema-path |
Custom schema — file, directory, or glob (default: axel/schema.asl) |
|
--migrations-dir |
Custom migrations directory (default: migrations) |
Schema commands
Section titled “Schema commands”axel diff
Section titled “axel diff”Diffs the current .asl schema against the last migration and writes a new migration.
axel diff --name "add users table"axel diff -n "add comments table"
axel generateis a deprecated alias foraxel diffand still works, printing a deprecation notice.
| Flag | Short | Description |
|---|---|---|
--name |
-n |
Human-readable label for the migration |
The generated migration is written to --migrations-dir with a sequential version prefix:
migrations/ 0001/ up.sql down.sql metadata.json 0002/ ...Required columns need a backfill
Section titled “Required columns need a backfill”ALTER TABLE … ADD COLUMN x TEXT NOT NULL fails on a table that already has rows. So when a
required field with no default is added to an existing table — or an existing optional field is
flipped to required — axel diff splits the change: the column is added nullable, a commented
backfill seam is left in place, and NOT NULL is applied in a follow-up statement. The command also
prints a warning naming the column.
ALTER TABLE "vendor" ADD COLUMN "description" TEXT;-- axel: "description" is required and has no default. Existing rows need a value-- before the NOT NULL below can be applied:-- UPDATE "vendor" SET "description" = <value> WHERE "description" IS NULL;ALTER TABLE "vendor" ALTER COLUMN "description" SET NOT NULL;warning: vendor.description is required with no default: existing rows must be backfilled in themigration before its SET NOT NULL succeedsReplace <value> with the backfill before running axel up. This is the one place a generated
migration is meant to be edited — you are filling in a value Axel cannot know, not changing the
DDL.
Declaring a default on the field avoids the whole dance: Axel then knows the value, writes the
UPDATE itself, and keeps NOT NULL inline. Required links get the same treatment, applied
after their foreign key constraint is in place.
axel up
Section titled “axel up”Applies all pending migrations in order.
axel upaxel --url postgres://... upAxel tracks applied migrations in a _axel_migrations table it creates on first run.
axel down
Section titled “axel down”Rolls back the last N migrations.
axel down 1 # roll back the most recent migrationaxel down 3 # roll back the last 3 migrationsaxel status
Section titled “axel status”Prints the state of all known migrations.
axel statusExample output:
0001_create_users applied0002_add_posts applied0003_add_comments pendingQuery commands
Section titled “Query commands”These commands work with AQL queries. They read the schema file but do not connect to a database.
axel validate
Section titled “axel validate”Parses and validates an ASL schema file. Exits with a non-zero status on errors.
axel validateaxel validate --schema axel/schema.asl| Flag | Short | Default | Description |
|---|---|---|---|
--schema |
-s |
axel/schema.asl |
The .asl schema — a file, a directory, or a glob such as schema/*.asl |
On success:
schema "axel/schema.asl" is valid (5 types)On failure (exits 1):
schema validation failed: • type "Post" extends unknown type "Taggable" • type "Comment" has a cycle in its inheritance chainaxel compile
Section titled “axel compile”Compiles an AQL query (or all queries in a project) to parameterized SQL.
Single-query mode
Section titled “Single-query mode”# Inline query — use single quotes so the shell doesn't expand $paramsaxel compile --aql 'select User { id, email } filter .id = $id'
# From a fileaxel compile --file queries/get_users.aql
# Write to a fileaxel compile --file queries/get_users.aql --out queries/get_users.sqlShell quoting: Always use single quotes around
--aqlvalues. Double quotes cause the shell to expand$paramas a shell variable before Axel sees it.
Warnings
Section titled “Warnings”Unrecognised function names are passed straight through to the SQL — that pass-through is how
arbitrary SQL and extension functions reach the output, so it cannot be an error. axel compile and
axel codegen instead print a warning on stderr, and the language server shows it as
a warning diagnostic:
warning: "distinct" is a SQL keyword, not a function; Postgres will reject distinct(...)Warnings never stop compilation, which makes them easy to miss — but they mark exactly the queries that compile cleanly and then fail against a real database.
Batch mode
Section titled “Batch mode”When --dir (-d) is supplied without --aql or --file, Axel finds all *.aql files under the project directory and compiles each one.
# Compile everything in the project, write .sql files alongside the .aql filesaxel -d ./myproject compile
# Write compiled .sql files to a separate directory (created automatically)axel -d ./myproject compile --output-dir ./sql| Flag | Short | Default | Description |
|---|---|---|---|
--aql |
AQL query string (mutually exclusive with --file) |
||
--file |
-f |
Path to a .aql file |
|
--out |
-o |
stdout | Output .sql file (single-query mode) |
--output-dir |
Output directory for compiled .sql files (batch or single mode) |
||
--schema-path |
axel/schema.asl |
Schema to compile against — file, directory, or glob |
Example output:
-- $1: active (bool)-- $2: min_age (int32)SELECT u.id AS id, u.email AS emailFROM "user" uWHERE u.active = $1 AND u.age >= $2;axel run
Section titled “axel run”Executes AQL queries directly against the connected PostgreSQL database and outputs the JSON results to stdout.
# Inline query with parameters:axel run -c "multi select User { id, name } limit \$limit;" limit=20
# Query from a file with JSON parameter flag:axel run -f queries/get_users.aql -p '{"limit": 20}'
# Using relaxed JSON or prefixed format:axel run queries/get_users.aql "params={skip: 1, limit: 20}"
# Execute multiple AQL query files sequentially:axel run ./axel/seeds/*.aql
# Execute multiple AQL query files concurrently:axel run --parallel ./axel/seeds/*.aql| Flag | Short | Default | Description |
|---|---|---|---|
--command |
-c |
AQL query string to execute | |
--aql |
-q |
Alias for --command |
|
--file |
-f |
Path to .aql file to execute |
|
--params |
-p |
Query parameters in JSON, relaxed JSON, or key=value format | |
--format |
pretty |
Output format: pretty or compact |
|
--parallel |
false |
Run multiple .aql files concurrently (default sequential) |
|
--schema-path |
axel/schema.asl |
Schema to compile against |
axel repl
Section titled “axel repl”Starts an interactive REPL session for writing, compiling, and testing AQL queries against a database.
# Start REPL in the current projectaxel repl
# Specify schema and database URLaxel repl --schema-path schema.asl --url postgres://localhost:5432/mydb
# Start in table output formataxel repl --format tableREPL Features
Section titled “REPL Features”- Multi-line Query Input: Automatically buffers multi-line queries until braces
{ ... }or parentheses close, or when ending with;. - Dual Mode: Executes queries against PostgreSQL when connected; compiles and previews SQL when no database connection is active.
- Tab Completion: Context-aware completion for AQL keywords, schema model names, field names, and meta-commands.
- Output Formats: Switch between
pretty(indented JSON),table(ASCII table), andcompactJSON with.format <fmt>. - Persistent History: Command history is saved across sessions to
~/.axel_history.
Meta-Commands
Section titled “Meta-Commands”| Command | Alias | Description |
|---|---|---|
.help |
\?, \h |
Show help and command cheatsheet |
.models |
\dt |
List all models in the loaded schema |
.schema [model] |
\d |
View schema overview or examine a specific model/enum/scalar |
.compile <query> |
\c |
Compile an AQL query to SQL without executing against the database |
.format [fmt] |
Get or set output format (pretty, table, compact) |
|
.param |
\p |
List active query parameters |
.param <k> <v> |
Set a session parameter (e.g. .param limit 10) |
|
.param json <obj> |
Set parameters from JSON (e.g. .param json {"skip": 5}) |
|
.param clear |
Clear all active session parameters | |
.reload [path] |
\r |
Reload schema from disk or load an alternative .asl file |
.clear |
\l |
Clear the terminal screen |
.history |
View recent command history | |
.exit, .quit |
\q |
Exit the REPL (or press Ctrl+D) |
| Flag | Short | Default | Description |
|---|---|---|---|
--format |
pretty |
Initial output format (pretty, table, compact) |
|
--schema-path |
axel/schema.asl |
Schema file, directory, or glob to load |
axel fmt
Section titled “axel fmt”Formats .asl schema and .aql query files in canonical style, preserving comments. Paths may be files or directories (searched recursively); with no paths, the current directory is formatted.
Inside a type body, members are printed in blocks — properties and links (then computed fields), constraints, indexes, policies, triggers — with one blank line between blocks and none within one. Fields keep the order you wrote them in, since that decides column order; everything else is diffed by name, so grouping it costs nothing:
type Post extends Base { required title: str; required link author: User; computed excerpt := .content;
constraint exclusive on (.title, .author);
index on (.title);
policy owner_only for all using ( .author = global current_user );}# Print the formatted result to stdoutaxel fmt schema.asl
# Rewrite files in placeaxel fmt -w .
# CI check — exits non-zero and lists files that aren't formattedaxel fmt --check .| Flag | Short | Description |
|---|---|---|
--write |
-w |
Write the result back to each file instead of stdout |
--check |
List files whose formatting differs and exit non-zero if any do |
Formatting is safe: if a reformatted file would not parse back to the same structure, the original is left untouched. Invalid source is reported as an error.
axel codegen
Section titled “axel codegen”Generates code from your schema and compiled AQL queries. See the Code Generation guide for full details.
# Goaxel -d ./myproject codegen -g go -o ./gen
# TypeScriptaxel -d ./myproject codegen -g ts -o ./gen
# External generator binaryaxel -d ./myproject codegen --plugin ./my-generator -o ./gen| Flag | Short | Default | Description |
|---|---|---|---|
--generator |
-g |
Built-in generator (go or ts) |
|
--plugin |
-p |
Path to external generator binary | |
--out-dir |
-o |
. |
Directory to write generated files into |
--query |
-q |
AQL file or glob pattern — repeatable | |
--schema-path |
Schema file, directory or glob (default: from config or axel/schema.asl) |
||
--option |
key=value forwarded to the generator — repeatable |
Typical workflow
Section titled “Typical workflow”# 1. Write your schemavim schema.asl
# 2. Validate itaxel validate
# 3. Generate and apply a migrationaxel diff -n "initial schema"axel up
# 4. Write queriesvim queries/list_posts.aql
# 5. Generate typed codeaxel codegen -g go -o ./gen
# 6. Compile queries to SQL (optional — codegen does this internally)axel compile --output-dir ./sql
# 7. Use the generated code in your applicationEnvironment variable
Section titled “Environment variable”DATABASE_URL is read as the default connection URL when --url and --config are not provided.
export DATABASE_URL=postgres://user:pass@localhost:5432/mydbaxel up