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} />;
}2. Image gallery with transforms + signed URLs
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.