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

WhenInsertUpdateDelete
BEFOREValidate / mutate before writeSame for replaceVeto deletes
AFTERSide effects (thumbnail, queue job)React to overwritesCleanup 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

TriggerEdge function
AFTER INSERTPush notification (mobile app)
BEFORE INSERTReject upload if virus scanner returns positive
AFTER UPDATEInvalidate CDN cache for the public URL
AFTER DELETEMirror deletion to an external archive