> Portal Navigation:
> 
> - Append `.md` to any URL under `https://dev.wix.com/docs/` to get its markdown version.
> - Pages are either content pages (article or reference text) or menu pages (a list of links to child pages).
> - To get a menu page, truncate any URL to a parent path and append `.md` (e.g. `https://dev.wix.com/docs/sdk.md`, `https://dev.wix.com/docs/sdk/core-modules.md`).
> - Top-level index of all portals: https://dev.wix.com/docs/llms.txt
> - Full concatenated docs: https://dev.wix.com/docs/llms-full.txt

## Resource: Upload Images to CMS

## Article: Upload Images to CMS

## Article Link: https://dev.wix.com/docs/go-headless/wix-managed-headless/full-integration-astro/feature-guides/upload-images-to-cms.md

## Article Content:

# Tutorial | Upload Images to CMS

This tutorial shows you how to let site visitors upload an image to a site collection with the collection having restricted permissions for site visitors. 

> **Note:** We'll be storing image content as a data URL for simplicity. You can enhance the project by using the [Media Files API](https://dev.wix.com/docs/api-reference/assets/media/media-manager/files/introduction?apiView=SDK.md).

The flow in this article uses:

- A [Wix-managed headless project](https://dev.wix.com/docs/go-headless/wix-managed-headless/about-wix-managed-headless.md). 
- An [HTTP endpoint](https://dev.wix.com/docs/build-apps/develop-your-app/develop-an-app-with-the-cli/supported-extensions/backend/http-endpoints/about-http-endpoints.md).
- [Data Items](https://dev.wix.com/docs/api-reference/business-solutions/cms/data-items/introduction.md) API. 
- [Elevation of API Calls](https://dev.wix.com/docs/go-headless/authentication/admin/elevate-api-call-permissions-with-the-astro-integration.md).

The end result is an image upload function that validates input and inserts new CMS items.

We'll use the following steps to build the upload flow:

1. Create a CMS collection for uploaded images.
1. Build a secure backend upload route.
1. Add a minimal frontend upload form.

## Before you begin

It's important to note the following points:

- You have a [headless project](https://dev.wix.com/docs/go-headless/get-started/quick-starts/wix-managed-headless/quick-start-with-the-wix-cli.md) setup completed.
- You have a CMS collection named `uploadedimages`.

## Step 1 | Create the CMS collection schema

In this step, you'll define the collection and fields in code.

At the end of this step, your `uploadedimages` collection will match the payload inserted by the HTTP endpoint.

To create the schema:

1. Create an `index.ts` file in the `/src/entities` folder.
1. Add fields that match your collection fields, for example:
   - `uploadedImage`
   - `imageTitle`
   - `uploaderName`
   - `submissionDateTime`
   - `isApproved` 

    ```ts
    export interface UploadedImages {
        _id: string;
        _createdDate?: Date;
        _updatedDate?: Date;
        uploadedImage?: string;
        imageTitle?: string;
        uploaderName?: string;
        submissionDateTime?: Date | string;
        isApproved?: boolean;
    }
    ```

## Step 2 | Build server logic for uploading images

In this step, you'll create an HTTP endpoint that accepts images from visitors and writes validated data to a collection.

At the end of this step, your code will:

- Validate content type and required fields.
- Insert an item using an elevated backend call.

To build the logic:

1. Create the `upload-image.ts` file in the `src/pages/api` folder.
1. Import the packages:

    ```ts
    import type { APIRoute } from "astro";
    import { items } from "@wix/data";
    import { auth } from "@wix/essentials";
    ```

1. Add constants:

    ```ts
    const COLLECTION_ID = "uploadedimages";
    const MAX_IMAGE_BYTES = 8 * 1024 * 1024;
    ```

1. Add helper functions for safe responses:

    ```ts
    const json = (status: number, body: unknown) =>
    new Response(JSON.stringify(body), {
        status,
        headers: { "content-type": "application/json" },
    });

    const badRequest = (message: string) => json(400, { error: message });
    ```

1. Add an HTTP endpoint:

    ```ts
    export const POST: APIRoute = async ({ request }) => {}
    ```

1. Inside the HTTP endpoint define helper functions for JSON responses and request validation.

    ```ts
    const contentType = request.headers.get("content-type") ?? "";
    if (!contentType.includes("application/json")) {
        return badRequest("Content-Type must be application/json.");
    }

    let payload: {
        uploadedImage?: unknown;
        imageTitle?: unknown;
        uploaderName?: unknown;
    };

    try {
        payload = await request.json();
    } catch {
        return badRequest("Request body must be valid JSON.");
    }

    const uploadedImage = typeof payload.uploadedImage === "string" ? payload.uploadedImage : "";

    const parsedImage = parseImageDataUrl(uploadedImage);
    if (!parsedImage) return badRequest("uploadedImage must be a valid base64 data URL.");
    ```

1. Elevate the `insert()` function from Data API:

    ```ts
    const elevatedInsert = auth.elevate(items.insert);
    ```

1. Insert a new item after all validations pass:

    ```ts
    try {
        const createdItem = await elevatedInsert(COLLECTION_ID, {
        _id: crypto.randomUUID(),
        uploadedImage,
        imageTitle,
        uploaderName,
        submissionDateTime: new Date(),
        isApproved: false,
        });

        return json(201, {
        success: true,
        itemId: createdItem?._id,
        });
        } catch (error) {
        console.error("Elevated upload insert failed:", error);
        return json(500, {
        error: "Upload failed. Please try again.",
        });
    }
    ```

## Step 3 | Add a minimal frontend upload form

In this step, you'll create a small form component that converts a selected file into a data URL and sends it to your backend.

To add the frontend form:

1. Create a page component.
1. Add inputs for file, title, and uploader name.
1. Convert the file to a base64 data URL with `FileReader`.
1. Call you backend endpoint with `fetch`.

## Full backend code

```ts
// src/pages/api/upload-image.ts
import type { APIRoute } from "astro";
import { items } from "@wix/data";
import { auth } from "@wix/essentials";

const COLLECTION_ID = "uploadedimages";
const MAX_IMAGE_BYTES = 8 * 1024 * 1024;

const json = (status: number, body: unknown) =>
  new Response(JSON.stringify(body), {
    status,
    headers: { "content-type": "application/json" },
  });

const badRequest = (message: string) => json(400, { error: message });

export const POST: APIRoute = async ({ request }) => {
  const contentType = request.headers.get("content-type") ?? "";
  if (!contentType.includes("application/json")) {
    return badRequest("Content-Type must be application/json.");
  }

  let payload: {
    uploadedImage?: unknown;
    imageTitle?: unknown;
    uploaderName?: unknown;
  };

  try {
    payload = await request.json();
  } catch {
    return badRequest("Request body must be valid JSON.");
  }

  const uploadedImage = typeof payload.uploadedImage === "string" ? payload.uploadedImage : "";

  const parsedImage = parseImageDataUrl(uploadedImage);
  if (!parsedImage) return badRequest("uploadedImage must be a valid base64 data URL.");

  const elevatedInsert = auth.elevate(items.insert);

  try {
    const createdItem = await elevatedInsert(COLLECTION_ID, {
      _id: crypto.randomUUID(),
      uploadedImage,
      imageTitle,
      uploaderName,
      submissionDateTime: new Date(),
      isApproved: false,
    });

    return json(201, {
      success: true,
      itemId: createdItem?._id,
    });
  } catch (error) {
    console.error("Elevated upload insert failed:", error);
    return json(500, {
      error: "Upload failed. Please try again.",
    });
  }
};
```

## Full frontend code

```tsx
import { useState } from "react";

export default function UploadForm() {
  const [selectedFile, setSelectedFile] = useState<File | null>(null);
  const [uploadedImage, setUploadedImage] = useState("");
  const [imageTitle, setImageTitle] = useState("");
  const [uploaderName, setUploaderName] = useState("");
  const [error, setError] = useState("");

  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    const file = e.target.files?.[0];
    if (!file) return;

    setSelectedFile(file);
    const reader = new FileReader();
    reader.onloadend = () => setUploadedImage(String(reader.result ?? ""));
    reader.readAsDataURL(file);
  };

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setError("");

    if (!selectedFile || !uploadedImage || !imageTitle || !uploaderName) {
      setError("Please fill in all fields and select an image.");
      return;
    }

    const response = await fetch("/api/upload-image", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ uploadedImage, imageTitle, uploaderName }),
    });

    if (!response.ok) {
      const result = await response.json().catch(() => null);
      setError(result?.error ?? "Upload failed.");
      return;
    }

    setSelectedFile(null);
    setUploadedImage("");
    setImageTitle("");
    setUploaderName("");
  };
  return (
    <form onSubmit={handleSubmit}>
      <input type="file" accept="image/*" onChange={handleFileChange} />
      <input
        type="text"
        placeholder="Image title"
        value={imageTitle}
        onChange={(e) => setImageTitle(e.target.value)}
      />
      <input
        type="text"
        placeholder="Uploader name"
        value={uploaderName}
        onChange={(e) => setUploaderName(e.target.value)}
      />
      <button type="submit">Upload image</button>
      {error ? <p>{error}</p> : null}
    </form>
  );
}
```

## See also

- [Wix-managed headless project](https://dev.wix.com/docs/go-headless/wix-managed-headless/about-wix-managed-headless.md)
- [About admin operations](https://dev.wix.com/docs/go-headless/authentication/admin/about-admin-operations.md)
- [Elevate API calls](https://dev.wix.com/docs/go-headless/authentication/admin/elevate-api-call-permissions-with-the-astro-integration.md)