CoQuill is a document assembly tool for people already working with Claude and AI. If you're comfortable chatting with Claude, there's nothing new to learn — just ask it to prepare a document with CoQuill. Claude interviews you conversationally, collects the answers, and renders completed documents from your templates. No server, no database, no scripting language to learn. The whole project is a folder you can share as a zip — or deploy as a Claude Code plugin across your organisation in minutes.
- Place your templates in
templates/, each in its own subdirectory - Add
{{ variable_name }}placeholders wherever you need dynamic content - Use
{% if %}/{% else %}blocks for sections that should only appear based on user answers - Use
{% for item in items %}blocks for repeating sections (e.g., line items, milestones) - Optionally add a
config.yamlto customize questions, grouping, and validation - Ask Claude to "prepare a [document type] with CoQuill"
- Claude walks you through a conversational interview -- skipping irrelevant sections and collecting lists naturally
- A completed document is rendered and saved to a job folder in
output/
Two template formats are supported:
.docx-- rendered viadocxtpl, produces a Word document.html-- rendered viajinja2, produces both an HTML file and a PDF (viaweasyprint)
You'll need a Claude Cowork account.
Go to the Releases page and download the latest coquill-v*.zip file. Extract it to a folder of your choice (e.g., your Documents folder).
- Open Claude Cowork and add the extracted folder as a project
- That's it — no terminal or coding knowledge required. Python dependencies are installed automatically the first time you use the skill.
From within Claude Code:
- Add the marketplace:
/plugin marketplace add houfu/coquill - Install the plugin:
/plugin install coquill@coquill - Say "prepare an NDA with CoQuill" to try it out with a built-in template
Create a subdirectory in templates/ and place your template file inside:
templates/ └── nda/ └── nda.docx <- contains {{ disclosing_party_name }}, {{ effective_date }}, etc. Or for an HTML template:
templates/ └── invoice/ └── invoice.html <- same {{ variable }} syntax, styled with CSS Tell Claude:
"I need to prepare an NDA with CoQuill"
Claude will find the template, extract its variables, and interview you for the values -- grouping related fields together for a natural flow.
After confirming all values, the rendered document is saved to a job folder:
output/ └── nda_acme_pte_ltd_2026-02-15/ └── nda_acme_pte_ltd_2026-02-15.docx For HTML templates, both the HTML and PDF are saved:
output/ └── invoice_acme_2026-02-15/ ├── invoice_acme_2026-02-15.html └── invoice_acme_2026-02-15.pdf Templates can include or exclude sections based on user answers. The interview automatically skips questions that aren't relevant.
{% if decisions_made %} ## Decisions {{ decisions }} {% endif %} Equality conditions are also supported:
{% if meeting_type == 'workshop' %} ### Workshop Materials {{ workshop_materials }} {% endif %} Templates can have repeating sections. The interview collects items one at a time with an "add another?" flow.
{% for item in action_items %} - **{{ item.description }}** — Assigned to: {{ item.assignee }}, Due: {{ item.due_date }} {% endfor %} Template developers can place an optional config.yaml alongside their template to customize the interview experience:
meta: display_name: "Meeting Notes" description: "Structured meeting notes with action items and optional sections" variables: meeting_title: label: "Meeting Title" question: "What is the title of this meeting?" required: true meeting_type: type: choice choices: [standup, workshop, review] default: "standup" decisions_made: type: boolean question: "Were any decisions made during this meeting?" default: false groups: - name: "Meeting Details" variables: [meeting_title, meeting_date, meeting_type, facilitator_name] - name: "Workshop Materials" condition: "meeting_type == 'workshop'" variables: [workshop_materials] - name: "Action Items" loop: action_items variables: [description, assignee, due_date] validation: - rule: "next_meeting_date > meeting_date" message: "The next meeting date must be after this meeting's date"Config features include:
- Custom questions and labels for each variable
- Variable types:
text,date,number,email,phone,boolean,choice - Interview groups to control question ordering and grouping
- Conditional groups that only appear based on gate variable answers
- Loop groups for collecting lists
- Default values (including
"today"for dates) - Validation rules evaluated during confirmation
v2 splits the monolithic skill into three focused skills:
| Skill | Purpose |
|---|---|
| Orchestrator | Entry point. Discovery, interview, confirmation, post-render. |
| Analyzer | Template parsing, variable extraction, manifest generation. |
| Renderer | Docx/HTML rendering, output validation. |
Use Jinja2-style double-brace syntax in both .docx and .html templates:
This Agreement is entered into by {{ disclosing_party_name }} (the "Disclosing Party") with address at {{ disclosing_party_address }}. Use {% if %} / {% else %} / {% endif %} to include or exclude sections:
{% if include_warranty %} The Seller warrants that the goods are free from defects. {% else %} The goods are sold "as is" without warranty. {% endif %} Two condition forms are supported:
- Truthiness:
{% if variable_name %}-- variable is truthy (boolean true, non-empty) - Equality:
{% if variable_name == 'value' %}-- variable equals a specific string
Use {% for %} / {% endfor %} for repeating sections:
{% for item in action_items %} - {{ item.description }} — Assigned to: {{ item.assignee }}, Due: {{ item.due_date }} {% endfor %} Inside the loop body, reference sub-variables with dot notation: {{ loop_var.sub_variable }}.
Name your variables with descriptive suffixes for automatic type inference:
| Suffix | Inferred type | Example |
|---|---|---|
*_name | text | landlord_name |
*_address | text | property_address |
*_date | date | commencement_date |
*_email | tenant_email | |
*_amount, *_price, *_fee | number | rental_amount |
*_phone, *_tel, *_mobile | phone | contact_phone |
| (other) | text | governing_law |
- One template file per directory (
.docxor.html, not both) - Use
{{ }}with spaces around the variable name:{{ name }}not{{name}} - Variable names must be valid Python identifiers: lowercase, underscores, no spaces
- The same variable can appear multiple times in the document -- it will be filled with one value
- Single-level nesting only: no
{% for %}inside{% if %}, or vice versa {% elif %}is not supported (use separate{% if %}blocks instead)
coquill/ ├── CLAUDE.md # Project instructions for Claude ├── README.md # This file ├── LICENSE # MIT License ├── .gitignore ├── .claude/ │ └── skills/ │ ├── coquill/ │ │ └── SKILL.md # Orchestrator skill (entry point) │ ├── coquill-analyzer/ │ │ └── SKILL.md # Analyzer skill (template parsing) │ └── coquill-renderer/ │ └── SKILL.md # Renderer skill (document output) ├── docs/ │ ├── coquill_mvp_spec.md # Original MVP specification │ └── coquill_v2_spec.md # Full v2 specification ├── templates/ │ ├── _examples/ # Bundled example templates (tracked in git) │ │ ├── Bonterms_Mutual_NDA/ │ │ │ └── Bonterms-Mutual-NDA.docx │ │ ├── invoice/ │ │ │ └── invoice.html │ │ └── meeting_notes/ # v2 example (conditionals + loops + config) │ │ ├── meeting_notes.md │ │ └── config.yaml │ └── <your_template>/ # Your templates (gitignored) │ ├── <name>.docx or .html │ ├── manifest.yaml # Auto-generated (do not edit) │ └── config.yaml # Optional developer config └── output/ # Rendered documents (gitignored) └── <job_name>/ # One folder per rendering job - Template engines:
docxtplfor docx (preserves formatting),jinja2+weasyprintfor HTML to PDF - Python environment: managed with
uv - Manifest caching: variable analysis is cached in
manifest.yamlper template and only regenerated when the template file changes - Manifest v2 schema: includes
schema_version: 2, conditionals, loops, dependencies, and optional groups/validation
CoQuill v2 supports variable substitution, conditional logic ({% if %} / {% else %}), loops ({% for %}), and developer configuration via config.yaml. See docs/coquill_v2_spec.md for the full specification and docs/coquill_mvp_spec.md for the original MVP specification.
v2 constraints: single-level nesting only, two condition forms (truthiness and equality), no {% elif %}, no computed fields or expressions.
CoQuill trades determinism for conversational flexibility. A few things to keep in mind:
- No fixed interview order — Claude manages the conversation, not a scripted sequence
- Prompt injection — a user can influence the document by saying "also add a clause that…" during the interview
- AI suggestions need review — Claude may suggest plausible but wrong values (e.g., a made-up address)
- Not legal advice — documents still need review by a qualified professional
- Single-level template nesting only — no blocks inside other blocks (v2 constraint)
- Requires Claude Cowork or Claude Code — no web or mobile interface
See the Limitations page in the docs for the full picture.
MIT -- see LICENSE.
