Delete Hooks
Authorize deletion and execute cleanup after S3 removes the object.
Enable object deletion with delete: true or a DeleteConfig object on a named route.
Unlike upload and download, object deletion is executed directly on your
server using DeleteObjectCommand after passing guard checks. The client
never deletes from S3 directly.
import { S3Client } from "@aws-sdk/client-s3";
import { dimahS3, errors, route } from "@dimah-s3/server";
export const awsS3 = new S3Client({/* env */});
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 errors.unauthorized();
const isOwner = await checkAvatarOwnership(session.userId, key);
if (!isOwner) throw errors.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 (INVALID_KEY).
- delete.guardhook
- HeadObject in S3S3
- DeleteObject in S3Only if it exists
- onDeletedDatabase cleanup
Delete is idempotent. When HeadObject finds nothing — an upload that was
presigned but never sent, an aborted multipart upload, or an object a bucket
lifecycle rule already removed — the server skips DeleteObject, still runs
onDeleted with existed: false, and responds with
{ success: true, existed: false }. A database row for that key is cleaned up
the same way as for a stored object.
Type reference
import type {
DeleteGuardContext,
DeleteOnDeletedContext,
} from "@dimah-s3/server";DeleteGuardContext
Prop
Type
DeleteOnDeletedContext
Prop
Type
Frequently asked questions
Use deleteMany on the server API or removeMany in the React client. Batch
deletion runs the same flow for each key — delete.guard, S3 DeleteObject
when the object exists, then onDeleted — and each result reports existed:
const { removeMany } = useDelete({ route: "avatar" });
await removeMany([key1, key2]);onDeleted does not run for a failed S3 deletion. The endpoint returns an
error response such as 502 S3_NETWORK_ERROR or INTERNAL_ERROR.