Markdown zu PDF
Ein Bericht, Release Notes oder die Antwort eines Modells sind oft schon Markdown. Senden Sie den Text als markdown, und die API liefert ein PDF mit eigenem Aussehen, ohne Vorlage und ohne eine Zeile CSS:
{
"markdown": "# Quartalsbericht\n\nDer Umsatz stieg um **12 %**.\n\n| Region | Umsatz |\n| --- | --: |\n| Nord | 1.200 |\n",
"settings": { "pdf": { "outline": true } }
}
markdown ist die vierte Quelle von POST /v1/renders, neben template, html und url, und der Einträge eines Batches: Senden Sie genau eine davon. Alles andere eines Renderings gilt wie bei html: Papier und Ränder, Kopf- und Fußzeile, filename, mode, Webhooks, Post-Processing, PDF/UA-1, PDF/A und eine E-Rechnung. Jeder Plan rendert Markdown, zu den Einheiten jedes PDF: eine je angefangene fünf Seiten, mit einem Test-Schlüssel nichts.
Was es liest
- GitHub Flavored Markdown: Überschriften, Hervorhebungen, Listen, Links, Bilder, Code, Zitate, Tabellen mit ihrer Ausrichtung, Aufgabenlisten (
- [x] erledigt), Durchgestrichenes (~~alt~~) und bloßehttps://-Adressen als Links. - Fußnoten:
[^1]im Text und[^1]: Die Quelle.irgendwo darunter; sie werden nummeriert und am Ende gedruckt. - Links zwischen Überschriften: Jede Überschrift bekommt eine ID nach der Regel von GitHub (
## Einrichtung und Kostenist#einrichtung-und-kosten, eine zweite Überschrift mit demselben Text#einrichtung-und-kosten-1), sodass[siehe die Kosten](#einrichtung-und-kosten)ein Link innerhalb des PDF ist. - Seitenumbrüche: Eine Zeile, die
\newpage,\pagebreakoder<!-- pagebreak -->ist, beginnt eine neue Seite. - Front Matter: Ein YAML-Block zwischen
----Zeilen ganz am Anfang wird aus dem Dokument entfernt. Seintitle,author,subjectundkeywordswerden die Eigenschaften des PDF, wosettings.pdf.metadatasie leer lässt.
---
title: Quartalsbericht Q3
author: Fennlor Studio GmbH
keywords: Bericht, 2026
---
# Quartalsbericht
Der Umsatz stieg um **12 %**[^1].
\newpage
## Ausblick \{#ausblick}
[^1]: Im Vergleich zu Q2.
Der Titel des PDF, den ein Reader in seiner Titelleiste zeigt und ein Screenreader zuerst ansagt, ist settings.pdf.metadata.title, sonst der title des Front Matter, sonst der Text der ersten Überschrift. Das locale der Anfrage (oder settings.locale) setzt die Sprache des Dokuments, <html lang>, mit oder ohne Engine.
Rohes HTML erscheint als Text
Markdown erlaubt HTML im Text, und HTML kann Skripte ausführen und Dinge laden. In der Vorgabe (settings.markdown.html: "safe") schreibt die API nichts davon:
- Rohes HTML erscheint als Text (ein Block davon im Theme
documentin einer kleinen grauen Festbreitenschrift, ein Tag in einem Satz in dessen Schrift), sodass sichtbar bleibt, was der Text enthielt, statt wortlos zu verschwinden.<script>alert(1)</script>wird als diese Zeichen gedruckt, ebenso ein<div style>. - Als Tags bleiben, ohne jedes Attribut:
<br>und<sup>,<sub>,<kbd>,<mark>,<u>,<ins>,<del>,<s>und<small>mit ihren schließenden Tags. Eine Tabellenzelle braucht<br>für eine zweite Zeile, und Modelle schreiben es. - HTML-Kommentare werden entfernt, bis auf den Seitenumbruch
<!-- pagebreak -->. - Links behalten
http,https,mailto,tel, relative Adressen und#Anker. Ein Link mit einem anderen Schema behält seinen Text und verliert den Link; einejavascript:-,vbscript:- oderfile:-Adresse bleibt der Text, der geschrieben wurde. - Bilder behalten
http,https, relative Adressen (die Dateibibliothek) unddata:image/in PNG, JPEG, GIF, WebP und SVG.
"html": "allow" schreibt rohes HTML, wie es steht, Skripte und Styles eingeschlossen, wie es ein html-Rendering tut. Die Regeln für Links und Bilder gelten weiter für die eigene Syntax von Markdown. Nutzen Sie es nur für Markdown, das Sie selbst geschrieben haben, etwa Ihre eigenen Release Notes mit einem gestalteten Kasten: nie für Text Ihrer Nutzer oder eines Modells, für den die Vorgabe gedacht ist.
Das Aussehen
settings.markdown | Werte | Vorgabe | Bedeutung |
|---|---|---|---|
theme | document, none | document | document gestaltet den Text; none ist allein der Druck-Reset, für ein eigenes Aussehen. |
html | safe, allow | safe | Was rohes HTML im Text tut (oben). |
breaks | true, false | false | Ein einzelner Zeilenumbruch wird ein Zeilenumbruch, wie in einem Chat. Aus, endet ein Absatz nur an einer Leerzeile. |
css | ein Stylesheet, ≤ 256 kB | keines | Kommt nach dem Theme. Der Konverter schreibt das Dokument in <div class="ff-markdown">. |
Der Block ist streng: Ein unbekannter Schlüssel, ein Theme oder Modus, den es nicht gibt, oder der Block neben einer anderen Quelle ergibt 400 invalid_request mit dem field, um das es geht, sodass ein Tippfehler nie ein Dokument in einem anderen Aussehen druckt. Das settings.markdown einer gespeicherten Vorlage wird nie gelesen.
document setzt den Text in Noto Sans und Code in einer Festbreitenschrift, die der Renderer hat, sodass ein Rendering keine Schrift lädt, mit Überschriften, die bei dem bleiben, was ihnen folgt, Tabellen mit Linien, Zitaten mit einem Balken und keinem Element, das breiter ist als die Seite. Hat die Organisation ein Brand-Kit, nehmen Überschriften seine Schrift für Überschriften, der Text seine Schrift für Text und deren Größe, und Links, Linien und Zitate seine Primärfarbe. none liest nichts aus dem Kit.
{
"markdown": "# Release Notes\n\n…",
"settings": {
"markdown": {
"theme": "none",
"css": ".ff-markdown { font-family: Georgia, serif; } .ff-markdown h1 { color: #0f766e; }"
}
}
}
Überschriften und Tabellen
- Die Ebenen der Überschriften werden in der Struktur des Dokuments geschlossen: Die erste Überschrift ist ein
h1, und keine Überschrift liegt mehr als eine Ebene unter der, unter der sie steht, was PDF/UA-1 und ein Screenreader erwarten. Eine Überschrift behält über eine Klasse die Größe, die Sie geschrieben haben (ff-h2für##), sodass ein Brief, der mit##beginnt, weiter so aussieht. - Eine leere Tabellenzelle enthält ein geschütztes Leerzeichen, denn der Browser schreibt für eine leere Zelle keine Zelle in die Struktur des PDF, und eine Tabelle mit einer fehlenden Zelle besteht PDF/UA-1 nicht.
Bilder
Ein Bild mit einem relativen Namen,  oder , ist die Datei dieses Namens in der Dateibibliothek des Workspace, wie in einer Vorlage. Laden Sie es einmal hoch, und jedes Dokument findet es. Ein Bild kann auch eine öffentliche https://-Adresse oder eine data:image/-Adresse sein.
formfeed pdf convert notes.md und markdown_to_pdf des lokalen MCP-Servers mit einem path schicken die Bilder im Ordner der Datei als data:image/-Adressen mit, sodass eine README.md mit ihren Screenshots umgewandelt wird, wie sie ist. Ein Pfad, der aus dem Ordner hinausführt, wird abgelehnt, ein Name ohne Datei dahinter bleibt ein Name der Bibliothek, und der Text mit seinen Bildern darf nicht über 1 MB wachsen.
Eine Vorlage über Daten
Mit einer engine ist der Text zuerst eine Vorlage über data und wird danach als Markdown gelesen, mit den Helpern, dem Brand-Kit und den geteilten Partials:
{
"markdown": "# Bericht für {{ customer.name }}\n\n{% for item in items %}- {{ item.name }}: {{ item.total | money }}\n{% endfor %}",
"engine": "jinja2",
"data": {
"customer": { "name": "Olvarest GmbH" },
"items": [{ "name": "Beratung", "total": 960 }]
}
}
Werte werden wie in jeder Vorlage HTML-maskiert, sodass ein Wert kein HTML hereinbringen kann; Markdown kann er bringen, und dafür ist ein Feld notes da. Markdown dekodiert in Code keine Entitäten, sodass ein maskierter Wert in einem Codeblock & zeigt: Schreiben Sie dort {{ value | safe }}, denn ein Codeblock druckt seinen Text ohnehin als Text. Der Helper markdown folgt in einem solchen Text dem Modus des Dokuments, sodass ein Dokument in safe auch durch {{ notes | markdown }} sicher bleibt.
validate_data gilt nicht: Es gibt kein gespeichertes Schema, gegen das die Daten geprüft würden, und die Anfrage ergibt 400 invalid_request.
Kopfzeile, Fußzeile und Seitenzahlen
Kopf- und Fußzeile sind HTML, wie bei jedem PDF: settings.header.html und settings.footer.html, mit der Engine gerendert, wo es eine gibt. Seitenzahlen sind die Klassen von Chromium:
{
"markdown": "# Handbuch\n\n…",
"settings": {
"margin": { "bottom": "20mm" },
"footer": {
"html": "<div style=\"font-size: 8pt; text-align: center; width: 100%\">Seite <span class=\"pageNumber\"></span> von <span class=\"totalPages\"></span></div>"
}
}
}
Unter Kopf- und Fußzeilen und Seitenzahlen steht, was dort funktioniert.
Lesezeichen, PDF/UA-1 und PDF/A
settings.pdf.outline: trueschreibt Lesezeichen aus den Überschriften, nach ihrer Ebene verschachtelt.settings.pdf.ua: truemacht die Datei zu einem PDF/UA-1-Dokument: getaggt, repariert und vor der Auslieferung an den maschinell prüfbaren Regeln von veraPDF gemessen, mit dem Urteil alsaccessibility. Ein Markdown-Dokument bringt mit, was diese Regeln verlangen: einen Titel, geschlossene Überschriften, Tabellenköpfe, gefüllte Zellen und die Sprache des Dokuments. Die Alternativtexte sind Ihre Sache: Schreiben Sie sie in![…]. Eine bestandene Prüfung nennt, welche Regeln die Datei bestanden hat, nach welchem Validator; barrierefrei macht sie ein Dokument nicht.settings.pdf.pdfamacht es zu einer PDF/A-Datei für die Archivierung.- Mit dem Block
_invoiceindataundpost.einvoiceist das PDF eine E-Rechnung, wie beihtml.
Grenzen
| Was | Grenze |
|---|---|
markdown | 1 MB in UTF-8, abgelehnt mit 400 invalid_request; das sind mehrere hundert Seiten Fließtext |
settings.markdown.css | 256 kB |
| Umwandeln des Textes | 5 Sekunden, die Zeitgrenze für das Füllen einer Vorlage, sonst 504 render_timeout |
| Alles danach | wie bei jedem PDF: Seiten- und Größengrenzen des Plans, die Zeitgrenzen des Browsers |
Ein Markdown-Rendering wird wie html dedupliziert: Derselbe Text mit denselben Einstellungen und Daten innerhalb von 24 Stunden antwortet mit der früheren Datei ohne Einheiten, solange Konverter und Theme dieselbe Version sind. engine_version nennt beide, etwa chromium 153.0.8010.12; engine none; markdown-it 15.0.3, rules 1, theme document 1.
Aus der CLI, einem Agenten oder den SDKs
npx formfeed pdf convert notes.md --theme document
formfeed pdf convert liest eine .md-Datei in jedem Plan als Markdown, mit --css <datei>, --settings <datei> (eine JSON-Datei mit Einstellungen des Renderings), --raw-html (html: "allow"), --landscape und --page-ranges, und speichert notes.pdf (Befehlsreferenz). Ein Agent hat das MCP-Werkzeug markdown_to_pdf. Die SDKs nehmen markdown in renders.create:
const render = await client.renders.create({
markdown: '# Notizen\n\n- eins\n- zwei',
settings: { markdown: { breaks: true } },
});
Markdown in einer Vorlage
Eine gespeicherte Vorlage, die ein Markdown-Feld neben anderem Inhalt zeigt, nutzt den Helper markdown: {{ notes | markdown({ html: 'safe' }) }}. Der Helper wandelt ein Fragment um, sodass die Regeln des Dokuments (Front Matter, Titel, geschlossene Überschriften, der Wrapper) bei der Quelle markdown bleiben, und rohes HTML bleibt, wenn Sie ihm nicht html: 'safe' geben, denn die Daten einer Vorlage stammen von ihrem Autor.