Skip to content

Configuration Reference#

The jac.toml file is the central configuration for Jac projects -- similar to pyproject.toml in Python or package.json in Node.js. It defines project metadata (name, version, entry point), manages dependencies (both PyPI and npm packages), sets defaults for CLI commands (test verbosity, server port, lint rules), configures built-in capabilities (LLM models, deployment targets), and supports environment-specific profiles (development vs. production).

You typically don't need to edit jac.toml manually for basic projects. The jac create command generates one with sensible defaults, and commands like jac install <pkg> and jac config set modify it for you. But understanding the full configuration surface is valuable when you need to customize build behavior, configure LLM providers, set up lint rules, or manage deployment settings.

jac commands locate jac.toml by walking up from the current working directory. The only exception is jac install -e <path>, which reads jac.toml from the resolved <path> so editable installs work from anywhere.

Creating a Project#

# Basic project
jac create myapp
cd myapp

# Full-stack web app (recommended for web development)
jac create myapp --use web-static
cd myapp

This creates a jac.toml with default settings. When using --use web-static, the scaffolded project includes:

myapp/
├── main.jac                  # Entry point with the client app
├── jac.toml                  # Project configuration (auto-generated)
├── components/
│   └── Button.cl.jac         # Example client component
├── assets/                   # Static assets
├── README.md
├── AGENTS.md                 # Points AI coding agents at `jac guide`
└── .gitignore

The auto-generated jac.toml for a --use web-static project looks like:

[project]
name = "myapp"
version = "1.0.0"
description = "Jac client application: myapp"
entry-point = "main.jac"

[dependencies.npm]
jac-client-node = "1.0.7"

[serve]
base_route_app = "app"

[client]

You typically don't need to modify this file until you add dependencies or customize settings.


Configuration Sections#

[project]#

Project metadata. Runtime fields (entry-point, jac-version) are used by jac run and jac start. Publishing fields (license, readme, keywords, requires-python, authors, maintainers, and [project.include]) are used by jac build --as wheel when building a distributable wheel. All publishing fields are optional -- a project that is never published only needs name.

[project]
name = "myapp"
version = "1.0.0"
description = "My Jac application"
entry-point = "main.jac"
kind = "service"   # drives `jac run` (omit to infer from the entry-point)
jac-version = ">=0.15.0"

# Publishing metadata -- only needed to run `jac build --as wheel`
license = "MIT"
readme = "README.md"
requires-python = ">=3.12"
keywords = ["jac", "ai"]
authors = [{ name = "Your Name", email = "you@example.com" }]
maintainers = [{ name = "Another Person", email = "them@example.com" }]

[project.urls]
homepage = "https://example.com"
repository = "https://github.com/user/repo"
Field Type Description
name string Project / PyPI package name (required)
version string Semantic version (default: 0.1.0)
description string One-line summary (also shown on PyPI)
entry-point string Main file for jac run (default: main.jac)
kind string Project kind that drives jac run dispatch (execute / serve / build). Empty = inferred from the entry-point codespace. One of: cli, cli-native, native-binary, native-lib, service, service-mesh, py-package, js-package, web-app, web-static, desktop, mobile
jac-version string Required Jac compiler version
license string SPDX license identifier (e.g. "MIT")
readme string Path to README file (default: README.md)
requires-python string Minimum Python version (e.g. ">=3.12")
keywords list Search keywords shown on PyPI
authors list of {name, email} Package authors
maintainers list of {name, email} Package maintainers
urls table Links shown on PyPI (declared under [project.urls])

Note: authors and maintainers also accept a plain string form (authors = ["Your Name"]), but the { name, email } table form is recommended -- it is what published packages' jac.toml files use and what PyPI renders. See [project.include] for controlling which files land in the wheel.


[dependencies]#

Python/PyPI packages:

[dependencies]
requests = ">=2.28.0"
numpy = "1.24.0"
byllm = ">=0.4.8"

[dev-dependencies]
pytest = ">=8.0.0"

[dependencies.git]
my-lib = { git = "https://github.com/user/repo.git", branch = "main" }

[dependencies.system]
git = "*"
ffmpeg = "*"

