Zum Hauptinhalt springen

Vorlagen schreiben

Eine Formfeed-Vorlage ist eine Webseite, die Chromium druckt. Wenn Sie HTML und CSS kennen, kennen Sie das meiste schon. Diese Seite behandelt, was beim Drucken und bei Formfeed besonders ist, in der Reihenfolge, in der Sie es meist brauchen. Die Syntax der drei Vorlagensprachen steht unter Vorlagensprachen, jede Einstellung in der Referenz der Einstellungen.

Was Chromium bekommt​

Formfeed rendert Ihre Vorlage mit den Daten und bettet das Ergebnis dann in ein vollständiges HTML-Dokument ein:

<!doctype html>
<html lang="de-DE">
<!-- aus settings.locale -->
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<base href="https://cdn.formfeed.dev/a/<workspace id>/" />
<!-- die Dateibibliothek Ihres Workspace, siehe Bilder -->
<title>Rechnung 2026-0042</title>
<!-- settings.pdf.metadata.title, mit den Daten gerendert -->
<!-- Ihr Tab Head / head.html -->
<style>
/* @font-face-Regeln für die genutzten Schriften der Organisation */
</style>
<style>
/* CSS-Variablen des Brand Kits: --brand-color-primary, … */
</style>
<style>
/* der Druck-Reset unten, mit @page aus den Einstellungen */
</style>
<style>
/* kompiliertes Tailwind, wenn settings.tailwind aktiv ist */
</style>
<style>
/* Ihr Tab CSS / style.css */
</style>
</head>
<body class="formfeed-body formfeed-pdf">
<!-- formfeed-image bei Bildvorlagen -->
<!-- Ihre gerenderte Vorlage -->
<!-- Chart.js und das Skript zum Anpassen von Text, nur wenn das Dokument sie nutzt -->
</body>
</html>

Der Druck-Reset liefert sinnvolle Standardwerte, die Sie in Ihrem CSS überschreiben können:

@page {
size: A4;
margin: 20mm 15mm 20mm 15mm;
} /* nur PDF, aus settings.paper und settings.margin */
html,
body {
margin: 0;
padding: 0;
}
body {
print-color-adjust: exact;
} /* Hintergründe und Farben werden wie gestaltet gedruckt */
img,
svg {
max-width: 100%;
}
table {
border-collapse: collapse;
break-inside: auto;
}
.page-break,
.new-page {
break-after: page;
}
.avoid-break {
break-inside: avoid;
}
tr {
break-inside: avoid;
} /* Tabellenzeilen werden nie über Seiten geteilt */
thead {
display: table-header-group;
} /* Tabellenköpfe wiederholen sich auf jeder Seite */
tfoot {
display: table-footer-group;
}

Die Regeln für img, svg und table haben keine Spezifität (sie stehen in :where()), jede Regel von Ihnen hat also Vorrang vor ihnen; die übrigen sind gewöhnliche Regeln, die Ihr CSS überschreibt, weil es später kommt. Eine Bildvorlage bekommt keine @page-Regel; stattdessen nehmen html und body Breite und Höhe aus settings.image an und blenden aus, was übersteht.

Daten​

