Project Setup

Copier vs Cookiecutter for CLI Templates

Choose a Python project template tool - Cookiecutter renders once, Copier can update generated projects later, and one question decides which you need.

Updated

Both tools render a directory of Jinja templates into a new project from a set of answers. The difference that matters is what happens six months later, when the template gains a CI step that every generated project should also have.

TL;DR

  • Cookiecutter renders once and forgets: simple, ubiquitous, enormous ecosystem.
  • Copier records the template version and answers in the project, so copier update can replay later template changes.
  • Choose by one question: will generated projects need to receive template updates?
  • For CLI projects, which usually diverge immediately, Cookiecutter is often enough.
  • Whichever you pick, give the template CI that generates a project and runs its tests.

Prerequisites

A project layout worth templating — the src/ tree, pyproject.toml, pre-commit configuration and CI workflow described in CLI project scaffolding.

The difference in one diagram

What "updating a template" means Copier records the template version and answers inside the generated project, so a later template change can be replayed into it as a merge. What "updating a template" means Generate answers recorded in the project Template changes new CI step, new lint rule copier update replays the diff Resolve conflicts like a merge once later in each project Cookiecutter has no step three: a generated project forgets where it came from.

Cookiecutter's model is a stamp: answers go in, files come out, and the relationship ends there. The generated project has no record of which template made it or which answers were given.

Copier writes a .copier-answers.yml into the project containing the template URL, the version tag and every answer. That file is what makes copier update possible: it re-renders the old version, re-renders the new one, and applies the difference to your project as a merge — conflicts included, exactly like a rebase.

Side by side

Cookiecutter and Copier compared A comparison of Cookiecutter and Copier across templating, updates, answer storage, conditional files and ecosystem size. Cookiecutter and Copier compared Aspect Cookiecutter Copier Update generated projects no yes — copier update Answers stored not in the project in .copier-answers.yml Conditional files delete in a hook declared in the template Migrations between versions none declarable Ecosystem very large smaller, growing Choose by one question: will generated projects need to receive template changes later?
# Cookiecutter
cookiecutter gh:yourorg/python-cli-template
# project_name [My CLI]: deploy-tool
# package_name [deploy_tool]:
# Copier
copier copy gh:yourorg/python-cli-template ./deploy-tool
# … same questions, plus .copier-answers.yml written into the project

# a year later, in that project:
copier update

Two more practical differences follow from the design.

Conditional files. Cookiecutter renders everything and expects a post-generation hook to delete what was not wanted. Copier declares exclusions in the template itself, which keeps the logic visible rather than hidden in a Python hook.

Migrations. Copier templates can declare migration scripts that run when updating across versions — useful when a change is not a simple file diff, such as renaming a configuration key in every generated project.

Which to choose

Which template tool? A decision diagram: use Copier when generated projects must track template updates, and Cookiecutter when they diverge immediately. Which template tool? Will you want to push template changes into existing projects? Yes — a fleet of similar services Copier update replays changes No — projects diverge at once Cookiecutter simpler, larger ecosystem For CLI projects, which usually diverge immediately, the simpler tool is often enough.

Choose Copier when you maintain a fleet: five services from the same template, where a change to the CI workflow or the linting configuration should reach all of them. The update mechanism is genuinely valuable there, and it is worth writing templates with merges in mind.

Choose Cookiecutter when generated projects diverge immediately, which is the normal case for CLI tools. Two tools generated from the same template look nothing alike after a month, so replaying template changes into them is a merge conflict rather than a convenience — and Cookiecutter's larger ecosystem means more existing templates to start from.

Choose neither if you start one project a year. A well-maintained example repository and ten minutes of copying is less machinery to own than a template nobody exercises.

Writing a template that can be updated

If you pick Copier, a few habits make copier update pleasant rather than a merge nightmare.

Keep generated files small and stable. A four-hundred-line workflow file regenerated with one changed line merges cleanly; a file that the project is expected to edit heavily does not.

Mark files the project owns. Anything a developer will rewrite immediately — the README body, the main module — is better generated once and excluded from updates, which Copier supports with skip_if_exists.

