Skip to content

Configuration

Terminology: This specification uses RFC 2119 keywords (MUST, SHOULD, MAY, etc.) to indicate requirement levels.

This document describes the .structyl/config.json configuration file.

Overview

Every Structyl project requires a .structyl/config.json file at the project root. This file:

  • Marks the project root (no other marker files needed)
  • Defines project metadata
  • Configures targets (languages and auxiliary)
  • Specifies test and documentation settings

Non-Goals

The following are explicitly out of scope for the configuration system:

  • Configuration inheritance/includes — Each project has exactly one config.json. No support for splitting configuration across files or inheriting from parent configs.
  • Dynamic configuration — Configuration is static JSON evaluated at load time. No template expressions, conditionals, or runtime evaluation.
  • Environment-specific variants — No config.dev.json / config.prod.json pattern. Use environment variables in commands for environment-specific behavior.
  • Automatic migration — Schema changes between major versions require manual migration. Structyl does not auto-upgrade configuration files.
  • Secret management — Credentials and secrets MUST NOT be stored in configuration. Use environment variables or secret management tools.

File Location

The configuration file MUST be named .structyl/config.json and placed at the project root directory. Structyl locates the project root by walking up from the current directory until it finds this file.

Format

Structyl uses JSON for configuration. Rationale:

  • Strict syntax — No ambiguity in parsing
  • Native Go supportencoding/json is battle-tested
  • IDE support — JSON Schema enables autocomplete and validation
  • No hidden complexity — Unlike YAML's multiple specs and implicit typing

