Docs › The <video-piper-thumbnail> web component
The <video-piper-thumbnail> web component
Let people on your site choose a video's thumbnail with the same picker the Video Piper app has: one of its frames, any moment of it (they scrub the video and press Use this frame), or their own image, then crop it to the video's shape, 16:9, square, 4:5 or freely, with a live preview. Save makes it the video's poster everywhere (posterUrl in status, listings and webhooks), and they can save again if they change their mind. Your API key stays on your server: the page gets a thumbnail token for one video.
// 1. Your server: an endpoint that makes a token (POST /thumbnail-tokens with your API key)
app.post("/api/thumbnail-token", async (req, res) => {
const videoId = req.query.video;
if (!(await mayChangeThumbnail(req.user.id, videoId))) return res.status(403).end(); // your own rule
const r = await fetch(`${API}/thumbnail-tokens`, { method: "POST",
headers: { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" },
body: JSON.stringify({ videoId, expiresIn: 900 }) });
res.status(r.status).json(await r.json()); // { token: "vpt_…", expiresAt, videoId }
});
// Program.cs (ASP.NET Core 8+), set up as in the recorder's example above.
// A one-video thumbnail token for <video-piper-thumbnail token-url="/api/thumbnail-token?video=VIDEO_ID">.
app.MapPost("/api/thumbnail-token", async (string video, ClaimsPrincipal user, IHttpClientFactory http) =>
{
// Check that this user may change this video's thumbnail (your own rule), then:
using var response = await http.CreateClient("videopiper").PostAsJsonAsync("thumbnail-tokens", new { videoId = video, expiresIn = 900 });
return Results.Content(await response.Content.ReadAsStringAsync(), "application/json", statusCode: (int)response.StatusCode);
}); // add .RequireAuthorization() so only your signed-in users get tokens
<!-- 2. Your page: the picker asks token-url for a token for this video when it loads -->
<script src="https://videopiper.com/thumbnail/thumbnail.js"></script>
<video-piper-thumbnail token-url="/api/thumbnail-token?video=VIDEO_ID"></video-piper-thumbnail>
<script>
document.querySelector("video-piper-thumbnail")
.addEventListener("vp-thumbnail-saved", (e) => console.log("new poster", e.detail.posterUrl));
</script>
| Attribute | Description |
|---|---|
token-url | Recommended. Your endpoint; the picker POSTs to it (with your site's cookies) when it loads, and expects {"token": "vpt_…", "videoId": "…"}, which is what POST /thumbnail-tokens returns. If the token expires while someone is choosing, it asks again once when they press Save. |
token + video-id | Or a token you made when the page loaded, and its video. Use token-url or these. |
title | A line under the heading, e.g. "Pick the cover people see first". |
- Events (they bubble):
vp-thumbnail-ready({videoId}: the choices have loaded),vp-thumbnail-saved({videoId, posterUrl, current}: a thumbnail was saved) andvp-thumbnail-error({code, message}, with a message fit to show). - Cropping happens in the browser. The picker sends the result as an image, so saving is instant, and big photos are resized to fit the 2 MB limit for you. A frame saved whole is sent as that frame.
- Styling: it fills its container's width and follows the visitor's light or dark setting. Theme it with
--vp-bg,--vp-panel,--vp-text,--vp-muted,--vp-line,--vp-accent,--vp-accent-softand--vp-bad. Its styles live in its shadow root and never touch your page. - Size:
thumbnail.jsincludes the picker (about 75 KB compressed), so load it only on the pages that use it. - Try every option live. In the app, Account → Playground (sign in) lets you set up the picker, run your page in an isolated frame with a stand-in for your server, see its events and errors, and copy the page and server code.
- Building your own picker instead? The API takes any moment (
time) and acropdirectly: see Thumbnails.
Thumbnail tokens
POST /thumbnail-tokens (with your API key, from your server) makes a token for choosing one video's thumbnail:
{ "videoId": "3f2a…", // one of your finished videos
"expiresIn": 900 } // 60 to 86400 seconds; default 900
→ 201 { "token": "vpt_…", "expiresAt": 1790910405, "videoId": "3f2a…" }
- What the browser can do with it, and nothing more:
GET /videos/{id}/thumbnails(the frames, the current choice, the length and a playback link) andPOST /videos/{id}/thumbnail, for that one video, as often as it likes until the token expires. Anything else (other videos, listing, deleting, uploads, your account, making tokens) answers403 THUMBNAIL_TOKEN_SCOPE, and another video's thumbnail404. - The video must be yours and finished when you make the token (
404 NOT_FOUND,409 NOT_READY). - It's shown once (only a hash is kept), expires on its own, and counts toward your key's rate limits. Your Account page has these snippets filled in with your addresses, and a place to try the picker.