Prérequis
- Node.js 22 ou ultérieur. Le script utilise fetch et les modules intégrés, sans paquet supplémentaire.
- Un compte Templatr, une clé API et du quota PDF disponible.
- Un terminal avec curl pour envoyer le modèle une première fois. Les commandes d’environnement ci-dessous utilisent un shell POSIX.
1. Préparer un modèle HTML réutilisable
Téléchargez ce fichier sous le nom invoice-template.html. Le modèle utilise des propriétés imbriquées et une boucle items. Dans cette boucle, item représente chaque objet du tableau items ; les valeurs sont échappées en HTML. Gardez les styles dans le modèle : JavaScript est désactivé pendant le rendu.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Invoice {{invoice.number}}</title>
<style>
@page { size: A4; margin: 18mm; }
body { font: 11pt Arial, sans-serif; color: #172033; }
h1 { font-size: 26pt; margin-bottom: 8mm; }
table { width: 100%; border-collapse: collapse; margin: 8mm 0; }
th, td { padding: 3mm; border-bottom: 1px solid #dbe2ea; text-align: left; }
th:last-child, td:last-child { text-align: right; }
thead { display: table-header-group; }
tr, .total { break-inside: avoid; }
.total { text-align: right; font-weight: bold; }
</style>
</head>
<body>
<h1>Invoice {{invoice.number}}</h1>
<p>From: {{seller.name}}</p>
<p>Bill to: {{customer.name}}</p>
<p>Date: {{invoice.date}}</p>
<table>
<thead><tr><th>Description</th><th>Quantity</th><th>Amount</th></tr></thead>
<tbody>{{ items loop }}<tr><td>{{item.description}}</td><td>{{item.quantity}}</td><td>{{item.amount}}</td></tr>{{ end loop }}</tbody>
</table>
<p class="total">Total: {{invoice.total}}</p>
<p>Sample document with fictional data. No payment is due.</p>
</body>
</html>
2. Préparer les valeurs JSON
Téléchargez invoice-data.json dans le même dossier. L’objet JSON constitue directement le corps de la requête, sans enveloppe data supplémentaire. Formatez dates, devises et totaux dans votre application. Templatr insère les valeurs à afficher ; il ne calcule pas le total à partir des lignes.
{
"seller": { "name": "Acme Studio" },
"customer": { "name": "Northstar Co." },
"invoice": { "number": "DEMO-0042", "date": "2026-09-23", "total": "EUR 299.00" },
"items": [
{ "description": "Template design", "quantity": 1, "amount": "EUR 199.00" },
{ "description": "Integration review", "quantity": 2, "amount": "EUR 100.00" }
]
}
3. Envoyer le modèle et conserver son identifiant
Créez une clé API dans les paramètres, puis définissez TEMPLATR_API_KEY dans l’environnement de votre serveur. Exécutez cette commande depuis le dossier du modèle. La réponse JSON contient template_id. Affectez cette valeur à TEMPLATR_TEMPLATE_ID et conservez-la : il est inutile d’envoyer le même HTML pour chaque document.
export TEMPLATR_API_KEY='YOUR_API_KEY'
curl --fail-with-body "https://api.templatr.app/upload" \
-H "Authorization: Bearer $TEMPLATR_API_KEY" \
-F "[email protected]" \
-F "name=Example invoice"
export TEMPLATR_TEMPLATE_ID='TEMPLATE_ID_FROM_RESPONSE'4. Générer et enregistrer le PDF
Enregistrez le script ci-dessous à côté de invoice-data.json. Il envoie le JSON à la route de téléchargement PDF, vérifie le statut HTTP et le type de contenu, puis écrit invoice.pdf uniquement si la réponse commence par un en-tête PDF. Conservez la clé dans les variables d’environnement du serveur, jamais dans le code du navigateur ni dans un dépôt public.
L’extension .mjs permet les imports et await sans configuration package.json. Le délai maximal du script est de 60 secondes.
import { readFile, writeFile } from "node:fs/promises";
const apiKey = process.env.TEMPLATR_API_KEY;
const templateId = process.env.TEMPLATR_TEMPLATE_ID;
if (!apiKey || !templateId) throw new Error("Set TEMPLATR_API_KEY and TEMPLATR_TEMPLATE_ID first.");
const data = JSON.parse(await readFile(process.argv[2] || "invoice-data.json", "utf8"));
const baseUrl = process.env.TEMPLATR_API_BASE_URL || "https://api.templatr.app";
const response = await fetch(`${baseUrl}/pdf/${encodeURIComponent(templateId)}?format=pdf`, {
method: "POST",
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
body: JSON.stringify(data),
signal: AbortSignal.timeout(60_000),
});
if (!response.ok) throw new Error(`PDF request failed (HTTP ${response.status}). Check the API error guide.`);
if (!response.headers.get("content-type")?.includes("application/pdf")) {
throw new Error("Expected an application/pdf response.");
}
const pdf = Buffer.from(await response.arrayBuffer());
if (pdf.subarray(0, 5).toString() !== "%PDF-") throw new Error("Response does not contain a PDF.");
await writeFile(process.argv[3] || "invoice.pdf", pdf);
console.log("PDF saved.");
5. Exécuter le script
Le premier argument est le fichier JSON, le second est le fichier PDF de sortie. Les variables d’environnement doivent être définies dans le même terminal.
node generate-pdf.mjs invoice-data.json invoice.pdfVérifications et erreurs
- Ouvrez invoice.pdf et vérifiez DEMO-0042, Northstar Co., les deux lignes et le total de EUR 299.00. Le document d’exemple est en anglais.
- Changez le nom du client et une description dans invoice-data.json, relancez le script et vérifiez les nouvelles valeurs.
- Testez les noms et descriptions longs avant d’utiliser vos données réelles. Une variable absente produit une chaîne vide.
- Pour une erreur 401, vérifiez la clé API. Pour 404, vérifiez que le modèle appartient au compte de la clé. Pour 429, contrôlez le quota du compte et l’en-tête Retry-After.
- Un délai dépassé ne prouve pas l’échec de la génération côté serveur. Vérifiez l’historique avant de réessayer : plusieurs requêtes réussies peuvent consommer plusieurs crédits.