# Source: https://docs.pinata.cloud/files/presigned-urls.md

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

# Presigned URLs

There are situations where you may need to upload a file client side instead of server side, but doing so might risk exposing an API key. To solve this you can create a presigned upload URL on the server and then pass it to the client for it to be consumed. Creating signed upload URLs can be done with either the [Files SDK](/sdk/upload/public/create-signed-url) or the [API](/api-reference/endpoint/create-signed-upload-url), and you can designate how long the URL is valid for, how large the file can be, the type of file allowed, or extra metadata like a name and [keyvalues](/files/key-files).

For a more robust example, check out our guides on Hono and React!

<CardGroup cols={2}>
  <Card href="/frameworks/hono" horizontal icon="fire" title="Hono Guide" />

  <Card href="/frameworks/react" horizontal icon="react" title="React Guide" />
</CardGroup>

## Usage

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

<CodeGroup>
  ```typescript SDK theme={null}
  import { type NextRequest, NextResponse } from "next/server";
  import { pinata } from "@/utils/config"; // Import the Pinata 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
        mimeTypes: ["text/*"], // Optional restriction for certain file types
        maxFileSize: 5000000 // Optional file size limit
      })
      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 });
    }
  }
  ```

  ```typescript API theme={null}
  import { type NextRequest, NextResponse } from "next/server";

  export const dynamic = "force-dynamic";

  export async function GET() {
    // Handle your auth here to protect the endpoint
    try {
      // Prepare payload data for request
      const data = JSON.stringify({
        network: "public",
        expires: 30, // Number of seconds the signed url is good for
        filename: "Client file", // Optional name
        allow_mime_types: [ "text/*" ], // Optional array of allowed file types
        max_file_size: 5000000 // optional max file size
      })
      // send request and parse response
      const urlRequest = await fetch("https://uploads.pinata.cloud/v3/files/sign", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
          Authorization: `Bearer ${process.env.PINATA_JWT}`
        },
        body: data
      })
      const urlResponse = await urlRequest.json()
      return NextResponse.json({ url: urlResponse.data }, { status: 200 }); // Returns the key data
    } catch (error) {
      console.log(error);
      return NextResponse.json({ text: "Error creating API Key:" }, { status: 500 });
    }
  }
  ```
</CodeGroup>

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

<Note>
  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.
</Note>

<CodeGroup>
  ```typescript SDK {3} theme={null}
  const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL from your server
  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
  ```

  ```typescript API {1-2,13} theme={null}
  const urlRequest = await fetch("/api/url"); // Fetches the temporary upload URL
  const urlResponse = await urlRequest.json(); // Parse response
  async function upload() {
    try {
      const formData = new FormData();

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

      formData.append("file", file);

      formData.append("network", "public")

      const request = await fetch(urlResponse.url, {
        method: "POST",
        body: formData,
      });
      const response = await request.json();
      console.log(response);
    } catch (error) {
      console.log(error);
    }
  }
  ```
</CodeGroup>

## Reference

Below are the availble parameters for presigned URLs

### expires

* Type: `number`

The number of seconds the signed URL should be valid for

```typescript {2} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
});
```

### name (Optional)

* Type: `string`

Name for the file to be uploaded

```typescript {3} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	name: "My Cool File"
});
```

### mimeTypes (Optional)

* Type: `string[]`

Specify allowed file mime types and prevent uploads from files that do not match. Accepts wildcard mime types as well.

```typescript {3-6} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	mimeTypes: [
	  "image/*",
		"application/json"
	]
});
```

### maxFileSize (Optional)

* Type: `number`

Restrict upload to a specified file size in `bytes`

```typescript {3} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	maxFileSize: 50000 // 50kb
});
```

### groupId (Optional)

* Type: `string`

The target groupId the file would be uploaded to

```typescript {3} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	groupId: "ad4bc3bf-8794-49e7-94ff-fea1ce745779"
});
```

### keyvalues (Optional)

* Type: `Record<string, string>`

Keyvalue pairs for the uploaded file

```typescript {3-5} theme={null}
const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	keyvalues: {
	  env: "prod"
	}
});
```

### date (Optional)

* Type: `number`

A UNIX timestamp of the date a URL is signed

```typescript {1-2,6} theme={null}
const date = Math.floor(new Date().getTime() / 1000);
//date: 1724943711

const url = await pinata.upload.public.createSignedURL({
	expires: 30,
	date: date
});
```