Skip to content

Configuring a template

Configuration sources

It is important that you understand how Copier works. It has 2 kinds of configurations:

  1. Settings for Copier itself. This includes things as minimal Copier version required, which subdirectory to render, tasks to run, etc.
  2. Answers. This is customized per template. The user answers template questions, and those answers are stored as variables available for the template at rendering time.

Copier reads settings from these sources, in this order of priority:

  1. Command line or API arguments.
  2. The copier.yml file. Settings here always start with an underscore (e.g. _min_copier_version).


Some settings are only available as CLI arguments, and some others only as template configurations. Some behave differently depending on where they are defined. Check the docs for each specific setting.

Copier obtains answers from these sources, in this order of priority:

  1. Command line or API arguments.
  2. Asking the user. Notice that Copier will not ask any questions answered in the previous source.
  3. Answer from last execution.
  4. Default values defined in the copier.yml file.

The copier.yml file

The copier.yml (or copier.yaml) file is found in the root of the template, and it is the main entrypoint for managing your template configuration. It will be read and used for two purposes:


For each key found, Copier will prompt the user to fill or confirm the values before they become available to the project template.


This copier.yml file:

name_of_the_project: My awesome project
number_of_eels: 1234
your_email: ""

Will result in a questionary similar to:

🎤 name_of_the_project
  My awesome project
🎤 number_of_eels (int)
🎤 your_email

Advanced prompt formatting

Apart from the simplified format, as seen above, Copier supports a more advanced format to ask users for data. To use it, the value must be a dict.

