homeaboutvideotry it nowpricingdocs
Guide

Manage and clean up files from your server

Find files by metadata, rename them, and delete the files that belong to a record you removed, so storage does not fill up with orphans. Examples for Node.js, Ruby, Python, PHP and cURL.

Why files get left behind

You store a file's ID in your database. When the record is deleted, the file is still there, still counted against your plan and still reachable by anyone who has the link. Cleaning up needs two things: a way to find the files that belonged to the record, and a way to delete them. The management API does both from your server, with your private key.

These calls need your private key, so make them from your server, never from a browser. They only ever see your own app's files.

Set up the client with a private key

import { Fileboy } from "@cd2/fileboy-node"

const fileboy = Fileboy({
  publicKey: process.env.FILEBOY_PUBLIC_KEY!,
  privateKey: process.env.FILEBOY_PRIVATE_KEY!,
})

Without a private key the client raises an error before it sends anything. A public key, or a token limited to uploads, is refused by the API with private_key_required.

Tag files when you upload them

The easiest way to find a record's files later is to say whose they are when you upload. Put your own IDs in the file's metadata.

const file = await fileboy.uploadFileFromPath("./exports/report.pdf", {
  metadata: { customerId: "cus_123", kind: "monthly-report" },
})

console.log(file.id) // save this

Find files

Filter by metadata, type prefix, label, name, hash and creation date. Results come one page at a time with a cursor, newest first unless you sort differently. The cursor stays correct if you delete files while you page through.

// one page, newest first. Needs the private key.
const page = await fileboy.listFiles({
  mime: "image/",
  metadata: { orderId: "42" },
  limit: 25,
})
page.data.forEach(file => console.log(file.id, file.name, file.labels))

// the next page
if (page.nextCursor) {
  await fileboy.listFiles({ mime: "image/", metadata: { orderId: "42" }, cursor: page.nextCursor })
}

Delete the files of a record

Delete one file by ID, or up to 100 at once. Deleting removes the stored data, including any resized copies Fileboy cached for an image, and sends a file.deleted webhook. Run this when the record is destroyed, for example in a model callback or a background job.

import { notFoundError } from "@cd2/fileboy-node"

try {
  await fileboy.destroyFile(file.id)
} catch (e) {
  if (!notFoundError.is(e)) throw e // already gone is fine
}
// up to 100 ids. Unknown ids are reported, not thrown.
const { deleted, notFound, failed } = await fileboy.destroyFiles(ids)
Everything for one order
// delete every file for an order that was cancelled, 100 at a time
let cursor: string | undefined
do {
  const page = await fileboy.listFiles({ metadata: { orderId }, limit: 100, cursor })
  if (page.data.length) await fileboy.destroyFiles(page.data.map(f => f.id))
  cursor = page.nextCursor ?? undefined
} while (cursor)

Deleting is safe to repeat. A single delete of a file that is already gone is a not found error you can ignore. Bulk delete lists unknown IDs under notFound instead of failing. If storage is briefly unreachable the file is left exactly as it was and shows up under failed, so you send it again.

Rename a file or change its metadata

Updating changes only what you send. Metadata is merged into what is there, a null value removes a key, and you can ask for it to be replaced instead. Labels are names that point at a single file, so adding a label that is on another file moves it.

// metadata is merged: a null value removes a key
const updated = await fileboy.updateFileData(file.id, {
  name: "invoice-1042-final.pdf",
  metadata: { status: "paid", draft: null },
  labels: { add: ["latest-invoice"] },
})

Videos, labels and usage

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 transcripts
const labels = await fileboy.listLabels()
await fileboy.createLabel("hero", { fileId: file.id })
await fileboy.destroyLabel("hero") // the file is kept
const usage = await fileboy.getUsage()
console.log(usage.files, usage.storageBytes, usage.limits.storageBytes)

Checked against the current libraries in November 2026. Found something that no longer works? Tell us.

Go deeper

Still choosing a tool?

Fileboy is small on purpose. This comparison says where it fits and where another option is the better call.

Fileboy vs S3 + Lambda · All comparisons

Try it

The free plan needs no card and includes 1,000 files, so you can run every example above.

More guides: How to add image upload to a React app · Resize images on the fly with a URL · Stream video with HLS without running ffmpeg · Add captions and subtitles to a video · All guides

Add Fileboy to your next project

We built Fileboy to stop rebuilding file handling for every app. Create an app, paste your key and upload a file. The free plan needs no card.