Das Objekt data der Anfrage ist der Wurzel-Scope der Vorlage: { "invoice": { "number": "42" } } macht invoice.number verfügbar. Halten Sie die Struktur Ihrer Daten nah an der Struktur des Dokuments. Die Vorlage entscheidet, wie etwas aussieht, Ihr Code nur, was es ist.

  • Beispieldaten nutzen die Vorschau im Editor, die Autovervollständigung und formfeed validate. Legen Sie pro Vorlage mehrere benannte Datensätze an (eine Position, fünfzig Positionen, alle optionalen Felder fehlen), damit Sie Grenzfälle sehen, bevor Ihre Kunden sie sehen. Der Datensatz default wird als sample_data der Version gespeichert, die übrigen als ihre data_sets (höchstens 50); in einem Projekt sind es data/default.json und data/<name>.json. Ein API-Rendering verwendet nie Beispieldaten, sondern immer die data der Anfrage.
  • Das Datenschema ist ein JSON Schema der erwarteten data. Eine Version speichert nur dann eines, wenn Sie es speichern (Als Contract hinterlegen im Editor, schema.json in einem Projekt, data_schema über die API). Ohne gespeichertes Schema leitet Formfeed eines aus dem Datensatz default ab, sobald danach gefragt wird: Integrationen (Zapier, n8n, Make, der MCP-Server) lesen es, um benannte Felder anzubieten, und GET /templates/{id}/schema liefert es. Die Prüfung der Daten eines Renderings gegen ein Schema (validate_data) und die Schema-Prüfung vor dem Veröffentlichen brauchen ein gespeichertes; siehe Typisierte Vorlagendaten.
  • Fehlende Werte werden in allen drei Sprachen als leerer Text gerendert, auch wenn ein ganzes Objekt fehlt ({{ customer.address.city }} ohne customer). Nutzen Sie default für bewusste Ersatzwerte: {{ customer.vat_id | default("—") }} in Jinja2, {{ customer.vat_id | default: "—" }} in Liquid, {{default customer.vat_id "—"}} in Handlebars. Eine Ausnahme: Der Helfer date gibt bei einem fehlenden, null- oder leeren Wert das heutige Datum aus. Setzen Sie ein optionales Datum deshalb in eine Bedingung: {% if invoice.paid_at %}{{ invoice.paid_at | date("P") }}{% endif %}.

Der Editor und formfeed validate prüfen die Vorlage gegen den angezeigten Datensatz (oder den, den --data nennt) und melden:

BefundSchwereWas zu tun ist
Syntaxfehler, ein nicht geschlossener Block, ein End-Tag, das nicht passtFehlerMarkup korrigieren; das Rendering schlüge mit template_syntax_error fehl.
Unbekannter Filter oder HelferFehlerDen Namen in der Helfer-Referenz prüfen.
"invoice.nubmer" is not present in the sample dataWarnungEin Tippfehler oder ein Feld, das dem Datensatz fehlt: im Datensatz ergänzen.
Ein Kompatibilitätsalias (currency für money)WarnungUmbenennen; der Editor bietet die Korrektur an.

Seiteneinrichtung​

Papierformat, Ausrichtung und Ränder stehen in den Einstellungen der Vorlage (im Editor das Panel Seiteneinrichtung):

{
"paper": { "format": "A4", "landscape": false },
"margin": { "top": "25mm", "right": "18mm", "bottom": "20mm", "left": "18mm" }
}

format akzeptiert A0 bis A6, Letter, Legal, Tabloid und Ledger, in beliebiger Groß- und Kleinschreibung. Für alles andere setzen Sie paper.width und paper.height ("width": "100mm", "height": "150mm" für ein Versandetikett). Längen in paper und margin nehmen die Einheiten mm, cm, in und px oder eine bloße Zahl in Pixeln; pt, em oder % lassen das Rendering fehlschlagen. Formfeed macht aus diesen Einstellungen die oben gezeigte @page-Regel. Wenn Sie die Seitengröße lieber per CSS steuern, auch mit unterschiedlichen Größen für verschiedene Seiten, setzen Sie "preferCssPageSize": true und schreiben Ihre eigenen @page-Regeln.

Vor dem Drucken setzt Chromium die Seite in einem Fenster, das so breit und hoch ist wie das Papier (A4 hochkant: 794 × 1123 CSS-Pixel). Ein Skript, das das Fenster misst, etwa eine Diagrammbibliothek, die ihre Größe danach wählt, sieht also die Breite der Seite.

Seitenumbrüche​

Chromium paginiert automatisch. Sie steuern das mit CSS:

<section class="cover">…</section>
<div class="page-break"></div>
<!-- oder {{ pageBreak() }} -->
<section class="terms avoid-break">…</section>
h2 {
break-after: avoid;
} /* Überschrift beim folgenden Text halten */
.signature {
break-inside: avoid;
}

Lange Tabellen funktionieren ohne Zusatzaufwand: Eine Zeile wandert als Ganzes auf die nächste Seite (geteilt wird nur eine Zeile, die höher ist als eine Seite), und ein <thead> wiederholt sich oben auf jeder Seite. Bauen Sie die Tabelle dafür mit <table>, <thead> und <tbody> statt mit divs, dann bekommen Sie das umsonst.

