Create a template#

This tutorial walks you through building a minimal Cookieplone template repository. By the end, you will have a working template that generates a small project on your machine.

Prerequisites:

  • uv installed.

  • Basic familiarity with Jinja2 templating syntax.

Step 1: Create the repository structure#

A template repository needs a repository configuration file at its root and at least one template directory. You will build this structure:

my-template/
├── cookieplone-config.json            ← repository configuration: lists the templates
└── templates/
    └── myproject/                     ← one template
        ├── cookieplone.json           ← the questions this template asks
        └── {{ cookiecutter.project_slug }}/
            ├── README.md
            └── pyproject.toml

Create the directories:

mkdir -p "my-template/templates/myproject/{{ cookiecutter.project_slug }}"

Cookieplone renders directory names too: in the generated output, {{ cookiecutter.project_slug }} takes the value of the project_slug answer.

Note

A plain directory is enough for this tutorial. If you keep your template repository in git, commit your files before you run Cookieplone. A git repository without any commit fails with an error such as ValueError: Reference at 'refs/heads/main' does not exist.

Step 2: Write the repository configuration#

The cookieplone-config.json file tells Cookieplone which templates the repository provides and how to group them in the selection menu. Create my-template/cookieplone-config.json:

{
  "version": "1.0",
  "title": "My templates",
  "description": "Templates for my organization.",
  "groups": {
    "projects": {
      "title": "Projects",
      "description": "Generators that create a new project.",
      "templates": ["myproject"]
    }
  },
  "templates": {
    "myproject": {
      "path": "./templates/myproject",
      "title": "My project",
      "description": "A minimal example project."
    }
  }
}
  • version is the version of the repository configuration format and must be "1.0".

  • templates maps each template ID to the directory that contains it.

  • groups organizes templates into categories. Each template must belong to exactly one group.

See Repository configuration (cookieplone-config.json) for every available key.

Step 3: Write the template questions#

Each template has a cookieplone.json file that defines the questions asked during generation. Create my-template/templates/myproject/cookieplone.json:

{
  "id": "myproject",
  "schema": {
    "title": "My project",
    "description": "A minimal example project.",
    "version": "2.0",
    "properties": {
      "project_title": {
        "type": "string",
        "title": "Project title",
        "description": "The human-readable name of your project.",
        "default": "My Project"
      },
      "project_slug": {
        "type": "string",
        "title": "Project slug",
        "description": "Identifier used for the directory name.",
        "default": "my-project"
      },
      "author_name": {
        "type": "string",
        "title": "Author name",
        "default": "Jane Developer"
      },
      "author_email": {
        "type": "string",
        "title": "Author email",
        "default": "jane@example.com"
      }
    }
  },
  "config": {
    "versions": {
      "python": "3.12"
    }
  }
}
  • id identifies the template.

  • schema holds the form: its version must be "2.0", and each entry in properties becomes a question, asked in the order it appears.

  • config holds generator settings that the user doesn't see. Here, versions pins a Python version that the template files can use.

See Schema v2 reference (cookieplone.json) for every field type and setting.

Step 4: Write the template files#

Cookieplone renders the files inside {{ cookiecutter.project_slug }} with Jinja2. Answers are available as {{ cookiecutter.<question> }}, and version pins as {{ versions.<key> }}.

Create my-template/templates/myproject/{{ cookiecutter.project_slug }}/README.md:

# {{ cookiecutter.project_title }}

Created by {{ cookiecutter.author_name }} <{{ cookiecutter.author_email }}>.

Create my-template/templates/myproject/{{ cookiecutter.project_slug }}/pyproject.toml:

[project]
name = "{{ cookiecutter.project_slug }}"
requires-python = ">={{ versions.python }}"
authors = [
    {name = "{{ cookiecutter.author_name }}", email = "{{ cookiecutter.author_email }}"},
]

Step 5: Run your template#

Cookieplone reads the template repository from the COOKIEPLONE_REPOSITORY environment variable. From the directory that contains my-template, run:

COOKIEPLONE_REPOSITORY=./my-template uvx cookieplone

Cookieplone shows a numbered list of categories, then a numbered list of the templates in the chosen category. Your repository has one of each, so press Enter twice to select the only category, then the only template. Answer the questions, or press Enter to accept each default. After the last question, Cookieplone shows your answers and asks you to confirm them.

To skip the prompts and accept every default, pass the template ID and --no-input:

COOKIEPLONE_REPOSITORY=./my-template uvx cookieplone myproject --no-input

Step 6: Inspect the output#

Cookieplone generates the project in the current directory:

my-project/
├── .cookieplone.json
├── pyproject.toml
└── README.md

With the default answers, my-project/README.md contains:

# My Project

Created by Jane Developer <jane@example.com>.

And my-project/pyproject.toml contains:

[project]
name = "my-project"
requires-python = ">=3.12"
authors = [
    {name = "Jane Developer", email = "jane@example.com"},
]

.cookieplone.json records your answers, so you can generate the project again with the same values. See Use an answers file.

What's next?#