Version the template with tags. copier update moves between tags, so releases give you something to move between and something to describe in a changelog.

Test the update path, not just generation. A CI job that generates from the previous tag, updates to HEAD and asserts the result still builds is what stops the update mechanism quietly breaking.

UX considerations

The people who use your template are colleagues on their first day of a project, which sets the bar for the experience.

Keep the questions few. Every prompt is a branch in the output that somebody has to test. Three or four — name, package name, Python version, maybe a licence — cover most templates. Anything else should have a sensible default and be editable afterwards.

Validate answers before rendering. A package name that is not a valid identifier produces a project that cannot be imported. Both tools support validation; a pre-generation check that fails fast is much kinder than a half-made directory.

Generate a first commit. A post-generation hook running git init and committing the skeleton means the first real change shows up as a reviewable diff against a known baseline.

Say what to do next. A three-line message at the end — install, run the tests, run the CLI — turns a generated directory into something the reader can act on.

Testing the behaviour

Both tools deserve the same CI treatment: generate a project on every push and put it through the steps a real one would face.

jobs:
  generate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: pipx install copier          # or cookiecutter
      - run: copier copy --defaults --trust . /tmp/out
      - working-directory: /tmp/out
        run: |
          pipx install uv
          uv sync
          uv run pytest -q
          uv build
          uv run --isolated --with dist/*.whl my-cli --version

--defaults (Cookiecutter: --no-input) forces the declared defaults to be genuinely valid rather than placeholders somebody was meant to replace. The final line is the check that matters most: the generated project builds a wheel whose entry point actually runs.

For Copier, add the update path as a second job — generate from the previous tag, copier update --defaults, then run the same steps.

What belongs in a CLI template

Whichever tool renders it, the content is the same, and the useful principle is to include what people forget rather than everything imaginable.

Worth generating: the src/ layout with a working package and entry point; a pyproject.toml with the build backend, metadata, dependencies and [project.scripts]; a dev dependency group; a pre-commit configuration with pinned revisions; a test that imports the package and one that invokes the CLI through the runner; a CI workflow that installs from the lockfile, runs the tests and smoke-tests the built wheel; a README with install and usage sections; and a .gitignore.

Worth leaving out: a Dockerfile nobody asked for, documentation scaffolding for a site that may never exist, a changelog full of placeholder text, and licence headers in every file. Each is something the first developer deletes, and a template that requires deletion trains people to distrust it.

The judgement call is dependency choices. A template that hard-codes Typer and Rich is opinionated in a way that saves an argument on day one and constrains a project that needed Click. Making that a question is reasonable; making it five questions is not.

Conclusion

Cookiecutter renders; Copier renders and remembers. If generated projects need to track the template, that memory is worth the extra ceremony; if they diverge the moment they exist — which most CLI projects do — the simpler tool is the right one. Either way, the template needs its own CI, because a template that is never generated is a template that is already broken.

Frequently asked questions

Can I migrate an existing Cookiecutter template to Copier?

Yes, and it is mostly mechanical: cookiecutter.json becomes copier.yml, the templated directory keeps its Jinja syntax, and hooks become tasks. What needs thought is which files should be excluded from future updates, since that concept has no Cookiecutter equivalent.

Does Copier work with existing Cookiecutter templates?

Partially — Copier can consume Cookiecutter-style templates, but you lose the update capability unless the answers file is present, which older templates do not write. For anything you plan to maintain, converting properly is the better path.

How do I stop a template going stale?

Generate from it in CI on every push and run the generated project's tests. It is the only reliable mechanism: a template exercised weekly fails in its own repository on a Tuesday, while one nobody generates fails on someone's first day.

Should the template pin dependency versions?

Pin the tooling it configures — pre-commit hook revisions, action versions, the minimum Python — and leave application dependency ranges open. Generate the lockfile in a post-generation hook rather than committing a stale one into the template.

Are there good public templates to start from?

Several, and the useful exercise is generating from two or three and comparing what they include. Treat them as a source of ideas rather than something to adopt wholesale: a template encodes its author's conventions, and the value comes from those conventions being yours.