Slide decks

A slide deck is an ordinary HTML page marked as a presentation. Once a page is a deck, Sharedrop adds a Present control that goes fullscreen with keyboard and tap navigation, so you can build a deck with an agent, drop it in, and present it on a TV or projector with no PowerPoint and no export step.

Nothing else about the page changes. A deck is sanitised, sandboxed, watermarked, shared, and made public or private exactly like any other HTML page. Slides is a presentation affordance layered on top, not a different security model.

Slide decks are available on every plan, including Free. Only present-only disappearing links require Pro.

What each surface can do

DashboardCLIREST APIMCP
Upload a deck via the in-file markerYesYesYesYes
Mark a deck explicitly at uploadn/aNo flag existsslides: true at finalizeslides: true at finalize
Turn an existing page into a deckYes, the Slides toggleNoNoNo
Open Present modePresent buttonOpen the URL with ?present=1?present=1?present=1
Present-only disappearing link (Pro)CheckboxNopresent_only: truepresent_only: true

The one path that exists only in the dashboard is converting a page you have already uploaded. If you are an agent, decide at upload time: either put the marker in the HTML or pass slides: true to finalize.

What makes a good deck

Structure your HTML as top-level <section> elements, one per slide:

<!doctype html>
<html>
  <head>
    <meta name="sharedrop:kind" content="slides" />
  </head>
  <body>
    <section><h1>Title slide</h1></section>
    <section><h2>Second slide</h2><p>...</p></section>
    <section><h2>Third slide</h2></section>
  </body>
</html>

In Present mode Sharedrop shows one <section> at a time and moves through them on key or tap. If a deck has no top-level <section> elements it still presents: it simply goes fullscreen as a single free-scrolling page.

Two details worth knowing:

  • Slide navigation and the counter appear only when there are two or more slides. A single-section deck goes fullscreen with no counter and nothing to step through.
  • If Sharedrop finds no top-level <section> elements directly inside <body>, it falls back to every <section> in the document, so a deck wrapped in a container still works.

Present mode supplies navigation only. It never restyles your deck: no background, no text colour, and no layout override on the visible slide. Your CSS is what the audience sees, so a deck that looks right in a browser looks right on the projector.

Marking a page as a deck

There are three ways to designate a deck. Any one of them is enough.

1. In-file marker (works on every upload path)

Declare it inside the HTML, so a plain upload becomes a deck automatically. Either form works:

<meta name="sharedrop:kind" content="slides" />
<html data-sharedrop-kind="slides">

Because the marker lives in the file, this path needs no special flag: sharedrop upload deck.html, a drag-and-drop, an API upload, or an MCP upload all produce a deck when the marker is present.

The attribute name is matched exactly (sharedrop:kind, data-sharedrop-kind), but the value is case-insensitive, so slides, Slides, and SLIDES all work. Any other value, or no marker at all, leaves the page an ordinary HTML page.

Marker detection runs on HTML uploads only. An MHTML web archive is not promoted by a marker, though you can still mark it from the dashboard afterwards.

2. Explicit flag at upload (API and MCP)

Set slides: true when you finalize an HTML upload (see REST API and MCP below). This is the path for an agent that wants to tag a deck without editing the document.

The flag belongs on finalize, not on sign. The sign step accepts a slides field for convenience but does nothing with it, so signing alone never produces a deck. It is also HTML-only: sending slides: true with a PDF or an image is silently ignored and the page keeps its real kind.

3. Dashboard toggle

Open an HTML or MHTML page in the dashboard, click Slides, and choose Mark as slides. The same control reads Unmark slides once the page is a deck. The option does not appear on images, PDFs, videos, or archives.

One asymmetry to be aware of: unmarking always returns a page to the html kind. If you marked an MHTML page as a deck and then unmark it, it becomes html, not mhtml.

Marking is reversible and lossless: it changes only the page's kind, never its bytes. Re-uploading new HTML over a deck keeps it a deck, even if the replacement file has no marker, so you can iterate on a deck without re-tagging it every time.

There is no API or CLI equivalent of this toggle. Converting a page after upload is a dashboard action.

