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.
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.
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: This function uploads files to Arweave and returns a URI that can be used in collection creation or moment creation.
Installation
Install the Turbo SDK and the Node.js polyfills required for browser environments:
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:
/** @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:
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:
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:
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:
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:
# Your Arweave wallet key (base64 encoded)
NEXT_PUBLIC_ARWEAVE_KEY=your_base64_encoded_arweave_key_here
Usage Examples
Basic Upload
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
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);
}
Use the uploadJson helper to upload plain objects directly:
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:
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
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
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
-
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.
-
Content-Type Tagging: The function automatically tags files with their MIME type and file name for proper handling.
-
Progress Tracking: Always implement progress tracking for better user experience, especially for larger files.
-
Error Handling: Wrap upload calls in try-catch blocks to handle network issues or insufficient Turbo Credits.
-
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:
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
- 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: