> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inprocess.world/llms.txt
> Use this file to discover all available pages before exploring further.

# Upload Files to Arweave

> **For Feria Nounish Artists:** This documentation is inspired by Gabriel Lago from Cali, Colombia, who is building a Feria Nounish MiniApp for the artists participating in this year's fair. The `uploadToArweave` function enables artists to upload their artwork and metadata to decentralized storage, creating permanent, onchain records of their contributions to the collective timeline.

Upload files to Arweave decentralized storage for use in your NFT collections and moments. This function works on **client-side** environments using the `@ardrive/turbo-sdk` web bundle.

<Note>
  This guide targets **Next.js 16**. The examples use the App Router and rely on Next.js 16's native `Buffer` support in browser bundles.
</Note>

> **Note:** This function uploads files to Arweave and returns a URI that can be used in [collection creation](/create-collection) or [moment creation](/moment/create).

## Installation

Install the Turbo SDK and the Node.js polyfills required for browser environments:

```bash theme={null}
pnpm add @ardrive/turbo-sdk buffer crypto-browserify stream-browserify process
```

## Next.js Configuration

The Turbo SDK uses Node.js built-ins (`buffer`, `crypto`, `stream`) that are not available in the browser. Add the following to your `next.config.js` to polyfill them and to prevent the SDK from being bundled on the server:

```js theme={null}
/** @type {import('next').NextConfig} */
const nextConfig = {
  // Prevent the Turbo SDK from being bundled — load it as a server external only
  serverExternalPackages: ["@ardrive/turbo-sdk"],

  // Turbopack alias (Next.js 16 dev server)
  turbopack: {
    resolveAlias: {
      buffer: "buffer/",
      crypto: "crypto-browserify",
      stream: "stream-browserify",
      process: "process/browser",
    },
  },

  // Webpack alias (production builds)
  webpack: (config, { isServer }) => {
    if (!isServer) {
      config.resolve.fallback = {
        ...config.resolve.fallback,
        buffer: require.resolve("buffer/"),
        crypto: require.resolve("crypto-browserify"),
        stream: require.resolve("stream-browserify"),
        process: require.resolve("process/browser"),
      };
    }
    return config;
  },
};

module.exports = nextConfig;
```

## Function Implementation

### client.ts

Create a singleton Turbo client authenticated with your Arweave wallet key:

```typescript theme={null}
import { TurboFactory } from "@ardrive/turbo-sdk/web";

const ARWEAVE_KEY = JSON.parse(
  Buffer.from(process.env.NEXT_PUBLIC_ARWEAVE_KEY as string, "base64").toString()
);

const turboClient = TurboFactory.authenticated({ privateKey: ARWEAVE_KEY });

export default turboClient;
```

### patchFetch.ts

The Turbo web SDK passes a `ReadableStream` as the fetch body, which browsers mark as "disturbed" after the first read. This helper intercepts those calls and converts the stream to a `Blob` so the body can be reused across SDK retries:

```typescript theme={null}
let baseOriginalFetch: typeof globalThis.fetch | null = null;
let patchCount = 0;

const patchFetch = (): (() => void) => {
  if (patchCount === 0) {
    baseOriginalFetch = globalThis.fetch;
    globalThis.fetch = async (input: RequestInfo | URL, init?: RequestInit) => {
      if (init?.body instanceof ReadableStream) {
        const blob = await new Response(init.body).blob();
        const { duplex: _duplex, ...rest } = init as RequestInit & { duplex?: string };
        return baseOriginalFetch!(input, { ...rest, body: blob });
      }
      return baseOriginalFetch!(input, init);
    };
  }
  patchCount++;

  let restored = false;
  return () => {
    if (restored) return;
    restored = true;
    patchCount--;
    if (patchCount === 0) {
      globalThis.fetch = baseOriginalFetch!;
      baseOriginalFetch = null;
    }
  };
};

export default patchFetch;
```

### uploadFile.ts

Core upload logic using the Turbo client:

