gmtales-docs Help

Writing

An article is standard Markdown plus a small set of written controls for metadata, visibility, and layout. Every control is an HTML comment, so a GMTales article is still a valid Markdown file that renders cleanly in any editor.

Standard Markdown Support

All the usual Markdown works: headings, emphasis, lists, links, images, inline code and code blocks, blockquotes, tables, and horizontal rules.

Titles

The first level-one heading (#) becomes the article's title, used for auto-linking, discovery, and display throughout the campaign. Every article needs one; directives may sit above it.

# Corin Vale

Titles don't have to be unique: GMTales disambiguates collisions, and a key gives an article a stable, unambiguous reference when you need one.

A comma in a title splits it into separately-matched names — # Ashford, Halden answers to "Ashford", "Halden" and "Halden Ashford" — so leave the comma out if you don't want the parts matched. See Linking.

Directives

Controls come in three forms:

  1. Single-line: <!-- key: value -->

  2. Flag: <!-- pinned -->, <!-- main -->

  3. Block: <!-- start: type -->… <!-- end: type -->

A block opens with start: or begin: (the two are interchangeable) and closes with end: naming the same type. The editor's autocomplete inserts start:.

<!-- begin: secret; party --> A weathered ranger watches the travellers from a corner booth. <!-- end: secret -->

Arguments

Everything that takes arguments takes them the same way. A declaration — a directive or a block header — separates its arguments with ;:

<!-- start: image; map.webp --> <!-- tree: Locations; Harrowfeld -->

Every declaration has an ordered list of named parameters, listed in the reference below. Write arguments by position, in that order, or by name as name=value:

<!-- start: marker; 48,30 --> by position <!-- start: marker; position=48,30 --> by name

Positional arguments come first, and once you name one, you name the rest. In practice that means the main value by position and anything further by name:

<!-- start: image; map.webp; thumbnail --> <!-- start: marker; 48,30; icon=keep --> <!-- start: time; 3 Frostwane, 999 5E; calendar=downs -->

Returning to position after a name is a validation error. Once a name has been given, position no longer says anything:

<!-- start: marker; icon=keep; 48,30 --> invalid: 48,30 follows a named argument

The remaining rules:

  • ; separates arguments; , does not. A comma belongs to the argument it sits in: a group list, a coordinate pair, a date. 48,30 is one argument, and so is gm, party.

  • An empty argument is a slip, not a blank. A stray trailing ;, a name= with nothing after it, and an empty entry in a comma-separated list (gm,, party) are all validation errors; leave the argument out instead.

  • An entry repeated in a comma-separated list is a validation error too, and case does not make it a different entry: alias: the Wood, The Wood names the same alias twice, since matching ignores case.

  • Only trailing optional arguments may be left out of the positional part. Writing more arguments than the declaration has parameters won't save, and a parameter can't be given twice, by position and then by name.

  • On/off arguments carry no value. Write thumbnail to turn it on and leave it out to turn it off; thumbnail=true is an error, because presence is the value. A bare on/off name counts as named, so <!-- start: image; thumbnail --> is the flag with no image. An image genuinely called "thumbnail" needs src=thumbnail.

  • An unknown name is a validation error, never a value. speaker=Ostren on a quote is an error, not an attribution.

  • Free text is unaffected. Only = introduces a name, so <!-- start: quote; Ostren: The Grey Chronicle --> is an attribution, colon and all. A positional value that genuinely needs a word= inside it goes in the block's body instead.

Argument Reference

Blocks

Block

Arguments, in order

box

—

columns

count — 2, 3 or 4

quote

attribution

secret

audience — group keys and me, comma-separated

image

src required, thumbnail (on/off)

marker

position required, icon

region

points required, child

time

at, from, to, calendar

timeline

calendar

Directives

Directive

Arguments, in order

type, key, calendar, artist, source, license

one required value, named after the directive

portrait

src required

labels

labels required — a comma-separated list

alias

aliases required — a comma-separated list

visibility

audience required — a comma-separated list of group keys and me

relevance

value required

tree

tree required, parent

time

at, description, from, to, calendar

parent

src required, position

marker, inside an image block

position required, description, icon

marker, outside one

position required, image required

marker is the only declaration with two lists. Outside an image block it has to name the image it pins to; inside one, the block already says which image that is.

A time directive written without a description dates the article itself, and an article has one time: one at, or one from and one to, across the whole document. A second bound of a kind already written is a validation error. See Chronology.

Controls Are Validated When You Save

GMTales never guesses at what you wrote. A control that could mean more than one thing, or that names something that doesn't exist, is a validation error: the save fails with a message naming the offending control, and the article stays as it was until the text is clean. Nothing is silently defaulted or dropped.

Mistake

Example

Unknown directive name

<!-- vsibility: gm -->

Unknown flag

<!-- pined -->

Unknown block type

<!-- start: secrt; gm -->

Unmatched block marker

<!-- start: secret --> with no <!-- end: secret -->

A block parameter it can't read

<!-- start: columns; 7 -->, <!-- start: image; map.webp; thumbnial -->

Content a restricted block doesn't accept

a paragraph of prose inside a box

A directive where it can't apply

<!-- key: map --> inside an image block

A block written where nothing reads it

a marker block outside an image block

A secret nested inside another block

a secret block inside a quote or columns

A stray comma in a list

<!-- labels: Downs,, Peaceful -->

A repeated entry in a list

<!-- alias: the Wood, The Wood -->

A reference to something that isn't there

![Map](nowhere.webp), <!-- visibility: conspiracy -->, icon=no-such-asset

An article with no # title

anything without a level-one heading, or a # with nothing after it

Because unrecognised names are validation errors, HTML comments are not quite free-form: a comment shaped <!-- name: value -->, such as <!-- TODO: rewrite this -->, or a single bare word, reads as a control and won't save under an unknown name. Keep scratch notes in a note or a private secret block.

The preview runs the same checks as the save, and it lists every problem it finds, so a document with three mistakes takes one round to fix. An unclosed or misspelled block marker is reported on its own, because nothing below it can be read until it is fixed. A few checks need the campaign's records — a key already in use, a tree parent that must exist, an event date that must parse in its calendar — and those are made by the save itself.

Recognised is not the same as allowed everywhere. An image's own content and the body of an image block each take a narrower set, and a directive that nothing reads there is a validation error.

One exception: a misspelled builtin: icon name cannot be checked at save time and simply shows no icon.

Drafts and Publishing

An article is finished when you say it is. The editor saves two different ways.

Save draft keeps your working copy. Nobody else can read it. Publish makes what is in the editor the article the campaign reads. Write across as many evenings as you like, saving a draft each time, and publish once at the end.

An asterisk on the button, Save draft *, means the editor holds changes you have not saved yet.

An article has one draft at a time. Saving a draft replaces the one before it, so there is no history of drafts to browse and no way back to an earlier one.

Two saves do less than they appear to. Saving a draft that says exactly what is already published throws the draft away, which is how you abandon a change: paste the published text back and save. Saving a draft identical to the draft already there writes nothing at all.

The Draft Banner

An article you have a draft on carries a banner at the top of the page, in the editor and on the article itself, with Publish and Discard draft beside it. Once the article has a published version, the banner's Compare shows it against your draft, with your unpublished changes marked as additions.

What Only You See

While a draft is open, everything you browse reads it. Your article list, search, automatic links and trees all show the draft's title, labels and text. Draft a dozen articles on the Siege Years and they link to each other in your own view before a word of it is public.

Everyone else reads the published version, and an article that has only ever been a draft is not in their list, their search, their feed or their links. They are not told a draft exists either: the badge, the banner and the count are yours alone.

In your own list, Draft marks an article with unpublished changes and Unpublished marks one that has never been published. Show drafts in the overview bar filters to just those, and appears only when you have one.

Discarding

Discard draft throws the working copy away and the article reads as its published version again. The published version is untouched.

What Publishing Does

Publishing is the moment the campaign hears about the article. It dates the article, puts it in the activity feed, marks it unread for the members who can read it, and rebuilds its links in both directions. Saving a draft does none of that, which is why a half-written article never announces itself.

Publishing also clears the draft. The published text becomes a revision and the draft is gone.

Publishing the same article from two tabs fails on the second rather than overwriting the first: GMTales says the article has been published again since you opened it, so open it afresh and make the change there. Both the published version and your draft survive.

A draft may give a key to an article that has none, but changing a key it already has is a validation error: a key is how the rest of the campaign refers to the article, not something a private working copy changes. Publish the change instead, or put the old key back.

Metadata Directives

Type

Files the article under one category, used by filters and type icons. A type is created the first time an article names it.

<!-- type: character -->

Labels

Free-form tags for filtering and search, comma-separated. Spaces around a comma are trimmed.

<!-- labels: Highcairn, Company, Major -->

Labels belong to the passage that declares them, so a labels directive written inside a secret block is read only by that secret's audience. One directive per passage: a second labels in the same passage is a validation error.

Key

An identifier that stays stable when the title changes, unique within the campaign. Used for explicit links.

<!-- key: corin-vale -->

Aliases

Alternate names the article can be auto-linked by, comma-separated. Like labels, aliases belong to the passage that declares them, one directive per passage.

<!-- alias: The Warden, Stonecrown, Ash-Hand -->

Relevance

A number for how central the article is. It drives the relevance sort and the relevance threshold filter in overviews. See Discovery.

<!-- relevance: 98 -->

Pinned

A pinned article sorts first in list view, whatever the sort order.

<!-- pinned --> # The Oathblade

Main

Marks the article as the campaign's landing point, typically its introduction. One per campaign; removing the directive un-sets it. The Main tab shows it as an article page, with the same subpages and control panel as any other article.

<!-- main -->

Only the campaign's creator can set the main article; in someone else's campaign, main in an article of yours is a validation error. Removing it is always allowed, and un-sets the campaign's landing point only when it is already this article.

A draft carrying this directive does not move the landing point. It takes effect when you publish.

Portrait

Sets the article's portrait from an uploaded image, referenced by file name. The portrait appears beside the article in lists and on the article page.

Any image you can see will do. Every upload is stored at portrait size as well, so there is nothing to prepare and nothing to upload twice. Naming your own upload types it Portrait in the gallery; naming someone else's leaves their type alone.

<!-- portrait: corin-vale.jpg -->

An article has one portrait; a second portrait directive is a validation error.

Visibility Control

Article-Level Visibility

The visibility directive restricts the whole article. me means author-only, a group key (like party or gm) targets that group, and multiple targets are comma-separated. Without the directive, the article is open to the whole campaign.

<!-- visibility: me, party -->

Secret Blocks

A secret block hides one passage of an otherwise visible article:

# The Crossed Keys The common room is loud, warm, and crowded with locals. <!-- start: secret; party --> A weathered ranger watches the travellers from a corner booth. The innkeeper calls him the Warden. <!-- end: secret -->

Secrets covers how visibilities combine and what they carry with them.

Formatting Blocks

Blocks differ in how much Markdown they accept:

Block

Body content

Secret

Any Markdown, including other blocks

Columns

Any Markdown, including other blocks

Time

Any Markdown, including other blocks

Quote

Any Markdown (nested blocks are not processed)

Box

Restricted — headings, key: value list items, and images only

Timeline

Restricted — list items or headings with paragraphs

Image

Restricted — a caption, description, and image directives

Marker

Restricted — the pin's text, and no blocks

Region

Restricted — the area's text, and no blocks

Box

A styled fact panel, rendered as a two-column table. Its body accepts exactly three kinds of content, in any order and any number:

Content

Renders as

A heading (# through ######)

A full-width title row spanning both columns

A list item of the form key: value

A row: the key on the left, the value on the right

A paragraph containing an image

A full-width image row spanning both columns

Anything else is a validation error.

<!-- start: box --> # Corin Vale ![Corin Vale](corin-vale.jpg) * **Origin**: Born in the Downs, of the old Highcairn line * **Title**: Warden of Highcairn; later King at Highcairn * **Role**: Ranger, member of the company * **Home**: Greyhaven (fostered); later Highcairn ## Later life 1. **Reign**: forty-two years 2. **Heir**: [Elin of Greyhaven](elin-greyhaven) <!-- end: box -->
  • The split is at the first colon in the item. **Title**: Warden of Highcairn; later King at Highcairn gives the key Title and keeps the rest, semicolons and all, as the value.

  • A list item with no colon is a validation error ("Each list item must use the format: key: value").

  • Bullet lists (*, -, +) and numbered lists both work and render identically; the bullets and numbers themselves are dropped.

  • Inline Markdown works in both key and value: bold, italics, code, links, auto-links. Bolding the key is convention, not a requirement.

  • Headings group rows inside one panel. The heading level doesn't change the rendering.

Columns

Accepts any Markdown at all, flowed into responsive columns. Other GMTales blocks may be nested inside and render normally.

<!-- start: columns --> ## The Four Quarters The Downs are divided into North, South, East, and West Quarters. The East Quarter is home to Millford and Greenhearth. ## Fenwick Fenwick lies east of the Mere and is considered slightly eccentric — its folk actually know how to swim. <!-- end: columns -->

The count argument widens it. The default is 2:

<!-- start: columns; 4 -->

The only counts are 2, 3 and 4; anything else is a validation error.

Timeline

A chronology. The body is written one of two ways; mixing them in one body puts the two entry styles side by side, so pick one.

Headings with paragraphs — the heading is the date, the paragraphs beneath it are the entry:

<!-- start: timeline --> ## Fifth Era 2931 Born in Greyhaven; his father killed two years later. ## Fifth Era 3018 Meets Mira Ashford at the Crossed Keys in Millford; joins the company. ## Fifth Era 3019 Crowned King at Highcairn; marries Elin of Greyhaven. <!-- end: timeline -->

List items of the form date: entry, split at the first colon:

<!-- start: timeline --> * 2931: Born in Greyhaven. * 3018: Joins the company at the Crossed Keys. * 3019: Crowned King at Highcairn. <!-- end: timeline -->
  • A paragraph before any heading is a validation error; there is no entry for it to belong to. So is a list item with no colon.

  • Entry bodies are full Markdown: bold, links, images and auto-links all work.

  • In the heading form the date is plain text: formatting in the heading is stripped rather than rendered, so write ## Fifth Era 3019, not ## *Fifth Era 3019*. In the list form the date keeps its formatting.

  • A calendar argument binds the timeline to an in-world calendar. It is then a list syntax over time mentions: each entry is dated against that Calendar and joins the chronology, and the block is sorted and grouped by in-world date rather than by the order you wrote it. Without the argument, the timeline is a plain list in your own order.

Quote

A stylized pull-quote. The body accepts any Markdown; the optional argument is the attribution, rendered beneath the quote with an em dash.

<!-- start: quote; Ostren, The Grey Chronicle --> *The road remembers every traveller who ever cursed it.* <!-- end: quote -->

With no attribution the quote renders without one; put the credit in the body if you'd rather style it yourself.

A secret block can't sit inside any other block; see Nesting.

Tree Relations

The tree directive places an article in a named tree, optionally under a parent:

<!-- tree: Locations; The Downs --> # Greenhearth

The tree name is required; the parent is optional. The tree itself is created the first time an article names it. The parent is another article, named by title or by key, and it must already exist in the campaign; a title matching two articles is a validation error rather than a guess, so use a key when titles collide.

An article joins several trees with one tree directive each. Naming the same tree twice in one article is a validation error:

<!-- tree: Locations; Harrowfeld --> <!-- tree: The Siege Years --> # Stonewatch

Cycles are not allowed. Trees are browsed in the tree view.

Image Blocks

An image block places an uploaded image and carries that image's record. Its src names the image, and the thumbnail flag makes it the article's thumbnail in overview lists:

<!-- start: image; greenhearth.webp; thumbnail --> ## Greenhearth The hill above Millford, with the blue front door at its heart. <!-- end: image -->

The body is not article prose. Its first heading is the caption and its paragraphs are the image's description. It accepts the labels, parent and marker directives and the marker and region blocks, and all of them describe the image, not the article. Everything else about an image belongs to its own content and is a validation error here. Images spells out each part.

For a simple inline picture, standard Markdown still works. The alt text prints under the picture and is kept as this reference's description; the image itself gains no description of its own, no labels and no pins:

![The road to Millford](millford-road.jpg)

An image reference resolves by file name, then key, then title; directories in the path are ignored. See Image references.

Import and Export

The same Markdown works in the web editor and in any editor of your own. Export your articles as .md files, edit them locally with search-and-replace, refactoring, or git, and import them back; every directive travels with the file. Image references survive the round trip because they resolve by file name, so keep image file names unique and stable.

What comes back lands as drafts by default, so an upload is something you read through and publish rather than something that takes effect the moment it arrives. Files covers the mechanics and the review that follows.

Complete Example

<!-- type: location --> <!-- labels: Downs, Peaceful --> <!-- key: greenhearth --> <!-- alias: The Hearth --> <!-- tree: Locations; The Downs --> <!-- relevance: 70 --> # Greenhearth Greenhearth is the house of the Ashford family, built into the south slope of the hill above Millford. It is the most comfortable address in the Downs, and its blue front door has seen more history than any door has a right to. <!-- start: box --> * **Type**: Hill-house * **Location**: The Hill, Millford, the Downs * **Owner**: Halden Ashford; later Mira Ashford <!-- end: box --> ## History Built by Torvin Ashford for his wife Elsa Bray, Greenhearth passed to Halden Ashford, and with it the quiet respectability of the family — until the morning Ostren came looking for someone to share in a journey. <!-- start: secret; gm --> The Greyfang is kept in a wrapped bundle above the mantelpiece. Halden has not been entirely honest about how he came by it. <!-- end: secret --> <!-- start: image; greenhearth.webp; thumbnail --> ## Greenhearth <!-- marker: 50,55; The blue front door --> <!-- marker: 30,40; The study window --> <!-- end: image --> <!-- start: quote --> *There has been a hearth on this hill for four hundred years.* — Ostren, The Grey Chronicle <!-- end: quote -->
14 September 2026