At the core of Pinata’s services is our IPFS APIs which allow you to upload files to either public or private IPFS. You can read more about the difference between the two here.

Let’s look at the multiple ways you can upload files!

How to Upload Files

Uploading files with Pinata is simple, whether you want to use the SDK or the API. Key things to know:

  • Uploads are done through multipart/form-data requests
  • The SDK and API accept File objects per the Web API Standard for Files
  • You can add additional info to your upload such as a custom name for the file, keyvalue metadata, and a target group destination for organization

Here is a simple example of how you might upload a file in Typescript

import { PinataSDK } from "pinata";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const file = new File(["hello"], "Testing.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);

This will return the following response

{
    id: "349f1bb2-5d59-4cab-9966-e94c028a05b7",
    name: "file.txt",
    cid: "bafybeihgxdzljxb26q6nf3r3eifqeedsvt2eubqtskghpme66cgjyw4fra",
    size: 4682779,
    number_of_files: 1,
    mime_type: "text/plain",
    group_id: null
}
  • id: The ID of the file used for getting info, updating, or deleting
  • name: The name of the file or the provided name in the addMetadata method
  • cid: A cryptographic hash based on the contents of the file
  • size: The size of the file in bytes
  • number_of_files: The number of files in a reference
  • mime_type: The mime type of the uploaded file
  • group_id: The group the file was uploaded to if applicable

Metadata

When uploading a file you can add additional metadata using the name or keyvalues methods after the selected upload method. This can include an optional name override or keyvalue pairs that can be used to searching the file later on

const upload = await pinata.upload.public
  .file(file)
  .name("hello.txt")
  .keyvalues({
    env: "prod"
  })

Groups

Pinata offers Private IPFS Groups to organize your content. You can upload a file to a group by using the group method.

const upload = await pinata.upload.public
  .file(file)
  .group("b07da1ff-efa4-49af-bdea-9d95d8881103")

Client Side Uploads

There are situations where you may need to upload a file client side instead of server side. A great example is in Next.js where there is a 4MB file size restriction for files passed through Next’s API routes. To solve this you can create a signed upload URL on the server and then pass it to the client for it to be consumed. This way your admin API key stays safe behind a server. Creating signed upload URLs can be done with either the Files SDK or the API, and you can designate how long the URL is valid for or if there is other infromation you want to include such as metadata or a group ID.

Setting up a server side API endpoint might look something like this:

import { type NextRequest, NextResponse } from "next/server";
import { pinata } from "@/utils/config"; // Import the Files SDK instance

export const dynamic = "force-dynamic";

export async function GET() {
  // Handle your auth here to protect the endpoint
  try {
    const url = await pinata.upload.public.createSignedURL({
      expires: 30, // The only required param
      name: "Client File",
      group: "my-group-id"
    })
    return NextResponse.json({ url: url }, { status: 200 }); // Returns the signed upload URL
  } catch (error) {
    console.log(error);
    return NextResponse.json({ text: "Error creating signed URL:" }, { status: 500 });
  }
}

Then back on the client side code, you can upload using the signed URL instead of the regular upload endpoint.

If you’re using the SDK you can use the .url() parameter on any of the upload methods and pass in the signed upload URL there. If you are using the API you can simply make the upload request using the signed URL as the endpoint.

const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL
const urlResponse = await urlRequest.json(); // Parse response
const upload = await pinata.upload.public
  .file(file)
  .url(urlResponse.url); // Upload the file with the signed URL

Upload Progress

If you happen to use the API as well as local files you can also track the progress of the upload using a library like got. Better support for upload progress will come in later versions of the SDK!

import fs from "fs";
import FormData from "form-data";
import got from "got";

async function upload() {
  const url = `https://uploads.pinata.cloud/v3/files`;
  try {
    let data = new FormData();
    data.append(`file`, fs.createReadStream("path/to/file"));
    const response = await got(url, {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.PINATA_JWT}`,
      },
      body: data,
    }).on("uploadProgress", (progress) => {
      console.log(progress);
    });
    console.log(JSON.parse(response.body));
  } catch (error) {
    console.log(error);
  }
}

If your file is larger than 100MB then a better approach is to follow the Resumable Upload Guide

Common File Recipes

Below are some common recipes for uploading a file.

Blob

Usually you can pass a Blob directly into the request but to help guarantee success we recommend passing it into a File object.

import { PinataSDK } from "pinata";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const text = "Hello World!";
const blob = new Blob([text]);
const file = new File([blob], "hello.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);

JSON

Pinata makes it easy to upload JSON objects using the json method.

import { PinataSDK } from "pinata";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const upload = await pinata.upload.public.json({
  id: 2,
  name: "Bob Smith",
  email: "[email protected]",
  age: 34,
  isActive: false,
  roles: ["user"],
});

Local Files

If you need to upload files from a local file source you can use fs to feed a file into a blob, then turn that blob into a File. Due to the buffer limit in Node.js you may have issues going beyond 2GB with this approach. Using Resumable Uploads with a client side file picker will help increase this.

Support for file streams will be coming in a later version of the SDK.

const { PinataSDK } = require("pinata");
const fs = require("fs");
const { Blob } = require("buffer");

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const blob = new Blob([fs.readFileSync("./hello-world.txt")]);
const file = new File([blob], "hello-world.txt", { type: "text/plain" });
const upload = await pinata.upload.public.file(file);

Folders

The SDK can accept an array of files using the fileArray method. Folders can also be uploaded via the API by creating an array of files and mapping over them to add them to the form data. This is different then having a single file entry and having multiple files for that one entry, which does not work.

Folder uploads are currently only supported on Public IPFS

import { PinataSDK } from "pinata-web3";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const file1 = new File(["hello world!"], "hello.txt", { type: "text/plain" })
const file2 = new File(["hello world again!"], "hello2.txt", { type: "text/plain" })
const upload = await pinata.upload.public.fileArray([file1, file2])

We also have other tools like the Pinata IPFS CLI which can be used to upload using API Keys!

URL

To upload a file from an external URL you can stream the contents into an arrayBuffer, which then gets passed into a new Blob that can then be uploaded to Pinata. This has been abstracted in the SDK using the url method.

import { PinataSDK } from "pinata";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const upload = await pinata.upload.public.url("https://i.imgur.com/u4mGk5b.gif");

base64

To upload a file in base64 simply turn the contents into a buffer that is passed into a Blob. Alternatively you can use the SDK for this as well using the base64 method.

import { PinataSDK } from "pinata";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const upload = await pinata.upload.public.base64("SGVsbG8gV29ybGQh");

Resumable Uploads

The upload endpoint https://uploads.pinata.cloud/v3/files is fully TUS compatible, so it can support larger files with the ability to resume uploads. Any file upload larger than 100MB needs to be uploaded through the TUS method, or through the legacy /pinFileToIPFS endpoint. The SDK handles this automatically when you use pinata.upload.<network>.file() by checking the file size before uploading.

At this time folder uploads must go through /pinFileToIPFS. Read the Folder Guide for more info!

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const upload = await pinata.upload.public.file(massiveFile);

If you want to take advantage of resumable uploads then we would recommend using one of the TUS clients and taking note of the following:

  • Upload chunk size must be smaller than 50MB
  • Instead of using the form data fields for group_id or keyvalues these can be passed directly into the upload metadata (see example below)
  • Headers must include the Authorization with your Pinata JWT

Here is an example of an upload to Pinata using the tus-js-client

tus-js-client
import * as tus from "tus-js-client";

async function resumeUpload(file) {
  try {
    const upload = new tus.Upload(file, {
      endpoint: "https://uploads.pinata.cloud/v3/files",
      chunkSize: 50 * 1024 * 1024, // 50MiB chunk size
      retryDelays: [0, 3000, 5000, 10000, 20000],
      onUploadUrlAvailable: async function () {
        if (upload.url) {
          console.log("Upload URL is available! URL: ", upload.url);
        }
      },
      metadata: {
        filename: "candyroad-demo.mp4", // name
        filetype: "video/mp4",
        group_id: "0192868e-6144-7685-9fc5-af68a1e48f29", // group ID
        network: "public",
        keyvalues: JSON.stringifiy({ env: "prod" }), // keyvalues
      },
      headers: { Authorization: `Bearer ${process.env.PINATA_JWT}` }, // auth header
      uploadSize: fileStats.size,
      onError: function (error) {
        console.log("Failed because: " + error);
      },
      onProgress: function (bytesUploaded, bytesTotal) {
        const percentage = ((bytesUploaded / bytesTotal) * 100).toFixed(2);
        console.log(percentage + "%");
      },
      onSuccess: function () {
        console.log("Upload completed!");
      },
    });

    upload.start();
  } catch (error) {
    console.log(error);
  }
}

Pin by CID

Another way you can upload content to Pinata is by transferring content that is already on IPFS. This could be CIDs that are on your own local IPFS node or another IPFS pinning service! You can do this with the “Pin by CID” button in the web app, like so:

Or you can pin by CID with the SDK using the cid method.

import { PinataSDK } from "pinata-web3";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const pin = await pinata.upload.public.cid("QmVLwvmGehsrNEvhcCnnsw5RQNseohgEkFNN1848zNzdng")

This will result in a request_id and Pinata will start looking for the file. Progress can be checked by using the queue method.

import { PinataSDK } from "pinata-web3";

const pinata = new PinataSDK({
  pinataJwt: process.env.PINATA_JWT!,
  pinataGateway: "example-gateway.mypinata.cloud",
});

const jobs = await pinata.files.public.queue().status("prechecking")

All possible filters are included in the API reference below, but these are the possible “status” filters:

status
string

Filter by the status of the job in the pinning queue (see potential statuses below)

  • prechecking Pinata is running preliminary validations on your pin request.
  • searching Pinata is actively searching for your content on the IPFS network. This may take some time if your content is isolated.
  • retrieving Pinata has located your content and is now in the process of retrieving it.
  • expired Pinata wasn’t able to find your content after a day of searching the IPFS network. Please make sure your content is hosted on the IPFS network before trying to pin again.
  • over_free_limit Pinning this object would put you over the free tier limit. Please add a credit card to continue pinning content.
  • over_max_size This object is too large of an item to pin. If you’re seeing this, please contact us for a more custom solution.
  • invalid_object The object you’re attempting to pin isn’t readable by IPFS nodes. Please contact us if you receive this, as we’d like to better understand what you’re attempting to pin.
  • bad_host_node You provided a host node that was either invalid or unreachable. Please make sure all provided host nodes are online and reachable.

Predetermining the CID

If you find yourself in a position where you want to pre-determine the CID before uploading you can use a combination of the ipfs-unixfx-importer and blockstore-core libraries.

import { importer } from "ipfs-unixfs-importer";
import { MemoryBlockstore } from "blockstore-core/memory";

export const predictCID = async (file: File, version: 0 | 1 = 1) => {
    try {
        const arrayBuffer = await file.arrayBuffer();
        const buffer = Buffer.from(arrayBuffer);
        const blockstore = new MemoryBlockstore();

        let rootCid: any;

        for await (const result of importer([{ content: buffer }], blockstore, {
            cidVersion: version,
            hashAlg: "sha2-256",
            rawLeaves: version === 1,
        })) {
            rootCid = result.cid;
        }

        return rootCid.toString();
    } catch (err) {
        return err;
    }
};

Usage will just require passing in a file object and the version of the CID you want to predict. Here is an example using a local file.

import fs from "fs"

const file = new File([fs.readFileSync("path/to-file")], "filename.extension");
const cid = await predictCID(file, 1);
console.log(cid);

Peering with Pinata

If you run IPFS infrastructure and would like to peer with Pinata’s nodes you can do so with the Kubo commands listed below. Rather than using full multiaddresses for our node IDs we use a DNS setup that is more stable and allows our infrastructure to be flexible.

ipfs swarm connect /dnsaddr/bitswap.pinata.cloud
ipfs config --json Peering.Peers '[{ "ID": "Qma8ddFEQWEU8ijWvdxXm3nxU7oHsRtCykAaVz8WUYhiKn", "Addrs": ["/dnsaddr/bitswap.pinata.cloud"] }]'

If you have any issues feel free to reach out!

Web App

You can also use the Pinata App to upload files. It’s as simple as clicking the “Upload” button in the top right corner of the files page. Select your file, give it a name, then upload. Once it’s complete you’ll see it listed in the files page.

Start uploading by signing up for a free account!