Dokumente als Komponenten.
DOCX als Ergebnis.
Setzen Sie Dokumente aus kleinen, typisierten Komponenten zusammen — mit dem JSX, das Sie ohnehin schreiben. Nutzen Sie die eingebauten KI-Funktionen für dynamische Dokumente und skalieren Sie die Dokumenterzeugung mit unserer Engine.
npx docxcelerate init my-documents {
"schemaVersion": "docxcelerate.config/v0",
"activePreset": "local",
"presets": {
"local": {
"build": { "outDir": "build" },
"upload": {
"endpoint": "",
"method": "POST",
"headers": {},
"body": "document"
}
},
"staging": {
"build": { "outDir": "build" },
"upload": {
"endpoint": "https://documents.staging.example.com/api/letters",
"method": "POST",
"headers": { "Authorization": "Bearer ${LETTERS_TOKEN}" },
"body": "document"
}
}
}
} import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
/**
* Formatting is ordinary TypeScript — no template language, so an Oxford comma
* and a singular/plural switch cost one line each.
*/
export const Conditions = paragraph<OfferData>({
id: "conditions",
render: (data) => {
const list = formatList(data.conditions);
const verb = data.conditions.length === 1 ? "condition is" : "conditions are";
return `This offer is conditional. The ${verb} ${list}. We will confirm ` +
`your place automatically once your results reach us.`;
},
});
function formatList(items: string[]): string {
if (items.length <= 1) return items[0] ?? "";
return `${items.slice(0, -1).join(", ")}, and ${items.at(-1)}`;
} import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
/**
* An optional field changes the paragraph rather than leaving a gap — the kind
* of branch that is awkward in a mail-merge and trivial here.
*/
export const Fees = paragraph<OfferData>({
id: "fees",
render: (data) => {
const base = `You have been assessed as a ${data.feeStatus} fee payer, so ` +
`tuition for your first year will be ${data.tuitionFee}.`;
if (!data.scholarship) {
return `${base} Details of loans, bursaries and hardship funding are in ` +
`the enclosed funding guide.`;
}
return `${base} We are also pleased to award you the ` +
`${data.scholarship.name}, worth ${data.scholarship.amount} for the ` +
`duration of your course. No separate application is needed.`;
},
}); import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
export const Greeting = paragraph<OfferData>({
id: "greeting",
render: (data) => `Dear ${data.applicantName},`,
}); export { Conditions } from "./conditions.node.ts";
export { Fees } from "./fees.node.ts";
export { Greeting } from "./greeting.node.ts";
export { NextSteps } from "./next-steps.node.ts";
export { Offer } from "./offer.node.ts";
export { SignOff } from "./signoff.node.ts";
export { TutorNote } from "./tutor-note.node.ts"; import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
export const NextSteps = paragraph<OfferData>({
id: "next-steps",
render: (data) =>
`To accept, reply through the applicant portal by ${data.replyBy}. ` +
`If you would like to visit ${data.college} before deciding, our open ` +
`afternoons run every Thursday through April and you are very welcome.`,
}); import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
export const Offer = paragraph<OfferData>({
id: "offer",
render: (data) =>
`I am delighted to offer you a place on the ${data.programme} at ` +
`${data.college}, beginning ${data.startDate}. Your application reference ` +
`is ${data.offerRef}; please quote it in any correspondence with us.`,
}); import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
export const SignOff = paragraph<OfferData>({
id: "sign-off",
render: (data) =>
`Yours sincerely, ${data.signatory.name}, ${data.signatory.title}, ${data.college}.`,
}); import { paragraph } from "docxcelerate";
import type { OfferData } from "../types.ts";
/**
* The one paragraph worth generating. Everything else in this document is
* deterministic, so this is the only node that needs an endpoint at all.
*/
export const TutorNote = paragraph<OfferData>({
id: "tutor-note",
placeholder: (data) =>
`A short personal note from ${data.interviewer} about ${data.applicantName}'s interview.`,
generalPrompt: (data) =>
`Write two warm, specific sentences from ${data.interviewer} to ` +
`${data.applicantName}, referring to their interest in ${data.portfolioTheme}. ` +
`Mention one thing they should read before term starts.`,
systemPrompt: () =>
"You are an admissions tutor. Be warm but never effusive, and never promise outcomes.",
negativePrompt: () =>
"Do not restate the offer, the conditions, or the reply deadline.",
}); import { cleanMinimalDocumentStyle } from "docxcelerate";
import { defineDocumentProject } from "docxcelerate/document";
import { documentTemplate } from "./document.tsx";
import { previewData } from "./preview-data.ts";
import type { OfferData } from "./types.ts";
export default defineDocumentProject<OfferData>({
id: "offer-of-admission",
name: "Offer of Admission",
version: "1.0.0",
template: documentTemplate,
style: cleanMinimalDocumentStyle,
previewData,
}); import { Document, Section, template } from "docxcelerate/template";
import {
Conditions,
Fees,
Greeting,
NextSteps,
Offer,
SignOff,
TutorNote,
} from "./nodes/index.ts";
import type { OfferData } from "./types.ts";
export const documentTemplate = template<OfferData>(
<Document id="offer-of-admission" title="Offer of Admission">
<Section id="your-offer" title="Your offer">
<Greeting />
<Offer />
<TutorNote />
</Section>
<Section id="conditions" title="Conditions">
<Conditions />
</Section>
<Section id="fees-and-funding" title="Fees and funding">
<Fees />
</Section>
<Section id="next-steps" title="Next steps">
<NextSteps />
<SignOff />
</Section>
</Document>,
); import type { OfferData } from "./types.ts";
export const previewData: OfferData = {
applicantName: "Maya Oyelaran",
programme: "MEng Structural Engineering",
college: "Ashcroft College",
startDate: "28 September 2026",
offerRef: "ADM-2026-4417",
conditions: [
"grades A*AA at A-level, including Mathematics and Physics",
"IELTS 7.0 overall, with no component below 6.5",
"a satisfactory enhanced DBS check",
],
replyBy: "15 May 2026",
portfolioTheme: "timber gridshell roofs and low-carbon structural systems",
interviewer: "Dr Priya Raman",
tuitionFee: "£9,535",
feeStatus: "Home",
scholarship: { name: "Ashcroft Engineering Scholarship", amount: "£3,000 per year" },
signatory: { name: "Dr Priya Raman", title: "Director of Undergraduate Admissions" },
}; export interface OfferData {
applicantName: string;
programme: string;
college: string;
startDate: string;
offerRef: string;
conditions: string[];
replyBy: string;
/** Context for the tutor's note — never printed verbatim. */
portfolioTheme: string;
interviewer: string;
tuitionFee: string;
feeStatus: "Home" | "International";
scholarship?: { name: string; amount: string };
signatory: { name: string; title: string };
} import { paragraph } from "docxcelerate";
import type { RepairsData } from "../types.ts";
/**
* An absent optional field should change the sentence, not print "undefined"
* or leave a blank line.
*/
export const Access = paragraph<RepairsData>({
id: "access",
render: (data) =>
data.accessNotes
? `We hold the following access note for your home: ${data.accessNotes} ` +
`Please let us know if this is out of date.`
: `Someone aged 18 or over needs to be home for the visit. If that is ` +
`not possible, call us and we will arrange access another way.`,
}); import { paragraph } from "docxcelerate";
import type { RepairsData } from "../types.ts";
export const Appointment = paragraph<RepairsData>({
id: "appointment",
render: (data) =>
`We have booked a ${data.trade} to visit ${data.address} on ` +
`${data.visitDate}, ${data.visitWindow}. Your job reference is ` +
`${data.jobRef}.`,
}); import { paragraph } from "docxcelerate";
import type { RepairsData } from "../types.ts";
export const Greeting = paragraph<RepairsData>({
id: "greeting",
render: (data) => `Dear ${data.residentName},`,
}); export { Access } from "./access.node.ts";
export { Appointment } from "./appointment.node.ts";
export { Greeting } from "./greeting.node.ts";
export { WhatToExpect } from "./what-to-expect.node.ts"; import { paragraph } from "docxcelerate";
import type { RepairsData } from "../types.ts";
export const WhatToExpect = paragraph<RepairsData>({
id: "what-to-expect",
placeholder: (data) => `What to expect during a ${data.trade}'s visit.`,
generalPrompt: (data) =>
`In three short sentences, explain what a ${data.trade} will do during a ` +
`routine repair visit, how long it usually takes, and what the resident ` +
`should move out of the way beforehand.`,
systemPrompt: () =>
"You write for social housing residents. Use plain English and short sentences.",
negativePrompt: () => "Do not repeat the appointment date or the job reference.",
}); import { cleanMinimalDocumentStyle } from "docxcelerate";
import { defineDocumentProject } from "docxcelerate/document";
import { documentTemplate } from "./document.tsx";
import { previewData } from "./preview-data.ts";
import type { RepairsData } from "./types.ts";
export default defineDocumentProject<RepairsData>({
id: "repairs-appointment",
name: "Repair Appointment",
version: "2.1.0",
template: documentTemplate,
style: cleanMinimalDocumentStyle,
previewData,
}); import { Document, Section, template } from "docxcelerate/template";
import { Access, Appointment, Greeting, WhatToExpect } from "./nodes/index.ts";
import type { RepairsData } from "./types.ts";
export const documentTemplate = template<RepairsData>(
<Document id="repairs-appointment" title="Your repair appointment">
<Section id="appointment" title="Your appointment">
<Greeting />
<Appointment />
</Section>
<Section id="on-the-day" title="On the day">
<WhatToExpect />
<Access />
</Section>
</Document>,
); import type { RepairsData } from "./types.ts";
export const previewData: RepairsData = {
residentName: "Tomasz Wójcik",
address: "12 Bracken Road, Leeds LS6 3QT",
jobRef: "RPR-88213",
trade: "plumber",
visitDate: "Tuesday 25 August",
visitWindow: "between 8am and 12pm",
accessNotes: "Key safe at the side gate; the code is with the scheme manager.",
}; export interface RepairsData {
residentName: string;
address: string;
jobRef: string;
trade: "plumber" | "electrician" | "joiner";
visitDate: string;
visitWindow: string;
accessNotes?: string;
} import { paragraph } from "docxcelerate";
import type { PolicyData } from "../types.ts";
export const Greeting = paragraph<PolicyData>({
id: "greeting",
render: (data) => `Dear ${data.holderName},`,
}); export { Greeting } from "./greeting.node.ts";
export { PremiumChange } from "./premium-change.node.ts";
export { Renewal } from "./renewal.node.ts";
export { Shopping } from "./shopping.node.ts"; import { paragraph } from "docxcelerate";
import type { PolicyData } from "../types.ts";
/**
* A price rise is the sentence customers actually read. Computing the
* direction and the percentage locally means it can never contradict the
* figures printed beside it.
*/
export const PremiumChange = paragraph<PolicyData>({
id: "premium-change",
render: (data) => {
const delta = data.newPremium - data.lastPremium;
const percent = Math.abs((delta / data.lastPremium) * 100).toFixed(1);
if (Math.abs(delta) < 0.01) {
return `Your premium is unchanged at ${money(data.newPremium)} a year.`;
}
const direction = delta > 0 ? "risen" : "fallen";
return `Your premium has ${direction} by ${percent}%, from ` +
`${money(data.lastPremium)} to ${money(data.newPremium)} a year. ` +
`That works out at ${money(data.newPremium / 12)} a month.`;
},
});
function money(value: number): string {
return value.toLocaleString("en-GB", { style: "currency", currency: "GBP" });
} import { paragraph } from "docxcelerate";
import type { PolicyData } from "../types.ts";
export const Renewal = paragraph<PolicyData>({
id: "renewal",
render: (data) =>
`Your ${data.cover.toLowerCase()} policy ${data.policyNumber} renews on ` +
`${data.renewalDate}. Unless you tell us otherwise, cover continues ` +
`automatically with an excess of ${money(data.excess)}.`,
});
function money(value: number): string {
return value.toLocaleString("en-GB", { style: "currency", currency: "GBP" });
} import { paragraph } from "docxcelerate";
import type { PolicyData } from "../types.ts";
export const Shopping = paragraph<PolicyData>({
id: "shopping-around",
placeholder: () => "A short note on comparing this renewal with other quotes.",
generalPrompt: (data) =>
`Write two sentences reminding ${data.holderName} that they can compare ` +
`this renewal against other quotes, and that doing so will not affect ` +
`their existing cover.`,
systemPrompt: () =>
"You write regulated insurance correspondence. Be neutral and never discourage switching.",
negativePrompt: () => "Do not quote a price, a percentage, or a competitor name.",
}); import { cleanMinimalDocumentStyle } from "docxcelerate";
import { defineDocumentProject } from "docxcelerate/document";
import { documentTemplate } from "./document.tsx";
import { previewData } from "./preview-data.ts";
import type { PolicyData } from "./types.ts";
export default defineDocumentProject<PolicyData>({
id: "policy-renewal",
name: "Policy Renewal",
version: "0.4.2",
template: documentTemplate,
style: cleanMinimalDocumentStyle,
previewData,
}); import { Document, Section, template } from "docxcelerate/template";
import { Greeting, PremiumChange, Renewal, Shopping } from "./nodes/index.ts";
import type { PolicyData } from "./types.ts";
export const documentTemplate = template<PolicyData>(
<Document id="policy-renewal" title="Your renewal">
<Section id="renewal" title="Your renewal">
<Greeting />
<Renewal />
<PremiumChange />
</Section>
<Section id="your-options" title="Your options">
<Shopping />
</Section>
</Document>,
); import type { PolicyData } from "./types.ts";
export const previewData: PolicyData = {
holderName: "Eleanor Whitfield",
policyNumber: "HC-2291-8840",
cover: "Contents and buildings",
renewalDate: "1 October 2026",
lastPremium: 284.4,
newPremium: 311.16,
excess: 250,
}; export interface PolicyData {
holderName: string;
policyNumber: string;
cover: "Contents" | "Buildings" | "Contents and buildings";
renewalDate: string;
lastPremium: number;
newPremium: number;
excess: number;
} Eine echte Dokumentvorschau, live gerendert — keine Screenshots und keine versteckten Tricks.
Dokumente schreiben wie Websites
Ein Dokument ist ein Baum aus typisierten Komponenten. Wer schon einmal React geschrieben hat, kennt diese Form bereits — Props, Komposition, kleine Dateien — und ist als Frontend-Entwicklerin oder -Entwickler schon am ersten Nachmittag produktiv, statt erst eine Templatesprache lernen zu müssen.
Für KI entworfen
Generierter Text ist ein Node-Typ, kein nachträglicher Aufsatz. Markieren Sie den einen Absatz, der wirklich ein Modell braucht, geben Sie ihm Prompts und einen Platzhalter, und lassen Sie den Rest deterministisch — so löst KI den schwierigen Teil, ohne dass das ganze Dokument ihr ausgeliefert ist.
Dokumente liegen in Ihrem Repository
Weil ein Dokument Quellcode ist, wird das Ändern eines Satzes zu einem Pull Request — mit Diff, mit Review, und noch ein Jahr später nachvollziehbar, wenn jemand fragt, wer die Formulierung zum Zahlungsrückstand geändert hat. Tests halten fest, dass ein Dokument das rendert, was Sie erwarten, sodass die CI den Fehler vor der Empfängerin oder dem Empfänger bemerkt.
Docxcelerate behandelt ein Dokument so, wie ein UI-Framework einen Bildschirm behandelt: kleine Komponenten, zu einem Baum zusammengesetzt, gerendert von etwas, das Papier versteht. Sie bekommen die Ergonomie eines Komponentenmodells, und die Empfängerin oder der Empfänger bekommt ein Word-Dokument.
Die Engine
In der Engine werden Dokumente tatsächlich geschrieben. Sie setzt Ihre Daten ein, führt die KI aus und gibt das fertige Dokument zurück. Jede Art von Node kann KI nutzen, nicht nur Absätze. Die Antwort des Modells wird entweder zum Text, geschrieben aus den Informationen, die Sie ihm geben, oder trifft eine Entscheidung, von der das Dokument abhängt.
Sie veröffentlichen ein Template einmal in der Engine. Danach kann jedes System ihre API mit einem Datensatz aufrufen und bekommt ein Dokument zurück. Eine kostenlose Engine steht zum Selbsthosten bereit. Die verwaltete Cloud betreibt die vollständige Fassung, mit vielem, das die kostenlose nicht hat, und kommt bald.
- 01 Bauen Das Framework macht aus Ihrem Dokument ein Paket. Dieser Schritt läuft auf Ihrem Rechner.
- 02 Veröffentlichen Sie senden das Paket an eine Engine. Die Engine speichert es und gibt ihm einen Namen.
- 03 Schreiben Ihre Anwendung sendet einen Datensatz. Die Engine gibt das fertige Dokument zurück.
Lesen Sie das Ganze
Jeder Node-Typ, jedes CLI-Flag und jede Datei, die ein Build schreibt — mit Vorschauen, die der echte Renderer erzeugt hat, sodass nichts auf der Seite einen Helper beschreiben kann, den es nicht mehr gibt.