Stripe Projects
fossasia/eventyay
A skill your agent uses when the user wants to provision infrastructure or third-party services using Stripe Projects.
Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the storage table, and deletion.
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install waynesutton/builder-skills convex-file-storage --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/convex-file-storage .claude/skills/convex-file-storage && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .claude/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storageType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install waynesutton/builder-skills convex-file-storage --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/convex-file-storage .agents/skills/convex-file-storage && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .agents/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install waynesutton/builder-skills convex-file-storage --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/convex-file-storage .cursor/skills/convex-file-storage && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .cursor/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/waynesutton/builder-skills.git --path skills/convex-file-storage--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install waynesutton/builder-skills convex-file-storage --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/convex-file-storage .gemini/skills/convex-file-storage && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .gemini/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install waynesutton/builder-skills convex-file-storageInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/convex-file-storage .github/skills/convex-file-storage && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .github/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add waynesutton/builder-skills --skill convex-file-storage -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install waynesutton/builder-skills convex-file-storage --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/waynesutton/builder-skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/convex-file-storage .opencode/skills/convex-file-storage && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "convex-file-storage" agent skill from https://github.com/waynesutton/builder-skills/tree/main/skills/convex-file-storage into .opencode/skills/convex-file-storage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "convex-file-storage", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
convex-file-storageHandles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the storage table, and deletion.
Convex File Storage is an agent skill from waynesutton/builder-skills. Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the storage table, and deletion. Use when users upload images or documents, when an action fetches a file from a third party, or when a file URL expires unexpectedly.
Its SKILL.md is about 2.7k tokens, which your agent loads only when the skill is triggered. The skill folder holds 5 other files, including assets (for example `agents/openai.yaml`).
It sits in Backend & APIs, covering File uploads and storage. The repository describes itself as: Builder skills for Convex apps. Convex patterns plus a PRD, task.md, changelog, and files.md workflow for Claude Code, Codex, Cursor, and OpenCode. The licence is Apache-2.0.
Read from SKILL.md and the folder at commit 82d1ce2. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are typescript).
From the folder's file list and the shell code blocks in SKILL.md.
Links to these hosts (documentation or services it may open):
docs.convex.devFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Convex File Storage loads about 2.7k tokens when it runs. Until then it costs about 77 tokens; SKILL.md has 589 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from waynesutton/builder-skills at commit 82d1ce2, republished under its Apache-2.0 licence (© waynesutton). 589 words, ~2,674 tokens.
.claude/skills/convex-file-storage/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.Produces the upload, store, serve, and delete functions for files in Convex. The one rule: persist the Id<"_storage">, never the URL string. URLs are resolved in queries with ctx.storage.getUrl at read time.
Keep your own table for app metadata (name, owner, purpose) and point at _storage by id. Convex keeps size, content type, and hash in the _storage system table.
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
files: defineTable({
storageId: v.id("_storage"),
name: v.string(),
contentType: v.string(),
size: v.number(),
ownerId: v.id("users"),
}).index("by_owner", ["ownerId"]),
});Three steps: a mutation hands out a short lived upload URL, the client POSTs the file to it, then a second mutation saves the returned storageId. Upload URLs expire after one hour, so generate a fresh one per upload and never store it.
// convex/files.ts
import { mutation, query } from "./_generated/server";
import { v } from "convex/values";
import { getCurrentUser } from "./lib/auth";
const MAX_BYTES = 10 * 1024 * 1024;
const ALLOWED_TYPES = ["image/png", "image/jpeg", "image/webp", "application/pdf"];
export const generateUploadUrl = mutation({
args: {},
returns: v.string(),
handler: async (ctx) => {
await getCurrentUser(ctx); // only signed in users get an upload slot
return await ctx.storage.generateUploadUrl();
},
});
export const saveFile = mutation({
args: { storageId: v.id("_storage"), name: v.string() },
returns: v.id("files"),
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
// Server side validation reads the real size and type from _storage.
// Client checks are for UX only; anyone can POST to the upload URL.
const meta = await ctx.db.system.get(args.storageId);
if (!meta) throw new Error("Upload not found");
if (meta.size > MAX_BYTES || !ALLOWED_TYPES.includes(meta.contentType ?? "")) {
await ctx.storage.delete(args.storageId);
throw new Error("File type or size not allowed");
}
return await ctx.db.insert("files", {
storageId: args.storageId,
name: args.name,
contentType: meta.contentType ?? "application/octet-stream",
size: meta.size,
ownerId: user._id,
});
},
});// src/FileUploader.tsx
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
import { Id } from "../convex/_generated/dataModel";
import { useState } from "react";
const MAX_BYTES = 10 * 1024 * 1024;
export function FileUploader() {
const generateUploadUrl = useMutation(api.files.generateUploadUrl);
const saveFile = useMutation(api.files.saveFile);
const [status, setStatus] = useState<"idle" | "uploading" | "error">("idle");
async function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
const file = e.target.files?.[0];
if (!file) return;
if (file.size > MAX_BYTES) return setStatus("error");
setStatus("uploading");
try {
const uploadUrl = await generateUploadUrl();
const res = await fetch(uploadUrl, {
method: "POST",
headers: { "Content-Type": file.type }, // recorded as the file's contentType
body: file,
});
if (!res.ok) throw new Error(`Upload failed: ${res.status}`);
const { storageId } = (await res.json()) as { storageId: Id<"_storage"> };
await saveFile({ storageId, name: file.name });
setStatus("idle");
} catch {
setStatus("error");
}
}
return <input type="file" accept="image/*,.pdf" onChange={handleChange} disabled={status === "uploading"} />;
}ctx.storage.store is available in actions and HTTP actions, not in mutations. Plain fetch works in the default runtime, so "use node" is only needed when a Node library produces the bytes. When building bytes yourself, wrap them first: new Blob([bytes], { type: "application/pdf" }). Without a type the file serves as application/octet-stream.
// convex/importFile.ts
import { action } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";
export const importFromUrl = action({
args: { url: v.string(), name: v.string(), ownerId: v.id("users") },
returns: v.id("files"),
handler: async (ctx, args) => {
const response = await fetch(args.url);
if (!response.ok) throw new Error(`Fetch failed: ${response.status}`);
// response.blob() carries the Content-Type header into blob.type
const blob = await response.blob();
const storageId = await ctx.storage.store(blob);
return await ctx.runMutation(internal.files.saveImported, {
storageId,
name: args.name,
ownerId: args.ownerId,
});
},
});Resolve the URL inside the query that returns the file. The client renders url directly in img, iframe, or a download link.
// convex/files.ts
export const listMine = query({
args: {},
returns: v.array(
v.object({
_id: v.id("files"),
name: v.string(),
contentType: v.string(),
size: v.number(),
url: v.union(v.string(), v.null()),
}),
),
handler: async (ctx) => {
const user = await getCurrentUser(ctx);
const files = await ctx.db
.query("files")
.withIndex("by_owner", (q) => q.eq("ownerId", user._id))
.order("desc")
.collect();
return await Promise.all(
files.map(async (f) => ({
_id: f._id,
name: f.name,
contentType: f.contentType,
size: f.size,
url: await ctx.storage.getUrl(f.storageId),
})),
);
},
});getUrl returns null when the file was deleted. Handle that in the UI instead of assuming a string.
Use this when the file needs an auth check, a custom Content-Disposition, or a stable path at https://<deployment>.convex.site/files/<storageId>. ctx.storage.get returns the Blob. Add the route to the router in convex/http.ts.
// convex/http.ts
import { Id } from "./_generated/dataModel";
http.route({
pathPrefix: "/files/",
method: "GET",
handler: httpAction(async (ctx, request) => {
const storageId = new URL(request.url).pathname.slice("/files/".length) as Id<"_storage">;
const identity = await ctx.auth.getUserIdentity();
if (!identity) return new Response("Unauthorized", { status: 401 });
const blob = await ctx.storage.get(storageId);
if (!blob) return new Response("Not found", { status: 404 });
return new Response(blob, {
headers: { "Content-Type": blob.type || "application/octet-stream" },
});
}),
});Read metadata with ctx.db.system.get(storageId) in any query or mutation, as saveFile does above. ctx.storage.getMetadata is deprecated and should not appear in new code. The document shape:
type StorageDoc = {
_id: Id<"_storage">;
_creationTime: number;
contentType?: string; // from the upload's Content-Type header or blob.type
sha256: string;
size: number; // bytes
};The matching validator is v.object({ _id: v.id("_storage"), _creationTime: v.number(), contentType: v.optional(v.string()), sha256: v.string(), size: v.number() }). ctx.db.system.query("_storage") lists every stored file, useful for finding orphans that no files row points to.
Delete the blob and the row in the same mutation so neither is orphaned. Check ownership on the row, not by trusting the client. If several rows can share one storageId, only delete the blob when the last reference goes.
// convex/files.ts
export const remove = mutation({
args: { fileId: v.id("files") },
returns: v.null(),
handler: async (ctx, args) => {
const user = await getCurrentUser(ctx);
const file = await ctx.db.get(args.fileId);
if (!file) return null; // idempotent: already gone
if (file.ownerId !== user._id) throw new Error("Not allowed");
await ctx.storage.delete(file.storageId);
await ctx.db.delete(args.fileId);
return null;
},
});| Mistake | Why it breaks | Do instead |
|---|---|---|
Saving the URL from getUrl or the upload URL in a document | Upload URLs expire in an hour; stored URLs go stale after deletes | Store Id<"_storage">, resolve with getUrl in the query |
v.string() for the storage id | Loses type safety, accepts garbage | v.id("_storage") |
Calling ctx.storage.store in a mutation | Not available there | Use an action or HTTP action, then runMutation to save |
Skipping Content-Type on the upload POST | File serves as application/octet-stream, images will not render inline | Set headers: { "Content-Type": file.type } |
| Validating size and type only in the browser | Anyone can POST to the upload URL directly | Check again with ctx.db.system.get in the save mutation |
ctx.storage.getMetadata(id) | Deprecated | ctx.db.system.get(id) |
Deleting the files row but not the blob | Storage bill keeps growing | Delete both in one mutation |
| Uploading large files through an HTTP action | 20MB request cap | Use the upload URL flow |
v.id("_storage") plus app level name, type, size, and ownergenerateUploadUrl requires a signed in user, and the client POSTs with Content-Type: file.typectx.db.system.get and deletes rejected uploadsurl from ctx.storage.getUrl and the UI handles nullctx.storage.store(blob) with a correct type, then runMutation to save the referencectx.storage.getctx.storage.getMetadata calls and no URL strings persisted in the database© waynesutton, Apache-2.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 3 other files (assets) in skills/convex-file-storage of waynesutton/builder-skills.
Open the folder on GitHubat commit 82d1ce2
Convex File Storage next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Convex File Storage this skillwaynesutton/builder-skills | 406 | — | ~2.7k | Automated safety check: Pass | Apache-2.0 | |
| Stripe Projectsfossasia/eventyay | 1.7k | 5 repos | ~2k | Automated safety check: Notes | Apache-2.0 | |
| FoundatioFoundatioFx/Foundatio | 2.1k | — | ~3.9k | Automated safety check: Pass | Apache-2.0 | |
| Spatialduckdb/duckdb-skills | 604 | 1 repos | ~1k | Automated safety check: Notes | MIT | |
| R Oopab604/claude-code-r-skills | 206 | 2 repos | ~1.9k | Automated safety check: Pass | MIT | |
| Edgestore Setupedgestorejs/edgestore | 454 | — | ~1.8k | Automated safety check: Pass | MIT |
fossasia/eventyay
A skill your agent uses when the user wants to provision infrastructure or third-party services using Stripe Projects.
FoundatioFx/Foundatio
A skill your agent uses when working with Foundatio infrastructure abstractions for .NET -- caching, queuing, messaging, file storage, distributed locking, or background jobs.
duckdb/duckdb-skills
Answer questions about spatial data using DuckDB. An agent skill from duckdb/duckdb-skills.
ab604/claude-code-r-skills
R object-oriented programming guide for S7, S3, S4, and vctrs.
edgestorejs/edgestore
A skill your agent uses when adding, extending, or troubleshooting EdgeStore file uploads, upload UI, or bucket access policies in a TypeScript/React application.
duckdb/duckdb-skills
Explore and query data on S3, Cloudflare R2, GCS, MinIO, or any S3-compatible storage.
waynesutton/builder-skills
Builds AI agents on the Convex agent component: threads, messages, tools that call queries and mutations, streaming, RAG with vector search, and workflows for multi step jobs.
waynesutton/builder-skills
Production patterns for Convex apps and the rules the @convex-dev/eslint-plugin enforces: validators, indexes, idempotent mutations, avoiding OCC conflicts, thin function wrappers, error handling.
waynesutton/builder-skills
Creates reusable Convex components with defineComponent, a clean client wrapper, their own schema, and an npm publish setup.
waynesutton/builder-skills
Schedules work in Convex: cron jobs in convex/crons.ts, one off scheduled functions with runAfter and runAt, batching large jobs, and cancelling or inspecting the queue.
waynesutton/builder-skills
Adds HTTP endpoints in convex/http.ts: webhook receivers with signature checks, REST style routes, CORS, auth headers, streaming responses, and file uploads over HTTP.
waynesutton/builder-skills
Changes a live Convex schema without downtime: make a field optional, backfill in batches, flip the validator, then clean up.
Categories
Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the storage table, and deletion. Convex File Storage is an agent skill from waynesutton/builder-skills. Handles files in Convex: upload URLs, storing blobs from actions, serving with getUrl or an HTTP action, metadata from the storage table, and deletion.
Convex File Storage fits situations like: users upload images; an action fetches a file from a third party; A file URL expires unexpectedly.
Run `npx skills add waynesutton/builder-skills --skill convex-file-storage -a claude-code`. Or copy the skill folder (skills/convex-file-storage in waynesutton/builder-skills) into .claude/skills/convex-file-storage in your project. Claude Code loads it when a task matches its description.
Run `npx skills add waynesutton/builder-skills --skill convex-file-storage -a codex`. Or copy the skill folder (skills/convex-file-storage in waynesutton/builder-skills) into .agents/skills/convex-file-storage in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add waynesutton/builder-skills --skill convex-file-storage -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/convex-file-storage, .gemini/skills/convex-file-storage, .github/skills/convex-file-storage and .opencode/skills/convex-file-storage in your project.
SKILL.md names no scripts, command-line tools or credentials: Convex File Storage is instructions for the agent only.
SKILL.md names 1 domain. As links in the text: docs.convex.dev. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Convex File Storage is published under the Apache-2.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 2.7k tokens (SKILL.md is roughly 11k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Convex File Storage: Stripe Projects (fossasia/eventyay, 1.7k stars), Foundatio (FoundatioFx/Foundatio, 2.1k stars), Spatial (duckdb/duckdb-skills, 604 stars) and R Oop (ab604/claude-code-r-skills, 206 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
waynesutton (a GitHub user) maintains it in waynesutton/builder-skills, which has 406 GitHub stars. The repository holds 17 skills in this directory. The repository was last updated on September 28, 2026.
Source: waynesutton/builder-skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.