Supported keys:

  • type: User input must match this type. Options are: bool, float, int, json, str, yaml (default).
  • help: Additional text to help the user know what's this question for.
  • choices: To restrict possible values.


    A choice value of null makes it become the same as its key.


    You are able to use different types for each choice value, but it is not recommended because you can get to some weird scenarios.

    For example, try to understand this 🥴

        type: yaml # If you are mixing types, better be explicit
            Nothing, thanks: "null" # Will be YAML-parsed and converted to null
            Value is key: null # Value will be converted to "Value is key"
            One and a half: 1.5
            "Yes": true
            Nope: no
            Some array: "[yaml, converts, this]"

    It's better to stick with a simple type and reason about it later in template code:

        type: str
            Nothing, thanks: ""
            Value is key: null # Becomes "Value is key", which is a str
            One and a half: "1.5"
            "Yes": "true"
            Nope: "no"
            Some array: "[str, keeps, this, as, a, str]"
  • default: Leave empty to force the user to answer. Provide a default to save them from typing it if it's quite common. When using choices, the default must be the choice value, not its key, and it must match its type. If values are quite long, you can use YAML anchors.

  • secret: When true, it hides the prompt displaying asterisks (*****) and doesn't save the answer in the answers file
  • placeholder: To provide a visual example for what would be a good value. It is only shown while the answer is empty, so maybe it doesn't make much sense to provide both default and placeholder.


    Multiline placeholders are not supported currently, due to this upstream bug.

  • multiline: When set to true, it allows multiline input. This is especially useful when type is json or yaml.

    validator: Jinja template with which to validate the user input. This template will be rendered with the combined answers as variables; it should render nothing if the value is valid, and an error message to show to the user otherwise.

  • when: Condition that, if false, skips the question.

    If it is a boolean, it is used directly, but it's a bit absurd in that case.

    If it is a string, it is converted to boolean using a parser similar to YAML, but only for boolean values.

    This is most useful when templated.

    If a question is skipped, its answer will be:

    • The default value, if you're generating the project for the 1st time.
    • The last answer recorded, if you're updating the project.


        type: str
        type: str
            - GPLv3
            - Public domain
        type: str
        default: |-
            {% if project_license == 'Public domain' -%}
                {#- Nobody owns public projects -#}
            {%- else -%}
                {#- By default, project creator is the owner -#}
                {{ project_creator }}
            {%- endif %}
        # Only ask for copyright if project is not in the public domain
        when: "{{ project_license != 'Public domain' }}"


    type: bool # This makes Copier ask for y/n
    help: Do you love Copier?
    default: yes # Without a default, you force the user to answer

    type: str # Any value will be treated raw as a string
    help: An awesome project needs an awesome name. Tell me yours.
    default: paradox-specifier

    type: str
    secret: true # This value will not be logged into .copier-answers.yml
    placeholder: my top secret password

# I'll avoid default and help here, but you can use them too
    type: int
    validator: "{% if age <= 0 %}Must be positive{% endif %}"

    type: float

    help: Tell me anything, but format it as a one-line JSON string
    type: json
    multiline: true

    help: Tell me anything, but format it as a one-line YAML string
    type: yaml # This is the default type, also for short syntax questions
    multiline: true

    # User will choose one of these and your template will get the value
        - The Bible
        - The Hitchhiker's Guide to the Galaxy

    # User will see only the dict key and choose one, but you will
    # get the dict value in your template
        MIT: &mit_text |
            Here I can write the full text of the MIT license.
            This will be a long text, shortened here for example purposes.
        Apache2: |
            Full text of Apache2 license.
    # When using choices, the default value is the value, **not** the key;
    # that's why I'm using the YAML anchor declared above to avoid retyping the
    # whole license
    default: *mit_text
    # You can still define the type, to make sure answers that come from --data
    # CLI argument match the type that your template expects
    type: str

    help: Do you live close to your work?
    # This format works just like the dict one
        - [at home, I work at home]
        - [less than 10km, quite close]
        - [more than 10km, not so close]
        - [more than 100km, quite far away]

Prompt templating

Most of those options can be templated using Jinja.

Keep in mind that the configuration is loaded as YAML, so the contents must be valid YAML and respect Copier's structure. That is why we explicitly wrap some strings in double-quotes in the following examples.

Answers provided through interactive prompting will not be rendered with Jinja, so you cannot use Jinja templating in your answers.


# default
    type: str

    type: str

    type: str
    # Notice that both `username` and `organization` have been already asked
    default: "{{ username }}@{{ organization }}.com"

# help
    type: str
    when: "{% if organization != 'Public domain' %}true{% endif %}"
    help: The person or entity within {{ organization }} that holds copyrights.

# type
    type: str
        - humans
        - machines

    type: "{% if target == 'humans' %}yaml{% else %}json{% endif %}"

# choices
    type: str
    help: Your title within {{ organization }}

        Copyright holder: "{{ copyright_holder }}"
        CEO: Alice Bob
        CTO: Carl Dave
        "{{ title }}": "{{ username }}"


Keep in mind that:

  1. You can only template inside the value...
  2. ... which must be a string to be templated.
  3. Also you won't be able to use variables that aren't yet declared.
    type: int

# Valid
    type: int
    default: "{{ your_age * 2}}"

# Invalid, the templating occurs outside of the parameter value
    type: str
    {% if your_age %}
    default: "yes"
    {% else %}
    placeholder: "nope"
    {% endif %}

# Invalid, `a_random_word` wasn't answered yet
    type: str
    placeholder: "Something different to {{ a_random_word }}"

# Invalid, YAML interprets curly braces
    type: str
    default: {{ 'hello' }}

Include other YAML files

The copier.yml file supports multiple documents. When found, they are merged (not deeply merged; just merged) and the latest one defined has priority.

It also supports using the !include tag to include other configurations from elsewhere.

These two features, combined, allow you to reuse common partial sections from your templates.


You can use Git submodules to sanely include shared code into templates.


This would be a valid copier.yml file:

# Copier will load all these files
!include shared-conf/common.*.yml

# These 3 lines split the several YAML documents
# These two documents include common questions for these kind of projects
!include common-questions/web_app.yml
!include common-questions/python-project.yml

# Here you can specify any settings or questions specific for your template,
    - .password.txt
custom_question: default answer

Conditional files and directories

You can take advantage of the ability to template file and directory names to make them "conditional", i.e. to only generate them based on the answers given by a user.

For example, you can ask users if they want to use pre-commit:

    type: bool
    default: false
    help: Do you want to use pre-commit?

And then, you can generate a .pre-commit-config.yaml file only if they answered "yes":

📁 your_template
├── 📄 copier.yml
└── 📄 {% if use_precommit %}.pre-commit-config.yaml{% endif %}.jinja


Note that the chosen template suffix must appear outside of the Jinja condition, otherwise the whole file won't be considered a template and will be copied as such in generated projects.

You can even use the answers of questions with choices:

    type: str
    help: What Continuous Integration service do you want to use?
        GitHub CI: github
        GitLab CI: gitlab
    default: github
📁 your_template
├── 📄 copier.yml
├── 📁 {% if ci == 'github' %}.github{% endif %}
│   └── 📁 workflows
│       └── 📄 ci.yml
└── 📄 {% if ci == 'gitlab' %}.gitlab-ci.yml{% endif %}.jinja


Contrary to files, directories must not end with the template suffix.


On Windows, double-quotes are not valid characters in file and directory paths. This is why we used single-quotes in the example above.

Available settings

Template settings alter how the template is rendered. They come from several sources.

Remember that the key must be prefixed with an underscore if you use it in the copier.yml file.


  • Format: str
  • CLI flags: -a, --answers-file
  • Default value: .copier-answers.yml

Path to a file where answers will be recorded by default. The path must be relative to the project root.


Remember to add that file to your Git template if you want to support updates.

Don't forget to read the docs about the answers file.


_answers_file: .my-custom-answers.yml


  • Format: bool
  • CLI flags: -C, --no-cleanup (used to disable this setting; only available in copier copy subcommand)
  • Default value: True

When Copier creates the destination path, if there's any failure when rendering the template (either in the rendering process or when running the tasks), Copier will delete that folder.

Copier will never delete the folder if it didn't create it. For this reason, when running copier update, this setting has no effect.


Not supported in copier.yml.


  • Format: dict|List[str=str]
  • CLI flags: -d, --data
  • Default value: N/A

Give answers to questions through CLI/API.

This cannot be defined in copier.yml, where its equivalent would be just normal questions with default answers.


Example CLI usage to take all default answers from template, except the user name, which is overridden, and don't ask user anything else:

copier -fd 'user_name=Manuel Calavera' copy template destination


  • Format: dict
  • CLI flags: N/A
  • Default value: {"keep_trailing_newline": true}

Configurations for the Jinja environment. Copier uses the Jinja defaults whenever possible. The only exception at the moment is that Copier keeps trailing newlines at the end of a template file. If you want to remove those, either remove them from the template or set keep_trailing_newline to false.

See upstream docs to know available options.


Copier 5 and older had different, bracket-based defaults.

If your template was created for Copier 5, you need to add this configuration to your copier.yaml to keep it working just like before:

    autoescape: false
    block_end_string: "%]"
    block_start_string: "[%"
    comment_end_string: "#]"
    comment_start_string: "[#"
    keep_trailing_newline: true
    variable_end_string: "]]"
    variable_start_string: "[["

By specifying this, your template will be compatible with both Copier 5 and 6.

Copier 6 will apply these older defaults if your min_copier_version is lower than 6, but that will be removed in the future.


  • Format: List[str]
  • CLI flags: -x, --exclude
  • Default value: ["copier.yaml", "copier.yml", "~*", "*.py[co]", "__pycache__", ".git", ".DS_Store", ".svn"]

Patterns for files/folders that must not be copied.

The CLI option can be passed several times to add several patterns.


When you define this parameter in copier.yml, it will replace the default value.

In this example, for instance, "copier.yml" will not be excluded:


    - "*.bar"
    - ".git"


When you add this parameter from CLI or API, it will not replace the values defined in copier.yml (or the defaults, if missing).

Instead, CLI/API definitions will extend those from copier.yml.

Example CLI usage to copy only a single file from the template

copier --exclude '*' --exclude '!file-i-want' copy ./template ./destination


  • Format: bool
  • CLI flags: -f, --force
  • Default value: False

Overwrite files that already exist, without asking.

Also don't ask questions to the user; just use default values obtained from other sources.


Not supported in copier.yml.


  • Format: bool
  • CLI flags: --defaults
  • Default value: False

Use default answers to questions, which might be null if not specified.


Not supported in copier.yml.


  • Format: bool
  • CLI flags: --overwrite
  • Default value: False

Overwrite files that already exist, without asking.

obtained from other sources.


Not supported in copier.yml.


  • Format: List[str]
  • CLI flags: N/A
  • Default value: []

Additional Jinja2 extensions to load in the Jinja2 environment. Extensions can add filters, global variables and functions, or tags to the environment.

The following extensions are always loaded:

You don't need to tell your template users to install these extensions: Copier depends on them, so they are always installed when Copier is installed.


Including an extension allows Copier to execute uncontrolled code, thus making the template potentially more dangerous. Be careful about what extensions you install.

Note to template writers

You must inform your users that they need to install the extensions alongside Copier, i.e. in the same virtualenv where Copier is installed. For example, if your template uses jinja2_time.TimeExtension, your users must install the jinja2-time Python package.

# with pip, in the same virtualenv where Copier is installed
pip install jinja2-time

# if Copier was installed with pipx
pipx inject copier jinja2-time


    - jinja_markdown.MarkdownExtension
    - jinja2_slug.SlugExtension
    - jinja2_time.TimeExtension


Examples of extensions you can use:

Search for more extensions on GitHub using the jinja2-extension topic, or other Jinja2 topics, or on PyPI using the jinja + extension keywords.


  • Format: List[dict]
  • CLI flags: N/A
  • Default value: []

Migrations are like tasks, but each item in the list is a dict with these keys:

  • version: Indicates the version that the template update has to go through to trigger this migration. It is evaluated using PEP 440.
  • before (optional): Commands to execute before performing the update. The answers file is reloaded after running migrations in this stage, to let you migrate answer values.
  • after (optional): Commands to execute after performing the update.

Migrations will run in the same order as declared here (so you could even run a migration for a higher version before running a migration for a lower version if the higher one is declared before and the update passes through both).

They will only run when new version >= declared version > old version. And only when updating (not when copying for the 1st time).

If the migrations definition contains Jinja code, it will be rendered with the same context as the rest of the template.

Migration processes will receive these environment variables:

  • $STAGE: Either before or after.
  • $VERSION_FROM: Git commit description of the template as it was before updating.
  • $VERSION_TO: Git commit description of the template as it will be after updating.
  • $VERSION_CURRENT: The version detector as you indicated it when describing migration tasks.
  • $VERSION_PEP440_FROM, $VERSION_PEP440_TO, $VERSION_PEP440_CURRENT: Same as the above, but normalized into a standard PEP 440 version string indicator. If your scripts use these environment variables to perform migrations, you probably will prefer to use these variables.


    - version: v1.0.0
          - rm ./old-folder
          # {{ _copier_conf.src_path }} points to the path where the template was
          # cloned, so it can be helpful to run migration scripts stored there.
          - invoke -r {{ _copier_conf.src_path }} -c migrations migrate $VERSION_CURRENT


  • Format: str
  • CLI flags: N/A
  • Default value: N/A

Specifies the minimum required version of Copier to generate a project from this template. The version must be follow the PEP 440 syntax. Upon generating or updating a project, if the installed version of Copier is less than the required one, the generation will be aborted and an error will be shown to the user.


If Copier detects that there is a major version difference, it will warn you about possible incompatibilities. Remember that a new major release means that some features can be dropped or changed, so it's probably a good idea to ask the template maintainer to update it.


_min_copier_version: "4.1.0"


  • Format: bool
  • CLI flags: -n, --pretend
  • Default value: False

Run but do not make any changes.


Not supported in copier.yml.


  • Format: bool
  • CLI flags: -q, --quiet
  • Default value: False

Suppress status output.


Not supported in copier.yml.


  • Format: List[str]
  • CLI flags: N/A
  • Default value: []

Question variables to mark as secret questions. This is especially useful when questions are provided in the simplified prompt format. It's equivalent to configuring secret: true in the advanced prompt format.


    - password

user: johndoe
password: s3cr3t


  • Format: List[str]
  • CLI flags: -s, --skip
  • Default value: []

Patterns for files/folders that must be skipped if they already exist.


For example, it can be used if your project generates a password the 1st time and you don't want to override it next times:

    - .secret_password.yml


  • Format: str
  • CLI flags: N/A
  • Default value: N/A

Subdirectory to use as the template root when generating a project. If not specified, the root of the template is used.

This allows you to keep separate the template metadata and the template code.


If your template is meant to be applied to other templates (a.k.a. recursive templates), use this option to be able to use updates.


_subdirectory: template

Can I have multiple templates in a single repo using this option?

The Copier recommendation is: 1 template = 1 Git repository.

Why? Unlike almost all other templating engines, Copier supports smart project updates. For that, Copier needs to know in which version it was copied last time, and to which version you are evolving. Copier gets that information from Git tags. Git tags are shared across the whole Git repository. Using a repository to host multiple templates would lead to many corner case situations that we don't want to support.

So, in Copier, the subdirectory option is just there to let template owners separate templates metadata from template source code. This way, for example, you can have different dotfiles for you template and for the projects it generates.

Example project with different .gitignore files

Project layout
📁 my_copier_template
├── 📄 copier.yml       # (1)
├── 📄 .gitignore       # (2)
└── 📁 template         # (3)
    └── 📄 .gitignore   # (4)
  1. Same contents as the example above.
  2. Ignore instructions for the template repo.
  3. The configured template subdirectory.
  4. Ignore instructions for projects generated with the template.

However, it is true that the value of this option can itself be templated. This would let you have different templates that all use the same questionary, and the used template would be saved as an answer. It would let the user update safely and change that option in the future.


With this questions file and this directory structure, the user will be prompted which Python engine to use, and the project will be generated using the subdirectory whose name matches the answer from the user:

_subdirectory: "{{ python_engine }}"
    type: str
        - poetry
        - pipenv
Project layout
📁 my_copier_template
├── 📄 copier.yaml # (1)
├── 📁 poetry
│   ├── 📄 {{ _copier_conf.answers_file }}.jinja # (2)
│   └── 📄 pyproject.toml.jinja
└── 📁 pipenv
│   ├── 📄 {{ _copier_conf.answers_file }}.jinja
    └── 📄 Pipfile.jinja
  1. The configuration from the previous example snippet.
  2. See the answers file docs to understand.


  • Format: List[str|List[str]]
  • CLI flags: N/A
  • Default value: []

Commands to execute after generating or updating a project from your template.

They run ordered, and with the $STAGE=task variable in their environment.

Example copier.yml:

    # Strings get executed under system's default shell
    - "git init"
    - "rm {{ name_of_the_project }}/"
    # Arrays are executed without shell, saving you the work of escaping arguments
    - [invoke, "--search-root={{ _copier_conf.src_path }}", after-copy]
    # You are able to output the full conf to JSON, to be parsed by your script
    - [invoke, end-process, "--full-conf={{ _copier_conf|to_json }}"]
    # Your script can be run by the same Python environment used to run Copier
    - ["{{ _copier_python }}",]


  • Format: str
  • CLI flags: N/A
  • Default value: .jinja

Suffix that instructs which files are to be processed by Jinja as templates.


_templates_suffix: .my-custom-suffix

An empty suffix is also valid, and will instruct Copier to copy and render every file, except those that are excluded by default. If an error happens while trying to read a file as a template, it will fallback to a simple copy (it will typically happen for binary files like images). At the contrary, if such an error happens and the templates suffix is not empty, Copier will abort and print an error message.


_templates_suffix: ""


Copier 5 and older had a different default value: .tmpl. If you wish to keep it, add it to your copier.yml to keep it future-proof.

Copier 6 will apply that old default if your min_copier_version is lower than 6, but that will be removed in the future.


  • Format: bool
  • CLI flags: g, --prereleases
  • Default value: False

Imagine that the template supports updates and contains these 2 Git tags: v1.0.0 and v2.0.0a1. Copier will copy by default v1.0.0 unless you add --prereleases.

Also, if you run copier update, Copier would ignore the v2.0.0a1 tag unless this flag is enabled.


This behavior is new from Copier 5.0.0. Before that release, prereleases were never ignored.


Not supported in copier.yml.


  • Format: str
  • CLI flags: -r, -vcs-ref
  • Default value: N/A (use latest release)

When copying or updating from a Git-versioned template, indicate which template version to copy.

This is stored automatically in the answers file, like this:

_commit: v1.0.0


Not supported in copier.yml.

By default, Copier will copy from the last release found in template Git tags, sorted as PEP 440.

Patterns syntax

Copier supports matching names against patterns in a gitignore style fashion. This works for the options exclude and skip. This means you can write patterns as you would for any .gitignore file. The full range of the gitignore syntax is supported via pathspec.

For example, with the following settings in your copier.yml file would exclude all files ending with txt from being copied to the destination folder, except the file a.txt.

    # match all text files...
    - "*.txt"
    # .. but not this one:
    - "!a.txt"

The .copier-answers.yml file

If the destination path exists and a .copier-answers.yml file is present there, it will be used to load the last user's answers to the questions made in the copier.yml file.

This makes projects easier to update because when the user is asked, the default answers will be the last ones they used.

The file must be called exactly {{ _copier_conf.answers_file }}.jinja (or ended with your chosen suffix) in your template's root folder) to allow applying multiple templates to the same subproject.

The default name will be .copier-answers.yml, but you can define a different default path for this file.

The file must have this content:

# Changes here will be overwritten by Copier; NEVER EDIT MANUALLY


Did you notice that NEVER EDIT MANUALLY part? It is important.

The builtin _copier_answers variable includes all data needed to smooth future updates of this project. This includes (but is not limited to) all JSON-serializable values declared as user questions in the copier.yml file.

As you can see, you also have the power to customize what will be logged here. Keys that start with an underscore (_) are specific to Copier. Other keys should match questions in copier.yml.

The path to the answers file must be expressed relative to the project root, because:

  • Its value must be available at render time.
  • It is used to update projects, and for that a project must be git-tracked. So, the file must be in the repo anyway.

Applying multiple templates to the same subproject

Imagine this scenario:

  1. You use one framework that has a public template to generate a project. It's available at
  2. You have a generic template that you apply to all your projects to use the same pre-commit configuration (formatters, linters, static type checkers...). You have published that in
  3. You have a private template that configures your subproject to run in your internal CI. It's found in

All 3 templates are completely independent:

  • Anybody can generate a project for the specific framework, no matter if they want to use pre-commit or not.
  • You want to share the same pre-commit configurations, no matter if the subproject is for one or another framework.
  • You want to have a centralized CI configuration for all your company projects, no matter their pre-commit configuration or the framework they rely on.

Well, don't worry. Copier has you covered. You just need to use a different answers file for each one. All of them contain a {{ _copier_conf.answers_file }}.jinja file as specified above. Then you apply all the templates to the same project:

mkdir my-project
cd my-project
git init
# Apply framework template
copier -a .copier-answers.main.yml copy .
git add .
git commit -m 'Start project based on framework template'
# Apply pre-commit template
copier -a .copier-answers.pre-commit.yml copy .
git add .
pre-commit run -a  # Just in case 😉
git commit -am 'Apply pre-commit template'
# Apply internal CI template
copier -a copy .
git add .
git commit -m 'Apply internal CI template'


After a while, when templates get new releases, updates are handled separately for each template:

copier -a .copier-answers.main.yml update
copier -a .copier-answers.pre-commit.yml update
copier -a update
Back to top