Skip to content

Instance API ​

The component instance obtained through bind:this implements PowerPointViewerApi: the shared cross-binding viewer contract (the same one behind React's ref handle and Vue's defineExpose) plus the Svelte binding's editing and export methods. All toolbar operations are also available as instance methods, so you can hide the chrome (showToolbar={false}, showThumbnails={false}) and drive the viewer from your own UI.

svelte
<script lang="ts">
	import { PowerPointViewer, type PowerPointViewerApi } from 'pptx-svelte-viewer';

	let { bytes }: { bytes: Uint8Array } = $props();
	let viewer = $state<PowerPointViewerApi>();
</script>

<PowerPointViewer source={bytes} bind:this={viewer} />

Snapshots, not stores

The getter methods (canUndo(), getZoom(), getSelectedElementIds(), ...) return plain snapshots; they are not reactive stores. To react to changes, use the callback props (onzoomchange, onselectionchange, ondirtychange, ...) from Component Props.

Serialisation ​

MethodSignatureDescription
getContent() => Promise<Uint8Array>Serialise the current presentation to .pptx bytes (alias of save()).
MethodSignatureDescription
goTo(index: number) => voidJump to a zero-based slide index (clamped).
goPrev() => voidGo to the previous slide.
goNext() => voidGo to the next slide.
getActiveSlideIndex() => numberZero-based index of the visible slide.
setActiveSlideIndex(index: number) => voidAlias of goTo.
getSlideCount() => numberNumber of slides in the loaded presentation.

Zoom ​

MethodSignatureDescription
getZoom() => numberEffective zoom scale (1 = 100%).
setZoom(level: number) => voidSet an explicit zoom scale (clamped).
zoomIn() => voidZoom in by one step.
zoomOut() => voidZoom out by one step.
zoomReset() => voidReset zoom to 100%.

Mode and presentation ​

MethodSignatureDescription
getMode() => ViewerModeCurrent mode: 'preview' | 'edit' | 'present' | 'master'.
setMode(mode: ViewerMode) => voidSwitch mode. 'present' enters fullscreen presentation (real Fullscreen API); any other mode exits it. 'edit' and 'master' imply editing.
ts
viewer?.setMode('present'); // start presenting; Esc exits

Slide access and manipulation ​

MethodSignatureDescription
getSlides() => readonly PptxSlide[]The full slide array (snapshot with full type information).
getSlide(index: number) => PptxSlide | undefinedA single slide by zero-based index.
getActiveSlide() => PptxSlide | undefinedThe currently active slide.
addSlide(afterIndex?: number) => voidAdd a blank slide after the given index (or at the end).
deleteSlides(indexes: number[]) => voidDelete slides by index (at least one slide is kept).
duplicateSlides(indexes: number[]) => voidDuplicate slides at the given indexes.
moveSlide(fromIndex: number, toIndex: number) => voidMove a slide to a new position.
toggleHideSlides(indexes: number[]) => voidToggle the hidden flag on slides.
isDirty() => booleanWhether the document has unsaved changes.

Element access and manipulation ​

MethodSignatureDescription
getElements(slideIndex?: number) => readonly PptxElement[]Elements on a slide (defaults to the active slide).
getElementById(id: string, slideIndex?: number) => PptxElement | undefinedA single element by id.
updateElement(id: string, updates: Partial<PptxElement>) => voidPatch element properties, e.g. { x: 100, width: 300 }.
updateElements(updates: readonly ElementUpdate[], options?: ElementUpdateOptions) => Promise<void>Update elements across slides in one undo step.
deleteElements(ids: string[]) => voidDelete elements by id from the active slide.
duplicateElement(id: string) => string | undefinedDuplicate an element; returns the new element's id.

Inserting an element ​

addElement(element: PptxElement): string | undefined appends a defensive copy to the active editable slide and selects it, returning its fresh ID. Coordinates are preserved; group descendants also receive fresh IDs. Pending text is committed through the existing editor path, with normal dirty-state and Undo/Redo behavior. Synchronous edits may share a history entry, but all insertions are retained. It returns undefined while loading, after a load error, without an active slide, or in read-only/protected, preview, presentation, or template/master editing modes.

Use a self-contained model or one from the current document. With a loaded viewer in edit mode:

ts
import { createImageElement } from 'pptx-viewer-core';

const image = createImageElement(pngDataUrl, { x: 40, y: 40, width: 160, height: 90 });
const insertedId = viewer?.addElement(image);

This method does not install clipboard listeners, fetch remote URLs, choose image dimensions, or import another document's relationships. A host-owned paste handler can read an image and call it. For a new data-URL image, use the factory above without inventing an imagePath, which denotes an existing archive part.

Loading a local image ​

The package also exports createImageElementFromFile(file, canvasSize, signal?). Given a local File or Blob, the slide size in pixels, and an optional AbortSignal:

