
Baker is a command-line tool that helps you quickly scaffold new projects. It supports language-independent hooks for automating routine tasks. Baker is written in Rust and distributed as a standalone binary. Precompiled binaries are available for popular platforms on the releases page.
- Installation
- Project template example
- Recipes
- Hooks
- Questions
- Built-in Filters
- Comparing Baker to other project generators
- Community Templates
You can install Baker using one of the following methods:
brew install aliev/tap/baker
curl --proto '=https' --tlsv1.2 -LsSf https://github.com/aliev/baker/releases/latest/download/baker-installer.sh | sh
powershell -ExecutionPolicy Bypass -c "irm https://github.com/aliev/baker/releases/latest/download/baker-installer.ps1 | iex"
Prebuilt binaries for all supported platforms are available on the releases page.
To get started, you can use the examples/demo template, which demonstrates the core features of Baker:
β Template configuration.
βββ baker.yaml
β
β The content of files with the `.baker.j2` extension will be processed by the templating engine
βββ CONTRIBUTING.md.baker.j2
β
β any other files will be copied as is,
βββ README.md
β
β unless they are listed in .bakerignore.
βββ .bakerignore
β
β File names can be templated
βββ {{project_slug}}
β βββ __init__.py
β
β any template features can be used, such as conditions:
βββ {% if use_tests %}tests{% endif %}
βββ __init__.py
As a quick start, you can run the following command to generate a project:
baker examples/demo my-project
Each component of this template is described in detail below.
The baker.yaml
file defines the directory as a template. It contains template settings and questions to be prompted to the user:
schemaVersion: v1
questions:
project_name:
type: str
help: Please enter the name of your project
project_author:
type: str
help: "Please enter the author's name for {{project_name}}"
project_slug:
type: str
help: Please enter the project slug (or press Enter to use the default)
default: "{{ project_name|lower|replace(' ', '_') }}"
use_tests:
type: bool
help: Will your project include tests?
default: true
The values of the help
and default
keys can include templates for value substitution. Each subsequent question has access to the answers of the previous ones as demonstrated in project_author
and project_slug
.
In addition to YAML, Baker also supports JSON due to its backward compatibility with JSON. If multiple configuration files exist in the template directory, Baker will load them in the following order of priority: baker.json
, baker.yaml
, and baker.yml
.
The content of files with the .baker.j2
extension will be processed by the templating engine and written to the target directory. The resulting files in the target directory will not include the .baker.j2
extension in their names.
The content of such files can include the questions
keys, which will be replaced by the corresponding user-provided answers during processing. Baker uses the MiniJinja this purpose. For more details on the syntax and capabilities of the templating engine, please refer to the MiniJinja documentation.
Example:
Content of CONTRIBUTING.md.baker.j2
# {{project_name}}
Author: {{project_author}}
Processed file in target directory: CONTRIBUTING.md
Content of CONTRIBUTING.md:
# MyAwesomeProject
Author: John Doe
Note: The template suffix (default: .baker.j2) is fully configurable in your baker.yaml file using the template_suffix option. You can set it to any value, as long as it starts with a . and has at least one character after the dot (e.g., .tpl, .jinja, .tmpl). This allows you to use custom extensions for your template files.
Example:
schemaVersion: v1
template_suffix: ".tpl"
With this configuration, files ending with .tpl will be processed as templates instead of .baker.j2.
File and directory names can be templated to dynamically adjust based on user input.
Example:
project_name:
type: str
help: Please enter the name of your project
project_slug:
type: str
help: Please enter the project slug (or press Enter to use the default)
default: "{{ project_name|lower|replace(' ', '_') }}"
βββ {{project_slug}}
β βββ __init__.py
This will create a directory named according to the value of the project_slug
provided by the user.
File and directory names can include conditions that control their creation. If a condition evaluates to false
, the corresponding file or directory will not be created. This feature is especially useful with Yes / No type questions, allowing you to dynamically include or exclude specific files and directories based on user responses.
Example:
use_tests:
type: bool
help: Will your project include tests?
default: true
βββ {% if use_tests %}tests{% endif %}
βββ __init__.py
In this example, if the user answers "no" the tests
directory will not be created.
The .bakerignore
file in the template root is used to exclude files and directories from being copied from the template. Bakerignore uses Globset syntax.
By default, Baker ignores the following files and patterns:
const DEFAULT_IGNORE_PATTERNS: &[&str] = &[
".git/**",
".git",
".hg/**",
".hg",
".svn/**",
".svn",
"**/.DS_Store",
".bakerignore",
"hooks",
"hooks/**",
"baker.yaml",
"baker.yml",
"baker.json",
];
You can specify multiple patterns for files to be included in the template engine. Then you can include templates or import macros in your templates.
schemaVersion: v1
template_globs:
- "*.tpl"
- "*.jinja"
questions:
project_name:
type: str
help: Please enter the name of your project
This will include all files ending with .tpl and .jinja in the template engine, allowing you to use them in your templates.
Passing default answers can be useful when the answers are already known, such as in a CI/CD pipeline.
Default answers can be provided using the --answers
option.
Example
# Alternatively, use --answers='{"name": "John"}'
echo '{"name": "John"}' | baker template my-project --answers=-
schemaVersion: v1
questions:
name:
type: str
help: What is your name?
The provided answer will be used as the default in the user prompt:
What is your name? [John]:
For fully automated workflows like CI/CD pipelines, you can combine --answers
with the --non-interactive
flag to completely skip all prompts:
baker template my-project --answers='{"project_name": "Example Project"}' --non-interactive
In --non-interactive
mode, Baker determines whether to skip user prompts based on two factors:
- The
--non-interactive
flag itself - The template's
ask_if
conditions (if defined)
When a prompt is skipped, Baker uses the following strategy to determine the answer:
- If an answer was already provided via the
--answers
parameter, use that value - If a default value (
default
) exists in the template configuration, use that - If neither exists, Baker will still prompt the user interactively for that question
For example, if your template contains:
schemaVersion: v1
questions:
project_name:
type: str
help: Please enter the name of your project
project_author:
type: str
help: Please enter the author's name
default: Anonymous
use_tests:
type: bool
help: Will your project include tests?
default: true
And you run:
baker template my-project --answers='{"project_name": "Example"}' --non-interactive
Baker will automatically use "Example" for project_name
, "Anonymous" for project_author
(from the default value), and true
for use_tests
(from the default value).
This is especially useful for CI/CD environments where interactive input isn't possible.
To skip the prompt entirely, you can use the ask_if
attribute:
schemaVersion: v1
questions:
name:
type: str
help: What is your name?
# Skips the prompt if "name" was provided in answers
ask_if: name is not defined or name == ''
A detailed description of the ask_if
key can be found in the Conditional Questions section.
Since Baker uses MiniJinja, it benefits from all MiniJinja features, including debugging. You can use the debug()
function to inspect the current context.
Example
schemaVersion: v1
questions:
first_name:
type: str
help: What is your name?
last_name:
type: str
help: "Hello, {{first_name}}. What is your last name?"
debug:
type: str
help: "{{debug()}}"
When you run the template, the debug()
function will output the current context:
baker example out
What is your name?: aaa
Hello, aaa. What is your last name?: bbb
State {
name: "temp",
current_block: None,
auto_escape: None,
ctx: {
"first_name": "aaa",
"last_name": "bbb",
},
env: Environment {
globals: {
"debug": minijinja::functions::builtins::debug,
"dict": minijinja::functions::builtins::dict,
"namespace": minijinja::functions::builtins::namespace,
"range": minijinja::functions::builtins::range,
},
tests: [
"!=",
"<",
"<=",
"==",
">",
">=",
"boolean",
"defined",
"divisibleby",
...
This output provides a detailed view of the current context, including defined variables, their values, and available functions, helping you troubleshoot and debug your templates effectively.
Hooks are useful for performing routine tasks before (pre-hook) or after (post-hook) project generation.
Baker executes hooks as separate processes, which makes them language-independent.
For a hook to be executed, it must meet two requirements:
- It must be located in the template directory
template_root/hooks/
and named according to thepre_hook_filename
orpost_hook_filename
specified in the configuration. - It must be an executable file (
chmod +x template_root/hooks/<hook_filename>
).
When generating a project containing a hook, Baker will issue a warning:
baker examples/hooks out
WARNING: This template contains the following hooks that will execute commands on your system:
examples/hooks/hooks/post
Do you want to run these hooks? [y/N]
This warning can be omitted by using the --skip-confirms=hooks
parameter.
The pre
hook can generate answers and pass them to baker
through stdout
:
#!/usr/bin/env python
import json
if __name__ == "__main__":
# Passing the default answers to baker
json.dump({"name": "John"}, sys.stdout)
The post
hook can consume the answers, which will be passed by baker
to the stdin
of the post
hook. The answers can be parsed as follows:
#!/usr/bin/env python
import json
import pathlib
from typing import Any, TypedDict
path = pathlib.Path()
class Input(TypedDict):
answers: dict[str, Any]
template_dir: str
output_dir: str
if __name__ == "__main__":
context: Input = json.load(sys.stdin)
output_dir_path = path / context["output_dir"]
template_dir_path = path / context["template_dir"]
The diagram below illustrates this process in more detail
graph LR
%% Data streams
Pre[hooks/pre] --> stdout1[stdout]
stdout1 --> |JSON answers| Baker[baker]
any_cmd --> |JSON answers| stdin2[stdin] --> Baker
Baker --> |JSON output| stdin3[stdin]
stdin3 --> Post[hooks/post]
Post --> stdout3[stdout]
%% Add descriptions
subgraph Pre-processing
Pre
end
subgraph Main Process
Baker
end
subgraph Post-processing
Post
end
%% Style
classDef process fill:#2d3436,stroke:#fff,stroke-width:2px,color:#fff
classDef stream fill:#3498db,stroke:#fff,stroke-width:2px,color:#fff
class Pre,Post,Baker process
class stdin2,stdin3,stdout1,stdout3 stream
By default, Baker looks for hook scripts named pre
and post
in the hooks
directory of your template. You can customize these filenames using the pre_hook_filename
and post_hook_filename
configuration options in your baker.yaml
file:
schemaVersion: v1
questions:
# Your regular questions here...
# Custom hook filenames
pre_hook_filename: "setup-environment"
post_hook_filename: "finalize-project"
With this configuration, Baker will:
- Look for a pre-hook script at
template_root/hooks/setup-environment
- Look for a post-hook script at
template_root/hooks/finalize-project
Hook filenames also support template strings, which can be used to create platform-specific hooks:
schemaVersion: v1
questions:
license:
type: str
help: "Please select a licence for {{platform.os}}"
default: MIT
choices:
- MIT
- BSD
- GPLv3
- Apache Software License 2.0
- Not open source
pre_hook_filename: "{{platform.family}}/pre"
post_hook_filename: "{{platform.family}}/post"
This configuration allows you to organize hooks by platform. For example:
hooks/
βββ unix/
β βββ pre
β βββ post
βββ windows/
βββ pre
βββ post
Baker will automatically select the appropriate hook based on the current platform.
Baker provides these platform variables that can be used in templates and hook filenames:
platform.os
- Operating system name (e.g., "linux", "macos", "windows")platform.family
- OS family (e.g., "unix", "windows")platform.arch
- CPU architecture (e.g., "x86_64", "aarch64")
You can use these variables in any template, including hook filenames, questions, help text, defaults, etc.
Baker supports various question components, which are described below.
Single Input prompts the user to enter a text value.
schemaVersion: v1
questions:
readme_content:
type: str
help: Please enter the content for CONTRIBUTING.md
default: My super duper project
type
: Must bestr
.help
: Should be a string, optionally containing aminijinja
template.default
: Should be a string, optionally containing aminijinja
template.
Please enter the content for CONTRIBUTING.md []:
schemaVersion: v1
questions:
include_tests:
type: bool
help: Do you want to include tests in the generated project?
default: true
type
: Must bebool
.help
: Should be a string, optionally containing aminijinja
template.default
: Should be a boolean value, defaulting tofalse
.
Do you want to include tests in the generated project? [Y/n]
schemaVersion: v1
questions:
favourite_language:
type: str
help: What is your favorite programming language?
default: Rust
choices:
- Python
- Rust
- Go
- TypeScript
type
: Must bestr
.help
: Should be a string, optionally containing aminijinja
template.choices
: Should be a list of strings.default
: Should be a string, optionally containing aminijinja
template.
What is your favorite programming language?:
Python
> Rust
Go
TypeScript
schemaVersion: v1
questions:
favourite_language:
type: str
help: What are your favorite programming languages?
multiselect: true
default:
- Python
- Rust
choices:
- Python
- Rust
- Go
- TypeScript
type
: Must bestr
.help
: Should be a string, optionally containing aminijinja
template.multiselect
: Must betrue
to enable multiple choice.default
: Should be a list of strings.choices
: Should be a list of strings.
What are your favorite programming languages?:
[x] Python
> [x] Rust
[ ] Go
[ ] TypeScript
The JSON type allows you to collect structured data from the user in JSON format. This is useful for configuration files, environment settings, and other structured data.
schemaVersion: v1
questions:
database_config:
type: json
help: Configure your database settings
schema: |
{
"type": "object",
"required": ["engine", "host", "port"],
"properties": {
"engine": {
"type": "string",
"enum": ["postgresql", "mysql", "sqlite", "mongodb"]
},
"host": {
"type": "string"
},
"port": {
"type": "integer",
"minimum": 1,
"maximum": 65535
}
}
}
default: |
{
"engine": "postgresql",
"host": "localhost",
"port": 5432
}
type
: Must bejson
.help
: Should be a string, optionally containing aminijinja
template.schema
: Optional JSON Schema for validation. Follows the JSON Schema standard.default
: JSON object, can be provided as a string or native YAML object.
When prompted for JSON input, the user is given multiple options:
- Open in external text editor
- Enter multi-line input in console
Configure your database settings - Choose input method:
> Use text editor
Enter inline
JSON data can be accessed in templates like any other nested structure:
Connection string: {{ database_config.engine }}://{{ database_config.host }}:{{ database_config.port }}
The YAML type works similarly to the JSON type but uses YAML syntax, which is more readable and less verbose.
schemaVersion: v1
questions:
environments:
type: yaml
help: Configure your deployment environments
default:
development:
url: http://localhost:8000
debug: true
staging:
url: https://staging.example.com
debug: true
production:
url: https://example.com
debug: false
type
: Must beyaml
.help
: Should be a string, optionally containing aminijinja
template.schema
: Optional JSON Schema for validation (same format as for JSON type).default
: YAML data, can be provided as a string or native YAML object.
Similar to JSON input, the user is prompted to choose an input method. YAML is particularly useful for configuration data due to its readability:
Define your environments:
development:
url: http://localhost:8000
debug: true
staging:
url: https://staging.example.com
debug: true
production:
url: https://example.com
debug: false
Template usage:
{% for env_name, env_config in environments|items %}
[{{ env_name }}]
URL={{ env_config.url }}
DEBUG={{ env_config.debug }}
{% endfor %}
Baker supports answer validation using the validation
attribute. The condition
attribute uses MiniJinja's expression language to validate user input, while error_message
provides feedback when validation fails.
Ensure a field is not empty:
schemaVersion: v1
questions:
age:
type: str
help: "Enter your age"
validation:
condition: "age"
error_message: "Value cannot be empty"
Check if a numeric value meets certain criteria:
schemaVersion: v1
questions:
age:
type: str
help: "Enter your age"
validation:
condition: "age|int >= 18"
error_message: "You must be at least 18 years old. You entered {{age}}."
The error message can include template variables to provide context about the invalid input.
Complex validation combining regex pattern matching with numeric validation and detailed error messages:
schemaVersion: v1
questions:
age:
type: str
help: Enter your age
validation:
condition: "age and (age|regex('[0-9]')) and (age|int >= 18)"
error_message: >
{% if not age %}Age is required field
{% elif not age|regex('[0-9]') %}Age must be numeric
{% elif not age|int >= 18 %}You must be at least 18 years old. You entered {{age}}
{% else %}Invalid input
{% endif %}
This example demonstrates:
- Required field validation using
age
- Pattern matching using
regex('[0-9]')
to ensure numeric input - Numeric value validation ensuring age is at least 18
- Conditional error messages that provide specific feedback based on the validation failure
If validation fails, Baker will:
- Display the appropriate error message
- Clear the invalid answer
- Prompt the user to try again
The ask_if
attribute is used to control the display of a question, using expression language from MiniJinja. It enables conditional logic to determine whether a question should be prompted based on user input or other contextual factors. In the following example, the py_framework
question is only prompted if the user selects Python
as the programming language in the language
question:
schemaVersion: v1
questions:
language:
type: str
help: What is your programming language?
default: Rust
choices:
- Python
- Rust
- Go
- TypeScript
py_framework:
type: str
help: What is your Python framework?
choices:
- Django
- FastAPI
- Pyramid
- Tornado
ask_if: "language == 'Python'"
Baker provides a set of built-in filters and functions to enhance the flexibility of your templates. These are powered by the MiniJinja templating engine and additional custom filters.
Filter Name | Description |
---|---|
camel_case |
Converts a string to camelCase. |
kebab_case |
Converts a string to kebab-case. |
pascal_case |
Converts a string to PascalCase. |
screaming_snake_case |
Converts a string to SCREAMING_SNAKE_CASE. |
snake_case |
Converts a string to snake_case. |
table_case |
Converts a string to table_case (lowercase with underscores). |
train_case |
Converts a string to Train-Case. |
plural |
Converts a word to its plural form. |
singular |
Converts a word to its singular form. |
foreign_key |
Converts a string to a foreign key format (e.g., user_id ). |
regex |
Applies a regular expression to transform a string. |
{{ "hello world" | camel_case }}
// Output: "helloWorld"
{{ "hello world" | kebab_case }}
// Output: "hello-world"
{{ "hello world" | pascal_case }}
// Output: "HelloWorld"
{{ "hello world" | screaming_snake_case }}
// Output: "HELLO_WORLD"
{{ "hello world" | snake_case }}
// Output: "hello_world"
{{ "Hello World" | table_case }}
// Output: "hello_world"
{{ "hello world" | train_case }}
// Output: "Hello-World"
{{ "car" | plural }}
// Output: "cars"
{{ "cars" | singular }}
// Output: "car"
{{ "User" | foreign_key }}
// Output: "user_id"
{{ "hello world" | regex: "world", "Rust" }}
// Output: "hello Rust"
Feature | Baker | Kickstart | cargo-generate | Copier | Cookiecutter | Yeoman |
---|---|---|---|---|---|---|
π’ Structured JSON/YAML input | β Native support with validation and schema | β | β | β | ||
π’ JSON Schema validation | β Enforce data validity with standard JSON Schema | β | β | β | β | |
π’ Complex data editing modes | β Editor/Console/File input for structured data | β | β | β | β | β |
π’ In-template debug() support | β
Use {{ debug() }} to inspect context |
β | β | β | β | |
π’ Structured hook communication | β pre/post hooks exchange structured JSON via stdin/stdout | β | β | β | β | β |
π’ Safe hook execution | β Warns before executing hooks | β | β | β | β | |
π’ Schema versioning for config | β Schema version ensures backward compatibility across Baker versions | β | β | β | β | β |
π’ YAML & JSON config support | β
Supports yaml and json configurations |
β Only TOML | β Only TOML | β Only YAML | β Only JSON | β In JS code |
π’ Platform-specific hooks | β
Use {{platform.family}}/pre etc. for OS-aware logic |
β | β | β | ||
π’ CI/CD-friendly answers piping | β
--answers=- or echo JSON into CLI |
β | β Via pre-filled YAML | --no-input only |
β Manual scripting | |
π’ Lightweight & Fast | β Rust binary, no runtime dependencies | β Rust binary | β Rust binary | β Requires Python | β Requires Python | β Requires Node.js |
π’ Simple CLI Interface | β
baker <template> <output> + --answers , --skip-confirms |
β Simple | β Requires Cargo usage | β More verbose | β Simple | β Requires generator install |
π’ Language-agnostic hooks | β Hooks can be in any language (Bash, Python, etc.) | β Yes | β Yes | β Yes | β Only JS | |
π’ Templated file/dir names | β Full MiniJinja templating in names & conditions | β Yes | β Yes | β Yes | β Yes | β Via JS logic |
π’ Templated prompts & defaults | β
Dynamic defaults using MiniJinja, conditional via ask_if |
β Yes | β Full Jinja | β Static only | β Full control in JS | |
π’ Glob-based ignore file | β
.bakerignore with advanced Globset syntax |
β Yes | β Yes | β
_exclude |
_copy_without_render |
β Manual filter in code |
π’ Cross-platform binaries | β Precompiled for Linux, macOS, Windows | β Yes | β Yes | β Yes | β Yes | β Yes |
π’ Language-agnostic scaffolding | β Works with any language / stack | β Yes | β Rust-focused | β Yes | β Yes | |
π’ Answers accessible in later questions | β
All previous answers available via MiniJinja in default , help , ask_if |
β Yes (Jinja context) | β | β Full control in JS | ||
π’ Templated engine | β Fast, safe, embedded Jinja2-like templating in Rust | Tera | Liquid | Jinja2 | Jinja2 | EJS |
This comparison was made based on available documentation. If you notice any inaccuracies or outdated information, please create an issue β I'll be happy to update the table accordingly.
See here for a list of community maintained templates built with baker.