Presenting

Open a deck and click Present (or add ?present=1 to its URL). Present opens the deck fullscreen on the isolated view origin with minimal chrome:

ActionKeys / gesture
Next slideRight arrow, Space, Page Down, click, or tap
Previous slideLeft arrow, Page Up
First / last slideHome / End
Toggle fullscreenF
ExitEsc

Clicking or tapping always moves forward. There is no click-to-go-back, so use Left arrow or Page Up to step back, which keeps a tap on a touchscreen or a presenter remote unambiguous.

The pointer auto-hides after about two and a half seconds of stillness and returns as soon as you move it, which keeps a stray cursor off the projector. A small slide counter (3 / 12) sits in the bottom right whenever the deck has more than one slide.

Browsers only allow fullscreen after a user gesture, so the deck opens filling the window and the first click or key both enters true fullscreen and advances. Esc leaves fullscreen; pressing it again returns to the previous page if there is one, so on a URL opened directly (a TV pointed at a link) it does nothing.

How Present mode is isolated

For a static deck, Present renders on the separate view origin under a policy written for this mode: no plugins, no framing, no fetch or XHR from the document, and the navigation controller admitted by a one-time nonce rather than by allowing inline scripts generally. Styles, fonts, and images still follow the page's own network setting. A static deck therefore runs exactly one script, the one Sharedrop injected, and nothing from the document itself.

An interactive deck is different: it keeps its own scripts and its own network rules, and Present gives it a full-window canvas rather than an injected controller. See the next section.

Present mode does not apply the usual iframe sandbox, because a sandboxed document cannot enter fullscreen. That is why the tighter per-response policy above replaces it. See sandbox and safety for the model that applies to a deck everywhere else.

Static vs interactive decks

A deck honours the page's security mode exactly like any other HTML page:

  • A static deck has its scripts stripped at upload. Sharedrop provides the navigation controller, so the deck is driven by the built-in Present shell.
  • An interactive deck (for example a reveal.js or impress.js bundle) keeps its own scripts and network rules and self-drives: Present gives it a full-window canvas and lets its own framework handle navigation.

Kiosk auto-advance

For an unattended screen, add autoplay=<seconds> to advance automatically and loop:

https://sharedrop.cloud/alex/abc123?present=1&autoplay=15

The deck advances every 15 seconds and loops back to the first slide at the end, so a screen pointed at that URL runs unattended indefinitely. autoplay must be a plain whole number of seconds: anything else (15s, 1.5, a negative) is ignored and the deck simply waits for input. It also needs more than one slide, since there is nothing to advance to otherwise.

If you have a Pro plan, you can hand out a present-only disappearing link: a public URL that opens straight into fullscreen Present mode and expires after a time window, a view count, or both. It is a variant of a normal disappearing link, available for slide decks only.

  • Dashboard: open the deck, expand Disappearing link, tick Open in fullscreen Present mode, and create the link.
  • API / MCP: pass present_only: true when you create the ephemeral link (see below).

While the link is live the page is temporarily public; when it expires the page reverts to its previous visibility, exactly like any disappearing link. Asking for a present-only link on a page that is not a deck is refused.

What present-only does. The link always lands in Present mode. The URL carries &present=1, and the deck opens in Present even if that parameter is stripped along the way, so a link pasted through a chat client or a URL shortener that mangles the query still arrives at a presentation rather than an ordinary page view.

What it does not do. It is not an access restriction. While any disappearing link is live the page itself is public, so someone holding the link can drop the whole ?e=... query and reach the page at its plain URL until the link expires. That is how disappearing links work in general, not something specific to Present mode. The expiry window and the view count are the parts that actually restrict access; present-only decides how the deck is shown, not who may see it.

Dashboard

A deck shows a Slides badge and glyph in your page list, and Slides appears as its own option in the kind filter, so you can pull up every deck you own in one click.

