Schema Validation

The @paperjsx/core package exports Zod schemas for validating PaperDocument JSON at the boundary of your system:

TYPESCRIPT
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

SchemaValidates
PaperDocumentSchemaFull document: meta, theme, slideSize, slides array
PaperSlideSchemaSingle slide: layout/master refs, transition, children
PaperNodeSchemaSingle 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 valid ColorModifier

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].xml is present and well-formed
  • All relationship IDs (r:id) in slide XML resolve to entries in the corresponding .rels file
  • No duplicate relationship IDs within a single .rels file
  • 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.

Calling `desktop_async` or `desktop_blocking` in the self-hosted engine throws `VALIDATION_BACKEND_UNAVAILABLE`. Use `structural` for self-hosted validation.

Quality Reports

Every render can produce a QualityReport (via renderWithQualityReport() or preflight()):

TYPESCRIPT
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

VerdictMeaning
native_editableAll slides fully editable in PowerPoint
editable_with_constraintsSome slides use anchored positioning but remain editable
visual_fallbackSome slides rendered as images for visual fidelity
rejectedOutput 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 deduction
  • native_anchored: -12 per slide
  • visual_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:

TYPESCRIPT
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

CodeCategoryTrigger
OVERFLOW_BODY_TEXTlayoutText overflows its container
TABLE_TOO_DENSElayoutTable has too many rows/columns for the space
CHART_LABEL_COLLISIONchartChart labels overlap
LAYOUT_SHOULD_SPLITlayoutSlide is dense enough to warrant splitting
FONT_FALLBACK_USEDtypographyRequested font unavailable, fallback substituted
BRAND_TOKEN_MISSINGbrandTemplate placeholder not matched
ASSET_MISSINGassetReferenced media could not be resolved
STRUCTURAL_VALIDATION_FAILEDvalidationPPTX archive failed structural checks
DESKTOP_VALIDATION_FAILEDvalidationPowerPoint desktop validation failed

Preflight

PaperEngine.preflight() validates a document without producing a PPTX file. It runs schema validation, compatibility analysis, and template preflight:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
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:

TYPESCRIPT
// 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:

JSON
{
  "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).

TYPESCRIPT
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;
    }
  }
}