Convex
@transloadit/convex is a Convex component that makes Transloadit feel native. It signs upload
options server-side, verifies webhooks, and stores results in Convex so your UI can update in real
time.
Install
npm i @transloadit/convex @uppy/core @uppy/transloadit
Setup
- Register the component:
// convex/convex.config.ts
import { defineApp } from 'convex/server'
import transloadit from '@transloadit/convex/convex.config'
const app = defineApp()
app.use(transloadit)
export default app
- Configure Convex authentication for your app. Set the server
credentials and the
tokenIdentifierof the one user allowed to try this upload flow. Retrieve that identifier from a trusted, authenticated server context usingctx.auth.getUserIdentity(), not from browser input:
npx convex env set TRANSLOADIT_KEY 'YOUR_AUTH_KEY'
npx convex env set TRANSLOADIT_SECRET 'YOUR_AUTH_SECRET'
npx convex env set TRANSLOADIT_UPLOADER_TOKEN_IDENTIFIER 'YOUR_APPROVED_TOKEN_IDENTIFIER'
- Expose only an authorized signing action. The component’s
makeTransloaditAPIwrappers do not add application authorization. This example accepts no client parameters and signs only the resize job below, for one expected upload, with a five-minute expiry:
// convex/transloadit.ts
import { Transloadit } from '@transloadit/convex'
import { ConvexError } from 'convex/values'
import { components } from './_generated/api'
import { action } from './_generated/server'
const transloadit = new Transloadit(components.transloadit)
export const createAssemblyOptions = action({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity()
if (identity === null) {
throw new ConvexError('Sign in to upload files.')
}
const allowedUploader = process.env.TRANSLOADIT_UPLOADER_TOKEN_IDENTIFIER
if (!allowedUploader || identity.tokenIdentifier !== allowedUploader) {
throw new ConvexError('Uploads are not enabled for this account.')
}
return transloadit.createAssemblyOptions(ctx, {
steps: {
resized: {
use: ':original',
robot: '/image/resize',
width: 800,
},
},
numExpectedUploadFiles: 1,
// Whole-second UTC avoids ambiguity in API date parsing.
expires: new Date(Date.now() + 5 * 60 * 1000).toISOString().replace(/\.\d{3}Z$/, 'Z'),
})
},
})
For a multi-user app, replace the single-user allowlist with your own membership and upload-quota checks before signing. Keep the allowed Instructions on the server. Result queries and webhook ingestion need their own access controls; see the component’s backend and webhook documentation.
Uppy client
Pass your app’s authenticated ConvexClient to this factory. Configure the client’s
setAuth() with your auth
provider before uploading. The callback calls the action through that client and returns its
{ params, signature, fields } result directly to Uppy:
// src/uploader.ts
import Uppy from '@uppy/core'
import Transloadit from '@uppy/transloadit'
import type { ConvexClient } from 'convex/browser'
import { api } from '../convex/_generated/api'
export function createUploader(convex: ConvexClient): Uppy {
return new Uppy({
restrictions: { maxNumberOfFiles: 1, allowedFileTypes: ['image/*'] },
}).use(Transloadit, {
waitForEncoding: true,
assemblyOptions: () => convex.action(api.transloadit.createAssemblyOptions, {}),
})
}
Use the returned Uppy instance with your file picker, then call uppy.upload(). The picker
restrictions help the user select a file; authorization and the allowed processing job are enforced
by the server action. Follow the Uppy guide for UI integrations.
Demo app
See the end-to-end wedding gallery demo at convex-demo.transload.it and the full source in the GitHub repo.