This guide will walk you through setting up Pinata in a Next.js app. If you don’t want to do all these steps manually, you can use npx create-pinata-app to use our Next.js Starter Template.

Manual Setup

Create an API Key and get Gateway URL

To create an API key, visit the Keys Page and click the “New Key” button in the top right. Once you do that you can select if you want your key to be admin or if you want to scope the privileges of the keys to certain endpoints or limit the number of uses. Make those selections, then give the key a name at the bottom, and click create key.

If you are just getting started we recommend using Admin privileges, then move to scope keys as you better understand your needs

Once you have created the keys you will be shown your API Key Info. This will contain your Api Key, API Secret, and your JWT. Click “Copy All” and save them somewhere safe!

The API keys are only shown once, be sure to copy them somewhere safe!

After you have your API key you will want to get your Dedicated Gateway domain. Dedicated Gateways are the fastest way to fetch content from IPFS, and are the ideal tool when building decentralized applications. When you create a Pinata account you’ll automatically have a Dedicated Gateway created for you! To see it, simply visit the Gateways Page see it listed there.

The gateway domains are randomly generated and might look something like this:

aquamarine-casual-tarantula-177.mypinata.cloud

Start up Next.js Project

As with any Next.js project we can start one up with the following command

npx create-next-app@latest

You shouldn’t need any other dependencies for this project as we’ll use the Pinata API and included Next.js libraries like node-fetch.

After making the project, create a .env.local file in the root of the project and put in the following variables:

PINATA_JWT=
NEXT_PUBLIC_GATEWAY_URL=

Use the JWT from the API key creation in the previous step as well as the Gateway Domain. The format of the Gateway domain should be https://mydomain.mypinata.cloud.

Create Client Side Form

Next we’ll want to make an upload form on the client side that will allow someone to select a file and upload it.

In the /app/page.tsx file take out the boiler plate code and use the following.

app/page.tsx
"use client";

import { useState, useRef } from "react";

export default function Home() {
  const [file, setFile] = useState("");
  const [cid, setCid] = useState("");
  const [uploading, setUploading] = useState(false);

  const inputFile = useRef(null);

  const uploadFile = async (fileToUpload) => {
    try {
      setUploading(true);
      const data = new FormData();
      data.set("file", fileToUpload);
      const res = await fetch("/api/files", {
        method: "POST",
        body: data,
      });
      const resData = await res.json();
      setCid(resData.IpfsHash);
      setUploading(false);
    } catch (e) {
      console.log(e);
      setUploading(false);
      alert("Trouble uploading file");
    }
  };

  const handleChange = (e) => {
    setFile(e.target.files[0]);
    uploadFile(e.target.files[0]);
  };

  return (
    <main className="w-full min-h-screen m-auto flex flex-col justify-center items-center">
      <input type="file" id="file" ref={inputFile} onChange={handleChange} />
      <button disabled={uploading} onClick={() => inputFile.current.click()}>
        {uploading ? "Uploading..." : "Upload"}
      </button>
    </main>
  );
}

This will take a file from the client side and upload it through an API route we are going to make next.

Next.js does have a file size limitation for what can be passed through the API routes, so if you need more than the limit then it is advised to make signed JWTs by folloing this guide.

Create API Route

Next.js is ideal for file uploads as it’s API routes keep keys hidden and unexposed to the client. In the last step we made a function that uploads to /api/files so now we need to create that route by making /app/api/files/route.ts in our app.

Once you have created that file you can paste in the following code.

app/api/files/route.ts
import { NextResponse, NextRequest } from "next/server";

export const config = {
  api: {
    bodyParser: false,
  },
};

export async function POST(request: NextRequest) {
  try {
    const data = await request.formData();
    const file: File | null = data.get("file") as unknown as File;
    data.append("file", file);
    data.append("pinataMetadata", JSON.stringify({ name: "File to upload" }));
    const res = await fetch("https://api.pinata.cloud/pinning/pinFileToIPFS", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${process.env.PINATA_JWT}`,
      },
      body: data,
    });
    const { IpfsHash } = await res.json();
    console.log(IpfsHash);

    return NextResponse.json({ IpfsHash }, { status: 200 });
  } catch (e) {
    console.log(e);
    return NextResponse.json(
      { error: "Internal Server Error" },
      { status: 500 }
    );
  }
}

This will accept a POST request from the client, create pinataMetadata, then send an API request to Pinata with the upload. Once complete it will return the CID to the client.

Pull Content from IPFS

After we have uploaded the file and get the CID back we can pass it through our Dedicatd Gateway to either download it or render it in our app. For this example we will assume the file is an image, but IPFS supports any kind of file type.

Back in app/page.ts we can add the following code.

app/page.tsx
"use client";

import { useState, useRef } from "react";

export default function Home() {
  const [file, setFile] = useState("");
  const [cid, setCid] = useState("");
  const [uploading, setUploading] = useState(false);

  const inputFile = useRef(null);

  const uploadFile = async (fileToUpload) => {
    try {
      setUploading(true);
      const data = new FormData();
      data.set("file", fileToUpload);
      const res = await fetch("/api/files", {
        method: "POST",
        body: data,
      });
      const resData = await res.json();
      setCid(resData.IpfsHash);
      setUploading(false);
    } catch (e) {
      console.log(e);
      setUploading(false);
      alert("Trouble uploading file");
    }
  };

  const handleChange = (e) => {
    setFile(e.target.files[0]);
    uploadFile(e.target.files[0]);
  };

  return (
    <main className="w-full min-h-screen m-auto flex flex-col justify-center items-center">
      <input type="file" id="file" ref={inputFile} onChange={handleChange} />
      <button disabled={uploading} onClick={() => inputFile.current.click()}>
        {uploading ? "Uploading..." : "Upload"}
      </button>
      {cid && (
        <img
          src={`${process.env.NEXT_PUBLIC_GATEWAY_URL}/ipfs/${cid}`}
          alt="Image from IPFS"
        />
      )}
    </main>
  );
}

This will check to make sure the CID has been set, then pass it through our Dedicated Gateway to render it in our img tag.

Next Steps

If you will be working with images a lot then you may want to look at our built in image optimizations for resizing images on the fly. Be sure to check out our API Reference to see what else you can do with the API like listing or deleting files, and check out Gateway Access Controls if you specifically need to fetch files outside your account that you have not uploaded yourself.