> 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/get-started/tutorials/wix-managed-headless/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/develop-your-project/wix-managed-headless/about-wix-managed-headless.md). - An [HTTP endpoint](https://dev.wix.com/docs/wix-cli/guides/development/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/wix-cli/guides/about-the-wix-cli.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/wix-cli/guides/get-started/quick-start-a-headless-project.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(null); const [uploadedImage, setUploadedImage] = useState(""); const [imageTitle, setImageTitle] = useState(""); const [uploaderName, setUploaderName] = useState(""); const [error, setError] = useState(""); const handleFileChange = (e: React.ChangeEvent) => { 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 (
setImageTitle(e.target.value)} /> setUploaderName(e.target.value)} /> {error ?

{error}

: null}
); } ``` ## See also - [Wix-managed headless project](https://dev.wix.com/docs/go-headless/develop-your-project/wix-managed-headless/about-wix-managed-headless.md) - [About admin operations](https://dev.wix.com/docs/go-headless/develop-your-project/admin-operations/about-admin-operations.md) - [Elevate API calls](https://dev.wix.com/docs/wix-cli/guides/about-the-wix-cli.md)