What you are building
A page that shows every ready video as a poster card, such as a lesson library, a help video index or a gallery of clips. The list comes from your server, using your private key. The posters and the playback need no key.
Listing videos needs your private key, so do it on the server only. Never put the private key in browser code.
List the videos on the server
listVideos returns one page at a time, newest first by default. Ask for videos that are ready, so a card never links to something still encoding. Each video carries its name, duration and ready-made URLs, including the poster.
import express from "express"
import { Fileboy } from "@cd2/fileboy-node"
const fileboy = Fileboy({
publicKey: process.env.FILEBOY_PUBLIC_KEY!,
privateKey: process.env.FILEBOY_PRIVATE_KEY!,
})
const app = express()
app.get("/api/videos", async (req, res) => {
const page = await fileboy.listVideos({
status: "ready",
limit: 24,
cursor: typeof req.query.cursor === "string" ? req.query.cursor : undefined,
})
res.json({
videos: page.data.map(video => ({
id: video.id,
name: video.name,
duration: video.duration,
poster: video.urls?.poster,
})),
nextCursor: page.nextCursor,
})
})const { data: videos } = await fileboy.listVideos({ status: "ready", limit: 20 })
await fileboy.updateVideo(videos[0].id, { name: "Launch day" })
await fileboy.destroyVideo(videos[0].id) // original, renditions, posters and transcriptspage = fileboy.list_videos(status: "ready", limit: 20)
fileboy.update_video(page.items.first.id, name: "Launch day")
fileboy.delete_video(page.items.first.id) # original, renditions, posters and transcriptspage = client.list_videos(status="ready", limit=20)
client.update_video(page.items[0].id, name="Launch day")
client.delete_video(page.items[0].id) # original, renditions, posters and transcripts$page = $fileboy->listVideos(['status' => 'ready', 'limit' => 20]);
$fileboy->updateVideo($page->data[0]->id, 'Launch day');
$fileboy->deleteVideo($page->data[0]->id); // original, renditions, posters and transcriptscurl -G https://api.fileboy.io/videos -H "Authorization: token YOUR_SIGNED_TOKEN" -d status=ready
curl -X DELETE https://api.fileboy.io/videos/VIDEO_ID -H "Authorization: token YOUR_SIGNED_TOKEN"Show the posters as a grid
The poster is an ordinary image URL, so it works in an img tag. Each card links to a page for that video.
import { useEffect, useState } from "react"
type Card = { id: string; name: string; duration: number | null; poster?: string }
export function VideoGrid() {
const [videos, setVideos] = useState<Card[]>([])
useEffect(() => {
fetch("/api/videos")
.then(res => res.json())
.then(body => setVideos(body.videos))
}, [])
return (
<div style={{ display: "grid", gap: 16, gridTemplateColumns: "repeat(auto-fill, minmax(240px, 1fr))" }}>
{videos.map(video => (
<a key={video.id} href={`/videos/${video.id}`}>
<img src={video.poster} alt="" style={{ width: "100%", aspectRatio: "16 / 9", objectFit: "cover" }} />
<div>{video.name}</div>
{video.duration != null && <small>{Math.round(video.duration)} seconds</small>}
</a>
))}
</div>
)
}Play a video on its own page
import "@cd2/fileboy-player"
import { useParams } from "react-router-dom"
export function VideoPage() {
const { id } = useParams<{ id: string }>()
return <fileboy-player video-id={id} preload="" />
}If your pages are not React, the same element works from a script tag, or use an iframe.
Keep the list fresh
- Page through longer libraries with
nextCursor. Paging stays stable while videos are added or deleted. - A
video.readyorvideo.deletedwebhook tells your server when to refresh a cached list, so you never poll. - Rename a video, or give it a description, tags and your own metadata, with
updateVideo, and delete it withdestroyVideo. Deleting removes the original, every rendition, the posters and the captions. - Find videos by tag or by your own ids with
listVideos({ tags: [...], metadata: {...} }), so you can show one course's or one customer's videos without keeping a list of ids.
Checked against the current libraries in November 2026. Found something that no longer works? Tell us.