```typescript theme={null}
import turboClient from "./client";

export const uploadFile = async (
  file: File,
  getProgress: (progress: number) => void = () => {}
): Promise<string> => {
  const uint8Array = new Uint8Array(await file.arrayBuffer());

  const { id } = await turboClient.uploadFile({
    fileStreamFactory: () => Buffer.from(uint8Array),
    fileSizeFactory: () => file.size,
    dataItemOpts: {
      tags: [
        { name: "Content-Type", value: file.type },
        { name: "File-Name", value: file.name },
      ],
    },
    events: {
      onProgress: ({ totalBytes, processedBytes }) => {
        getProgress(Math.round((processedBytes / totalBytes) * 100));
      },
    },
  });

  return `ar://${id}`;
};
```

### uploadToArweave.ts

The main entry point that applies the fetch patch around the upload:

```typescript theme={null}
import { uploadFile } from "./uploadFile";
import patchFetch from "./patchFetch";

const uploadToArweave = async (
  file: File,
  getProgress: (progress: number) => void = () => {}
): Promise<string> => {
  const restoreFetch = patchFetch();
  try {
    return await uploadFile(file, getProgress);
  } finally {
    restoreFetch();
  }
};

export default uploadToArweave;
```

## Parameters

| Parameter   | Type                       | Required | Description                                                 |
| ----------- | -------------------------- | -------- | ----------------------------------------------------------- |
| file        | File                       | Yes      | The file to upload to Arweave                               |
| getProgress | (progress: number) => void | No       | Optional callback function to track upload progress (0-100) |

## Return Value

Returns a Promise that resolves to a string containing the Arweave URI in the format: `ar://{transaction_id}`

## Environment Setup

You'll need to set up your Arweave wallet key as an environment variable:

```bash theme={null}
# Your Arweave wallet key (base64 encoded)
NEXT_PUBLIC_ARWEAVE_KEY=your_base64_encoded_arweave_key_here
```

## Usage Examples

### Basic Upload

```typescript theme={null}
import uploadToArweave from "@/lib/arweave/uploadToArweave";

const file = document.getElementById("fileInput").files[0];
const arweaveUri = await uploadToArweave(file);
console.log("File uploaded to:", arweaveUri);
// Output: ar://abc123...
```

### Upload with Progress Tracking

```typescript theme={null}
import uploadToArweave from "@/lib/arweave/uploadToArweave";

const file = document.getElementById("fileInput").files[0];

const handleProgress = (progress: number) => {
  console.log(`Upload progress: ${progress}%`);
  document.getElementById("progressBar").style.width = `${progress}%`;
};

try {
  const arweaveUri = await uploadToArweave(file, handleProgress);
  console.log("Upload complete:", arweaveUri);
} catch (error) {
  console.error("Upload failed:", error);
}
```

### Upload JSON Metadata

Use the `uploadJson` helper to upload plain objects directly:

```typescript theme={null}
import { uploadJson } from "@/lib/arweave/uploadJson";

const metadata = {
  name: "Feria Nounish Collection",
  description: "Artwork from the Feria Nounish fair",
  image: "https://example.com/collection-image.jpg",
  external_link: "https://ferianounish.com",
};

const contractUri = await uploadJson(metadata);
console.log("Contract URI:", contractUri);
```

The `uploadJson` helper is implemented as:

```typescript theme={null}
import uploadToArweave from "./uploadToArweave";

export async function uploadJson(json: object): Promise<string> {
  const jsonString = JSON.stringify(json);
  const file = new File([jsonString], "upload.json", {
    type: "application/json",
  });
  return uploadToArweave(file);
}
```

### Upload for Moment Content

```typescript theme={null}
import uploadToArweave from "@/lib/arweave/uploadToArweave";

const momentContent = {
  title: "My Feria Moment",
  content: "This is my contribution to the collective timeline",
  media: "https://example.com/my-artwork.jpg",
  timestamp: new Date().toISOString(),
};

const contentBlob = new Blob([JSON.stringify(momentContent, null, 2)], {
  type: "application/json",
});
const contentFile = new File([contentBlob], "moment.json", {
  type: "application/json",
});

const momentUri = await uploadToArweave(contentFile);
console.log("Moment URI:", momentUri);
```