Zwei Dinge arbeiten dagegen:

  • Ein <tfoot> wiederholt sich unten auf jeder Seite, genau wie der Kopf. Setzen Sie eine Gesamtsumme in ein eigenes, letztes <tbody>, oder geben Sie dem tfoot display: table-row-group und stellen Sie es hinter das <tbody>; dann steht es einmal, am Ende.
  • Eine Tabelle in einem Element mit display: inline-block wird gar nicht paginiert: Chromium schneidet sie an der Seitenkante ab, mitten durch die Zeile, die dort steht, und der Kopf wiederholt sich nicht. Nehmen Sie einen Block als Hülle.
tbody tr:last-child {
break-before: avoid;
} /* die letzte Zeile nie allein auf einer Seite */
.group {
break-inside: avoid;
} /* an einem <tbody>: eine Gruppe von Zeilen zusammenhalten */

Kopfzeilen, Fußzeilen und Seitenzahlen​

Eine laufende Kopf- oder Fußzeile gehört in die Tabs Kopfzeile und Fußzeile im Editor (header.html und footer.html in einem Projekt, settings.header.html und settings.footer.html über die API). Sie werden einmal gerendert, mit derselben Vorlagensprache und denselben Daten wie der Hauptteil, und Chromium stempelt das Ergebnis auf jede Seite eines PDFs. Bildausgaben haben keine Kopf- oder Fußzeile.

Chromium füllt diese Klassen auf jeder Seite mit den aktuellen Werten:

KlasseWert
pageNumberAktuelle Seitenzahl
totalPagesGesamtzahl der Seiten
dateDruckdatum, in einem Format, das Chromium wählt
titleDokumenttitel (settings.pdf.metadata.title, mit den Daten gerendert)

Für ein Datum in Ihrem eigenen Format schreiben Sie es stattdessen mit dem Helfer date: {{ "now" | date("dd.MM.yyyy") }}.

<!-- Tab Fußzeile -->
<div
style="display: flex; justify-content: space-between; width: 100%; font-size: 8pt; color: #666;"
>
<span>{{ company.name }} · Rechnung {{ invoice.number }}</span>
<span
>Seite <span class="pageNumber"></span> von <span class="totalPages"></span
></span>
</div>

Kopf- und Fußzeilen werden in einem eigenen Kontext gedruckt. Das hat diese Folgen:

  1. Ihr Stylesheet gilt dort nicht. Gestalten Sie die Elemente in Kopf- und Fußzeile inline, wie oben. Schriften der Organisation, die CSS-Variablen des Brand Kits und kompiliertes Tailwind werden für Sie eingebettet.
  2. Sie sitzen im Seitenrand. Machen Sie margin.top (bzw. margin.bottom) größer als die Kopfzeile (bzw. Fußzeile) selbst, sonst läuft der Text des Hauptteils darunter. settings.header.height legt die Höhe des Kastens ausdrücklich fest; den Rand ändert sie nicht.
  3. Sie starten klein. Formfeed gibt ihnen eine Schriftgröße von 10 px und 10 mm seitlichen Innenabstand; setzen Sie Ihre eigenen Größen inline. Die Box reicht unabhängig von den Seitenrändern über die ganze Seitenbreite; settings.header.padding (und footer.padding) ersetzt den seitlichen Innenabstand, "0" für Designs, die bis zum Rand laufen.
  4. Sie laden keine Bilder. Ein <img> mit einer URL bleibt dort leer, auch mit Dateien aus Ihrer Bibliothek. Die Logos des Brand Kits sind die Ausnahme: Formfeed setzt sie als Data-URI ein. Für jedes andere Bild nehmen Sie einen Data-URI, etwa einen QR-Code aus dem Helfer qrcode.
  5. Skripte laufen dort nicht.

Schriften​

  • Google Fonts und andere Webfonts: Fügen Sie den <link> in den Tab Head ein. Der Render-Worker lädt die Dateien, speichert sie im Cache und wartet vor dem Drucken, bis die Schriften geladen sind.

    <link
    href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap"
    rel="stylesheet"
    />
  • Eigene Schriftdateien: Laden Sie sie in der App unter Schriften hoch. Der Plan Free hat keine, Starter bis zu 5 Dateien, Pro und höher beliebig viele; jeder Schnitt und Stil ist eine Datei. Verweisen Sie in Ihrem CSS auf den Familiennamen (font-family: "Brand Sans"): Formfeed ergänzt die @font-face-Regel für jede hochgeladene Familie, deren Name irgendwo im CSS, im Head oder im Markup der Vorlage vorkommt, auch in Kopf- und Fußzeilen. Schriften der Organisation erreichen Renderings gespeicherter Vorlagen; eine Ad-hoc-Anfrage mit html bekommt sie nicht.

  • Schriften der Marke: Die Schriften für Überschriften und Fließtext aus dem Brand Kit stehen als var(--brand-font-heading) und var(--brand-font-body) bereit.

  • Nennen Sie eine Schrift immer ausdrücklich. Die Ersatzschriften von Chromium auf den Render-Servern sind nicht die auf Ihrem Laptop.

Bilder​

  • Nutzen Sie eine Datei aus der Bibliothek des Workspace ({{ asset('logo.png') }}), eine absolute HTTPS-URL oder einen Data-URI. Auch ein relativer Pfad wird gegen die Bibliothek aufgelöst, weil der <base href> des Dokuments dorthin zeigt: <img src="logo.png"> findet die Datei mit diesem Namen.

  • Der Worker lädt Bilder über einen Cache und wartet vor dem Drucken auf sie. Er lädt nur https:, nie http:, und aus Sicherheitsgründen lehnt er private Adressen, Loopback-Adressen und Cloud-Metadaten-Adressen ab. Ein Bild auf localhost oder in Ihrem internen Netz wird also nicht geladen. Hosten Sie es öffentlich oder betten Sie es als Data-URI ein. Ein abgelehntes oder nicht ladbares Bild erscheint als defektes Bild; das Rendering schlägt deshalb nicht fehl.

  • Der Helfer image platziert ein Bild in fester Größe mit einem Zuschnittmodus (cover, contain, fill); geben Sie ihm ein alt, das sagt, was es zeigt:

    {{ image(product.photo, { width: 120, height: 80, fit: "cover", alt:
    product.name }) }}
  • QR-Codes, Barcodes und SEPA-Zahlungscodes entstehen ohne Netzwerkzugriff, als SVG-Data-URIs für das src eines <img>: <img src="{{ qrcode(order.url, { size: 120 }) }}" alt="…">. Siehe qrcode, barcode und epcQr in der Helfer-Referenz.

hinweis

Laden Sie Bilder und PDFs auf der Seite Dateien in der App hoch oder mit POST /files. Eine Datei wird über ihren Namen adressiert: Ein neues logo.png ersetzt das alte für jede Vorlage, die es nutzt, aber nicht sofort: Das CDN hält eine Datei bis zu eine Stunde, und der Render-Worker hält die Bilder, die ein Dokument lädt, bis zu eine Woche. Ein ersetztes oder gelöschtes Bild kann also so lange noch in gerenderten Dokumenten erscheinen. Muss eine Änderung im nächsten Dokument zu sehen sein, laden Sie die Datei unter einem neuen Namen hoch (logo-2026.png) und ändern die Vorlage entsprechend; siehe Caching. Die Bibliothek ist für jeden erreichbar, der die URL kennt; legen Sie daher keine vertraulichen Dokumente dort ab.

Tailwind CSS​

Setzen Sie "tailwind": true in den Einstellungen (Tailwind-CSS-Utility-Klassen in der Seiteneinrichtung) und nutzen Sie Utility-Klassen in Ihrem Markup. Formfeed kompiliert genau die Klassen in den class-Attributen des gerenderten Dokuments, der Kopf- und der Fußzeile (Tailwind v4 mit seinem Standard-Theme). Es gibt also kein CDN-Skript und keinen Purge-Schritt. Klassen, die aus Ihren Daten kommen, funktionieren, weil die Vorlage zuerst gerendert wird. Ihr eigenes CSS lädt weiterhin nach Tailwind und hat Vorrang.

<h1 class="text-2xl font-semibold text-slate-900">
Rechnung {{ invoice.number }}
</h1>

