A writing toolkit that turns Markdown into books. Write a novel, essay, or paper in plain text files, and writekit generates ePub, HTML, PDF, DOCX, and Markdown output.
It's similar to what programmers use for building apps, but for structured text: chapters, parts, characters, outlines, front/back matter — assembled into a finished book.
For plugin authors, see PLUGINS.md.
You need Node.js (version 18 or later). Then open a terminal and run:
npm install -g writekitNow you can use the writekit command. In this guide we will use the shorter alias wk.
wk init my-novelYou'll be asked for a title, author name, and language. A new folder is created with everything you need to start writing.
Open the manuscript/ folder and start writing in Markdown — a simple text format where **bold** makes bold, *italic* makes italic, and # Heading makes a heading. If you've ever written a message on Slack, Discord, or Reddit, you already know the basics.
When you create a project, writekit asks what type of text you're writing:
| Type | Best for | Build sections |
|---|---|---|
| novel | Novels, novellas, long-form fiction | cover, title page, TOC, content, back cover, about, colophon |
| collection | Anthologies — short stories, poems, essays | cover, title page, TOC, content, back cover, about, colophon |
| essay | Single long-form non-fiction | cover, title page, TOC, content, back cover, about, colophon |
| paper | Academic papers with bibliography | title block, abstract, content, bibliography |
Each type sets up only the folders and files you need. You choose the type once at creation:
wk init my-essay --type essayYou can also install external type packages. A package named writekit-type-screenplay makes the type available as screenplay:
npm install writekit-type-screenplay
wk init my-script --type screenplayExternal type packages are discovered from node_modules in the current folder or any parent folder. By default they should include a type.yaml file at package root. If needed, the package can point somewhere else with package.json -> writekit.type.definition.
If the type also needs custom logic, it can export a runtime plugin with hooks such as onInit, onCheck, onBuild, and onSync. See PLUGINS.md.
cd my-novel
wk build htmlOpen the generated file in build/ with your browser. That's your book.
When you create a project, writekit generates this structure:
my-novel/
├── config.yaml Title, author, language, and other metadata
├── style.yaml Writing rules — point of view, tense, tone
├── timeline.yaml Key events in chronological order
├── synopsis.md A short summary of your book
├── backcover.md Back cover text — the reader-facing pitch
│
├── outline/ Plan your story before writing
│ ├── plot.md The overall arc (acts, major beats)
│ └── chapters/ One file per chapter outline
│
├── manuscript/ Your actual text
│ ├── dedication.md (optional) Dedication page
│ ├── preface.md (optional) Preface
│ ├── foreword.md (optional) Foreword
│ ├── prologue.md (optional) Prologue (novel only)
│ ├── 01-chapter-one.md Numbered chapter files
│ ├── 02-chapter-two.md
│ ├── epilogue.md (optional) Epilogue (novel only)
│ ├── afterword.md (optional) Afterword
│ └── appendix.md (optional) Appendix
│
├── characters/ Character sheets (name, role, backstory)
├── world/ Locations, cultures, systems
├── contributors/ Author, translator, editor bios
├── notes/ Free-form ideas, research, anything
├── reference/ External material (images, PDFs, sources)
├── assets/ Cover image (cover.jpg or cover.png), illustrations
│
└── build/ Generated output (PDF, ePub, HTML, DOCX, MD)
└── reports/ Auto-generated summaries of your project
You only need to care about the folders. The rest is handled by writekit.
For longer books, you can organize chapters into parts:
manuscript/
├── prologue.md
├── part-01/
│ ├── part.yaml # title: "The Beginning"
│ ├── 01-the-arrival.md
│ └── 02-the-journey.md
├── part-02/
│ ├── part.yaml # title: "The End"
│ └── 01-the-return.md
└── epilogue.md
Parts are created with wk add part "Title" and chapters are assigned with wk add chapter "Title" --part 1. Front/back matter files (prologue, epilogue, etc.) stay in the manuscript root, outside any part.
When parts exist, wk check will warn about numbered chapter files left in the root — they should be assigned to a part.
These special sections are recognized by filename and rendered in the correct position:
| Section | Position | Available for |
|---|---|---|
dedication.md |
front | novel, collection, essay |
epigraph.md |
front | novel, collection, essay |
preface.md |
front | novel, collection, essay |
foreword.md |
front | novel, collection, essay |
prologue.md |
front | novel |
epilogue.md |
back | novel |
afterword.md |
back | novel, collection, essay |
appendix.md |
back | novel, collection, essay, paper |
author-note.md |
back | novel, collection, essay |
Add them with wk add prologue, wk add epilogue, etc. Remove with wk remove prologue.
| Command | What it does |
|---|---|
wk init <name> |
Create a new project (prompts for type, title, author, language) |
wk init <name> --type essay |
Create a project of a specific type |
wk add chapter <title> |
Add a new chapter |
wk add chapter <title> --part 1 |
Add a chapter inside part 1 |
wk add part <title> |
Add a new part (creates manuscript/part-NN/) |
wk add character <name> |
Add a character sheet (novel only) |
wk add location <name> |
Add a place to your world (novel only) |
wk add concept <term> |
Add a concept/term definition (essay, paper) |
wk add argument <claim> |
Add an argument sheet (essay only) |
wk add note <title> |
Add a note |
wk add event <description> |
Add a timeline event (novel only) |
wk add author <name> |
Add an author (creates contributor sheet) |
wk add translator <name> |
Add a translator (creates contributor sheet) |
wk add editor <name> |
Add an editor (creates contributor sheet) |
wk add illustrator <name> |
Add an illustrator (creates contributor sheet) |
wk add source <title> |
Add a bibliography source (paper only) |
wk add prologue |
Add a prologue (novel only) |
wk add epilogue |
Add an epilogue (novel only) |
wk add dedication |
Add a dedication page |
wk add epigraph |
Add an epigraph (opening quotation) |
wk add preface |
Add a preface |
wk add foreword |
Add a foreword |
wk add afterword |
Add an afterword |
wk add appendix |
Add an appendix |
wk add author-note |
Add an author's note |
wk remove chapter <number> |
Remove a chapter and renumber remaining |
wk remove part <number> |
Remove a part (moves chapters to root) |
wk remove author <name> |
Remove an author from the project |
wk remove character <name> |
Remove a character sheet |
wk remove location <name> |
Remove a worldbuilding entry |
wk remove note <name> |
Remove a note |
wk remove prologue |
Remove the prologue (and other sections similarly) |
wk rename character <old> <new> |
Rename a character — updates file, frontmatter, and all references |
wk rename location <old> <new> |
Rename a location — updates file, frontmatter, and all references |
wk rename concept <old> <new> |
Rename a concept — updates file, frontmatter, and all references |
| Command | What it does |
|---|---|
wk build html |
Build as a web page (best for preview) |
wk build epub |
Build as an ePub ebook |
wk build pdf |
Build as a PDF document |
wk build docx |
Build as a Word document |
wk build md |
Build as a single Markdown file (the complete book) |
wk build all |
Build all formats at once |
wk build |
Build using your preferred formats from config.yaml |
wk build clean |
Delete all generated files |
If the built-in formats are not enough, you can add a local format plugin in formats/ inside your project:
// formats/plaintext.mjs
export default {
name: "plaintext",
extension: "txt",
async build(ctx) {
return "# " + ctx.config.title + "\n" + ctx.chapters.map((ch) => ch.title).join("\n");
},
};Then build it with:
wk build plaintextLocal formats can also be listed in build_formats in config.yaml.
You can also install a format plugin as an npm package. A package named writekit-format-latex makes the format available as latex:
npm install writekit-format-latex
wk build latexThe package should export a default plugin object with a build(ctx) function. By default, writekit loads the package entrypoint; if needed, the package can override it with package.json -> writekit.format.entry.
Format plugins can also expose configSchema, and then read their validated options from format_options.<format> in config.yaml.
| Command | What it does |
|---|---|
wk import <file.md> |
Import a Markdown file — splits it into manuscript chapters by # Heading boundaries |
wk import <file.md> --start-at 5 |
Start chapter numbering at 5 |
wk import <file.md> --part 2 |
Import into part 2 |
wk export |
Export entire project to a single structured Markdown file (config, characters, world, outline, manuscript, notes) |
wk export -o path.md |
Export to a specific file |
The export produces a self-contained .md with all source files — useful for giving full context to an LLM, archiving, or sharing.
Manage translations of your project. The translation system creates a standalone writekit project for each target language.
| Command | What it does |
|---|---|
wk translate init --to en |
Create a translation project in translations/en/ |
wk translate init --to en --context |
Also copy context dirs (characters, world, outline) |
wk translate init --to en --translator "Name" |
Set the translator name |
wk translate init --to en --output ../book-en |
Create in a custom directory |
wk translate glossary |
Show glossary — highlight untranslated entries |
wk translate status |
Show translation progress per file |
wk translate verify |
Check glossary consistency in translated text |
wk translate sync |
Add new source chapters, flag removed ones |
wk translate diff |
Show which files are behind the source |
The translation project contains:
translation.yaml— links back to the source projecttranslation-glossary.yaml— proper names extracted from characters, world, concepts, contributors, configmanuscript/— empty chapter files withsource_pathandsource_hashin frontmatter for drift detection
Each target is a full writekit project — wk check and wk build work on it independently.
| Command | What it does |
|---|---|
wk check |
Validate your project — checks missing files, broken YAML, naming issues, and explicit cross-references |
wk watch |
Watch for changes and rebuild all formats from build_formats in config. Reloads config on every change. |
wk sync |
Synchronize derived fields — contributor roles, AGENTS.md, reports |
wk stats |
Show detailed statistics — word count, reading time, chapter balance, word frequency |
Themes control how your book looks in HTML, ePub, and DOCX formats.
| Command | What it does |
|---|---|
wk theme list |
See available themes |
wk theme use <name> |
Switch to a different theme |
wk theme create <name> |
Create your own theme (copies the default as a starting point) |
Writekit ships with two themes:
- default — serif, traditional book typography (Georgia, warm brown accents)
- minimal — clean sans-serif, modern (Calibri, blue accents)
The built-in themes live inside the writekit package and cannot be modified. When you run wk theme create my-theme, writekit copies the default theme into your project's themes/my-theme/ folder. You can then edit those files freely.
A theme folder contains:
themes/my-theme/
├── theme.yaml # Name, description, DOCX font/color settings
├── html.css # Styles for HTML and PDF output
└── epub.css # Styles for ePub output (simpler CSS)
To switch themes: wk theme use my-theme (updates config.yaml).
When you update writekit via npm, the built-in themes are refreshed but your custom themes in themes/ are never touched.
Place font files in your theme's fonts/ folder or in assets/fonts/:
themes/my-theme/fonts/
├── MySerif.woff2
└── MySans.ttf
Or project-wide:
assets/fonts/
├── CustomBody.woff2
└── CustomHeading.otf
Supported formats: .woff2, .woff, .ttf, .otf. Fonts are automatically embedded in HTML (as base64 @font-face) and ePub (as ZIP entries). Then reference them in your theme CSS:
body { font-family: "CustomBody", Georgia, serif; }
h1, h2 { font-family: "CustomHeading", sans-serif; }You can customize Word output by providing a .docx template with your own styles, fonts, and formatting:
- Project template:
assets/template.docx - Theme template:
themes/my-theme/template.docx
When building DOCX, writekit generates the content normally, then merges it into the template — keeping the template's styles, fonts, numbering, and settings. If no template is found, the built-in defaults are used.
To create a template: build your project as DOCX, open it in Word, adjust styles and fonts, then save as assets/template.docx.
Writekit uses Markdown — a simple way to format text that's readable even without rendering.
| What you write | What you get |
|---|---|
# Chapter Title |
A big heading |
## Section |
A smaller heading |
**bold text** |
bold text |
*italic text* |
italic text |
> a quote |
A blockquote |
- item |
A bullet list |
1. item |
A numbered list |
[text](url) |
A clickable link |
`code` |
Inline code |
--- |
A horizontal line |
 |
An image |
{width=50%} |
An image with explicit width |
Text with a note[^1] |
A footnote reference |
[^1]: The footnote text. |
The footnote definition |
All of these work in every output format (HTML, ePub, PDF, Word). Images use local paths relative to the project root (e.g. assets/photo.jpg).
Use the standard Markdown footnote syntax:
This claim needs a source[^1]. Another point here[^2].
[^1]: Smith, *On Writing*, 2023.
[^2]: This is a longer note that explains
the point in more detail.Footnotes are rendered at the bottom of each chapter in HTML/ePub/PDF, and as native Word footnotes in DOCX.
Place images in assets/ and reference them in your text:
To control the width (so images don't overflow the text area):
{width=50%}The image will never exceed the text width regardless of its original size. In all formats, the aspect ratio is preserved.
Some files have a special header at the top between --- marks. This is called frontmatter and contains metadata about that file:
---
chapter: 1
title: The Beginning
pov: "Marco"
draft: 1
---
# The Beginning
It was a dark and stormy night...Writekit uses this metadata for validation, reports, and building your book. You don't need to memorize the fields — wk add creates files with the right frontmatter already in place.
Manuscript chapters (all types):
| Field | Required | Description |
|---|---|---|
title |
yes | Chapter title |
chapter |
novel, paper | Chapter number (auto-assigned by wk add) |
pov |
novel only | Point-of-view character |
author |
collection | Per-chapter author (for anthologies with multiple writers) |
draft |
no | Draft number (tracked in reports and checked for consistency) |
Front/back matter sections (prologue.md, epilogue.md, etc.):
| Field | Required | Description |
|---|---|---|
title |
no | Custom title (defaults to i18n label, e.g. "Prologo" in Italian) |
show_title |
no | Set to false to hide the heading (useful for dedication) |
toc |
no | Set to false to exclude from table of contents |
Part definition (part.yaml inside manuscript/part-NN/):
| Field | Required | Description |
|---|---|---|
title |
yes | Part title (e.g. "The Beginning") |
Characters (novel only):
| Field | Required | Description |
|---|---|---|
name |
yes | Character name |
role |
yes | protagonist, antagonist, supporting, minor |
aliases |
no | Alternative names, nicknames |
age |
no | Age or age range |
relationships |
no | List of relationships |
World / Locations (novel only):
| Field | Required | Description |
|---|---|---|
name |
yes | Location name |
type |
yes | location, culture, system, etc. |
Contributors (all types):
| Field | Required | Description |
|---|---|---|
name |
yes | Full name |
roles |
auto | Auto-derived from config.yaml (author, translator, editor, illustrator) |
The body of a contributor file is their biography, rendered in the "About the Author" section.
Arguments (essay only):
| Field | Required | Description |
|---|---|---|
claim |
yes | The argument's central claim |
related |
no | Related concept names |
Concepts (essay, paper):
| Field | Required | Description |
|---|---|---|
term |
yes | The term or concept name |
related |
no | Related concept/argument names |
The main settings file. The most important fields:
title: "My Novel"
author: "Your Name"
language: en
build_formats: # What 'wk build' generates by default
- html
- epub
theme: default # Which theme to useOther fields (subtitle, genre, ISBN, publisher, etc.) are optional and used in the book's colophon page.
If you place a cover.jpg or cover.png in the assets/ folder, it will automatically appear in all output formats. You can also set cover: path/to/image in config.yaml.
The optional print_preset field in config.yaml controls page size, margins, and print layout features:
print_preset: trade # 6×9in, US trade paperback| Preset | Size | Page numbers | Running header | Mirror margins | Recto start |
|---|---|---|---|---|---|
screen |
A4 | no | no | no | no |
a4 |
210×297mm | yes | no | no | no |
a5 |
148×210mm | yes | yes | yes | yes |
pocket |
4.25×7in | yes | yes | yes | yes |
digest |
5.5×8.5in | yes | yes | yes | yes |
trade |
6×9in | yes | yes | yes | yes |
royal |
6.14×9.21in | yes | yes | yes | yes |
kdp |
6×9in + bleed | yes | yes | yes | yes |
ingramspark |
6×9in + bleed | yes | yes | yes | yes |
lulu |
6×9in + bleed | yes | yes | yes | yes |
- Page numbers — centered in footer (PDF) or in header corners (DOCX)
- Running header — DOCX: book title on verso (left), chapter title on recto (right). PDF: book title centered.
- Mirror margins — inner margin (gutter) is larger for binding, alternates sides on odd/even pages
- Recto start — chapters and parts start on right-hand pages (blank page inserted if needed)
If print_preset is omitted, all project types default to screen — an A4 layout with no print features, optimized for on-screen reading. When you're ready to print or publish, add print_preset to your config.yaml:
print_preset: a5 # EU standard book
# print_preset: trade # 6×9 in US trade paperback
# print_preset: kdp # Amazon KDP with bleedFor the rare cases where you want a preset as a base but need a small adjustment, you can add layout overrides:
print_preset: trade
layout:
running_header: false
page_numbers: true
recto_start: true
margin:
inner: 24
outer: 18Supported layout overrides are intentionally limited:
page_numbersrunning_headerrecto_startmargin.innermargin.outer
You can extend presets locally or via npm packages:
- local:
presets/<name>.mjs|js|cjs - package:
writekit-preset-<name>
Example local preset:
// presets/roomy.mjs
export default {
preset: {
name: "Roomy",
description: "Large trim with generous inner margin",
width: 160,
height: 240,
margin: { top: 20, bottom: 20, inner: 26, outer: 18 },
bleed: 3,
mirrorMargins: true,
pageNumbers: true,
runningHeader: true,
rectoStart: true
}
};Then in config.yaml:
print_preset: roomyFor the full plugin API, including type hooks and format options, see PLUGINS.md.
The language field controls editorial labels ("Table of Contents", "Colophon", etc.) and section titles (prologue, epilogue, etc.).
| Code | Language | Code | Language | Code | Language |
|---|---|---|---|---|---|
en |
English | ru |
Russian | nl |
Dutch |
it |
Italian | ar |
Arabic | pl |
Polish |
fr |
French | hi |
Hindi | tr |
Turkish |
de |
German | zh |
Chinese | sv |
Swedish |
es |
Spanish | ko |
Korean | el |
Greek |
pt |
Portuguese | ja |
Japanese |
Chinese and Japanese use native numerals for parts and chapters (第一部, 第一章). Korean uses 제1부, 제1장.
Writing rules that help keep your text consistent:
pov: third-person # first-person, third-person, or omniscient
tense: past # past or present
tone: "" # e.g., "dark", "humorous", "formal"
voice: "" # e.g., "conversational", "literary"
rules: [] # Any specific rules, e.g., "no adverbs"These are especially useful when working with an AI assistant — they help maintain a consistent voice throughout the book.
Each project type comes with typographic defaults (paragraph indent, spacing, alignment, etc.). You can override any of them in style.yaml:
typography:
paragraph_indent: "0" # no indent (default for paper)
paragraph_spacing: 0.5rem # space between paragraphs
text_align: left # left or justify
line_height: "2.0" # double spacing
hyphenation: false # disable auto-hyphenation
scene_break: "* * *" # how --- renders in chapters
chapter_opening: large # large, medium, or small top space
orphans_widows: 3 # min lines at page top/bottom
chapter_heading: label_number_title # how chapter titles appear
part_heading: label_number_title # how part titles appearOnly include the properties you want to change — the rest use the defaults for your project type.
Control how chapter and part titles are displayed:
| Format | Chapter example | Part example |
|---|---|---|
title (default) |
The Arrival | The Beginning |
label_number_title |
Chapter 1 / The Arrival | Part I / The Beginning |
label_number |
Chapter 1 | Part I |
number_title |
1 / The Arrival | I / The Beginning |
number |
1 | I |
Parts use Roman numerals (I, II, III). For Chinese/Japanese, native numerals are used (第一部, 第一章). For Korean: 제1부, 제1장.
In collection projects, each chapter can have an author field in its frontmatter. This author is displayed below the chapter title and in the table of contents — useful for anthologies with multiple contributors.
Every time you build, writekit generates reports in build/reports/:
- status.md — Word count per chapter, total progress, latest draft, missing draft metadata, draft distribution
- cast.md — Which characters appear in which chapters
- relationships.md — Character relationships as a graph and per-character summary
- locations.md — Which locations appear in which chapters
- timeline.md — Your timeline formatted and readable
- changelog.md — Difference from the previous build snapshot (added, removed, updated chapters)
These are auto-generated and overwritten on every build. Don't edit them.
wk check also validates timeline.yaml: it warns if an event points to a missing chapter, or if the chronological timeline goes backward relative to chapter order.
If your book is intentionally non-linear, you can keep the timeline chronological and disable only the chapter-order warning:
type_options:
timeline:
allow_non_linear: trueWritekit can be used programmatically. All core functions are exported from the main package:
import { loadConfig, loadChapters, buildFormat, checkProject } from "writekit";
const config = await loadConfig("./my-novel");
const chapters = await loadChapters("./my-novel");
// Build a specific format
const result = await buildFormat("html", "./my-novel", config, chapters, theme);
// Validate
const { warnings, errors } = await checkProject("./my-novel");Available exports include project loading (loadConfig, loadChapters, loadContributors, loadParts), build (buildFormat, hasFormat, allFormatNames), validation (checkProject, syncProject), typography (loadTypography), type system (loadType, allTypeNames), presets (getPreset, resolvePrintPreset), translation functions, and utilities (fileExists, slugify, supportedLanguages).
See src/index.ts for the complete list.
wk build html produces a self-contained HTML file in build/. You can host it anywhere that serves static files.
GitHub Pages — Push build/ to a gh-pages branch, or configure Settings > Pages to serve from a folder. See GitHub Pages docs.
Netlify — Set the build command to npm i -g writekit && wk build html and the publish directory to build/. See Netlify docs.
Vercel — Same approach: install writekit in the build step, output to build/. See Vercel docs.
Any static host — Just upload the contents of build/. The HTML output is a single file with all CSS and images embedded inline — no server required.
For ePub distribution, wk build epub produces a standard .epub file you can upload to any ebook store or attach to a GitHub Release.
MIT