Delete Hooks
Authorize deletion and run cleanup after S3 removes the object.
Enable object deletion with delete: true or a DeleteConfig object on a named route.
Unlike upload and download, deletes run on your server with DeleteObject
after the guards pass. The client never deletes from S3 directly.
import { dimahS3, route, s3Error } from "@dimah-s3/server";
import { awsS3 } from "./aws-s3";
export const s3 = dimahS3({
client: awsS3,
bucket: process.env.S3_BUCKET!,
routes: {
avatar: route({
delete: {
guard: async ({ key, request }) => {
const session = await getSession(request);
if (!session) throw s3Error("UNAUTHORIZED");
const isOwner = await checkAvatarOwnership(session.userId, key);
if (!isOwner) throw s3Error("FORBIDDEN");
},
onDeleted: async ({ key, request }) => {
const session = await getSession(request);
await db.user.update({
where: { id: session.userId },
data: { avatarKey: null },
});
},
},
}),
},
});Delete lifecycle flow
Before delete.guard, the server resolves the named route and rejects keys
outside its keyPrefix (KEY_INVALID).
- route.guardhook
- delete.guardhook
- DeleteObjectS3
- onDeletedDatabase cleanup
Delete is idempotent. A key that never landed, or one that is already gone,
succeeds and still runs onDeleted, so the bookkeeping for a pending or
expired upload is cleaned up. The response is { key, bucket }.
The S3 call does not stop when the client disconnects, so onDeleted runs
for every delete that happened.
Deleting many keys
deleteMany (POST /delete/batch) deletes up to 100 keys of one route
(DELETE_MANY_MAX_KEYS) in one DeleteObjects request:
- The route guard runs once for the call.
- Each key is checked against the route namespace and
delete.guard. onDeletedruns for each deleted key.- A per-key failure (an invalid key, a guard, an S3 error, or an
onDeletedthrow) goes intofailedand does not fail the other keys.
const { deleted, failed } = await s3.api.deleteMany(
{ route: "avatar", keys: [key1, key2] },
{ headers: await headers() },
);
// failed: [{ key, error: { code, message, params? } }]A route error, such as UNKNOWN_ROUTE or a route guard rejection, fails the
whole call. So does a DeleteObjects request that fails as a whole.
Type reference
import type {
DeleteGuardContext,
DeleteOnDeletedContext,
} from "@dimah-s3/server";DeleteGuardContext
Prop
Type
DeleteOnDeletedContext
Prop
Type
Frequently asked questions
Use s3.api.deleteMany on the server, or removeMany from useDelete in
React:
const { removeMany } = useDelete({ route: "avatar" });
const { deleted, failed } = await removeMany([key1, key2]);onDeleted does not run for a failed S3 deletion. A single delete returns an
error such as 502 S3_NETWORK_ERROR or 500 INTERNAL_ERROR. In
deleteMany, a key that S3 refused is reported in failed with
INTERNAL_ERROR; the S3 error itself is logged and passed to onError.