Embed SDK
The embed SDK places a published Deckyard presentation in an iframe on another website and lets that page drive it: go to a slide, follow along as the viewer navigates, read the current position. It is one dependency-free script served by your Deckyard instance.
This page describes core Deckyard 1.34.0. The source of truth is client/embed-sdk.js (the SDK), server/routes/static/embed.js (the embed route) and server/utils/embed-html/ (the player inside the iframe) in the Deckyard repository. For a task-oriented introduction, and for embedding a deck without a running instance, see Embedding.
Quick start
Section titled “Quick start”<div id="deck"></div><script src="https://your-instance.com/client/embed-sdk.js"></script><script> const embed = PresentationSystemEmbed.createDeckEmbed({ el: document.getElementById('deck'), publishId: '3f9a1c2e', options: { baseUrl: 'https://your-instance.com', }, });
embed.on('slidechange', ({ slideIndex, totalSlides }) => { console.log(`Slide ${slideIndex + 1} of ${totalSlides}`); });</script>The deck must be published. The Publish dialog in the editor generates this snippet with your instance’s URL, the deck’s publish id and its language filled in.
The publish id
Section titled “The publish id”A published deck’s public URL is /p/<publishId>-<slug>. The publish id is the 8-character hexadecimal part before the first hyphen: in /p/3f9a1c2e-quarterly-review it is 3f9a1c2e. Republishing the deck keeps it.
The embed is public: whoever has the publish id can load the deck, and no login is involved.
createDeckEmbed
Section titled “createDeckEmbed”PresentationSystemEmbed.createDeckEmbed({ el, publishId, options }) // → controllerThe script defines one global, window.PresentationSystemEmbed, with one function.
| Argument | Type | Required | Description |
|---|---|---|---|
el | HTMLElement | yes | The element the embed is appended to. Throws when it is not an element. |
publishId | string | yes | The deck’s publish id. Throws when empty. |
options | object | no | See Options. |
The function appends a wrapper <div class="ps-embed-wrap"> holding the iframe to el and returns a controller.
Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
baseUrl | string | The host page’s origin | Origin of the Deckyard instance. Set it whenever the page is not served by Deckyard itself. |
slug | string | none | Appended to the embed URL as <publishId>-<slug>. Optional: a missing slug works, a wrong one redirects to the right one. |
controls | boolean | true | Show the control bar above the slide: previous, next, slide counter, language buttons and fullscreen. |
ui | "default" or "min" | "default" | "min" hides the control bar entirely, whatever controls says. |
start | number | 0 | 0-based index of the first slide shown. Values past the end show the last slide. |
loop | boolean | false | Wrap from the last slide to the first and back. |
allowFullscreen | boolean | true | Allow fullscreen on the iframe and show the fullscreen button. |
lang | string | The deck’s own language | Which language version to show, as a deck language code (nl, en-GB, de, …; en means en-GB). An unknown code falls back to the deck’s language. |
langSwitch | boolean | false | Show a button per language version in the control bar. Only appears when the deck has more than one version. |
allowedOrigins | string[] | [location.origin] | Origins whose commands the embedded player accepts. See Security. |
aspectRatio | number | 16 / 9 | Aspect ratio of the wrapper. Invalid or non-positive values fall back to 16/9. |
title | string | "Embedded presentation" | The iframe’s title, read out by screen readers. |
onReady | function | none | Called with the ready payload. |
onSlideChange | function | none | Called with the slidechange payload. |
onError | function | none | Called with the error payload. |
controls, loop, allowFullscreen, ui, lang, langSwitch, start and allowedOrigins travel to the player as query parameters on the iframe URL; aspectRatio, title and the callbacks stay in the host page.
Controller
Section titled “Controller”| Member | Description |
|---|---|
next() | Go to the next slide. At the last slide this does nothing, unless loop is on. |
prev() | Go to the previous slide. At the first slide this does nothing, unless loop is on. |
goToSlide(index) | Go to a 0-based slide index, clamped to the deck. |
getState() | Returns { slideIndex, slideId, totalSlides, publishId }. |
on(event, handler) | Subscribe to an event. Returns a function that unsubscribes. |
off(event, handler) | Unsubscribe a handler. |
destroy() | Remove the wrapper and iframe from the page and stop listening for messages. |
Navigation methods send a message and return immediately; they do not wait for the slide to change. Listen for slidechange to know it happened.
getState() returns the last state the player reported, not a live query: it is correct once ready and the first state event have arrived, and slideIndex is 0 before that.
The controller also carries _iframe, _wrapper and _src (the iframe element, the wrapper element and the iframe URL). The underscore marks them as internals.
Events
Section titled “Events”| Event | When | Payload |
|---|---|---|
ready | The player has loaded | { publishId, totalSlides } |
state | Right after ready, with the starting position; and in answer to GET_STATE | { publishId, slideIndex, slideId, totalSlides } |
slidechange | The slide changed, through the controller, the control bar or the keyboard | { publishId, slideIndex, slideId, totalSlides } |
error | The player failed while handling a command from the host page | { message } |
slideId is the slide’s stable id in the deck, so it survives reordering, unlike slideIndex. The first slide shown does not trigger slidechange; read the starting position from state.
error does not report a deck that fails to load. An unknown or unpublished publish id makes the iframe show a not-found page, and a render failure shows an error page inside the iframe; neither sends a message.
Every event is delivered three ways:
- to handlers registered with
on()(event names are case-insensitive), - to the matching
onReady/onSlideChange/onErrorcallback (there is none forstate), - as a
CustomEventdispatched on the wrapper element, with the payload inevent.detail. It does not bubble, so listen onembed._wrapperitself.
Errors thrown inside your handlers are swallowed and do not stop other handlers.
The embedded player
Section titled “The embedded player”The iframe loads /embed/<publishId> or /embed/<publishId>-<slug> from the instance. You can also put that URL in a plain <iframe> without the SDK and pass the same options as query parameters: booleans as 1/0 or true/false, allowedOrigins as a comma-separated list. slideIndex is accepted as an alias of start.
Inside the iframe:
- One slide is visible at a time, rendered on a 1600×900 stage that scales to the iframe.
- The keyboard works once the iframe has focus: arrow keys, Space, Page Up and Page Down navigate, Home and End jump to the first and last slide, F toggles fullscreen.
- A video on a slide loads when the slide becomes active (autoplaying if the slide is set to) and pauses when the viewer moves on.
- Slides that only exist in a live session, and slides hidden from the published version, are left out, as on the published page.
- The page is marked
noindex,nofollowand may be framed by any site.
postMessage protocol
Section titled “postMessage protocol”The SDK is a wrapper around a small postMessage protocol. You need it only to control a plain <iframe>, or to send the two commands the controller does not wrap: GET_STATE and SET_OPTIONS.
Every message in both directions has this shape:
{ source: 'presentation-system-embed', type: 'NEXT', payload: {} }Messages with a different source are ignored.
Host page to player
Section titled “Host page to player”type | payload | Effect |
|---|---|---|
NEXT | none | Next slide |
PREV | none | Previous slide |
GOTO | { slideIndex } | Go to a 0-based slide index |
GET_STATE | none | Player answers with STATE |
SET_OPTIONS | Any of controls, loop, allowFullscreen, ui, langSwitch, allowedOrigins, start | Changes those options without reloading; start also navigates |
SET_OPTIONS leaves an option unchanged when its value is not a boolean (controls, loop, allowFullscreen, langSwitch), not min or default (ui), or not an array (allowedOrigins). Two values are converted rather than checked: every allowedOrigins entry is turned into a string, and empty entries are dropped; any start that is not null goes through Number(), so "2" goes to index 2 and a value that is not a number, such as "bad", goes to index 0. SET_OPTIONS cannot change the language: that needs a new iframe URL.
embed._iframe.contentWindow.postMessage( { source: 'presentation-system-embed', type: 'SET_OPTIONS', payload: { ui: 'min', loop: true }, }, 'https://your-instance.com',);Player to host page
Section titled “Player to host page”READY, STATE, SLIDE_CHANGE and ERROR, with the payloads listed under Events. The SDK only accepts them from its own iframe and from the instance’s origin.
Security
Section titled “Security”allowedOriginsguards the player, not your page. The player only obeys commands from a parent page whose origin is in the list. An empty list, or*, accepts commands from any page that frames the deck. The SDK defaults to the host page’s own origin, so list your other origins (staging, a second domain) explicitly.- Events go to any parent. The player posts its events with target origin
*, so any page that frames a deck can follow its position. Events carry the publish id and slide positions, nothing else from the deck. - The publish id is the authorization. Anyone with it can view the published deck, embedded or not. To stop an embed, unpublish the deck.
- The iframe gets
referrerpolicy="strict-origin-when-cross-origin"andloading="lazy", but nosandboxattribute. It is kept out of the host page’s DOM only when the instance and the host page are on different origins, which is the normal case; an embed served from the same origin as the page can reach its parent.
Styling
Section titled “Styling”The wrapper <div class="ps-embed-wrap"> fills the width of el, keeps its height at the aspect ratio (with a padding fallback for browsers without aspect-ratio), and has a black background, 12px rounded corners and hidden overflow. These are inline styles, so override them on embed._wrapper.style, or size the embed by giving el a width.
Related
Section titled “Related”- Embedding - embedding a deck, including the standalone HTML export for sites without an instance
- Public API: Publishing - publish and unpublish from code