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:
authorsandmaintainersalso accept a plain string form (authors = ["Your Name"]), but the{ name, email }table form is recommended -- it is what published packages'jac.tomlfiles 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 requestswithout 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 installs2.32.5,jac.tomlgetsrequests = "~=2.32". Thejac updatecommand 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:
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:
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:
[check]#
Defaults for jac check:
[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 likeno-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:
To add specific rules on top of defaults:
To enable only specific rules:
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:
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):
See Prometheus Metrics for details.
Kubernetes Secrets (scale):
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):
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:
[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:
[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:
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:
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#
- CLI Reference - Command-line interface documentation
- Publishing Packages - Building and uploading wheels to PyPI