Numi
Numi is a deterministic resource code generator for Apple projects. It turns asset catalogs, localization resources, fonts, and file lists into generated code using built-in or custom templates, with first-class support for multi-module repositories and CI verification.
Why Numi
- Generates code from
.xcassets,.strings,.xcstrings, fonts, and file-based inputs - Supports built-in templates and custom Minijinja templates
- Auto-discovers job-matched custom templates from
Templates/ortemplates/when a job omitstemplate - Works well in modular repos through workspace manifests and shared defaults
- Avoids rewriting unchanged outputs — deterministic, byte-stable generation
- Verifies checked-in generated files with
numi check - Supports per-job and workspace-level generation hooks, including inline shell hooks for tasks like formatting
- Incremental caching — asset catalogs and strings files are parsed once and skipped when untouched
Numi started as a modern SwiftGen replacement path, but its core model is broader: parse resources into a stable context, then render the output shape your project actually wants.
Install
With the install script (macOS and Linux):
|
With Homebrew:
With Cargo:
Set NUMI_VERSION to install a specific release, or NUMI_INSTALL_DIR to change the install location.
Quick Start
If your repo has a root workspace manifest, numi generate and numi check auto-detect it — from the repo root or any member directory.
A Minimal Config
= 1
[]
= "internal"
[]
= "module"
[]
= "Generated/Assets.swift"
[[]]
= "xcassets"
= "Resources/Assets.xcassets"
[]
= "swift"
= "swiftui-assets"
The starter config shipped with numi init lives in docs/examples/starter-numi.toml.
Implicit Custom Templates
If a job does not set template.path or template.builtin, Numi will look beside the active
numi.toml for one of these files:
Templates/<job>.jinja
Templates/<job>.template.jinja
templates/<job>.jinja
templates/<job>.template.jinja
Examples for a strings job:
Templates/strings.jinja
Templates/strings.template.jinja
Set auto_lookup = false under [jobs.<name>.template] to disable this fallback for a job. In
workspace mode, the same setting is available under [workspace.defaults.jobs.<name>.template].
Workspace manifests can also provide a custom template for every job with one matching input type:
[]
= "Templates/strings.template.jinja"
Exact job defaults still win over type defaults when both apply.
Custom Template Variables
Jobs can pass project-specific knobs to custom or built-in templates through a job-local
variables table:
[]
= "Generated/Strings.swift"
[]
= "Templates/strings.jinja"
[]
= "AppStrings"
= "Bundle.module"
= true
= ["Foundation"]
Templates read those values from the top-level variables object:
{% for import in variables.imports %}import {{ import }}
{% endfor %}
enum {{ variables.enum_name }} {}
Command Reference
| Command | Purpose |
|---|---|
numi generate |
Generate outputs for one config or workspace |
numi check |
Check whether generated outputs are up to date |
numi init |
Write a starter numi.toml in the current directory |
numi config locate |
Print the resolved config path |
numi config print |
Print the resolved manifest with defaults materialized |
numi dump-context --job <name> |
Print the template context for one job |
Current Limitations
.xcstringsplural and device-specific variations are currently skipped with warnings- the shipped Swift
l10nbuilt-in is still conservative and does not yet expose every placeholder-aware output shape a custom template can implement dump-contextandconfig printare single-config tools and reject workspace manifests
Docs
- docs/context-schema.md: stable template context reference
- docs/migration-from-swiftgen.md: migration notes from SwiftGen
- docs/spec.md: full product and technical specification
- docs/examples/starter-numi.toml: starter manifest
- docs/crates-io-release.md: release workflow notes
For detailed guides on workspace manifests, generation hooks, custom templates, supported inputs, built-in templates, and CI integration, see the numi website.
Development