ts
import { createImageElementFromFile } from 'pptx-svelte-viewer';

const image = await createImageElementFromFile(file, canvasSize, signal);

The helper preserves the image bytes and returns a centred ImagePptxElement, fitted to the slide without upscaling. It resolves null for invalid/unreadable images or dimensions, cancellation, or unavailable browser APIs. The helper does not use browser APIs until called; decoding requires those APIs.

This only constructs an element: it does not change a deck, history, selection, or clipboard. After await, verify that the same document and destination slide are still active and editable, then pass a non-null result to addElement. Abort pending work when that destination is abandoned. A slide ID alone is not a document identity, and decoding success does not guarantee that every browser image format round-trips in PowerPoint. No automatic paste listener is installed.

Selection ​

MethodSignatureDescription
getSelectedElementIds() => string[]Ids of the currently selected elements.
selectElements(ids: string[]) => voidProgrammatically select elements.
clearSelection() => voidClear the selection.
getSelectedElementId() => string | nullThe selected top-level element id, or null.

Editing ​

Active when editable is set (see Getting Started > Editing).

MethodSignatureDescription
undo() => voidUndo the last committed edit.
redo() => voidRedo the last undone edit.
canUndo() => booleanWhether an undo step is available (snapshot; not reactive).
canRedo() => booleanWhether a redo step is available.
deleteSelected() => voidDelete the selected element (no-op when nothing is selected).
save(format?: PptxSaveFormat) => Promise<Uint8Array>Serialise the edited slides to bytes ('pptx' | 'ppsx' | 'pptm').
downloadAs(format: PptxSaveFormat, fileName?: string) => Promise<void>Save + trigger a browser download in the given format.
downloadPptx(fileName?: string) => Promise<void>Save + download as .pptx (default name).
packageForSharing(fileName?: string) => Promise<void>Assemble and download the sharing package.

Keyboard shortcuts, active whenever editing is enabled: Ctrl/Cmd+Z undo, Ctrl/Cmd+Shift+Z redo, Delete/Backspace delete, Ctrl/Cmd+D duplicate, arrow keys nudge (with Shift for larger steps), Escape deselect.

Export and print ​

MethodSignatureDescription
exportSlidePng(index?: number) => Promise<void>Export a slide as a PNG download (defaults to the current slide).
copySlideAsImage(index?: number) => Promise<void>Copy a slide to the system clipboard as a PNG image.
exportPdf(options?: ExportPdfOptions) => Promise<void>Multi-page PDF download, one slide per page.
exportGif(options?: ExportGifOptions) => Promise<void>Animated GIF download.
exportVideo(options?: ExportVideoOptions) => Promise<void>WebM video download.
print(options?: PrintOptions) => Promise<boolean>Open the browser print dialog (slides / handouts / notes / outline).

See Export & Print for the option shapes, pipelines, and the standalone SVG export functions.

UI customization ​

The handle also carries the whole ViewerCustomizationApi, so the chrome can be customised while the viewer runs. Each call re-renders the affected UI immediately.

ts
viewer?.hideRibbonTab('draw');
viewer?.lockSetting('general.userName', 'Ada Lovelace');
viewer?.hideContextMenuCommand('delete');
viewer?.remapShortcut('duplicate', 'Mod+Shift+D');
viewer?.setFeatureEnabled('ai', false);
viewer?.setPanelVisible('notes', false);
viewer?.updateCustomization({ hiddenDialogs: ['print'] });
const current = viewer?.getCustomization();
MethodEffect
getCustomization() / setCustomization(c) / updateCustomization(patch) / resetCustomization()Read, replace, merge or clear the whole ViewerCustomization.
hideRibbonTab / showRibbonTab, hideToolbarButton / showToolbarButtonRibbon tabs and top-level toolbar buttons.
hideOptionsPage, hideOptionsSection, hideSetting (and show*)File > Options pages, sections and settings.
lockSetting(id, value, hidden?) / unlockSetting(id) / setSettingDefault(id, value)Pin a setting (read-only or hidden) or set a host default.
hideBackstagePage / hideBackstageCard (and show*)File tab pages and action cards.
hideContextMenuCommand / hideCanvasContextMenuCommand (and show*)Element and empty-canvas right-click entries.
disableShortcut / enableShortcut / remapShortcutEditor keyboard shortcuts.
setPanelVisible, setFeatureEnabled, setDialogAvailablePanels, feature areas (AI, collaboration, ...) and dialogs.

The full id reference and recipes are in the UI Customization guide.

Example: external controls ​

svelte
<script lang="ts">
	import { PowerPointViewer, type PowerPointViewerApi } from 'pptx-svelte-viewer';

	let { bytes }: { bytes: Uint8Array } = $props();
	let viewer = $state<PowerPointViewerApi>();
	let current = $state(0);
	let count = $state(0);
</script>