[dependencies.system] declares OS (apt) packages your app needs at runtime. On a jac-scale Kubernetes deploy they are installed into the service container at startup (Debian only; keys are apt package names). See System Dependencies.

Version specifiers:

Format Example Meaning
Exact "1.0.0" Exactly 1.0.0
Minimum ">=1.0.0" 1.0.0 or higher
Range ">=1.0,<2.0" 1.x only
Compatible "~=1.4.2" 1.4.x

Default behavior: When you run jac install requests without a version, the package is installed unconstrained and then the actual installed version is queried. A compatible-release spec (~=X.Y) is recorded -- e.g., if pip installs 2.32.5, jac.toml gets requests = "~=2.32". The jac update command also uses this format when writing updated versions back.


[optional-dependencies]#

Optional dependency groups that users can install on demand with jac install --extras <group>. Useful for heavy or situational dependencies (monitoring, test infrastructure, database drivers) that most users don't need.

[optional-dependencies.data]
pymongo = ">=4.0,<5.0"
redis = ">=7.0,<8.0"

[optional-dependencies.monitoring]
prometheus-client = ">=0.21.0,<1.0.0"

[optional-dependencies.all]
"mypkg[data,monitoring]" = "*"

Install a group at the command line:

jac install --extras data monitoring
jac install -e . --extras all    # editable install + extras

Version specifiers follow the same rules as [dependencies]. Use "*" or "latest" to express no constraint (the package is installed without a version pin).

Group composition:

An entry whose name matches <project-name>[group,...] is not installed as a package - it expands the listed groups transitively. In the example above, "mypkg[data,monitoring]" = "*" under [optional-dependencies.all] means --extras all pulls in everything from both data and monitoring.

Third-party extras syntax (e.g. "testcontainers[mongodb,redis]") passes through to pip unchanged.


[run]#

Defaults for jac run:

[run]
session = ""            # Session name for persistence
main = true             # Run as main module
cache = true            # Use bytecode cache
topology_index = true   # Build topology index for graph query optimization
diagnostics = "error"   # Diagnostic verbosity: "error", "all", or "none"

The diagnostics setting controls how compilation errors and warnings are reported during jac run:

Value Behavior
"error" Show errors with full details, suppress warnings, exit code 1 on errors
"all" Show both errors and warnings, exit code 1 on errors
"none" Suppress all diagnostics, always exit code 0

The CLI flag -e / --diagnostics overrides this setting.


[serve]#

Defaults for jac start:

[serve]
port = 8000              # Server port
session = ""             # Session name
main = true              # Run as main module
cl_route_prefix = "cl"   # URL prefix for client apps
base_route_app = ""      # Client app to serve at /

# Optimistic-concurrency policy for concurrent check-then-create races
# (see Persistence -> Concurrent writes).
on_conflict = "retry"        # "retry": abort + replay so the loser converges
                             # "fail":  no replay, return HTTP 409 immediately
conflict_max_attempts = 5    # max walker/function attempts under "retry"
conflict_backoff_ms = 0      # linear backoff between replay attempts (0 = none)

on_conflict controls what happens when two concurrent requests race a "look it up, create it if missing" against the same node and the loser's commit is rejected. retry (default) re-runs the request against the now-current graph so it converges on the winner's node; fail surfaces a typed 409 write_conflict for the client to handle. See Persistence -> Concurrent writes: check-then-create for the full model.


[build]#

Build configuration:

[build]
typecheck = false   # Enable type checking
dir = ".jac"        # Build artifacts directory

The dir setting controls where all build artifacts are stored:

  • .jac/cache/ - Bytecode cache
  • .jac/venv/ - Project virtual environment
  • .jac/client/ - Client-side builds
  • .jac/data/ - Runtime data

[test]#

Defaults for jac test:

[test]
directory = ""          # Scopes no-argument `jac test` discovery (empty = walk project root)
filter = ""             # Filter pattern
verbose = false         # Verbose output
fail_fast = false       # Stop on first failure
max_failures = 0        # Max failures (0 = unlimited)

When directory is set, jac test with no file argument collects tests only from that directory (resolved against the project root), so application modules whose top-level with entry runs on import are not pulled into test collection.


[format]#

Defaults for jac fmt:

[format]
outfile = ""        # Output file (empty = in-place)

[check]#

Defaults for jac check:

[check]
print_errs = true   # Print errors to console

[check.lint]#

Configure which auto-lint rules are active during jac check --lint and jac check --lint --fix. Rules use a select/ignore model with two group keywords:

  • "default" - code-transforming rules only (safe, auto-fixable)
  • "all" - every rule, including unfixable rules like no-print
[check.lint]
select = ["default"]          # Code-transforming rules only (default)
ignore = ["combine-has"]      # Disable specific rules
exclude = []                  # File patterns to skip (glob syntax)

To enable all rules including warning-only rules:

[check.lint]
select = ["all"]              # Everything, including no-print

To add specific rules on top of defaults:

[check.lint]
select = ["default", "no-print"]  # Defaults + no-print warnings

To enable only specific rules:

[check.lint]
select = ["combine-has", "remove-empty-parens"]

Available lint rules:

Rule Name Code Description Group
staticmethod-to-static W3001 Convert @staticmethod decorator to static keyword default
combine-has W3002 Combine consecutive has statements with same modifiers default
combine-glob W3003 Combine consecutive glob statements with same modifiers default
init-to-can W3004 Convert def __init__ / def __post_init__ to can init / can postinit default
remove-empty-parens W3005 Remove empty parentheses from declarations (def foo()def foo) default
remove-kwesc W3006 Remove unnecessary backtick escaping from non-keyword names default
hasattr-to-null-ok W3007 Convert hasattr(obj, "attr") to null-safe access (obj?.attr) default
simplify-ternary W3008 Simplify x if x else default to x or default default
remove-future-annotations W3009 Remove import from __future__ { annotations } (not needed in Jac) default
fix-impl-signature W3010 Fix signature mismatches between declarations and implementations default
remove-import-semi W3011 Remove trailing semicolons from import from X { ... } default
no-print E3012 Error on bare print() calls (use console abstraction instead) all
strip-comments W3050 Remove all comments opt-in
strip-docstrings W3051 Remove all docstrings opt-in

Diagnostic codes can be suppressed inline with # jac:ignore[CODE] comments. See the full Errors & Warnings reference for all diagnostic codes.

Opt-in (deslop) rules:

strip-comments and strip-docstrings are destructive "deslop" rules: they delete content rather than restructure it. Unlike every other rule, they are never activated by select = ["all"] or select = ["default"]; they fire only when named explicitly. A project that wants them on by default lists them alongside its other selections:

[check.lint]
select = ["default", "strip-comments", "strip-docstrings"]

The two are independent, so you can strip comments while keeping docstrings (or vice versa). With a rule selected, jac fmt --lintfix removes the content and jac check reports it. They are also the rules driving jac precommit when configured.

Excluding files from lint:

Use exclude to skip files matching glob patterns:

[check.lint]
select = ["all"]
exclude = [
    "docs/*",
    "*/examples/*",
    "*/tests/*",
    "legacy_module.jac",
]

Patterns are matched against file paths relative to the project root. Use * for single-directory wildcards and ** for recursive matching.


[dot]#

Defaults for jac dot (graph visualization):

[dot]
depth = -1          # Traversal depth (-1 = unlimited)
traverse = false    # Traverse connections
bfs = false         # Use BFS (default: DFS)
edge_limit = 512    # Maximum edges
node_limit = 512    # Maximum nodes
format = "dot"      # Output format

[cache]#

Bytecode cache settings:

[cache]
enabled = true   # Enable caching
dir = "cache"    # Cache subdirectory under the build dir (i.e. .jac/cache).
                 # An absolute path relocates the cache wholesale.

[storage]#

Scale Configuration

The [storage] section is provided by the built-in scale subsystem (part of jaclang core). Its cloud backends (S3/GCS/Azure) require the relevant client libraries in the project venv -- declare the backend in config and run jac install to pull them in.

File storage configuration:

[storage]
storage_type = "local"       # Storage backend (local)
base_path = "./storage"      # Base directory for files
create_dirs = true           # Auto-create directories
Field Description Default
storage_type Storage backend type "local"
base_path Base directory for file storage "./storage"
create_dirs Automatically create directories true

Environment Variable Overrides:

Variable Description
JAC_STORAGE_TYPE Storage type (overrides config)
JAC_STORAGE_PATH Base directory (overrides config)
JAC_STORAGE_CREATE_DIRS Auto-create directories ("true"/"false")

Configuration priority: jac.toml > environment variables > defaults.

See Storage Reference for the full storage API.


Capability settings#

Built-in capabilities (byLLM, scale, the client framework) are configured in top-level tables named after the capability:

# byLLM settings (model identity split from call params)
[byllm.model]
default_model = "gpt-4o"
api_key = "${OPENAI_API_KEY}"

[byllm.call_params]
temperature = 0.7

# Server settings (scale)
[scale.server]
port = 8000
host = "0.0.0.0"
docs_enabled = true              # Set to false to disable /docs, /redoc, /openapi.json

# Webhook settings (scale)
[scale.webhook]
secret = "your-webhook-secret-key"
signature_header = "X-Webhook-Signature"
verify_signature = true
api_key_expiry_days = 365

# Kubernetes version pinning (scale) -- scale, byLLM, the MCP server, and the
# client/desktop framework all ship inside the `jac` binary, so they need no
# pinning. Use this only to pin a genuine third-party PyPI plugin for the pod image.
[scale.kubernetes.plugin_versions]
my_plugin = "1.2.3"          # pin a version, or "none" to skip, "latest" to track

Prometheus Metrics (scale):

[scale.monitoring]
enabled = true
endpoint = "/metrics"
namespace = "myapp"
walker_metrics = true

See Prometheus Metrics for details.

Kubernetes Secrets (scale):

[scale.secrets]
OPENAI_API_KEY = "${OPENAI_API_KEY}"
DATABASE_PASSWORD = "${DB_PASS}"

See Kubernetes Secrets for details.

See also Scale Webhooks and Kubernetes Deployment for more options.

Built-in Local Models (byllm):

[byllm.model]
default_model = "local:gemma-4-e4b"   # in-process llama.cpp; no API key, no daemon

[byllm.local]
default_alias  = "gemma-4-e4b"        # used when default_model is unset
n_gpu_layers   = -1                   # -1 = offload all layers to GPU; 0 = CPU only
n_ctx          = 0                    # 0 = use the alias's bundled default
auto_download  = false                # true = skip the first-run TTY prompt

Bundled aliases are downloaded as Q4_K_M GGUFs into ~/.cache/jac/models/<alias>/ on first use and managed via jac model list/pull/rm. See Built-in Local Models for the full reference and jac model for cache management.

Frontend Framework (jac-client):

[client]
framework = "react"   # "react" (default), "solid" (experimental), or "preact"

Controls which JavaScript framework the cl compiler target emits. The default is "react".

Value Status Notes
"react" Stable Default. Uses React hooks and @vitejs/plugin-react.
"solid" Experimental Uses Solid signals and vite-plugin-solid. API may change.
"preact" Stable Drop-in React alternative with a smaller bundle.

Switching frameworks automatically adjusts the installed npm packages and the generated Vite config; no other changes are needed. Delete your .jac/client/ build cache after switching so the previous framework's output is not mixed in.

Solid support is experimental

The solid framework target is under active development. Some jac-client features (error boundaries, suspense slots, advanced routing) may not yet be fully supported. Check the release notes before upgrading.

Import Path Aliases (jac-client):

[client.paths]
"@components/*" = "./components/*"
"@utils/*" = "./utils/*"
"@shared" = "./shared/index"

Defines custom import aliases applied to Vite resolve.alias, TypeScript compilerOptions.paths, and the Jac module resolver. See jac-client Import Path Aliases for details.

NPM Registry Configuration (jac-client):

[client.npm.scoped_registries]
"@mycompany" = "https://npm.pkg.github.com"

[client.npm.auth."//npm.pkg.github.com/"]
_authToken = "${NODE_AUTH_TOKEN}"

This generates an .npmrc file during dependency installation for private/scoped npm packages. See jac-client NPM Registry Configuration for details.

Build-Time Constants (jac-client):

Define global variables that are replaced at compile time in client code via the [client.vite.define] section:

[client.vite.define]
"globalThis.API_URL" = "\"https://api.example.com\""
"globalThis.FEATURE_ENABLED" = true
"globalThis.BUILD_VERSION" = "\"1.2.3\""

These values are inlined by Vite during bundling. String values must be double-quoted (JSON-encoded). In client code, access them directly:

cl {
    def:pub Footer() -> JsxElement {
        return <p>Version: {globalThis.BUILD_VERSION}</p>;
    }
}

[scripts]#

Custom command shortcuts:

[scripts]
dev = "jac run main.jac"
test = "jac test -v"
build = "jac build main.jac -t"
lint = "jac check . --lint --fix"
format = "jac fmt ."

Run with:

jac x dev
jac x test

[environments]#

Environment-specific overrides:

[environment]
default_profile = "development"

[environments.development]
[environments.development.run]
cache = false
[environments.development.byllm]
model = "gpt-3.5-turbo"

[environments.production]
inherits = "development"
[environments.production.run]
cache = true
[environments.production.byllm]
model = "gpt-4"

Activate a profile:

JAC_PROFILE=production jac run main.jac

Environment Variable Interpolation#

Use environment variable interpolation inside jac.toml values:

[byllm.model]
api_key = "${OPENAI_API_KEY}"                       # Required
default_model = "${MODEL:-gpt-4o-mini}"             # With default
base_url = "${BASE_URL:?Base URL is required}"      # Required with error
Syntax Description
${VAR} Use variable (error if not set)
${VAR:-default} Use default if not set
${VAR:?error} Custom error if not set

[project.include]#

Controls which files and directories jac build --as wheel collects into the wheel.

Note: Earlier releases used a separate [package] / [package.include] section for publishing metadata. As of jaclang 0.15, [package] has been merged into [project] -- all publishing fields now live under [project] (see above), and file-inclusion rules live under [project.include]. Plain [package] tables are no longer read.

[project.include]
# Explicit list of package directories to include.
# Defaults to a directory matching the package name (hyphens replaced with underscores).
packages = ["mylib", "mylib_utils"]

[project.include.data]
# "*" sets global file patterns for all packages.
"*" = ["**/*.jac", "**/*.py", "**/*.pyi", "py.typed"]

# Per-package overrides add extra patterns on top of globals.
mylib = ["**/*.lark", "data/*.json", "templates/**/*"]
Key Description
packages Glob list of package directories to ship. Defaults to one directory named after the project (hyphens → underscores).
data Map of file-glob patterns. The "*" key sets global patterns for every package; a per-package key adds extra patterns on top.

Simple patterns without a path separator (e.g. "*.jac") are matched recursively, so sub-packages are covered automatically.

Default included patterns (when [project.include.data] is absent):

