Skip to content

Saving & Round-tripping

handler.save(slides, options?) serializes your (possibly edited) slides back into a valid .pptx ZIP archive and returns a Uint8Array.

ts
const bytes = await handler.save(data.slides); // => Uint8Array

Use the originating handler

Call save() on the handler that loaded or created the data. It holds the in-memory ZIP - media, masters, themes, custom XML parts, VBA - that the save pipeline reuses for anything you didn't touch. Saving through a different handler would have nothing to round-trip.

What the save pipeline does

The pipeline (in PptxHandlerRuntimeSavePipeline) runs these stages in order:

  1. Resolve conformance - 'preserve' (default) keeps the Strict/Transitional class detected at load; 'strict' / 'transitional' force one.
  2. Reconcile the slide list - added, removed, and reordered slides are mirrored into ppt/presentation.xml and its relationships; notes/handout master infrastructure is created if newly needed.
  3. Process each slide - rebuilds the shape tree from element data: text paragraphs and run properties, shape styles, effects, transforms, relationship updates (images, charts, media, ink), animation timing trees, and notes. Newly embedded media registers its extension for step 4.
  4. Rebuild [Content_Types].xml - slide overrides plus Default entries for every used media/ink extension.
  5. Comments - classic comment parts are pruned/re-emitted, ppt/commentAuthors.xml is regenerated (or removed, including its relationship, to avoid PowerPoint's repair prompt), and modern (threaded) comments are persisted.
  6. Masters, layouts, themes - typed mutations from the save options are applied; every master/layout/theme you did not list passes through verbatim from the loaded archive.
  7. Embedded fonts - fonts loaded with raw data are re-embedded automatically (lossless by default); the embeddedFonts / embeddedFontList options override or remove them.
  8. Presentation-level parts - presentation.xml (sections, custom shows, photo album, kinsoku, modify verifier, slide size), presProps.xml, viewProps.xml, tableStyles.xml, document properties (docProps/core.xml, app.xml, custom.xml), tag collections, notes/handout masters.
  9. Charts, SmartArt, OLE - pending chart/diagram/SmartArt XML updates are flushed and their content types ensured.
  10. Preservation passes - custom XML parts, the thumbnail, and the VBA project are carried forward untouched; digital signatures are stripped (an edited package would fail validation anyway).
  11. Output format overrides - .ppsx / .pptm content-type switches if requested.
  12. Strict conversion - if the effective conformance is Strict, all parts are converted from Transitional back to Strict namespace URIs.
  13. ZIP hygiene - JSZip's auto-created directory entries are removed (ISO/IEC 29500-2 forbids folder entries as parts; PowerPoint's OPC loader shows the repair dialog otherwise), then the archive is generated as a Uint8Array.

Round-trip guarantees

The engine is built for edit a fraction, keep the rest:

Preserved verbatim (unless you edit it)Rebuilt on every save
Slide masters and layoutsppt/presentation.xml slide list + rels
Theme parts (ppt/theme/theme*.xml)Each slide's p:spTree from element data
Media binaries (ppt/media/*)[Content_Types].xml
Embedded fonts (raw data re-embedded)Comment parts and comment authors
VBA projects and custom XML partsDocument properties
Thumbnail (docProps/thumbnail.jpeg)Chart/SmartArt parts you touched
Table styles (def GUID, unmodelled XML)

Anything the data model does not represent is never regenerated from scratch - it flows from the loaded ZIP into the saved one byte-for-byte.

Signatures do not survive saving

save() always strips XML digital signature parts, because any modification invalidates them. Re-sign the output with the pptx-viewer-core/signature-node helpers if you need a signed result.

OOXML Strict and Transitional

Office 365 can save files in ISO/IEC 29500 Strict mode, which uses purl.oclc.org namespace URIs instead of the common Transitional (ECMA-376) schemas.openxmlformats.org ones. On load the engine detects Strict conformance and normalizes 48 mapped namespace URI pairs (PresentationML, DrawingML families, officeDocument relationship types, schemaLibrary, descriptions) to Transitional for internal processing; on save it converts back according to the conformance option:

ts
// Keep whatever the source file used (default)
await handler.save(data.slides);

// Force Strict Open XML output
await handler.save(data.slides, { conformance: 'strict' });

// Downgrade a Strict file to Transitional
await handler.save(data.slides, { conformance: 'transitional' });

The Open Packaging Conventions namespaces (ISO/IEC 29500-2: content types, relationships, core properties) and Markup Compatibility (ISO/IEC 29500-3: mc:AlternateContent) are conformance-independent and intentionally never remapped - real Strict files from Office keep them in canonical form, and so does this engine. The root conformance="strict" attribute on p:presentation is added or dropped to match the effective class.

Save options

save(slides, options?) takes a PptxHandlerSaveOptions object for parts that live outside the per-slide element model. All fields are optional; omitting a field round-trips the original part untouched.

OptionTypePurpose
coreProperties / appProperties / customPropertiestyped objects / arrayDocument metadata (docProps/*.xml).
sectionsPptxSection[]Slide section grouping.
customShowsPptxCustomShow[]Custom slide-show definitions.
presentationPropertiesPptxPresentationPropertiesShow type, looping, print settings, etc.
slideMasters / slideLayoutsPptxSlideMaster[] / PptxSlideLayout[]Typed mutations (clrMap, background, hf flags) applied per path; unlisted masters/layouts pass through verbatim.
notesMaster / handoutMastertyped objectsNotes/handout master updates (incl. slidesPerPage).
headerFooterPptxHeaderFooterPresentation-level header/footer flags.
viewPropertiesPptxViewPropertiesppt/viewProps.xml.
tags / customerDataarraysTag collections (ppt/tags/tag*.xml) and customer-data references.
photoAlbumPptxPhotoAlbump:photoAlbum metadata.
kinsokuPptxKinsoku | nullEast Asian line-break settings; null removes.
modifyVerifierPptxModifyVerifier | nullWrite-protection verifier; null removes it, undefined preserves the existing one.
tableStylesParsedTableStyleMapTable-style edits for ppt/tableStyles.xml (unmodelled XML preserved).
embeddedFonts / embeddedFontListPptxEmbeddedFont[] / ... | nullOverride or remove embedded fonts; default is lossless re-embedding.
outputFormat'pptx' | 'ppsx' | 'pptm'Standard, slide-show, or macro-enabled output (see below).
conformance'strict' | 'transitional' | 'preserve'OOXML conformance class of the output (default 'preserve').
ts
const bytes = await handler.save(data.slides, {
	coreProperties: { ...data.coreProperties, title: 'Final Report' },
	sections: data.sections,
	conformance: 'preserve',
});

Output formats and byte types

The return type is always a plain Uint8Array, directly writable with node:fs, Bun.write, or a browser Blob.

outputFormatExtensionBehaviour
'pptx'.pptxStandard presentation (default).
'ppsx'.ppsxSlide-show file; opens straight into presentation mode.
'pptm'.pptmMacro-enabled presentation; requires the loaded file's VBA data.

Writing the result

ts
import { writeFile } from 'node:fs/promises';

const bytes = await handler.save(data.slides);
await writeFile('output.pptx', bytes);
ts
const bytes = await handler.save(data.slides);
await Bun.write('output.pptx', bytes);
ts
const bytes = await handler.save(data.slides);
const blob = new Blob([bytes], {
	type: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
});
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.pptx';
a.click();
URL.revokeObjectURL(url);

Saving encrypted output

To write a password-protected file, use saveEncrypted instead of save. It accepts the same save options plus an encryption block:

ts
const bytes = await handler.saveEncrypted(data.slides, 'secret');
// => Uint8Array of an encrypted OLE2 package

See /core/encryption for algorithm options and details.

A note on exportSlides

The handler also exposes exportSlides(slides, options) with options.format: 'pdf' | 'png' | 'svg'. In the default runtime this is a backend hook, not a working exporter: no rendering backend is configured, so it reports an EXPORT_BACKEND_UNAVAILABLE compatibility warning and returns empty byte arrays. For headless per-slide output use the SVG exporter; for raster output use a viewer binding's export pipeline in a browser.

Released under the Apache-2.0 License.