<PowerPointViewer
	source={bytes}
	showToolbar={false}
	showThumbnails={false}
	bind:this={viewer}
	onload={({ slideCount }) => (count = slideCount)}
	onslidechange={(index) => (current = index)}
/>

<div>
	<button onclick={() => viewer?.goPrev()}>Prev</button>
	<span>Slide {current + 1} of {count}</span>
	<button onclick={() => viewer?.goNext()}>Next</button>
	<button onclick={() => viewer?.setMode('present')}>Present</button>
</div>

Lower-level building blocks ​

The pptx-svelte-viewer/viewer entry point additionally exports the viewer's internal framework-free state helpers (ViewerState, PresentationLoader, clampSlideIndex, fitScale, resolveNavigationKey, zoomInPercent, zoomOutPercent) for hosts building custom chrome on the same primitives. These are lower-level than the component API and not needed for typical embedding.

pptx-svelte-viewer/internals: slide-transition helpers ​

pptx-viewer-shared (the framework-agnostic logic every binding bundles) is a private, unpublished workspace package: it is never on npm, so code outside this monorepo cannot import from it directly. A host embedding its own presentation surface (a custom stage, outside the full PowerPointViewer) still needs the transition resolver/keyframes and overlay component, so they are re-exported from the pptx-svelte-viewer/internals subpath instead:

ts
import {
	resolveSlideTransition,
	resolveTransitionDurationMs,
	SLIDE_TRANSITION_KEYFRAMES,
	PresentationTransitionOverlay,
} from 'pptx-svelte-viewer/internals';

resolveSlideTransition maps a PptxSlideTransition to the CSS animation shorthands for the outgoing/incoming layers; resolveTransitionDurationMs resolves its effective duration (ms), honoring an authored duration, the legacy spd token, and PowerPoint's own defaults; SLIDE_TRANSITION_KEYFRAMES (alias SLIDE_TRANSITION_KEYFRAMES_CSS) is the @keyframes block those animation names reference, injected once via a <style> element. Also exported: getSlideTransitionAnimations, getCinematicTransitionAnimations, getP14TransitionAnimations (the classic / cinematic / exotic sub-resolvers), CINEMATIC_TRANSITION_KEYFRAMES / P14_TRANSITION_KEYFRAMES_ALL (their keyframe sub-blocks), resolveDirection / resolveDirection8 / resolveOrientation / resolveWheelSpokeCount, the supporting constants (RANDOM_ELIGIBLE_TYPES, INSTANT, DEFAULT_TRANSITION_DURATION_MS, DEFAULT_MORPH_DURATION_MS, TRANSITION_SPEED_DURATION_MS, EASE, WHEEL_SPOKE_COUNTS), and the PresentationTransitionOverlay component itself.

As with every other binding's internals entry, this is not covered by semver: reach for it only when the curated pptx-svelte-viewer / pptx-svelte-viewer/viewer exports genuinely cannot do what you need, and pin an exact version if you depend on it.

Openable file kinds ​

The package root re-exports the shared answer to "can the viewer open this file?", so a host's drop target and its <input accept> cannot disagree with the loader. Hand-rolled endsWith chains drift: every demo in this repo once shipped .pptx,.ppt,.json, which refused on drop a .pptm that File > Open inside the viewer accepted without complaint.

ts
import {
	PPTX_OPEN_ACCEPT,
	PRESENTATION_OPEN_EXTENSIONS,
	isSupportedPresentationFile,
	isLegacyBinaryPresentation,
	presentationBaseName,
	savedPresentationFileName,
	type SavedPresentationFormat,
} from 'pptx-svelte-viewer';
ExportTypeDescription
PPTX_OPEN_ACCEPTstringReady-made <input type="file" accept> value: .pptx,.ppsx,.pptm,.potx,.ppt,.json.
PRESENTATION_OPEN_EXTENSIONSreadonly string[]The same list unjoined, for a drop target that wants to test extensions itself.
isSupportedPresentationFile(name?: string | null) => booleanCheap pre-filter for a picked or dropped file name. Extension-only; the real answer is the loader's sniff.
isLegacyBinaryPresentation(name?: string | null) => booleanTrue for the binary PowerPoint 97-2003 family (.ppt / .pps / .pot), which the viewer reads but never writes.
presentationBaseName(name?: string | null, fallback?: string) => stringThe file-name stem, directories and any loadable extension removed (a path like decks/report.ppt becomes report).
savedPresentationFileName(name?: string | null, format?: SavedPresentationFormat) => stringThe name a saved copy should be offered under: report.ppt becomes report.pptx.
SavedPresentationFormat'pptx' | 'ppsx' | 'pptm'The formats the save path can produce. Binary .ppt is deliberately absent: output is always OpenXML.

savedPresentationFileName is the one that matters on Save As. Output is always an OpenXML package, so keeping a legacy source extension would hand the user a .ppt whose bytes are a ZIP, which PowerPoint refuses to open.

Released under the Apache-2.0 License.