Its detail view gains a Present button (available to anyone who can view the page, not only the owner). For the owner there is also the Slides control, which holds three things:

  • Mark as slides / Unmark slides, the post-upload toggle.
  • A copyable present link (the page URL with ?present=1). It works for anyone who can already see the page, so it is the right thing to paste into a calendar invite, or into a TV browser when the deck is public. A private deck opened on a signed-out screen bounces to sign-in, so for that case use a present-only disappearing link instead.
  • The present-only disappearing link option described above, on Pro.

CLI

Requires @sharedrop/cli. See the CLI guide for installation and authentication.

The CLI has no dedicated slides flag, and it does not need one: put the in-file marker in your HTML and a normal upload becomes a deck.

# deck.html contains <meta name="sharedrop:kind" content="slides" />
sharedrop upload deck.html --title "Q3 review"

# confirm it landed as a deck
sharedrop get <slug> --json | jq -r .data.kind    # -> slides

Present it by opening the returned URL with ?present=1, or from the dashboard:

https://sharedrop.cloud/alex/abc123?present=1

Re-uploading over a deck keeps it a deck, so sharedrop update <slug> deck.html needs no extra step. Present-only disappearing links have no CLI command; create those from the dashboard, the API, or MCP.

REST API

A deck is created through the standard streamed upload. To mark it explicitly, add slides: true to the finalize step; otherwise include the in-file marker in the HTML.

curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"object_key": "...", "upload_token": "...", "slides": true}' \
  https://sharedrop.cloud/api/upload/finalize

slides applies to HTML only and is ignored for other kinds. The finished page reports "kind": "slides".

Put the flag on finalize. The sign step accepts slides but ignores it, so signing with the flag and finalizing without it produces an ordinary HTML page.

There is no endpoint that changes an existing page's kind to slides. If you need a page you have already uploaded to become a deck, either re-upload it with the marker or the flag, or use the dashboard toggle.

Create an ephemeral link with present_only: true on a deck:

curl -X POST \
  -H "Authorization: Bearer sd_..." \
  -H "Content-Type: application/json" \
  -d '{"expires_in_seconds": 86400, "present_only": true}' \
  https://sharedrop.cloud/api/v1/pages/<page_id>/ephemeral-links

The response echoes present_only and the returned url already carries &present=1. Listing the page's links returns both faithfully, so a link re-copied from GET /api/v1/pages/<page_id>/ephemeral-links behaves identically to the one you created. Note the field is snake_case here (present_only); a misspelled field is ignored rather than rejected, so check the echoed value rather than assuming it took.

Errors to expect:

  • 400 VALIDATION_ERROR, message Present-only links are available for slide decks only., when the page is not a deck.
  • 402 TIER_LIMIT on Free, since disappearing links are a Pro feature.
  • 409 if the page is shared (shared with named people), because a disappearing link makes a page temporarily public.

MCP

An agent marks a deck by passing slides: true to finalize_upload after the usual create_upload → PUT bytes flow:

// finalize_upload
{ "object_key": "...", "upload_token": "...", "slides": true }

The in-file marker works too, so a deck the agent generated with the <meta> tag becomes a deck without any flag. Either way, finalize_upload returns "kind": "slides", which is the thing to check before telling the user they have a deck.

Re-uploading with the same page_id keeps the page a deck, so an agent iterating on a presentation does not need to re-send the flag each round.

To hand the user a fullscreen link for a TV, create a present-only disappearing link with create_ephemeral_link:

// create_ephemeral_link
{ "page_id": "...", "expires_in_seconds": 86400, "present_only": true }

present_only is slides-only and returns an error on any other page, and the link itself is Pro-only. The response echoes present_only so you can confirm the flag took, which matters because an unrecognised field is ignored rather than rejected. list_ephemeral_links reports it faithfully too.

A complete agent flow for "build me a deck and put it on the TV":

1. create_upload({ filename: "q3.html", content_type: "text/html", size_bytes })
2. PUT the raw bytes to upload_url
3. finalize_upload({ object_key, upload_token, slides: true })   -> kind: "slides"
4. create_ephemeral_link({ page_id, expires_in_seconds: 14400, present_only: true })
5. give the user that url; it lands straight in Present mode on any screen it is pasted into