## React Hook Example

```typescript theme={null}
import { useState } from "react";
import uploadToArweave from "@/lib/arweave/uploadToArweave";

export const useArweaveUpload = () => {
  const [isUploading, setIsUploading] = useState(false);
  const [progress, setProgress] = useState(0);
  const [error, setError] = useState<string | null>(null);

  const uploadFile = async (file: File): Promise<string> => {
    setIsUploading(true);
    setProgress(0);
    setError(null);

    try {
      const uri = await uploadToArweave(file, setProgress);
      setIsUploading(false);
      return uri;
    } catch (err) {
      setError(err instanceof Error ? err.message : "Upload failed");
      setIsUploading(false);
      throw err;
    }
  };

  return { uploadFile, isUploading, progress, error };
};

// Usage in component
const MyComponent = () => {
  const { uploadFile, isUploading, progress, error } = useArweaveUpload();

  const handleFileUpload = async (
    event: React.ChangeEvent<HTMLInputElement>
  ) => {
    const file = event.target.files?.[0];
    if (file) {
      try {
        const uri = await uploadFile(file);
        console.log("Uploaded to:", uri);
      } catch (err) {
        console.error("Upload failed:", err);
      }
    }
  };

  return (
    <div>
      <input type="file" onChange={handleFileUpload} />
      {isUploading && (
        <div>
          <p>Uploading... {progress}%</p>
          <progress value={progress} max={100} />
        </div>
      )}
      {error && <p style={{ color: "red" }}>Error: {error}</p>}
    </div>
  );
};
```

## File Types Supported

The function supports any file type that can be stored on Arweave:

* **Images**: PNG, JPEG, GIF, SVG, WebP
* **Documents**: PDF, TXT, MD
* **Data**: JSON, CSV, XML
* **Media**: MP4, MP3, WebM
* **Archives**: ZIP, TAR

## Best Practices

1. **File Size**: Turbo uploads are paid with Credits (not AR tokens directly). Larger files consume more credits. Top up your Turbo Credits balance at [https://turbo.ar.io/topup](https://turbo.ar.io/topup).

2. **Content-Type Tagging**: The function automatically tags files with their MIME type and file name for proper handling.

3. **Progress Tracking**: Always implement progress tracking for better user experience, especially for larger files.

4. **Error Handling**: Wrap upload calls in try-catch blocks to handle network issues or insufficient Turbo Credits.

5. **Metadata Structure**: When uploading JSON metadata, follow standard NFT metadata schemas for compatibility with marketplaces.

## Integration with Collection Creation

After uploading your metadata to Arweave, use the returned URI in your collection creation:

```typescript theme={null}
const contractUri = await uploadToArweave(metadataFile);

const createContractData = encodeFunctionData({
  abi: tokenAbi,
  functionName: "createContract",
  args: [
    contractUri, // Use the Arweave URI here
    collectionName,
    royaltyConfig,
    smartAccount.address,
    [],
  ],
});
```

## Error Handling

Common errors and solutions:

* **Insufficient Turbo Credits**: Ensure your Arweave wallet has enough Turbo Credits for the upload. Top up at [https://turbo.ar.io/topup](https://turbo.ar.io/topup)
* **Invalid wallet key**: Verify your `NEXT_PUBLIC_ARWEAVE_KEY` is correctly base64 encoded
* **ReadableStream error**: The `patchFetch` helper handles browser stream compatibility automatically
* **File too large**: Consider compressing or optimizing your files before upload

## Next Steps

After uploading files to Arweave:

* [Create a collection](/collections/create) using your uploaded metadata URI
* [Create moments](/moment/create) with your uploaded content
* [Browse the timeline](/timeline) to see your contributions