For validation, use the JSON Schema (published URL: https://structyl.akinshin.dev/schema/config.json). A separate toolchains schema validates custom toolchain definitions when extending the built-in toolchains.

Configuration Sections

$schema (optional)

IDE integration field for JSON Schema validation. Structyl ignores this field during parsing per the Extensibility Rules.

json
{
  "$schema": "https://structyl.akinshin.dev/schema/config.json"
}

See Schema Validation for details on local vs published schema URLs.

toolchains.json Reference File

When structyl init creates a project, it copies .structyl/toolchains.json containing built-in toolchain definitions. This file:

  • Provides IDE autocompletion for toolchain names in configuration
  • Documents available commands for each toolchain
  • Is NOT read by Structyl at runtime (the built-in toolchain template is authoritative)

To refresh after a Structyl upgrade: delete the file and re-run structyl init on an existing project.

project (required)

Project metadata used in documentation and package generation.

json
{
  "project": {
    "name": "myproject",
    "description": "A multi-language library",
    "homepage": "https://myproject.dev",
    "repository": "https://github.com/user/myproject",
    "license": "MIT"
  }
}
FieldRequiredDescription
nameYesProject name
descriptionNoShort description
homepageNoProject website URL
repositoryNoSource repository URL
licenseNoSPDX license identifier

Project Name Constraints:

  • Length: 1-128 characters
  • MUST start with a lowercase letter (a-z)
  • MAY contain lowercase letters, digits, and hyphens
  • Hyphens MUST NOT be consecutive (my--project is invalid)
  • Hyphens MUST NOT be trailing (my-project- is invalid)
  • Pattern: ^[a-z][a-z0-9]*(-[a-z0-9]+)*$

Validation Error: Invalid project names cause exit code 2 with message: project.name: must match pattern ^[a-z][a-z0-9]*(-[a-z0-9]+)*$

version

Version management configuration. See version-management.md for details.

json
{
  "version": {
    "source": ".structyl/PROJECT_VERSION",
    "files": [
      {
        "path": "cs/Directory.Build.props",
        "pattern": "<Version>.*?</Version>",
        "replace": "<Version>{version}</Version>"
      }
    ]
  }
}

Version file fields:

FieldTypeDefaultDescription
pathstringRequiredFile path relative to project root
patternstringRequiredRegex pattern to match (RE2 syntax)
replacestringRequiredReplacement string with {version} placeholder
replace_allbooleanfalseReplace all matches instead of requiring exactly one

By default, the pattern MUST match exactly once. Set replace_all: true for files with multiple version occurrences.

Note on placeholder syntax: Version file replacements use {version} syntax (curly braces without $), while command variable interpolation uses ${version} syntax. This distinction exists because version file patterns use regex replacement where $ has special meaning (backreferences). For command variables, see commands.md.

targets

Build targets configuration. See targets.md for details.

json
{
  "targets": {
    "cs": {
      "type": "language",
      "title": "C#",
      "toolchain": "dotnet"
    },
    "py": {
      "type": "language",
      "title": "Python",
      "toolchain": "uv",
      "commands": {
        "demo": "uv run python examples/demo.py"
      }
    },
    "img": {
      "type": "auxiliary",
      "title": "Image Generation",
      "commands": {
        "build": "python scripts/generate_images.py",
        "clean": "rm -rf output/images"
      }
    }
  }
}

Target Fields

FieldTypeDefaultDescription
typestringRequired¹"language" or "auxiliary"
titlestringRequiredDisplay name
toolchainstringAuto-detectToolchain preset (see toolchains.md)
toolchain_versionstringFrom toolchainOverride mise tool version for this target
directorystringTarget keyDirectory path relative to root
cwdstringdirectoryWorking directory for commands
commandsobjectFrom toolchainCommand definitions/overrides
varsobject{}Variables for command interpolation
envobject{}Environment variables
depends_onarray[]Targets that must build first
demo_pathstringNonePath to demo source (for doc generation)

¹ Required in explicit mode. In auto-discovery mode, type is inferred from the slug. See targets.md for details.

Validation Errors:

Missing FieldExit CodeError Message
type2targets.{name}: type is required
title2targets.{name}: title is required

Command Definitions

Commands can be defined in several forms:

json
{
  "commands": {
    "build": "cargo build",
    "build:release": "cargo build --release",

    "check": ["lint", "format-check"],

    "bench": null
  }
}

Supported command definition types:

TypeDescription
stringShell command to execute
arraySequence of command references (executed in order)
nullCommand is explicitly disabled for this target

Use the colon (:) naming convention for command variants. See commands.md for details.

toolchains

Custom toolchain definitions. See toolchains.md for details.

json
{
  "toolchains": {
    "cargo-workspace": {
      "extends": "cargo",
      "commands": {
        "build": "cargo build --workspace",
        "test": "cargo test --workspace"
      }
    }
  }
}

tests

Reference test system configuration. See test-system.md for details.

json
{
  "tests": {
    "directory": "tests",
    "pattern": "**/*.json",
    "comparison": {
      "float_tolerance": 1e-9,
      "tolerance_mode": "relative"
    }
  }
}

Note: The pattern field configures Structyl's internal test runner. The public pkg/testhelper Go package uses *.json (immediate directory only), not recursive patterns. See test-system.md for details.

documentation

Documentation generation settings.

json
{
  "documentation": {
    "readme_template": "templates/README.template.md",
    "placeholders": ["version", "features", "demo"]
  }
}
FieldTypeDefaultDescription
readme_templatestringNonePath to README template file
placeholdersstring[][]Supported placeholder names

docker

Docker configuration. See docker.md for details.

json
{
  "docker": {
    "compose_file": "docker-compose.yml",
    "env_var": "STRUCTYL_DOCKER",
    "services": {
      "cs": { "base_image": "mcr.microsoft.com/dotnet/sdk:8.0" },
      "py": { "base_image": "python:3.13-slim" }
    },
    "targets": {
      "cs": {
        "platform": "linux/amd64",
        "environment": { "CI": "true" }
      }
    }
  }
}
FieldTypeDefaultDescription
compose_filestringdocker-compose.ymlPath to compose file
env_varstringSTRUCTYL_DOCKEREnv var to enable Docker mode
servicesobject{}Per-target Docker service overrides for image building
targetsobject{}Per-target Docker runtime configuration

docker.services fields (per-target image building configuration):

FieldTypeDefaultDescription
services[target].base_imagestringNoneBase Docker image
services[target].dockerfilestringNoneCustom Dockerfile path
services[target].platformstringNoneTarget platform (e.g., linux/amd64)
services[target].volumesstring[][]Additional volume mounts

See docker.md for complete volume mount semantics and cache volume recommendations.

docker.targets fields (per-target runtime configuration):

FieldTypeDefaultDescription
targets[target].platformstringNoneTarget platform (e.g., linux/amd64)
targets[target].cache_volumestringNoneVolume path for build cache
targets[target].entrypointstringNoneContainer entrypoint override
targets[target].environmentobject{}Additional environment variables

mise

Mise build tool integration configuration.

json
{
  "mise": {
    "auto_generate": false,
    "extra_tools": {
      "golangci-lint": "latest"
    }
  }
}
FieldTypeDefaultDescription
auto_generatebooleantrueRegenerate mise.toml before target command execution. When true, synchronizes tool versions with toolchain config. Set false and use structyl mise sync for manual control.
extra_toolsmap[string]string{}Additional mise tools to install

Semantics:

  • When auto_generate: true (or absent/omitted), Structyl regenerates mise.toml before executing target commands. This ensures mise tool versions stay synchronized with toolchain requirements.

    ⚠️ Warning: Regeneration overwrites the entire mise.toml file. User-added tasks, environment variables, or custom sections are NOT preserved. For mixed usage (Structyl-managed tasks alongside custom mise tasks), set auto_generate: false and run structyl mise sync manually when toolchain configuration changes.

    Commands that trigger regeneration:

    • Standard commands: clean, restore, build, test, check, check:fix, bench, demo, doc, pack, publish
    • CI commands: ci, ci:release
    • Any custom command executed via structyl <custom-cmd> [target]

    Commands that do NOT trigger regeneration:

    • Query commands: version, targets, config validate
    • Generation commands: dockerfile, github, mise sync, completion
    • Release workflow: release
    • Docker-specific: docker-build, docker-clean
    • Project initialization: init
    • Upgrade: upgrade

    Note: This command list is informative and subject to change. Future versions MAY add new commands to either category.

  • When auto_generate: false is explicitly set, Structyl does not auto-regenerate mise.toml. Use structyl mise sync to manually regenerate when needed.

  • extra_tools entries are merged with toolchain-detected tools and written to mise.toml. Keys are tool names, values are version specifiers (e.g., "latest", "1.54.0", ">=1.50").

release

Release workflow configuration.

json
{
  "release": {
    "tag_format": "v{version}",
    "extra_tags": ["go/v{version}"],
    "pre_commands": ["mise run check"],
    "remote": "origin",
    "branch": "main"
  }
}
FieldTypeDefaultDescription
tag_formatstringv{version}Git tag format ({version} replaced)
extra_tagsstring[][]Additional tags to create (e.g., go/v{version} for Go module versioning)
pre_commandsstring[][]Commands to run before release
remotestringoriginGit remote for --push flag
branchstringmainBranch to release from

Note: The remote field specifies the git remote used by structyl release --push. If omitted, defaults to origin.

ci

Custom CI pipeline configuration. Overrides the default ci command steps.

json
{
  "ci": {
    "steps": [
      {
        "name": "restore",
        "target": "all",
        "command": "restore"
      },
      {
        "name": "lint",
        "target": "all",
        "command": "check",
        "depends_on": ["restore"]
      }
    ]
  }
}
FieldTypeDefaultDescription
stepsarray[]CI pipeline step definitions
steps[].namestringRequiredStep name for display and references
steps[].targetstringRequiredTarget name or "all"
steps[].commandstringRequiredStructyl command name (e.g., build, test, check)
steps[].flagsstring[][]Additional flags appended to the command invocation
steps[].depends_onstring[][]Step names that must complete first
steps[].continue_on_errorbooleanfalseContinue pipeline if step fails

Example with flags:

json
{
  "ci": {
    "steps": [
      {
        "name": "test-verbose",
        "target": "rs",
        "command": "test",
        "flags": ["--", "--nocapture"]
      }
    ]
  }
}

When executed, flags are appended to the resolved command: cargo test -- --nocapture.

artifacts

Artifact collection configuration for CI builds.

json
{
  "artifacts": {
    "output_dir": "artifacts",
    "targets": {
      "cs": [{ "source": "bin/Release/*.nupkg", "destination": "nuget" }],
      "py": [{ "source": "dist/*.whl", "destination": "wheels" }]
    }
  }
}
FieldTypeDefaultDescription
output_dirstringartifactsBase output directory for artifacts
targetsobject{}Per-target artifact specifications
targets[target][].sourcestringRequiredGlob pattern for source files
targets[target][].destinationstring""Subdirectory within output_dir
targets[target][].renamestringNoneRename pattern for collected files

Minimal Configuration

The smallest valid configuration:

json
{
  "project": {
    "name": "myproject"
  }
}

With this minimal config, Structyl uses all defaults:

  • Version source: .structyl/PROJECT_VERSION
  • Tests directory: tests
  • Targets: auto-discovered from directories with recognized toolchain files

Full Configuration Example

json
{
  "project": {
    "name": "pragmastat",
    "description": "Multi-language statistical library",
    "homepage": "https://pragmastat.dev",
    "repository": "https://github.com/user/pragmastat",
    "license": "MIT"
  },
  "version": {
    "source": ".structyl/PROJECT_VERSION",
    "files": [
      {
        "path": "cs/Directory.Build.props",
        "pattern": "<Version>.*?</Version>",
        "replace": "<Version>{version}</Version>"
      },
      {
        "path": "py/pyproject.toml",
        "pattern": "version = \".*?\"",
        "replace": "version = \"{version}\""
      },
      {
        "path": "rs/pragmastat/Cargo.toml",
        "pattern": "version = \".*?\"",
        "replace": "version = \"{version}\""
      }
    ]
  },
  "targets": {
    "cs": {
      "type": "language",
      "title": "C#",
      "toolchain": "dotnet",
      "vars": {
        "test_project": "Pragmastat.Tests",
        "demo_project": "Pragmastat.Demo"
      },
      "commands": {
        "test": "dotnet run --project ${test_project}",
        "demo": "dotnet run --project ${demo_project}"
      }
    },
    "go": {
      "type": "language",
      "title": "Go",
      "toolchain": "go",
      "commands": {
        "demo": "go run ./demo"
      }
    },
    "kt": {
      "type": "language",
      "title": "Kotlin",
      "toolchain": "gradle"
    },
    "py": {
      "type": "language",
      "title": "Python",
      "toolchain": "uv",
      "commands": {
        "demo": "uv run python examples/demo.py"
      }
    },
    "r": {
      "type": "language",
      "title": "R",
      "commands": {
        "build": "R CMD build .",
        "test": "Rscript -e \"testthat::test_local()\"",
        "clean": "rm -rf *.tar.gz"
      }
    },
    "rs": {
      "type": "language",
      "title": "Rust",
      "toolchain": "cargo",
      "cwd": "rs/pragmastat"
    },
    "ts": {
      "type": "language",
      "title": "TypeScript",
      "toolchain": "pnpm",
      "commands": {
        "demo": "pnpm exec ts-node examples/demo.ts"
      }
    },
    "img": {
      "type": "auxiliary",
      "title": "Image Generation",
      "commands": {
        "build": "python scripts/generate_images.py",
        "clean": "rm -rf output/images"
      }
    },
    "pdf": {
      "type": "auxiliary",
      "title": "PDF Manual",
      "depends_on": ["img"],
      "commands": {
        "build": "latexmk -pdf manual.tex",
        "build:release": "latexmk -pdf manual.tex",
        "clean": "latexmk -C"
      }
    },
    "web": {
      "type": "auxiliary",
      "title": "Website",
      "depends_on": ["img", "pdf"],
      "commands": {
        "restore": "npm ci",
        "build": "npm run build",
        "build:release": "npm run build -- --mode production",
        "serve": "npm run serve",
        "clean": "rm -rf dist/"
      }
    }
  },
  "tests": {
    "directory": "tests",
    "comparison": {
      "float_tolerance": 1e-9,
      "tolerance_mode": "relative",
      "nan_equals_nan": true
    }
  },
  "docker": {
    "compose_file": "docker-compose.yml",
    "env_var": "STRUCTYL_DOCKER"
  }
}

Variable Syntax

Structyl uses two different placeholder syntaxes depending on context:

ContextSyntaxExampleReason
Command interpolation${var}${version}, ${target}Shell-like syntax familiar to developers
Version file replacement{var}{version}Avoids conflict with regex $ backreferences

Command variables (used in commands definitions):

json
{
  "commands": {
    "build": "dotnet build /p:Version=${version}"
  }
}

Version file placeholders (used in version.files[].replace):

json
{
  "version": {
    "files": [{
      "path": "Cargo.toml",
      "pattern": "version = \".*?\"",
      "replace": "version = \"{version}\""
    }]
  }
}

The distinction exists because version file patterns use regex syntax where $ has special meaning (backreferences like $1). Using {version} avoids ambiguity. See commands.md for the full list of available command variables.

Schema Validation

To enable IDE autocomplete and validation, add the schema reference:

json
{
  "$schema": "https://structyl.akinshin.dev/schema/config.json",
  "project": {
    "name": "myproject"
  }
}

Or use the local schema file (relative to project root):

json
{
  "$schema": "./schema/config.schema.json",
  "project": {
    "name": "myproject"
  }
}

Note: The local schema file is named config.schema.json following the .schema.json naming convention. The published URL (config.json) redirects to this same file on the documentation server. Use the published URL for external references and the local path when the schema is bundled with your project.

Schema vs Runtime Validation

The JSON Schema is designed for IDE validation (autocomplete, syntax checking). Structyl's runtime parser applies lenient validation to support forward compatibility:

AspectJSON Schema (IDE)Runtime (Structyl)
Unknown fieldsMay rejectIgnored with warning
PurposeEditor assistanceExecution
StrictnessFull schema validationRequired fields only

This design allows newer configurations to be opened in IDEs using older schema versions (with warnings) while ensuring Structyl itself remains forward-compatible per Extensibility Rule 3.

© 2026 Andrey Akinshin MIT