Markdown to Textile: Converting Docs for Redmine and Forum Wikis
A practical guide to converting Markdown to Textile markup for Redmine, Trac and forum wikis, with the heading, bold and code differences that trip people up.
Markdown to Textile: Converting Docs for Redmine and Forum Wikis
If your whole team writes Markdown, the first time you paste a tidy ## Heading, **bold** and a fenced code block into a Redmine issue is a small shock. Nothing renders. The description field shows the raw hashes and asterisks, your code block keeps its three backticks, and the ticket looks broken to everyone who opens it. Redmine, older Trac installs and a surprising number of forums and wikis do not parse Markdown at all. They parse Textile, a markup language that came out before Markdown and never went away in those tools.
This guide walks through what actually changes when you go from Markdown to Textile, the differences that catch people, and how to move existing docs into Redmine without retyping a single heading.
Why Textile still shows up
Textile predates Markdown by a few years and was the default rich-text format for a generation of Ruby and PHP web apps. The big one you still hit today is Redmine: issue descriptions, comments and wiki pages all parse Textile out of the box. Trac installs that have been running since the 2000s often expect it too, as do a few RedCloth-based sites and self-hosted forums.
The frustration is that Textile and Markdown solve the same problem with different punctuation. Both mark headings, bold, links, lists and code. They just spell those tokens differently. So your muscle memory produces Markdown, the field expects Textile, and the two disagree at exactly the characters that matter.
The heading rule that trips everyone
Here is the single difference that confuses people most. In Markdown, a heading is a run of leading hash characters: # for level one, ## for level two, on down to ######. In Textile, a heading is the letter h, the level number, and then a literal dot, followed by a space and the text.
So a Textile h1 heading is written h1. with a dot. That trailing dot is not decoration and not optional. Drop it and Textile treats the whole line as a paragraph that happens to start with the letters "h1". The dot is the token. New writers stare at h1. and assume it is a typo until they see it render.
The mapping is one to one across all six levels:
# Titlebecomesh1. Title## Sectionbecomesh2. Section### Subsectionbecomesh3. Subsection
Count the hashes, emit h plus that number plus a dot plus a space, and you are done. Some writers add trailing hashes for symmetry, like ## Done ##. Those get stripped, so the result is a clean h2. Done with no stray characters on the right.
Bold, italic and the rest
Emphasis is where a hand conversion quietly breaks. Markdown bold is two asterisks: **shipped**. Textile bold is a single asterisk: *shipped*. If you forget to halve it, Textile reads your double asterisks as a bold span that contains a stray asterisk on each edge, and the output looks wrong in a way that is hard to spot at a glance.
Italic flips the other way. Markdown italic is one asterisk or an underscore. Textile italic is an underscore span, _note_. Strikethrough goes from Markdown's ~~old~~ to a single-hyphen Textile span, -old-. Inline code moves from backticks to at-signs: a git push in backticks becomes @git push@, where the at-sign is Textile's monospace token.
The order of operations matters here, which is why doing this by hand is risky. You have to rewrite the double-asterisk bold before you touch single-asterisk italic, or a word like snake_case gets mangled by an underscore rule firing too early. A tool that processes the double-token forms first sidesteps that whole class of bug.
A worked example
Here is a small Markdown block and the Textile it should become. Input:
# Release notes
We **shipped** the new importer. Run `migrate.sh` first.
See the [setup guide](https://example.com/setup).
Output in Textile:
h1. Release notes
We *shipped* the new importer. Run @migrate.sh@ first.
See the "setup guide":https://example.com/setup.
Notice every line changed its punctuation but kept its structure. The heading gained h1. with the dot. Bold dropped to a single asterisk. The inline command picked up at-signs. And the link inverted completely: Markdown puts the label in brackets and the URL in parentheses, while Textile quotes the label, follows it with a colon, then the bare URL. Images work the same way, wrapped in exclamation marks around the address, with the alt text dropped because Textile keys the embed off the URL itself.
Migrating real docs into Redmine
The payoff is moving content you already have. A pull-request body you wrote in GitHub Markdown, with a heading, a bullet list of changes and a fenced diff, can land on the linked Redmine issue as a real heading, a real list and a bc. block instead of symbol soup. (The fenced block becomes bc., Textile's block-code marker, with the language tag dropped because the plain form is the most portable across Redmine versions.) The same goes for an LLM answer you want on a runbook wiki, or a README section that needs to live where non-engineers can read it.
I keep a Redmine instance for an internal project, and the part that used to cost me the most time was lists and links. Hand-converting a fifteen-line README section meant re-quoting every link, swapping every bullet, and then re-reading the rendered page to find the two I missed. The first time I ran a section through the Markdown to Textile converter and pasted the result, the whole thing rendered on the first save, links and all. What had been a five-minute retype became a paste. The bullet lists turn into Textile's asterisk and hash markers, and the links arrive in the quote-then-colon form, so there is nothing left to fix by hand.
Common mistakes to avoid
Three errors account for most of the broken Redmine tickets I have seen:
- Leaving double asterisks for bold. Textile reads
**word**as a bold span with a literal asterisk on each side. Halve it to*word*. - Keeping the language tag on a code fence and expecting highlighting. The portable Textile marker is a bare
bc.with no language word. A straypythonleft in the markup just becomes part of the code text. - Pasting the Markdown link form unchanged. Textile prints
[label](url)literally. It needs"label":urlinstead, with the label quoted before the colon.
Each of these is a one-character habit from Markdown that Textile reads differently, which is exactly why a converter that does the pass for you beats editing by hand.
When you need a different target
Textile is one of several markup dialects you might be converting toward. If your team lives in Atlassian tools instead of Redmine, the Markdown to Jira converter does the same job for Jira's wiki markup, which uses its own heading and code conventions. The structure of your document stays the same across all of these targets. Only the tokens change, which is the whole reason a single regex pass can rewrite a heading, a bold span or a link cleanly and let you get back to the actual work.
Made by Toolora · Updated 2026-06-13