Tutorials

Four end-to-end recipes covering the most common storage scenarios. Each one stands alone; the snippets are written against anvilent for TypeScript but every driver carries the same surface.

1. User avatar uploads (React + anvilent)

import { AnvilClient } from "anvilent";

const client = await AnvilClient.connect("anvil://localhost:7474/app");

export function AvatarUpload({ userId }: { userId: string }) {
  async function handleFile(file: File) {
    await client.storage
      .from("avatars")
      .upload(`${userId}.png`, file, {
        contentType: file.type,
        upsert: true,
      });
  }
  return (
    <input
      type="file"
      accept="image/*"
      onChange={(e) => e.target.files && handleFile(e.target.files[0]!)}
    />
  );
}

// And to render - uses the bucket's public URL with a server-side resize.
function Avatar({ userId }: { userId: string }) {
  const { publicUrl } = client.storage
    .from("avatars")
    .getPublicUrl(`${userId}.png`, {
      transform: { width: 64, height: 64, resize: "cover", format: "webp" },
    });
  return <img src={publicUrl} width={64} height={64} />;
}

Keep the gallery's bucket private so only authenticated users can browse; serve thumbnails via the render endpoint, mint a short-lived signed URL on demand for full-resolution downloads.

// List the user's photos.
const { items } = await client.storage
  .from("photos")
  .list(`${userId}/`, { sortBy: { column: "created_at", order: "desc" } });

// Build a thumbnail URL per photo. The render endpoint is server-side
// auth-checked - RLS still applies.
const thumbs = items.map((it) => ({
  path: it.path,
  url: client.storage.from("photos").getPublicUrl(it.path, {
    transform: { width: 320, format: "webp" },
  }).publicUrl,
}));

// On click, mint a 5-minute signed URL for the original.
async function downloadOriginal(path: string) {
  const { signedUrl } = await client.storage
    .from("photos")
    .createSignedUrl(path, 300, { transform: undefined });
  window.location.href = signedUrl;
}

3. Secure document sharing (auth + RLS + signed URLs)

Tenants upload contracts; only members of the deal team can see them; external counterparties get a one-off signed link with a 24-hour expiry that revokes the moment the deal closes.

-- Schema: every Contract has an owning Deal, every Deal has team members.
CREATE POLICY contract_team_read
  ON LABEL `storage.Object`
  FOR SELECT
  USING (
    EXISTS {
      MATCH (o:`storage.Object` {bucket_id: "contracts"})
            -[:CONTRACT_FOR]->(d:Deal)<-[:MEMBER_OF]-(u:User)
      WHERE u.id = auth.uid() AND o.path = row.path
    }
  );

-- Share with an outside party for 24 hours.
CALL signed_url("contracts", "deal-2026-05-15.pdf", 86400) YIELD url;

-- Closing the deal revokes every link for that bucket.
CALL revoke_bucket_signed_urls("contracts");

4. Automatic thumbnail generation (trigger + edge function)

See Triggers & Edge Functions for the full walkthrough - the short version is AFTER INSERT on :storage.Object → EXECUTE EDGE FUNCTION → re-upload via the client SDK using the render endpoint to do the heavy lifting.