Triggers & Edge Functions
Storage objects fire the standard trigger lifecycle - every CRUD event has a BEFORE and AFTER hook on both the storage.Object label and the storage.objects collection. This lets you post-process uploads (thumbnail, virus-scan, OCR), enforce custom invariants, or kick off downstream workflows.
Available events
| When | Insert | Update | Delete |
|---|
| BEFORE | Validate / mutate before write | Same for replace | Veto deletes |
| AFTER | Side effects (thumbnail, queue job) | React to overwrites | Cleanup external state |
Example: auto-thumbnail
-- Drop a 200x200 thumbnail into a sibling bucket on every avatar upload.
CREATE TRIGGER avatar_thumbnail
AFTER INSERT ON LABEL `storage.Object`
WHEN row.bucket_id = "avatars" AND row.mime_type STARTS WITH "image/"
EXECUTE EDGE FUNCTION generate_thumbnail
WITH ARGS {
source_bucket: "avatars",
source_path: row.path,
dest_bucket: "thumbnails",
width: 200,
height: 200
};Edge function recipe (TypeScript)
// edge_functions/generate_thumbnail.ts
import { AnvilClient } from "anvilent";
export async function handler(args: {
source_bucket: string;
source_path: string;
dest_bucket: string;
width: number;
height: number;
}) {
// service_role JWT is injected by the runtime - bypasses RLS.
const client = anvil.client;
// Re-encode via the built-in render endpoint.
const url = client.storage
.from(args.source_bucket)
.getPublicUrl(args.source_path, {
transform: { width: args.width, height: args.height, format: "webp" },
}).publicUrl;
const blob = await fetch(url).then((r) => r.blob());
await client.storage
.from(args.dest_bucket)
.upload(args.source_path.replace(/\.[^.]+$/, ".webp"), blob, {
contentType: "image/webp",
upsert: true,
});
}Other patterns
| Trigger | Edge function |
|---|
| AFTER INSERT | Push notification (mobile app) |
| BEFORE INSERT | Reject upload if virus scanner returns positive |
| AFTER UPDATE | Invalidate CDN cache for the public URL |
| AFTER DELETE | Mirror deletion to an external archive |