Skip to content

CLI Reference

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)

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)

The simplest way to configure Axel. Point it at your project folder and it figures out the rest:

Terminal window
axel -d ./myproject validate
axel -d ./myproject compile --aql 'select User { id, email };'
axel -d ./myproject up
axel -d ./myproject codegen -g go -o ./gen

Discovery order inside --dir:

  1. axel.yaml — if found, loaded as the full config
  2. schema.asl — used as the schema if no axel.yaml
  3. default.asl — fallback schema name
  4. schema/ — a directory of .asl files, merged into one schema

For explicit control, or when the config lives outside the project directory:

# yaml-language-server: $schema=https://raw.githubusercontent.com/struckchure/axel/main/schema.json
database-url: postgres://user:pass@localhost:5432/mydb
schema-path: ./schema/main.asl # or a glob: ./schema/*.asl
migrations-dir: ./migrations
rel-load-strategy: query # query | join
codegen:
generator: go # go | ts
out-dir: ./db/generated
queries:
- ./queries/*.aql
options:
package: generated
Terminal window
axel --config axel.yaml <command>

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.

Terminal window
axel init
axel 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)

Diffs the current .asl schema against the last migration and writes a new migration.

Terminal window
axel diff --name "add users table"
axel diff -n "add comments table"

axel generate is a deprecated alias for axel diff and 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/
...

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 requiredaxel 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 the
migration before its SET NOT NULL succeeds

Replace <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.


Applies all pending migrations in order.

Terminal window
axel up
axel --url postgres://... up

Axel tracks applied migrations in a _axel_migrations table it creates on first run.


Rolls back the last N migrations.

Terminal window
axel down 1 # roll back the most recent migration
axel down 3 # roll back the last 3 migrations

Prints the state of all known migrations.

Terminal window
axel status

Example output:

0001_create_users applied
0002_add_posts applied
0003_add_comments pending

These commands work with AQL queries. They read the schema file but do not connect to a database.

Parses and validates an ASL schema file. Exits with a non-zero status on errors.

Terminal window
axel validate
axel 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 chain

Compiles an AQL query (or all queries in a project) to parameterized SQL.

Terminal window
# Inline query — use single quotes so the shell doesn't expand $params
axel compile --aql 'select User { id, email } filter .id = $id'
# From a file
axel compile --file queries/get_users.aql
# Write to a file
axel compile --file queries/get_users.aql --out queries/get_users.sql

Shell quoting: Always use single quotes around --aql values. Double quotes cause the shell to expand $param as a shell variable before Axel sees it.

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.

When --dir (-d) is supplied without --aql or --file, Axel finds all *.aql files under the project directory and compiles each one.

Terminal window
# Compile everything in the project, write .sql files alongside the .aql files
axel -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 email
FROM "user" u
WHERE u.active = $1 AND u.age >= $2;

Executes AQL queries directly against the connected PostgreSQL database and outputs the JSON results to stdout.

Terminal window
# 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

Starts an interactive REPL session for writing, compiling, and testing AQL queries against a database.

Terminal window
# Start REPL in the current project
axel repl
# Specify schema and database URL
axel repl --schema-path schema.asl --url postgres://localhost:5432/mydb
# Start in table output format
axel repl --format table
  • 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), and compact JSON with .format <fmt>.
  • Persistent History: Command history is saved across sessions to ~/.axel_history.
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

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 );
}
Terminal window
# Print the formatted result to stdout
axel fmt schema.asl
# Rewrite files in place
axel fmt -w .
# CI check — exits non-zero and lists files that aren't formatted
axel 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.


Generates code from your schema and compiled AQL queries. See the Code Generation guide for full details.

Terminal window
# Go
axel -d ./myproject codegen -g go -o ./gen
# TypeScript
axel -d ./myproject codegen -g ts -o ./gen
# External generator binary
axel -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

Terminal window
# 1. Write your schema
vim schema.asl
# 2. Validate it
axel validate
# 3. Generate and apply a migration
axel diff -n "initial schema"
axel up
# 4. Write queries
vim queries/list_posts.aql
# 5. Generate typed code
axel 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 application

DATABASE_URL is read as the default connection URL when --url and --config are not provided.

Terminal window
export DATABASE_URL=postgres://user:pass@localhost:5432/mydb
axel up