Gut zu wissen:

  • Die Basis-Stile von Tailwind (Preflight) kommen mit: Überschriften verlieren ihre Größen, Listen ihre Aufzählungszeichen, Abstände werden zurückgesetzt. Gestalten Sie diese Elemente mit Klassen oder in Ihrem CSS.
  • Der Tab CSS läuft nicht durch Tailwind, @apply, @theme und @config bewirken dort also nichts. Für eine Markenfarbe nehmen Sie einen beliebigen Wert wie bg-[var(--brand-color-primary)].
  • Eine Klasse, die ein Skript erst nach dem Rendern in die Seite setzt, wird nicht kompiliert. Die Vorschau im Editor kompiliert im Browser und zeigt sie trotzdem; prüfen Sie eine solche Vorlage deshalb mit Echtes Rendering.

Diagramme​

Der Helfer chart zeichnet ein Chart.js-Diagramm. Geben Sie ihm einfache Daten (labels mit values oder mehrere series), damit Ihre Integration Zahlen sendet und keine Chart.js-Konfiguration; siehe Diagramme aus einfachen Daten:

{{ chart({ type: "bar", labels: sales.labels, values: sales.values, width: 480,
height: 240, alt: "Umsatz je Quartal" }) }}

Eine vollständige Chart.js-Konfiguration in data und options funktioniert ebenfalls und hat Vorrang vor den einfachen Feldern:

{{ chart({ type: "bar", data: { labels: months, datasets: [{ label: "Umsatz",
data: revenue }] }, width: 480, height: 240 }) }}

Das Diagramm ist 480 × 240 Pixel groß, solange Sie width und height nicht setzen. Animationen sind abgeschaltet, damit Vorschau und PDF übereinstimmen. alt sagt in Worten, was das Diagramm zeigt; ein Screenreader liest es anstelle des Bildes vor.

JavaScript​

JavaScript läuft in der Seite wie in einem Browser. Vor dem Drucken wartet Formfeed, bis die Seite geladen ist, das Netzwerk eine halbe Sekunde geruht hat und alle Schriften geladen sind; das Laden darf bis zu 30 Sekunden dauern. Baut ein Skript danach noch Inhalte auf, sagen Sie Formfeed in den Einstellungen, worauf es warten soll:

{ "waitFor": { "selector": "#report-ready", "timeoutMs": 8000 } }

und fügen Sie dieses Element ein, wenn Ihr Skript fertig ist. waitFor.timeoutMs ist standardmäßig 5.000 und höchstens 30.000; ist das Element bis dahin nicht erschienen, schlägt das Rendering mit render_timeout fehl. waitFor.delayMs ergänzt als letztes Mittel eine feste Pause (höchstens 10 Sekunden). Alle Schlüssel stehen unter Auf Inhalte warten.

Skripte sehen Locale und Zeitzone der Einstellungen (navigator.language, Intl, Date), und das Fenster hat die Größe des Papiers (siehe Seiteneinrichtung). Skripte in Kopf- und Fußzeile laufen nicht.

Lokalisierung​

Drei Einstellungen bestimmen jedes Datum, jede Zahl und jeden Währungsbetrag, den die Helfer formatieren:

{ "locale": "de-DE", "timezone": "Europe/Berlin", "currency": "EUR" }

{{ 1234.5 | money }} gibt dann 1.234,50 € aus, und {{ invoice.date | date("PPP") }} gibt 11. September 2026 aus. Eine Render-Anfrage kann die Locale pro Dokument mit "locale": "fr-FR" überschreiben (ein locale in den settings der Anfrage hat Vorrang davor). Ohne Einstellungen ist die Locale en-GB, die Zeitzone Europe/Berlin und die Währung EUR.

Für Text fügen Sie Übersetzungen hinzu (Panel Übersetzungen, i18n.json in einem Projekt) und nutzen t:

{
"en": { "invoice.title": "Invoice {number}" },
"de": { "invoice.title": "Rechnung {number}" }
}
<h1>{{ t("invoice.title", { number: invoice.number }) }}</h1>

