Schema Validation
The @paperjsx/core package exports Zod schemas for validating PaperDocument JSON at the boundary of your system:
import {
PaperDocumentSchema,
PaperSlideSchema,
PaperNodeSchema,
} from "@paperjsx/core/engine";
const result = PaperDocumentSchema.safeParse(inputJson);
if (!result.success) {
for (const issue of result.error.issues) {
console.error(`${issue.path.join(".")}: ${issue.message}`);
}
}
Schema Hierarchy
| Schema | Validates |
|---|---|
PaperDocumentSchema | Full document: meta, theme, slideSize, slides array |
PaperSlideSchema | Single slide: layout/master refs, transition, children |
PaperNodeSchema | Single node: discriminated union on type field |
The schemas enforce structural limits:
- Max 1000 child nodes per slide
- Max 16384 data points per chart series
- Max 100 columns per table
- Max 1000 text runs per paragraph array
- Font size range: 1-4000
- Color values must be
#RRGGBB, a scheme token, or a validColorModifier
Validation Modes
The engine supports four validation modes, configured via EngineRenderOptions.validationMode:
none
No post-render validation. The engine still validates the input document schema, but does not inspect the generated PPTX archive. This is the default and the fastest option.
structural
After generating the PPTX buffer, the engine unzips it and runs structural checks:
[Content_Types].xmlis present and well-formed- All relationship IDs (
r:id) in slide XML resolve to entries in the corresponding.relsfile - No duplicate relationship IDs within a single
.relsfile - All referenced files exist in the ZIP archive
- Presentation XML references valid slide paths
If any check fails, the QualityReport.structuralValidation.status is set to "failed" and the render throws STRUCTURAL_VALIDATION_FAILED.
desktop_async / desktop_blocking
These modes submit the rendered PPTX to an external PowerPoint desktop validation backend that opens the file in PowerPoint and checks for repair prompts, rendering errors, and visual regressions. Only available through the hosted V2 API.
Quality Reports
Every render can produce a QualityReport (via renderWithQualityReport() or preflight()):
interface QualityReport {
documentVerdict: QualityDocumentVerdict;
editabilityScore: number; // 0-100
deckScore: number; // 0-100
repairRisk: "low" | "medium" | "high";
findings: QualityFinding[];
slideReports: SlideQualityReport[];
structuralValidation: StructuralValidationSummary;
repairSummary: RepairSummary;
contractPassed: boolean;
}
Document Verdicts
| Verdict | Meaning |
|---|---|
native_editable | All slides fully editable in PowerPoint |
editable_with_constraints | Some slides use anchored positioning but remain editable |
visual_fallback | Some slides rendered as images for visual fidelity |
rejected | Output violates the requested contract or failed validation |
Editability and Deck Scores
editabilityScore starts at 100 and deducts points per slide based on compatibility:
native_safe: no deductionnative_anchored: -12 per slidevisual_fallback: -35 per slide
deckScore further adjusts for repair risk (-8 for medium, -20 for high), fallback count (-2 each), and caps at 35 for rejected documents.
Quality Findings
Each QualityFinding is an actionable issue:
interface QualityFinding {
code: QualityFindingCode;
severity: "info" | "warning" | "error";
message: string;
slideIndex?: number;
componentPath?: string;
category: "layout" | "brand" | "asset" | "chart" | "validation" | "typography";
blocking: boolean;
machineFixHint?: string;
recommendedAction?: string;
}
Finding Codes
| Code | Category | Trigger |
|---|---|---|
OVERFLOW_BODY_TEXT | layout | Text overflows its container |
TABLE_TOO_DENSE | layout | Table has too many rows/columns for the space |
CHART_LABEL_COLLISION | chart | Chart labels overlap |
LAYOUT_SHOULD_SPLIT | layout | Slide is dense enough to warrant splitting |
FONT_FALLBACK_USED | typography | Requested font unavailable, fallback substituted |
BRAND_TOKEN_MISSING | brand | Template placeholder not matched |
ASSET_MISSING | asset | Referenced media could not be resolved |
STRUCTURAL_VALIDATION_FAILED | validation | PPTX archive failed structural checks |
DESKTOP_VALIDATION_FAILED | validation | PowerPoint desktop validation failed |
Preflight
PaperEngine.preflight() validates a document without producing a PPTX file. It runs schema validation, compatibility analysis, and template preflight:
const report = await PaperEngine.preflight(doc, {
outputMode: "strict_editable",
});
console.log("Verdict:", report.documentVerdict);
console.log("Score:", report.deckScore);
console.log("Findings:", report.findings.length);
Use preflight to gate renders: check the score or verdict before committing compute to a full render.
Repair Mode
Set repairMode: "structural" to automatically fix common OOXML issues in the rendered output:
const { pptx, qualityReport } = await PaperEngine.renderWithQualityReport(doc, undefined, {
repairMode: "structural",
});
const { repairSummary } = qualityReport;
console.log("Repair state:", repairSummary.state);
// "not_requested" | "not_needed" | "repaired" | "failed"
console.log("Actions taken:", repairSummary.actions.length);
Each RepairAction describes what was fixed:
interface RepairAction {
id: string; // e.g., "fix-duplicate-rid"
description: string; // Human-readable description
file: string; // Archive path that was modified
}
You can also use the standalone repair utilities:
// Validate an existing PPTX buffer
const validation = await PaperEngine.validate(buffer);
// Repair an existing PPTX buffer
const repaired = await PaperEngine.repair(buffer);
// Validate then repair in one pass
const { buffer, repairSummary, finalValidation } = await PaperEngine.validateAndRepair(buffer);
V2 API: Policy Result
The hosted V2 API /v2/preflight endpoint returns a policyResult alongside the quality report. The policy result evaluates the quality report against your configured preflight policy:
{
"status": "success",
"data": {
"quality_report": { "deckScore": 92, "documentVerdict": "native_editable", "..." : "..." },
"policyResult": {
"blocking": false,
"reason": null
}
}
}
If policyResult.blocking is true, the response status changes to "blocked", signaling that the document should not proceed to rendering. Configure the threshold via preflightPolicy.renderIfScoreAbove in the request body.
Error Handling
Schema validation failures during render() or preflight() throw PaperError with code VALIDATION_FAILED and phase validation. Contract violations throw COMPATIBILITY_CONTRACT_VIOLATION with phase serialization (or template for unsafe templates).
import { PaperError } from "@paperjsx/core/engine";
try {
await PaperEngine.render(doc);
} catch (err) {
if (err instanceof PaperError) {
switch (err.code) {
case "VALIDATION_FAILED":
console.error("Invalid document:", err.message);
break;
case "COMPATIBILITY_CONTRACT_VIOLATION":
console.error(`Slide ${err.slideIndex} violates contract:`, err.message);
break;
case "STRUCTURAL_VALIDATION_FAILED":
console.error("Archive corruption:", err.message);
break;
}
}
}