#http use server
21 messages · Page 1 of 1 (latest)
Hi @tame harbor. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:
You’ve run into two separate limits that interact:
-
HTTP actions
- Run in the Convex (V8) runtime, not Node. [HTTP limits]
- Can’t use Node-only libraries like
sharp. [Node runtime] - Have a 20 MB request/response limit. [HTTP limits]
-
Node actions
- Required for
sharp(must be in a"use node"file). [Choosing runtime; Node runtime] - Have a 5 MB argument size limit. [Limits]
- Required for
The docs and Discord threads don’t describe a way to “bypass” the 5 MB Node action argument limit. Instead, the recommended pattern is:
- Use V8 (non‑node) code to receive/store the large file, then
- Have a Node action read it from storage and process it, so the large data never travels as an argument to the Node action.
Concretely, based on the guidance:
- Use an HTTP action or Convex‑runtime action to:
- Accept the upload (up to 20 MB for HTTP actions). [Upload via HTTP; HTTP limits]
- Store it in Convex file storage (
ctx.storage.store– not shown in the snippets, but implied by the file‑storage docs). [Serve files]
- Then call a Node action that:
- Receives only a small identifier (e.g. storage ID or URL) as its argument (well under 5 MB).
- Uses
ctx.storage.getorgetUrl+fetchto load the file inside Node and runsharpon it. [Serve files; sharp thread]
- This is exactly the pattern Convex staff suggested when someone hit the Node‑action argument limit: store the file via the Convex runtime, then “kick off a node action which reads the file from storage and processes it.” [Node arg limit thread]
So, even if you “can’t use an action” for the upload itself because of the 5 MB Node limit, you can:
- Use V8 HTTP action for upload (20 MB limit, no Node APIs).
- Use Node action only for processing, passing just a small reference (ID/URL), not the raw file bytes.
If your files are larger than 20 MB, the docs only mention using upload URLs instead of HTTP actions; they don’t describe a way to process >20 MB files with sharp inside Convex, so I can’t give a supported pattern beyond what’s documented. [Upload via HTTP]
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@tame harbor Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@clear stratus so i have to call an action but like an arraybuffer mex size is to 1 MB what are the solution?
Let me check the knowledge base for you again. One moment! :books:
You don’t have to pass the file bytes (ArrayBuffer) as an argument to the action at all—that’s the key workaround.
From the docs and Discord threads, the supported pattern is:
-
Receive and store the big file in a V8 context
- Use an HTTP action (V8 runtime) to accept the upload and call
ctx.storage.store(blob)to save it. [Upload HTTP; Serve files] - The HTTP action request can be up to 20 MB. [HTTP limits]
- Use an HTTP action (V8 runtime) to accept the upload and call
-
Call a Node action with only a small argument
- The Node action should receive just the
storageId(anId<"_storage">) or a URL, which is tiny and well under the 5 MB Node‑argument limit. [State limits] - Inside the Node action (with
"use node"), load the file usingctx.storage.get(storageId)orctx.storage.getUrl(storageId)+fetch, and then runsharpor other Node‑only code on the bytes. [Serve files; sharp thread]
- The Node action should receive just the
This is exactly what Convex staff suggested when someone hit the Node‑action argument limit: use a Convex‑runtime (V8) function to store the file, then “kick off a node action which reads the file from storage and processes it.” [Node arg limit]
So you never send an ArrayBuffer as an action argument; you only send an ID/URL. That avoids both:
- The ArrayBuffer / argument size limits on actions.
- The lack of Node APIs in HTTP actions.
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
@tame harbor Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@clear stratus can you make me an example of an action endpoint who take and store an image.
ps this action should be esaly use by the front end
Let me check the knowledge base for you again. One moment! :books:
Here’s a minimal, “frontend‑friendly” pattern using upload URLs, which avoids size limits on action arguments and is the recommended way to upload files from the client. [Upload URLs]
1. Convex actions/mutations (convex/images.ts)
// convex/images.ts
import { mutation } from "./_generated/server";
import { v } from "convex/values";
// 1) Generate a short‑lived upload URL
export const generateUploadUrl = mutation({
args: {},
handler: async (ctx) => {
return await ctx.storage.generateUploadUrl();
},
});
// 2) Save the storageId returned by the upload into your DB
export const saveImage = mutation({
args: {
storageId: v.id("_storage"),
},
handler: async (ctx, args) => {
await ctx.db.insert("images", {
storageId: args.storageId,
createdAt: Date.now(),
});
},
});
generateUploadUrl is the “endpoint” your frontend calls first; saveImage is the second step to persist the file reference. [Generate URL]
2. Frontend usage (React example)
// src/UploadImage.tsx
import { FormEvent, useRef, useState } from "react";
import { useMutation } from "convex/react";
import { api } from "../convex/_generated/api";
export function UploadImage() {
const generateUploadUrl = useMutation(api.images.generateUploadUrl);
const saveImage = useMutation(api.images.saveImage);
const inputRef = useRef<HTMLInputElement>(null);
const [file, setFile] = useState<File | null>(null);
async function handleSubmit(e: FormEvent) {
e.preventDefault();
if (!file) return;
// 1) Get upload URL from Convex
const postUrl = await generateUploadUrl();
// 2) POST the file directly to Convex storage
const result = await fetch(postUrl, {
method: "POST",
headers: { "Content-Type": file.type },
body: file,
});
const { storageId } = await result.json();
// 3) Save storageId in your DB
await saveImage({ storageId });
setFile(null);
if (inputRef.current) inputRef.current.value = "";
}
return (
<form onSubmit={handleSubmit}>
<input
ref={inputRef}
type="file"
accept="image/*"
onChange={(e) => setFile(e.target.files?.[0] ?? null)}
disabled={file !== null}
/>
<button type="submit" disabled={!file}>
Upload
</button>
</form>
);
}
This follows the same 3‑step flow as the official docs example and is easy to call from any frontend: call `generateUploadUrl`, `fetch` the file to that URL, then call `saveImage` with the returned `storageId`. [[Upload URLs](<https://docs.convex.dev/file-storage/upload-files#uploading-files-via-upload-urls>)]
----------------------
Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:
----------------------
@tame harbor Kapa.ai is still learning and improving, please let me know how I did by reacting below.
@clear stratus i dont feel this is safe since i can edit the front end and upload infinit image generating url
Let me check the knowledge base for you again. One moment! :books:
You’re right to think about this; the safety doesn’t come from the frontend, it comes from what your Convex mutations allow.
Two key points from the docs:
-
The frontend is untrusted.
Anyone can modify it, so you must assume an attacker can callgenerateUploadUrlandsaveImageas much as they want. -
You control access in Convex functions, not in the UI.
Convex’s guidance is: “Use some form of access control for all public functions” and checkctx.auth.getUserIdentity()(or other unguessable identifiers) in every public query/mutation/action. [Best practices; Auth docs]
So to make uploads “safe”, you need to:
1. Authenticate in your upload mutations
// convex/images.ts
import { mutation } from "./_generated/server";
import { v, ConvexError } from "convex/values";
export const generateUploadUrl = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError("Unauthenticated");
}
// Optional: enforce per-user limits here (see next section)
return await ctx.storage.generateUploadUrl();
},
});
export const saveImage = mutation({
args: { storageId: v.id("_storage") },
handler: async (ctx, { storageId }) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
throw new ConvexError("Unauthenticated");
}
// Look up the user record if you keep one
// and enforce any business rules (quota, roles, etc.)
await ctx.db.insert("images", {
storageId,
userToken: identity.tokenIdentifier,
createdAt: Date.now(),
});
},
});
This follows the recommended pattern: authenticate at the start of each public function and only then proceed. [Auth best practices; Best practices]
2. Add rate limiting / quotas
To prevent “infinite” uploads by an authenticated user, you add authorization logic like:
- Max N images per user.
- Max uploads per minute/hour (rate limiting).
- Only certain roles can upload.
Convex explicitly recommends rate limiting user actions like logins or email resets; you can apply the same idea to uploads. [Operational maturity]
Example idea (simplified):
export const generateUploadUrl = mutation({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new ConvexError("Unauthenticated");
const now = Date.now();
const oneHourAgo = now - 60 * 60 * 1000;
const recentUploads = await ctx.db
.query("images")
.withIndex("by_user_and_time", (q) =>
q.eq("userToken", identity.tokenIdentifier).gt("createdAt", oneHourAgo)
)
.collect();
if (recentUploads.length > 100) {
throw new ConvexError("Upload rate limit exceeded");
}
return await ctx.storage.generateUploadUrl();
},
});
(You’d need an index like by_user_and_time on images(userToken, createdAt).)
3. Remember: URLs themselves are not the security boundary
- The upload URL is short‑lived (expires in 1 hour). [Upload URLs]
- The real protection is who is allowed to call the mutation that generates that URL, and how often.
So: yes, if you leave generateUploadUrl totally open, someone could spam uploads. The fix is not to trust the frontend, but to:
- Require auth in
generateUploadUrlandsaveImage. - Enforce per‑user limits / rate limiting in those mutations.