In Liquid ist das {{ "invoice.title" | t: number: invoice.number }}, in Handlebars {{t "invoice.title" number=invoice.number}}. Ein {name} im Text wird durch den Wert dieses Namens ersetzt, und durch nichts, wenn Sie keinen übergeben.

t sucht zuerst die exakte Locale (de-AT), dann die Sprache (de), dann en und gibt zuletzt den Schlüssel selbst aus. Eine fehlende Übersetzung ist so sichtbar statt leer.

Dateinamen​

Legen Sie den Namen der Ausgabedatei pro Anfrage mit filename fest. Er wird so verwendet, wie Sie ihn senden: Vorlagenausdrücke darin werden nicht gerendert, bauen Sie den Namen also in Ihrem Code.

{
"template": "invoice",
"data": { "invoice": { "number": "2026-0042" } },
"filename": "rechnung-2026-0042.pdf"
}

Zeichen außer den Buchstaben A–Z und a–z, Ziffern, _, ., -, (, ) und Leerzeichen werden zu _ (aus Müller wird M_ller), der Name wird auf 150 Zeichen gekürzt, und die Endung der Ausgabe wird ergänzt, wenn sie fehlt. Ohne filename heißt die Datei document.pdf (bzw. .png, .jpg, .webp).

Bildvorlagen​

Eine Vorlage der Art image rendert einen Screenshot statt eines PDFs: Social Cards, Zertifikate als PNG, Produktetiketten. Der Viewport kommt aus den Einstellungen:

{ "image": { "width": 1200, "height": 630, "deviceScaleFactor": 2 } }

deviceScaleFactor: 2 erzeugt eine Datei mit 2.400 × 1.260 Pixeln, die auf Bildschirmen mit hoher Pixeldichte scharf bleibt. Fordern Sie "output": "png", "jpg" oder "webp" an (die beiden letzten mit image.quality; WebP mit 100 ist verlustfrei), oder setzen Sie image.format, damit die Vorlage entscheidet, wenn die Anfrage keine Ausgabe nennt. Setzen Sie image.transparent für einen transparenten PNG- oder WebP-Hintergrund (die Seite selbst darf keinen haben), image.fullPage, um die gesamte Scrollhöhe aufzunehmen, oder image.clipSelector, um ein einzelnes Element aufzunehmen. Alle Schlüssel stehen unter Bildausgabe.

Bildvorlagen lassen sich auch auf einer Arbeitsfläche gestalten statt schreiben: siehe visueller Editor.

Text an seine Box anpassen​

Ein Titel wechselnder Länge in einer Box fester Größe läuft entweder über oder lässt die Box halb leer. Markieren Sie die Box mit data-ff-fit="shrink", und ihr Text bekommt die größte Schriftgröße, mit der er passt, zwischen seiner CSS-Größe und data-ff-fit-min (in px; ohne Angabe die halbe CSS-Größe):

<div class="title" data-ff-fit="shrink" data-ff-fit-min="36">
<h1>{{ post.title }}</h1>
</div>
.title {
width: 1040px;
height: 320px;
overflow: hidden;
}
.title h1 {
margin: 0;
font-size: 76px;
line-height: 1.1;
}

Die Box braucht eine feste Breite und Höhe; der Text ist ihr erstes Kindelement (oder die Box selbst, wenn sie keins hat). Text, der selbst in der Mindestgröße nicht passt, behält die Mindestgröße, und das overflow der Box entscheidet, was zu sehen ist. Das Anpassen läuft, nachdem die Schriften geladen sind und bevor der Render die Seite aufnimmt, und es funktioniert auch in PDF-Vorlagen. Die Vorschauen passen genauso an; Safari wählt womöglich eine etwas kleinere Größe als der Render, den Echtes Rendering exakt zeigt.

Partials​

Ein Partial ist ein Fragment, das eine Vorlage über seinen Namen einbindet: {% include "footer" %} (Jinja2 und Liquid) oder {{> footer}} (Handlebars). Es gibt zwei Arten:

  • Partials der Vorlage gehören zu einer Version einer Vorlage, wie unten beschrieben.
  • Geteilte Partials gehören der Organisation. Sie werden auf der Seite Partials oder mit formfeed partials bearbeitet, und jede Vorlage derselben Engine, die eines einbindet, rendert mit seiner aktuellen Version. Nutzen Sie sie für das, was jedes Dokument gleich zeigen muss, etwa einen Briefkopf oder den rechtlichen Fußtext.

