Features
Onchain Folders
Onchain folders are a powerful way to organize transactions on Irys. Use them to reference onchain data by logical names instead of transaction IDs.

Why Use Onchain Folders?
- Logical GroupingCreate organized and readable structures for onchain data.
- Human-Readable ReferencingReplace transaction IDs with logical names, improving accessibility.
- Cross-Ownership GroupingInclude any transactions on Irys, even if they weren't created by you.
- FlexibilityAdd new files to existing folders at any time.
How The Irys Gateway Resolves Onchain Folders
Each onchain folder is uniquely identifiable by a manifest ID. To download transactions in an onchain folder, request them from the Irys gateway using a URL formatted as:
https://gateway.irys.xyz/:manifestId/:pathNameThe gateway then:
- 1Looks up the manifest by ID.
- 2Looks in the manifest to see if the path exists.
- 3Returns the transaction associated with the path if found.
- 4Returns 404 if not found.
For example, if you have a manifest with ID 8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME containing the following:
| Tx ID | Path Name |
|---|---|
| DTMcqFqwaDukaYxs7iK2fa6CuMtyi7sN93rBGSAa13Ug | foo1.png |
| 5TQU2ETHGRPjJKPoeQkkgMB6zRpK8ptheWF8jdkbtJHR | foo2.png |
| 8nond6kkdYS14QjA5tZNCRDQQrgVNd7gdhx3L4XRJD1b | foo3.png |
https://gateway.irys.xyz/8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME/foo1.pngCreating Onchain Folders
Automatically
When you upload groups of files using the Irys SDK's uploadFolder() function or the CLI's upload-dir command, an onchain folder is automatically created. The return value contains the manifest ID, which can be combined with the original file names as above.
Manually
To manually create an onchain folder:
- 1Create a JavaScript Map object where each entry maps a unique transaction ID to a unique path. (Paths are arbitrary; you can use anything that conforms to valid URL syntax.)
- 2Create a Manifest object by passing the Map object to irys.uploader.generateFolder().
- 3Upload the Manifest object to Irys.
const createOnchainFolder = async () => {
const irysUploader = await getIrysUploader();
// You can map ANY logical name to ANY transaction ID
const map = new Map();
map.set("image-1.png", "DTMcqFqwaDukaYxs7iK2fa6CuMtyi7sN93rBGSAa13Ug");
map.set("image-2.png", "5TQU2ETHGRPjJKPoeQkkgMB6zRpK8ptheWF8jdkbtJHR");
map.set("image-3.png", "8nond6kkdYS14QjA5tZNCRDQQrgVNd7gdhx3L4XRJD1b");
const manifest = await irysUploader.uploader.generateFolder({ items: map });
console.log({ manifest });
const tags = [
{ name: "Type", value: "manifest" },
{ name: "Content-Type", value: "application/x.irys-manifest+json" },
];
const receipt = await irysUploader.upload(JSON.stringify(manifest), { tags });
console.log(`Manifest uploaded to https://gateway.irys.xyz/${receipt.id}`);
console.log(`File 1 available at https://gateway.irys.xyz/${receipt.id}/image-1.png`);
console.log(`File 2 available at https://gateway.irys.xyz/${receipt.id}/image-2.png`);
console.log(`File 3 available at https://gateway.irys.xyz/${receipt.id}/image-3.png`);
};Mutable Onchain Folders
Mutable onchain folders let you add new files to an existing folder after it's created. By using a single, static URL that always points to the most recent version, you can expand an NFT collection, add updated documents, or manage evolving datasets without needing to change the original reference. This approach ensures consistency while allowing dynamic updates over time, with the security that only the original creator can modify the folder's contents.
How to Create Mutable Onchain Folders
- 1Upload the Initial Folder: Use the SDK or CLI to upload your initial set of files. This will create a base manifest ID that uniquely identifies the folder.
- 2Reference Files Using a Mutable URL: Reference files in this folder using a URL formatted as https://gateway.irys.xyz/mutable/:manifestId/:fileName. This URL will always resolve to the most recent version of the folder.
- 3Upload New Files: When you need to add new files, upload them individually using the SDK or CLI.
- 4Create an Onchain Folder: Manually create a new onchain folder that includes both the original files and any new files as outlined above.
- 5Tag the New Manifest: Upload the new onchain folder to Irys, tagging it with Root-TX equal to the original manifest ID. This links the new onchain folder to the original folder; the "mutable" URL remains consistent.
- 6Access the Updated Folder: The URL https://gateway.irys.xyz/mutable/:manifestId/:fileName will now point to the latest version of the folder, including all newly added files.
import { Uploader } from "@irys/upload";
import { Ethereum } from "@irys/upload-ethereum";
import "dotenv/config";
import fetch from "node-fetch";
const getIrysUploader = async () => {
const irysUploader = await Uploader(Ethereum).withWallet(process.env.PRIVATE_KEY);
return irysUploader;
};
const downloadOriginalManifest = async (originalManifestId: string) => {
try {
const response = await fetch(`https://gateway.irys.xyz/${originalManifestId}`);
if (!response.ok) throw new Error("Failed to fetch original manifest");
return response.json();
} catch (error) {
console.error("Error downloading original manifest", error);
throw error;
}
};
const appendToManifest = (originalManifest: any, newFiles: Map<string, string>) => {
newFiles.forEach((txId, fileName) => {
originalManifest.paths[fileName] = { id: txId };
});
return originalManifest;
};
const uploadManifest = async (manifest: any, originalManifestId: string): Promise<void> => {
const irysUploader = await getIrysUploader();
const manifestTags = [
{ name: "Type", value: "manifest" },
{ name: "Content-Type", value: "application/x.irys-manifest+json" },
{ name: "Root-TX", value: originalManifestId },
];
try {
const manifestResponse = await irysUploader.upload(JSON.stringify(manifest), { tags: manifestTags });
console.log(`Manifest uploaded ==> https://gateway.irys.xyz/mutable/${originalManifestId}`);
} catch (e) {
console.error("Error uploading manifest", e);
}
};
const main = async () => {
try {
// Your original manifest ID
const originalManifestId = "8eNpkShMwdbiNBtGuVGBKp8feDZCa21VppX2eDi3eLME";
// Step 1: Download the original manifest
const originalManifest = await downloadOriginalManifest(originalManifestId);
// Step 2: Prepare new files to add to the manifest
const newFiles = new Map<string, string>();
newFiles.set("new-1.png", "4pTiwGwur38s4vyVD8EERxDYgAGDM8kyzEh9c5QPF9Zw");
newFiles.set("new-2.png", "BieiKJE1Nh6ydCYqxmDjHFGzuG7enRr6NYmKisqSwUQo");
// Step 3: Append new files to the manifest
const updatedManifest = appendToManifest(originalManifest, newFiles);
// Step 4: Upload the updated manifest
await uploadManifest(updatedManifest, originalManifestId);
} catch (e) {
console.error("Error in main execution", e);
}
};
main();