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 });
}