Gibt es beide unter einem Namen, gewinnt das Partial der Vorlage; siehe wie ein Name aufgelöst wird. In Jinja2 laden {% import "macros" as m %} und {% from "macros" import price %} Makros auf dieselbe Weise aus einem Partial der einen oder anderen Art.

Partials der Vorlage liegen im Ordner partials/ des Projekts. formfeed templates push sendet die Partials, die eine Vorlage einbindet, verschachtelte und die in Kopf- oder Fußzeile eingeschlossen, und sie werden mit der Version gespeichert. API-Renderings und die Vorschau im Editor lösen sie deshalb auf, und formfeed templates pull schreibt sie zurück nach partials/. Partials, die formfeed partials pull als geteilt vermerkt hat, bleiben aus der Version heraus, weil sie live aufgelöst werden.

Ein Partial der Vorlage gehört zu der Version, die es benutzt: Eine veröffentlichte Vorlage ist in sich vollständig, das Bearbeiten von partials/footer.html ändert bis zum nächsten push nichts, und eine ältere Version behält das Partial, mit dem sie veröffentlicht wurde. push verweigert eine Vorlage, deren Include weder eine Datei noch ein geteiltes Partial hat, und formfeed validate meldet das. Ein Rendering, das ein Partial einbindet, das es nicht gibt, schlägt mit template_runtime_error fehl.

Vorlagen aus der App

Der Web-Editor hat keinen Partials-Tab: Er behält die Partials der geöffneten Version und rendert mit ihnen und mit den geteilten Partials der Organisation. Ein Fragment, das in der App geschriebene Vorlagen einbinden sollen, legen Sie als geteiltes Partial auf der Seite Partials an.

Limits​

LimitWertWenn es überschritten wird
Ausführung der Vorlage (der Schritt der Vorlagensprache)5 Sekundenrender_timeout; Liquid meldet template_runtime_error
Gerendertes HTML20 MBoutput_too_large
Verschachtelte Partials8 Ebenen in Handlebars; Jinja2 und Liquid haben keine EbenengrenzeHandlebars: render_timeout. Ein Partial, das sich selbst einbindet, schlägt in jeder Sprache fehl.
Ad-hoc-html in einer Anfrage5 MB400 invalid_request
Laden der Seite, Warten auf waitFor.selectorje 30 Sekunden (für den Selektor gilt waitFor.timeoutMs, standardmäßig 5 Sekunden)render_timeout
Anfragen nach Bildern, Schriften und anderen Dateien1.000 pro RenderingWeitere Anfragen werden blockiert; das Rendering läuft ohne sie weiter.
Seiten pro PDFLimit des Plans: 20 (Free), 200 (Starter), 500 (Pro), 1.000 (Business), 5.000 (Enterprise)page_limit_exceeded
Größe der AusgabedateiLimit des Plans: 10 MB (Free), 50 MB (Starter), 100 MB (Pro), 250 MB (Business und Enterprise)output_too_large

Ein Rendering, das ein Limit überschreitet, schlägt mit einem Problem fehl, das sagt, welches Limit es erreicht hat.

Fehlersuche​

  • Geben Sie die Daten aus, die tatsächlich angekommen sind: <pre>{{ invoice | json }}</pre> in Jinja2, <pre>{{ invoice | json | escape }}</pre> in Liquid, <pre>{{json invoice}}</pre> in Handlebars.
  • Nutzen Sie Echtes Rendering im Editor oder formfeed render mit einem Test-Schlüssel, um das exakte PDF zu sehen, das die Produktion erzeugt. Beides ist kostenlos; im Plan Free trägt die Datei eine Testmarke.
  • Ein Vorlagenfehler nennt Zeile und Spalte. Die Problemliste im Editor nennt außerdem den Tab (Vorlage, Kopfzeile oder Fußzeile) und springt dorthin, wenn Sie auf die Meldung klicken. Ein Problem der API trägt dieselbe Position als location.line und location.column; in welchem Teil die Zeile liegt, sagt es nicht.