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

# Uploading Files

> Learn how to upload files to NoCloud Storage

NoCloud uses a two-step upload process:

1. **Get a signed URL** from the [Generate Signed URL](/cloud/api-reference/storage/signed-url) endpoint
2. **Upload the file** directly to the signed URL

There are two modes depending on whether you provide query parameters:

* **Non-allocated**: No query parameters — returns a signed URL. Upload with a **POST** request using form data.
* **Pre-allocated**: Pass `contentType` and `size` as query parameters to pre-allocate storage — returns a signed URL along with `mediaId` and `mediaUrl`. Upload with a **PUT** request.

This approach allows for efficient, direct-to-storage uploads without proxying through your backend.

## Simple Upload (Non-allocated)

<CodeGroup>
  ```bash cURL theme={null}
  # Step 1: Get signed URL
  SIGNED_URL_RESPONSE=$(curl -s -X GET \
    "https://api.nonefivem.com/cloud/storage/signed-url" \
    -H "Authorization: Bearer your-api-key")

  # Extract the signed URL

  UPLOAD_URL=$(echo $SIGNED_URL_RESPONSE | jq -r '.url')

  # Step 2: Upload file via POST with form data

  curl -X POST "$UPLOAD_URL" \
   -F "file=@./screenshot.png" \
   -F 'metadata={"category":"screenshots"}'

  ```

  ```javascript JavaScript theme={null}
  async function uploadFile(file) {
    // Step 1: Get signed URL
    const signedUrlResponse = await fetch(
      "https://api.nonefivem.com/cloud/storage/signed-url",
      {
        headers: {
          Authorization: "Bearer your-api-key"
        }
      }
    );

    if (!signedUrlResponse.ok) {
      throw new Error("Failed to get signed URL");
    }

    const { url } = await signedUrlResponse.json();

    // Step 2: Upload file via POST with form data
    const formData = new FormData();
    formData.append("file", file);
    // Optionally attach metadata
    formData.append("metadata", JSON.stringify({ category: "screenshots" }));

    const uploadResponse = await fetch(url, {
      method: "POST",
      body: formData
    });

    if (!uploadResponse.ok) {
      throw new Error("Failed to upload file");
    }

    return await uploadResponse.json();
  }

  // Usage
  const file = document.querySelector('input[type="file"]').files[0];
  const result = await uploadFile(file);
  console.log("File uploaded:", result);
  ```

  ```typescript TypeScript theme={null}
  async function uploadFile(file: File | Blob): Promise<unknown> {
    // Step 1: Get signed URL
    const signedUrlResponse = await fetch(
      "https://api.nonefivem.com/cloud/storage/signed-url",
      {
        headers: {
          Authorization: "Bearer your-api-key"
        }
      }
    );

    if (!signedUrlResponse.ok) {
      throw new Error("Failed to get signed URL");
    }

    const { url } = await signedUrlResponse.json();

    // Step 2: Upload file via POST with form data
    const formData = new FormData();
    formData.append("file", file);
    // Optionally attach metadata
    formData.append("metadata", JSON.stringify({ category: "screenshots" }));

    const uploadResponse = await fetch(url, {
      method: "POST",
      body: formData
    });

    if (!uploadResponse.ok) {
      throw new Error("Failed to upload file");
    }

    return await uploadResponse.json();
  }
  ```
</CodeGroup>

## Pre-allocated Upload

Pass `contentType` and `size` to pre-allocate storage. The response includes `mediaId` and `mediaUrl` so you know the file's location before uploading.

<CodeGroup>
  ```bash cURL theme={null}
  # Step 1: Get signed URL with pre-allocation
  SIGNED_URL_RESPONSE=$(curl -s -X GET \
    "https://api.nonefivem.com/cloud/storage/signed-url?contentType=image/png&size=1048576" \
    -H "Authorization: Bearer your-api-key")

  # Extract the signed URL

  UPLOAD_URL=$(echo $SIGNED_URL_RESPONSE | jq -r '.url')

  # Step 2: Upload file to signed URL via PUT

  curl -X PUT "$UPLOAD_URL" \
   -H "Content-Type: image/png" \
   --data-binary @./screenshot.png

  ```

  ```javascript JavaScript theme={null}
  async function uploadFile(file) {
    // Step 1: Get signed URL with pre-allocation
    const params = new URLSearchParams({
      contentType: file.type,
      size: file.size.toString()
    });

    const signedUrlResponse = await fetch(
      `https://api.nonefivem.com/cloud/storage/signed-url?${params}`,
      {
        headers: {
          Authorization: "Bearer your-api-key"
        }
      }
    );

    if (!signedUrlResponse.ok) {
      throw new Error("Failed to get signed URL");
    }

    const { url, mediaId, mediaUrl } = await signedUrlResponse.json();

    // Step 2: Upload file directly to signed URL via PUT
    const uploadResponse = await fetch(url, {
      method: "PUT",
      body: file
    });

    if (!uploadResponse.ok) {
      throw new Error("Failed to upload file");
    }

    // Return the file info
    return { id: mediaId, url: mediaUrl };
  }

  // Usage
  const file = document.querySelector('input[type="file"]').files[0];
  const { id, url } = await uploadFile(file);
  console.log("File uploaded:", url);
  ```

  ```typescript TypeScript theme={null}
  interface SignedUrlResponse {
    url: string;
    expiresAt: string;
    mediaId: string;
    mediaUrl: string;
  }

  interface UploadResult {
    id: string;
    url: string;
  }

  async function uploadFile(file: File | Blob): Promise<UploadResult> {
    // Step 1: Get signed URL with pre-allocation
    const params = new URLSearchParams({
      contentType: file.type,
      size: file.size.toString()
    });

    const signedUrlResponse = await fetch(
      `https://api.nonefivem.com/cloud/storage/signed-url?${params}`,
      {
        headers: {
          Authorization: "Bearer your-api-key"
        }
      }
    );

    if (!signedUrlResponse.ok) {
      throw new Error("Failed to get signed URL");
    }

    const { url, mediaId, mediaUrl }: SignedUrlResponse =
      await signedUrlResponse.json();

    // Step 2: Upload file directly to signed URL via PUT
    const uploadResponse = await fetch(url, {
      method: "PUT",
      body: file
    });

    if (!uploadResponse.ok) {
      throw new Error("Failed to upload file");
    }

    return { id: mediaId, url: mediaUrl };
  }
  ```
</CodeGroup>

## Upload with Metadata (Pre-allocated)

When using the pre-allocated flow, you can attach metadata via query parameters:

```javascript theme={null}
const params = new URLSearchParams({
  contentType: "image/png",
  size: file.size.toString(),
  // Metadata as JSON string
  metadata: JSON.stringify({
    oderId: "order_123",
    visitorId: 456,
    isPublic: true
  })
});

const response = await fetch(
  `https://api.nonefivem.com/cloud/storage/signed-url?${params}`,
  {
    headers: { Authorization: "Bearer your-api-key" }
  }
);
```

<Note>
  Metadata constraints: - Maximum 10 keys - Key names: alphanumeric,
  underscores, hyphens only (max 128 chars) - Values: string (max 512 chars),
  number, or boolean - Total metadata size: max 2KB
</Note>

## Upload with Progress Tracking

For larger files, you may want to track upload progress. This example uses the pre-allocated flow:

```javascript theme={null}
async function uploadWithProgress(file, onProgress) {
  // Get signed URL with pre-allocation
  const { url, mediaId, mediaUrl } = await getSignedUrl(file);

  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();

    xhr.upload.addEventListener("progress", (event) => {
      if (event.lengthComputable) {
        const percentComplete = (event.loaded / event.total) * 100;
        onProgress(percentComplete);
      }
    });

    xhr.addEventListener("load", () => {
      if (xhr.status >= 200 && xhr.status < 300) {
        resolve({ mediaId, mediaUrl });
      } else {
        reject(new Error(`Upload failed with status ${xhr.status}`));
      }
    });

    xhr.addEventListener("error", () => reject(new Error("Upload failed")));

    xhr.open("PUT", url);
    xhr.setRequestHeader("Content-Type", file.type);
    xhr.send(file);
  });
}

// Usage
await uploadWithProgress(file, (progress) => {
  console.log(`Upload progress: ${progress.toFixed(1)}%`);
});
```

<Info>
  Signed URLs expire after 15 minutes. Generate a new URL if the upload doesn't
  complete in time.
</Info>
