Storage
Learn how to set up S3 storage in your Indie Kit application
Setup already done? Skip to the next section 👇🏻
Bucket Policy
After creating your S3 bucket, you need to configure the bucket policy to allow public read access to files in the public
folder.
Navigate to your bucket's Permissions tab, scroll to Bucket Policy, and add the following policy:
Note: Replace
nanobananai
with your actual bucket name in the Resource ARN.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::nanobananai/public/*"
}
]
}
CORS Policy
To enable cross-origin requests, configure the CORS policy for your bucket.
Navigate to your bucket's Permissions tab, scroll to Cross-origin resource sharing (CORS), and add the following configuration:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"POST",
"DELETE"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": []
}
]
2. Using inside Indie Kit
Indie Kit provides three ways to upload files to S3, depending on your use case:
Method 1: Using the S3Uploader Component
The easiest way to add file uploads with a pre-built UI component.
When to use: When you need a quick file upload interface with minimal setup.
import { S3Uploader } from "@/components/ui/s3-uploader";
export default function MyPage() {
return (
<S3Uploader
presignedRouteProvider="/api/app/upload-input-images"
variant="button"
maxFiles={10}
multiple
accept="image/*"
onUpload={async (fileUrls) => {
console.log("Uploaded files:", fileUrls);
// Handle the uploaded file URLs
}}
/>
);
}
Method 2: Using ClientS3Uploader Class
For programmatic control over the upload process without UI constraints.
When to use: When you need custom UI or want to trigger uploads programmatically.
import { ClientS3Uploader } from "@/lib/s3/clientS3Uploader";
import { useMemo, useCallback } from "react";
export default function MyComponent() {
const uploader = useMemo(
() => new ClientS3Uploader({
presignedRouteProvider: "/api/app/upload-input-images"
}),
[]
);
const handleFileUpload = useCallback(
async (file: File) => {
try {
const fileUrl = await uploader.uploadFile(file);
console.log("File uploaded:", fileUrl);
return fileUrl;
} catch (error) {
console.error("Upload failed:", error);
}
},
[uploader]
);
return (
<input
type="file"
onChange={(e) => {
const file = e.target.files?.[0];
if (file) handleFileUpload(file);
}}
/>
);
}
Method 3: Server-Side Upload
For uploading files directly from your server/API routes.
When to use: When processing files on the server (e.g., image optimization, PDF generation) before uploading.
import { uploadFromServer } from "@/lib/s3/uploadFromServer";
// In your API route or server action
export async function POST(request: Request) {
const formData = await request.formData();
const file = formData.get("file") as File;
// Convert file to buffer or base64
const buffer = await file.arrayBuffer();
const base64 = Buffer.from(buffer).toString("base64");
const fileUrl = await uploadFromServer({
file: base64,
path: "uploads/documents/my-file.pdf",
contentType: "application/pdf"
});
return Response.json({ url: fileUrl });
}