Content Authoring
How to write, structure, and publish cocktail and whiskey pages to BookStack — including templates, image conventions, and the automated CI quality checks.
Repository Structure
Content lives in two top-level directories, each mapped to a dedicated BookStack book:
repo/
├── cocktails/
│ ├── negroni.md
│ ├── old-fashioned.md
│ └── images/
│ ├── negroni.jpg
│ └── old-fashioned.jpg
├── whiskeys/
│ ├── ardbeg-10.md
│ ├── glenfiddich-12.md
│ └── images/
│ ├── ardbeg-10.jpg
│ └── glenfiddich-12.jpg
└── bookstack-config.json
File Naming Convention
File names are used to derive the BookStack page title. The sync script converts the filename stem to Title Case, treating hyphens and underscores as word separators:
| Filename | BookStack page title |
|---|---|
old-fashioned.md | Old Fashioned |
gin_and_tonic.md | Gin And Tonic |
mojito.md | Mojito |
glenfiddich-12.md | Glenfiddich 12 |
Use lowercase, hyphen-separated words. Numbers are preserved.
Cocktail Page Template
Every file in cocktails/ must include the following sections (H2 headings). The CI test suite verifies their presence.
Required sections
| Section | Purpose |
|---|---|
## Details | Metadata table — author, date, glassware, season, allergens |
## Recipe | Ingredients and method / instructions |
## History | Origin and background of the cocktail |
## Notes | Serving suggestions, variations, pairing notes |
Minimal example

---
## Details
| Field | Value |
|---------------|----------------|
| Author | Your Name |
| Date Created | 2026-04-10 |
| Updated | 2026-04-10 |
---
## Recipe
**Ingredients**
- 30 ml gin
- 30 ml sweet vermouth
- 30 ml Campari
**Method**
Stir all ingredients over ice. Strain into a rocks glass. Garnish with orange peel.
---
## History
The Negroni is said to have been invented in Florence in 1919 …
---
## Notes
Works well as a pre-dinner aperitivo. Try substituting mezcal for gin for a smoky twist.
Whiskey Page Template
Every file in whiskeys/ must include these sections. Whiskey pages have stricter structural requirements than cocktail pages.
Required sections
| Section | Required sub-sections | Purpose |
|---|---|---|
## Details | — | Metadata table with specific required fields (see below) |
## Tasting Notes | ### Nose, ### Palate, ### Finish | Structured sensory evaluation |
## History | — | Distillery background and product history |
## Notes | — | Food pairing, serving recommendations, personal notes |
Required Details table fields
The ## Details table must contain at least these four rows (the CI test checks for them):
| Field | Example |
|---|---|
Type | Single Malt Scotch |
Origin | Islay, Scotland |
Distiller | Ardbeg Distillery |
ABV | 46% |
Minimal example

---
## Details
| Field | Value |
|----------------|------------------------|
| Author | Your Name |
| Date Created | 2026-04-10 |
| Updated | 2026-04-10 |
| Type | Single Malt Scotch |
| Origin | Islay, Scotland |
| Distiller | Ardbeg Distillery |
| Age Statement | 10 Years |
| ABV | 46% |
---
## Tasting Notes
### Nose
Peat smoke, lemon, vanilla, and a hint of toffee.
### Palate
Rich and peaty with dark chocolate, espresso, and dried fruit.
### Finish
Long, smoky finish with a warming sweetness.
---
## History
Ardbeg Distillery was founded in 1815 on the southern shore of Islay …
---
## Notes
Best enjoyed neat or with a few drops of still water to open it up.
Images
Every page must have a leading image as its very first line:

Conventions
- Images live in
<content-dir>/images/ - The image filename must match the Markdown filename (e.g.,
negroni.md→images/negroni.jpg) - Only
.jpgimages are expected by the CI tests - Images are uploaded to the BookStack Gallery API automatically during sync
- The CI test verifies that every page has an image reference and that the referenced file exists on disk
- A reverse check also runs: every image in
images/must have a corresponding Markdown file
Auto-generated placeholder images
When a new Markdown file is pushed to develop without a corresponding image, the
image generation workflow automatically creates a
placeholder image using Pillow and commits it back to the branch. You can replace it with a
real image at any time.
Authoring Rules
# Page Title H1 headings in your Markdown. BookStack renders the page title from its own UI. The CI test will fail if an H1 is detected.
Markdown style
- Use H2 (
##) for top-level sections and H3 (###) for subsections - Separate sections with a horizontal rule (
---) for visual clarity in BookStack - Use tables (pipe-delimited) for structured metadata in the Details section
- Files must not be empty — the CI will catch zero-byte files
Markdown lint
The markdown linter uses the rules defined in .markdownlint.json. Common rules to be aware of:
- Consistent heading style
- No trailing spaces
- Blank line required before and after headings and code blocks
Spell check
Spell checking uses cspell
with the config in .cspell.json. Spell-check failures appear as inline
annotations on the PR diff. They are warnings only — the pipeline
continues even if words are flagged. Add custom words to .cspell.json to
suppress false positives.
What CI Checks on Your Content
The test suite in tests/test_content.py runs parametrised checks against every page:
Cocktail pages
| Check | What it verifies |
|---|---|
| Required sections | ## Details, ## Recipe, ## History, ## Notes all present |
| No H1 heading | File does not contain a line starting with # |
| Image reference present | File starts with  |
| Image file exists | The referenced image file is on disk |
| Not empty | File size is greater than 0 |
Whiskey pages
| Check | What it verifies |
|---|---|
| Required sections | ## Details, ## Tasting Notes, ## History, ## Notes all present |
| No H1 heading | File does not contain a line starting with # |
| Image reference present | File starts with  |
| Image file exists | The referenced image file is on disk |
| Details table fields | Type, Origin, Distiller, ABV rows present in Details table |
| Tasting Notes subsections | ### Nose, ### Palate, ### Finish all present |
| Not empty | File size is greater than 0 |
Coverage checks
| Check | What it verifies |
|---|---|
| Cocktail image coverage | Every .jpg in cocktails/images/ has a matching .md file |
| Whiskey image coverage | Every .jpg in whiskeys/images/ has a matching .md file |
Publishing Workflow
-
1
Create or edit a Markdown file
Write your content in
cocktails/orwhiskeys/. Add an image toimages/with the matching name. -
2
Commit and push to
developThe CI pipeline starts automatically. If an image is missing, the image-generation workflow creates a placeholder and commits it.
-
3
CI runs quality gates
Secret scan → lint → tests → spell check → sync to local BookStack. If any gate fails, Claude Autofix attempts a fix automatically.
-
4
PR created automatically
After a successful CI run, a pull request from
develop → mainis created (or updated if one already exists). -
5
Merge the PR
Merging to
maintriggers the CD pipeline, which syncs the pages to the remote production BookStack instance.
Content Generators
The scripts in .github/scripts/ can generate entire batches of correctly
structured pages from data definitions:
| Script | Output | Usage |
|---|---|---|
generate-cocktail-pages.py |
One .md per cocktail in cocktails/ |
python3 .github/scripts/generate-cocktail-pages.py |
generate-whiskey-pages.py |
One .md per whiskey in whiskeys/ |
python3 .github/scripts/generate-whiskey-pages.py |
generate-cocktail-images.py |
Placeholder .jpg images in cocktails/images/ |
Run automatically by CI when images are missing |
generate-whiskey-images.py |
Placeholder .jpg images in whiskeys/images/ |
Run automatically by CI when images are missing |
delete-whiskey-pages.py |
Removes whiskey pages from BookStack | Run manually when retiring pages |
The generator scripts embed cocktail and whiskey data definitions directly in the source. Generated pages conform to the template format including inline HTML pill-group checkboxes for glassware, seasons, and allergens (cocktails) and structured tasting note subsections (whiskeys).