Pattern Description
**/*.jac Jac source files
**/*.py Python source files
**/*.pyi Type stub files
**/*.lark Lark grammar files
**/py.typed PEP 561 type marker
**/*.jir Pre-compiled JIR bytecode (collected if already present -- see jac build)
_precompiled/manifest.json JIR precompile manifest

Always excluded (regardless of patterns):

  • Directories: .jac/, __pycache__/, dist/, build/, venv/, .venv/, env/, .git/, .hg/, node_modules/, *.egg-info/
  • File suffixes: .pyc

[entrypoints]#

Declare console scripts and other entry-point groups. Maps directly to entry_points.txt in the wheel's .dist-info.

[entrypoints.scripts]
# Installs a "mylib" CLI command pointing to mylib.cli:main
mylib = "mylib.cli:main"

The [entrypoints.scripts] group is written as [console_scripts] in entry_points.txt, which is the standard pip convention for installing CLI commands. After a user runs pip install mylib, the mylib command is available on their PATH.

Any other [entrypoints.<group>] table is written through to the wheel metadata verbatim, for consumers that discover packages via importlib.metadata.entry_points(). (Jac itself no longer loads any entry-point group at startup -- the former jac plugin group is defunct.)


CLI Override#

Most settings can be overridden via CLI flags:

# Override run settings
jac run --no-cache main.jac

# Override test settings
jac test --verbose -x

# Override serve settings
jac start --port 3000

Complete Example#

[project]
name = "my-ai-app"
version = "1.0.0"
description = "An AI-powered application"
entry-point = "main.jac"

[dependencies]
byllm = ">=0.4.8"
requests = ">=2.28.0"

[dev-dependencies]
pytest = ">=8.0.0"

[run]
main = true
cache = true
topology_index = true

[serve]
port = 8000
cl_route_prefix = "cl"

[test]
directory = "tests"
verbose = true

[build]
typecheck = true
dir = ".jac"

[check.lint]
select = ["all"]
ignore = []
exclude = []

[byllm.model]
default_model = "${LLM_MODEL:-gpt-4o-mini}"
api_key = "${OPENAI_API_KEY}"

[scripts]
dev = "jac run main.jac"
test = "jac test"
lint = "jac check . --lint --fix"

.jacignore#

The .jacignore file controls which Jac files are excluded from compilation and analysis. Place it in the project root.

Format#

One pattern per line, similar to .gitignore:

# Comments start with #
vite_client_bundle.impl.jac
test_fixtures/
*.generated.jac

Each line is a filename or pattern that should be skipped during Jac compilation passes (type checking, formatting, etc.).


Environment Variables#

General#

Variable Description
NO_COLOR Disable colored terminal output
NO_EMOJI Disable emoji in terminal output
JAC_PROFILE Activate a configuration profile (e.g., production)
JAC_BASE_PATH Override base directory for data/storage
JAC_DATA_PATH Override the base directory for application data (graph storage, user db)
JACPATH Colon-separated extra search path for Jac module resolution (like PYTHONPATH)
JAC_SCHEMA_REPAIR Schema-drift handling on load: repair (default) or strict
JAC_STRICT_PERMISSIONS Enable strict permission checking for security-sensitive operations (1/true)

Storage#

Variable Description
JAC_STORAGE_TYPE Storage backend type
JAC_STORAGE_PATH Base directory for file storage
JAC_STORAGE_CREATE_DIRS Auto-create directories

Scale: Database#

Variable Description
MONGODB_URI MongoDB connection URI
REDIS_URL Redis connection URL
FIRESTORE_PROJECT_ID Firestore / Firebase project ID
FIREBASE_PROJECT_ID Shared Firebase project ID fallback for Auth SSO, Firestore, Storage

Project ID vars (FIREBASE_AUTH_PROJECT_ID, FIRESTORE_PROJECT_ID, JAC_STORAGE_FIREBASE_PROJECT_ID, JAC_STORAGE_GCS_PROJECT_ID) override FIREBASE_PROJECT_ID when set.

Scale: Authentication#

Variable Description Default
JWT_SECRET Secret key for JWT signing supersecretkey
JWT_ALGORITHM JWT algorithm HS256
JWT_EXP_DELTA_DAYS Token expiration in days 7
SSO_HOST SSO callback host URL http://localhost:8000/sso
SSO_GOOGLE_CLIENT_ID Google OAuth client ID None
SSO_GOOGLE_CLIENT_SECRET Google OAuth client secret None
EMAILER_SMTP_PASSWORD SMTP password for the built-in email sender None

Scale: Microservices#

Variable Description
JAC_SV_ROUTES JSON object mapping service module names to URL route prefixes
JAC_SV_<MODULE>_URL Point an sv import of <MODULE> at a remote provider URL

Client#

Variable Description
JAC_CLIENT_SKIP_NPM_INSTALL Skip npm install during client build setup
JAC_MOBILE_PLATFORM Mobile platform selection for dev/build (auto, android, ios)

Scale: Webhooks#

Variable Description
WEBHOOK_SECRET Secret for webhook HMAC signatures
WEBHOOK_SIGNATURE_HEADER Header name for signature
WEBHOOK_VERIFY_SIGNATURE Enable signature verification
WEBHOOK_API_KEY_EXPIRY_DAYS API key expiry in days

Scale: Kubernetes#

Deployment settings (app name, namespace, node port, CPU/memory requests and limits, registry credentials) are configured in jac.toml under [scale.kubernetes] -- see the Kubernetes reference. At deploy time, jac-scale injects these variables into every pod:

Variable Description
K8S_APP_NAME Application name (used by observability and admin tooling inside the pod)
K8S_NAMESPACE Namespace the workload runs in

See Also#