Add S3 uploads to Vercel apps without storing long-lived AWS keys

A Vercel app can accept a file upload and store the object in Amazon S3 without putting a long-lived AWS access key in source code or deployment variables. Vercel’s Amazon S3 Marketplace integration now connects an S3 resource to a Vercel project, supplies connection details as environment variables, and authenticates with short-lived OIDC credentials. The practical work remains yours: authorize the uploader, choose the object key, validate the file, and decide who can read it.
This guide walks through the setup boundary, the server-side upload path, and the security decisions that still matter after credentials are handled. It is for a team adding file uploads to a Vercel-hosted application, especially one that already uses AWS and wants its storage flow near the project configuration.
Choose the integration for the workflow you have
Amazon S3 and Vercel Blob solve overlapping but different setup problems. Vercel Blob is a storage service for files in a Vercel app, with Vercel-managed delivery features. The S3 Marketplace integration is a fit when the application already depends on AWS, when the team wants S3 resources visible beside its Vercel project, or when its storage workflow uses S3-specific services such as S3 Tables or S3 Vectors.
The Marketplace path keeps project setup together: create or connect the resource in the Vercel dashboard, then use the values made available to the project. A linked AWS account has an important constraint: the Vercel flow supports buckets created through Vercel, rather than attaching an arbitrary pre-existing bucket. If an application already writes to a bucket that was created elsewhere, check the current setup path before planning a migration. Do not assume “connect an AWS account” means every bucket in that account is selectable.
The integration also does not remove the need to understand AWS permissions. OIDC changes how the application obtains credentials; the IAM role still determines which AWS actions those credentials can perform. Treat the role policy, project environment, and upload route as separate controls.
Connect the S3 resource to the project
Start in the Vercel Marketplace from the project that will run the upload route. Create an S3 resource through the guided flow or link the AWS account you intend to use. Review the environment variables that Vercel adds and the project environments to which the connection applies. Keep production and preview behavior deliberate; a preview deployment should not automatically inherit broader access than it needs.
Vercel’s integration uses short-lived OIDC credentials instead of storing static AWS keys in the application. That means you do not paste an IAM user’s long-lived access key into an environment variable or commit it in a local file. The application still needs ordinary configuration such as the bucket name and region, and AWS still needs a role with a trust relationship for the intended Vercel project and environment.
For new integrations, inspect the connection details and the resulting IAM access before sending real user files. Confirm the role can write only to the bucket and key prefix the application requires. If a function only stores user uploads, it should not receive broad permissions to list every bucket or alter bucket policy. Keep any administrative bucket operations outside the request path.
11 production screens. Login and database wired.
The SaaS Dashboard Kit includes login, a database, and a working dashboard. Live demo at saas.otf-kit.dev.
Build a server-side upload route
The Vercel changelog’s example uses createS3 from @vercel/aws, then sends an AWS SDK PutObjectCommand from a route handler. The outline is small:
// lib/s3.ts
import { createS3 } from "@vercel/aws";
export const s3 = createS3();// app/api/upload/route.ts
import { PutObjectCommand } from "@aws-sdk/client-s3";
import { s3 } from "@/lib/s3";
export async function POST(request: Request) {
// First authenticate the user and verify upload permission.
const formData = await request.formData();
const file = formData.get("file");
if (!(file instanceof File)) {
return Response.json({ error: "A file is required." }, { status: 400 });
}
// Apply application-specific size and content checks here.
const objectKey = `uploads/${crypto.randomUUID()}`;
await s3.send(new PutObjectCommand({
Bucket: process.env.S3_BUCKET_NAME!,
Key: objectKey,
Body: Buffer.from(await file.arrayBuffer()),
ContentType: file.type || "application/octet-stream",
}));
return Response.json({ key: objectKey });
}
An existing AWS account can be linked; buckets provisioned through the Vercel connection are new buckets. The older buckets shown remain untouched.
This is a route shape, not a complete production policy. The example deliberately leaves authentication and validation as explicit steps because they depend on the application. A successful AWS write does not prove that the requester is allowed to upload for the account identified by the URL or form.
The object key should be generated on the server. Do not accept a bucket name or unrestricted key from the browser: a user could otherwise choose another tenant’s path or overwrite an object the app treats as authoritative. Use an opaque identifier for the storage key and save the original filename as display metadata after normalizing it. Scope each upload to the authenticated user or tenant, and ensure the IAM policy and application checks agree about the allowed prefix.
Set upload limits before reading an entire file into memory. Check the declared MIME type, but do not trust it as proof of file contents; inspect or decode the bytes when the file type matters. Restrict accepted formats, cap size, and rate-limit requests. For larger uploads, design a multipart or direct-to-storage flow separately, including how a client receives narrowly scoped authorization and how unfinished uploads are cleaned up.
Decide who can read each object
Storage access is a product decision. A profile image meant to be public and an invoice or user export should not share the same read path. Keep private objects behind application authorization, and return a short-lived download mechanism only after checking the caller’s access. If a file is intentionally public, make that choice explicit and avoid placing sensitive content in that bucket or prefix.

