Template Structure

July 6, 2026 · View on GitHub

A Cookiecutter template is a directory with a specific structure. This guide describes each component in detail.


Template Anatomy

my-template/
├── cookiecutter.json              # Variables and defaults
├── hooks/                         # Pre/post generation scripts (optional)
│   ├── pre_gen_project.py
│   └── post_gen_project.py
├── {{ cookiecutter.project_name }}/  # Template directory (Jinja2 in the name)
│   ├── {{ cookiecutter.module_name }}.py
│   ├── README.md
│   ├── setup.py
│   └── ...
└── docs/                          # Template docs (not generated)

cookiecutter.json

Defines the variables the user fills in during generation. It is a plain JSON where each key is a variable and each value is the default.

Value Types

{
  "project_name": "My Project",
  "project_slug": "my_project",
  "author_name": "Your Name",
  "version": "0.1.0",
  "use_database": ["no", "sqlite", "postgresql"],
  "license": ["MIT", "BSD-3", "GPLv3", "Apache-2.0", "proprietary"],
  "python_version": "3.11"
}
  • String → free text prompt with that default.
  • Array → selection prompt (list of choices).
  • Number → numeric prompt.
  • Boolean → not natively supported; use "y" / "n" or arrays ["yes", "no"].

Derived Variables (Jinja2 in JSON)

Cookiecutter supports Jinja2 inside cookiecutter.json to compute derived values:

{
  "project_name": "My Project",
  "project_slug": "{{ cookiecutter.project_name|lower|replace(' ', '_')|replace('-', '_') }}",
  "pkg_name": "{{ cookiecutter.project_slug|replace('_', '') }}"
}

When entering project_name = "My Awesome Project":

  • project_slugmy_awesome_project (auto-computed, not prompted).
  • pkg_namemyawesomeproject.

Note: Derived variables with Jinja2 are not shown in the interactive prompt. They are computed automatically.


The {{ cookiecutter.xxx }}/ Directory

The template root directory uses Jinja2 in its name. When generated, it is replaced by the variable value:

{{ cookiecutter.project_name }}/     →  My Project/

Jinja2 in File and Subdirectory Names

{{ cookiecutter.project_name }}/
├── {{ cookiecutter.module_name }}.py
├── {{ cookiecutter.author|lower }}_config.yaml
└── tests/
    └── test_{{ cookiecutter.module_name }}.py

Jinja2 in File Content

Any file inside the template is processed with Jinja2:

# setup.py
from setuptools import setup

setup(
    name="{{ cookiecutter.project_slug }}",
    version="{{ cookiecutter.version }}",
    author="{{ cookiecutter.author_name }}",
)
# README.md
# {{ cookiecutter.project_name }}

Author: {{ cookiecutter.author_name }}
License: {{ cookiecutter.license }}

Conditional Files

Cookiecutter does not natively support conditionals on file existence. This is handled with:

  1. post_gen_project.py hooks — delete files based on conditions.
  2. Jinja2 conditionals inside the file — leave content empty.

Example with post-gen hook

# hooks/post_gen_project.py
import os
import shutil

if not {{ cookiecutter.use_docker|tojson }}:
    os.remove("Dockerfile")
    shutil.rmtree("docker/")

Example with Jinja2 conditional

{% if cookiecutter.use_docker == "yes" %}
FROM python:{{ cookiecutter.python_version }}-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
{% endif %}

hooks/ Directory

Contains Python scripts that run before and after generating the project.

hooks/
├── pre_gen_project.py     # Before creating the project
└── post_gen_project.py    # After creating the project

See Hooks Guide for full details.


Special Files and Directories

_copy_without_render

A file named _copy_without_render at the template root lists paths that are NOT processed with Jinja2:

# _copy_without_render
*.html
docs/_build/
node_modules/
*.min.js

Useful for files that contain {{ }} but are not Jinja2 (e.g., Angular, Vue, Go templates).

_extensions/

Directory for custom Jinja2 filters:

_extensions/
└── my_filters.py
# _extensions/my_filters.py
def slugify(value):
    return value.lower().replace(' ', '-').replace('_', '-')

Usage in cookiecutter.json:

{
  "_extensions": ["my_filters.slugify"],
  "project_slug": "{{ cookiecutter.project_name|slugify }}"
}

Naming Conventions

VariableConventionExampleTypical Use
project_nameHuman readableMy Awesome ProjectREADME, docs
project_slugsnake_casemy_awesome_projectdirectory names, imports
pkg_nameno separatorsmyawesomeprojectPyPI, setup.py
repo_namekebab-casemy-awesome-projectGitHub repo name
module_namesnake_casecoremain module name

Next Steps