The Marketplace S3 connection uses short-lived OIDC credentials rather than static AWS keys for the upload flow; the scene is conceptual, not a Vercel or OTF integration screen.
Do not rely on an unguessable object key as the authorization rule. Random identifiers reduce accidental discovery, but the app still needs to check ownership before returning a download URL or proxying bytes. Consider whether a user can replace a file, whether each replacement should create a new version, and how deletion requests propagate to S3 and the app’s metadata store.
Think through consistency as well. An upload can succeed in S3 while a database write fails, or the reverse if the application records an object before storage confirms it. Store the object key and upload state in a way that supports retries and cleanup. Make the route safe to retry where possible, and avoid returning a success response until the application has enough proof to show the file to the user.
Test the full path before opening it to users
Use a small, non-sensitive file in a development or preview environment. Verify the route rejects an unauthenticated request, a missing file, an oversized file, and a file type the product does not accept. Then upload an allowed file and confirm that the generated key uses the intended tenant prefix and that an authorized user can retrieve it.
Test the negative cases, too: a user from another tenant should not be able to overwrite or read the object; a preview project should not gain production access by accident; and a failed metadata write should not leave a misleading success state. Check the Vercel function logs and AWS-side audit trail without logging file bytes, user credentials, or temporary tokens.
Finally, test the deployment environments that matter. A route that works locally can still fail if the project is not connected to the S3 resource in Preview or Production, if the bucket region is wrong, or if the AWS role trust policy does not match the project and environment. Keep a short runbook with the resource owner, bucket and region, allowed prefixes, expected environment variables, and a safe way to rotate or narrow permissions.
Where an app template fits
Adding S3 is a storage integration task; it does not create the rest of the product around it. If the same team also needs an editable SaaS application shell, the OTF SaaS Dashboard template is an optional $99 starting point with a Vite and Hono app, sign-in, projects, teams, and sample analytics screens. It does not include Amazon S3 or configure this Marketplace integration. Keep the storage connection in the application’s own Vercel and AWS setup.
For an example of how a small Hono backend can be deployed on Vercel’s alternative serverless platform, see our guide to running a Hono backend on AWS Lambda. Use that as a separate deployment discussion; it is not a prerequisite for the Vercel Marketplace path described here.
Sources
- Amazon S3 joins the Vercel Marketplace — October 14, 2026. Marketplace setup, environment variables, OIDC credentials, upload example, linked-account limits, and S3 versus Blob use cases.
- SaaS Dashboard template and pricing — checked October 15, 2026. The kit is $99 and does not include this S3 integration.
Ship the product, not the setup.
- 11 production screens — auth, team, analytics, and settings
- Login and a real database are wired in
- AI configs pre-tuned so your agent extends instead of regenerates