---
title: "User uploads | Customer's Canvas On-Premises"
canonical: "https://customerscanvas.com/docs/on-premises/design-editor/apis/user-uploads/"
---

# User uploads

> When you integrate Design Editor with your application, you may need to manipulate files in the private user gallery. For example, you may want to upload files without using the image manager or move/delete these files programmatically. You can directly access the **..\\userdata\\\<userId>\\images\\** folder, but this can be difficult or even impossible in some cases. To solve this problem, Design Editor provides the `Gallery` controller described in this topic.

:::note
Do not confuse this controller with the [`UserImages`](https://customerscanvas.com/docs/on-premises/design-editor/concepts/state-files.md#patching-images) controller, which you can use to extract user files from a state file.
:::

It is recommended to [download the code sample](pathname:///files/PrivateGalleryAPI.zip) demonstrating how to work with the private gallery and authentication API and refer to this sample when reading this topic.

Photos uploaded by the user are usually quite sensitive information. An irresponsible attitude to security in the API development can lead to a privacy violation. Therefore, authorization is required to use this API. You must create a token on the server and pass it in each request as described in [Auth tokens](https://customerscanvas.com/docs/on-premises/design-editor/apis/auth-tokens.md). In the code sample, you can see how to work with these tokens.

| Function | Request type | URL |
| :--- | :--- | :--- |
| [Get a file list](#getting-a-file-list) | GET | **~/api/Gallery/Private/&#123;userId&#125;/files** |
| [Upload files](#uploading-files) | POST | **~/api/Gallery/Private/&#123;userId&#125;/files** |
| [Download an image](#downloading-an-image) | GET | **~/api/Gallery/Private/&#123;userId&#125;/files/download/&#123;filePath&#125;** |
| [Get a preview](#getting-a-preview) | GET | **~/api/Gallery/Private/&#123;userId&#125;/files/&#123;filePath&#125;** |
| [Rename and move a file](#renaming-and-moving-a-file) | PUT | **~/api/Gallery/Private/&#123;userId&#125;/files/&#123;filePath&#125;** |
| [Delete a file](#deleting-a-file) | DELETE | **~/api/Gallery/Private/&#123;userId&#125;/files/&#123;filePath&#125;** |
| [Create a folder](#creating-a-folder) | POST | **~/api/Gallery/Private/&#123;userId&#125;/folders** |
| [Get a folder list](#getting-a-folder-list) | GET | **~/api/Gallery/Private/&#123;userId&#125;/folders** |
| [Rename or move a folder](#renaming-and-moving-a-folder) | PUT | **~/api/Gallery/Private/&#123;userId&#125;/folders/&#123;folderPath&#125;** |
| [Delete a folder](#deleting-a-folder) | DELETE | **~/api/Gallery/Private/&#123;userId&#125;/folders/&#123;folderPath&#125;** |

## Getting a File List

To get a list of user images, you can use the **`~/api/Gallery/Private/{userId}/files`** endpoint. This GET request takes the `userId` required parameter. Also, you can pass the following optional parameters.

### URL Parameters

The Gallery controller can take the following parameters when retrieving the list of user images:

-   `Path` - the folder to retrieve images, relative to the **..\\userdata\\\<userId>\\images\\** folder. The default value is `null`, which means the controller returns files from the **\\images\\** folder.
-   `PageSize` - the number of images that fit one page. The maximum value is `100`. The default value is `20`.
-   `Page` - the requested page. The default value is `0`.
-   `OrderBy` - the sort order, either `Name`, `Date`, or `Size`. The default value is `null`, which means the controller returns unsorted files.
-   `OrderByDescending` - enables the descending sort order. The default value is `false`.
-   `SvgAction` - the way in which SVG files are processed, either `Vector` or `Shape`. The default value is `Vector`.

### Request Example

```javascript title="Getting a File List"
const getFilesList = async (user, token, ...inputs) => {
  // Use URLSearchParams to build a clean query string from input elements
  const searchParams = new URLSearchParams();
  
  inputs.forEach(input => {
    if (input.value !== "") {
      searchParams.append(input.id, input.value);
    }
  });

  const queryString = searchParams.toString();
  const url = `https://example.com{user}/files${queryString ? '?' + queryString : ''}`;
  
  console.log("Request URL:", url);

  try {
    const response = await fetch(url, {
      method: "GET",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      // Log status text and try to log response body if available.
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    const data = await response.json();
    console.log("Success:", data);
  } catch (error) {
    console.error("Failed to fetch file list:", error.message);
  }
};
```

### Success Response

For example, you can get the following response for the case when the default user only has the **badge.jpg** file in the **\\images\\** folder.

```text
Status Code: 200 OK
Content:
{  
  "success": true,
  "total": 1,
  "page": 0,
  "pageSize": 20,
  "items": [{  
    "storageId": "4F9957DEABA2E5FBED4B2CACA5C7E5FB.jpg",
    "name": "badge.jpg",
    "path": "",
    "width": 750.0,
    "height": 1050.0,
    "dateCreated": "2020-06-20T07:41:25.000",
    "editingEnabled": true,
    "isUserImage": true,
    "isVector": false,
    "originalUrl": "https://example.com/api/Gallery/Private/default/files/badge.jpg",
    "previewUrl": "https://example.com/api/Gallery/Private/default/files/badge.jpg",
    "deleteUrl": "https://example.com/api/Gallery/Private/default/files/badge.jpg",
    "pageIndex": 0,
    "linkedPages": [ ]
  }]
}
```

Here, the `path` property contains subfolders in which this item is located, relative to **\\images\\**. The `linkedPages` array contains page names for multipage PDF files, and `pageIndex` is the page number of this item in the PDF file.

### Error Response

You can receive the following error responses:

- ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```
    
- ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Key
    ```

- ```text
    Status Code: 404 Not Found
    Content: Folder does not exist
    ```

## Uploading Files

To upload images to the user folder, you can use the **`~/api/Gallery/Private/{userId}/files`** endpoint. This POST request takes the `userId` required parameter.

### URL Parameters

The Gallery controller can take the following two parameters when uploading user images:

-   `Path` - the destination folder for uploading images, relative to the **..\\userdata\\\<userId>\\images\\** folder. The default value is `null`, which means the controller uploads files to the **\\images\\** folder.
-   `OverwriteExistingFiles` - enables overwriting files if this file name already exists. By default, this value is `false`, and the controller adds a suffix to the file name, for example, **_1**, **_2**, and so on.

### Request Payload

You can pass a number of files as `multipart/form-data` in this POST request.

### Request Example

```javascript title="Uploading Files"
const uploadFile = async (user, token, filesToUpload) => {
  const formData = new FormData();
  
  // Iterate through the first 3 input elements and append their files
  for (const input of Array.from(filesToUpload).slice(0, 3)) {
    if (input.files[0]) {
      formData.append("userfiles[]", input.files[0]);
    }
  }

  const url = `https://example.com{user}/files`;

  try {
    const response = await fetch(url, {
      method: "POST",
      headers: { 
        "X-CCToken": token 
      },
      // Fetch automatically sets 'multipart/form-data' with the correct boundary
      body: formData
    });

    if (!response.ok) {
      const errorBody = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorBody);
      throw new Error(`Server error: ${response.status}`);
    }

    const result = await response.json();
    console.log("Success:", result);
  } catch (error) {
    console.error("Failed to upload file(s):", error.message);
  }
};
```

### Success Response

This request results in the same success response described in the previous section.

### Error Response

You can receive the following error responses:

-   ```text 
    Status Code: 400 Bad Request
    Content: Invalid file
    ```
    
-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text 
    Status Code: 500 Internal Server Error
    Content: Error while uploading file
    ```

## Downloading an Image

To download an original image or its fragment, you can use the **`~/api/Gallery/Private/{userId}/files/download/{filePath}`** endpoint. This GET request takes the `userId` and `filePath` required parameters and outputs an image to a stream.

### URL Parameters

The `Gallery` controller can take the following parameters when downloading user images:

-   `X-CCToken` - the [authentication token](https://customerscanvas.com/docs/on-premises/design-editor/apis/auth-tokens.md).
-   `Width` - the width of the resulting image, in pixels. The default value is the width of the original image.
-   `Height` - the height of the resulting image, in pixels. The default value is the height of the original image.
-   `X` - the x-coordinate of the fragment, in pixels. The default value is `0`.
-   `Y` - the y-coordinate of the fragment, in pixels. The default value is `0`.
-   `OriginalWidth` - the width of the image fragment, in pixels. The default value is the width of the original image.
-   `OriginalHeight` - the height of the image fragment, in pixels. The default value is the height of the original image.
-   `Angle` - the rotation angle of the fragment relative to the fragment center, in degrees. The default value is `0`.

### Request Example

```javascript title="Downloading an Image"
<script>
    const downloadImage = (user, fileName, ...inputs) => {
        const baseURL = "https://example.com/cc/";

        const params = new URLSearchParams();
        inputs.forEach(input => {
            if (input.value) {
                params.append(input.id, input.value);
            }
        });

        const queryString = params.toString();
        const finalUrl = `${baseURL}api/Gallery/Private/${user}/files/download/${fileName}${queryString ? '?' + queryString : ''}`;
        
        console.log("Download URL:", finalUrl);

        document.getElementById("originalImage").innerHTML = `<img src="${finalUrl}" alt="Original Image" />`;
    };
</script>
<div id="originalImage"></div>
```

### Success Response

```text
Status Code: 200 OK
Content-type: image/jpeg
```

### Error Response

You can receive the following error response:

```text
Status Code: 403 Forbidden
Content: Invalid Security Token
```

## Getting a Preview

To preview user images, you can use the **`~/api/Gallery/Private/{userId}/files/{filePath}`** endpoint. This GET request takes the `userId` and `filePath` required parameters and outputs a preview image to a stream.

### URL Parameters

The `Gallery` controller can take the following parameters when rendering previews of user images:

-   `X-CCToken` - the [authentication token](https://customerscanvas.com/docs/on-premises/design-editor/apis/auth-tokens.md).
-   Width - the maximum width of this preview, up to 500 pixels. The default value is `500`.
-   `Height` - the maximum height of this preview, up to 500 pixels. The default value is `500`.
-   `PsdAction` - the way in which PSD files are processed, either `Design` or `Merged`. The default value is `Design`.
-   `SvgAction` - the way in which SVG files are processed, either `Vector` or `Shape`. The default value is `Vector`.
-   `PageIndex` - the index of a page in a PDF file, starting from 0. The default value is `0`.
-   `ThemeConfig` - the object representing _Product Themes_. You need to pass this object when a private gallery may contain PSD templates with the `<THMB>` marker.
-   `ThemeName` - the theme name that you define in the **clientConfig.json** file.

### Request Example

```javascript title="Getting a Preview"
<script>
    const previewFile = (user, fileName, ...inputs) => {
        const baseURL = "https://example.com";

        const params = new URLSearchParams(
            inputs.filter(i => i.value).map(i => [i.id, i.value])
        ).toString();

        const finalUrl = `${baseURL}api/Gallery/Private/${user}/files/${fileName}${params ? '?' + params : ''}`;

        document.getElementById("preview").innerHTML = `<img src="${finalUrl}" alt="Preview" />`;
    };
</script>
<div id="preview"></div>
```

### Success Response

```text
Status Code: 200 OK
Content-type: image/jpeg
```

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 400 Bad Request
    Content: Width/Height is out of range
    ```
    
-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

## Renaming and Moving a File

To rename or move user images, you can use the **`~/api/Gallery/Private/{userId}/files/{filePath}`** endpoint. This PUT request takes the userId and filePath required parameters.

### URL Parameters

The `Gallery` controller can take the following optional parameters:

-   `Name` - the new file name.
-   `Path` - the new file path, relative to the **..\\userdata\\\<userId>\\images\\** folder.

### Request Example

```javascript title="Renaming and Moving a File"
const renameFile = async (user, token, fileName, newFileName, whereTo) => {
  const baseUrl = `https://example.com{user}/files/${fileName}`;
  const params = new URLSearchParams();

  if (newFileName) params.append("Name", newFileName);
  if (whereTo) params.append("Path", whereTo);

  const queryString = params.toString();
  const url = queryString ? `${baseUrl}?${queryString}` : baseUrl;

  try {
    const response = await fetch(url, {
      method: "PUT",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    console.log("Success: The file was renamed or moved.");
  } catch (error) {
    console.error("Failed to rename or move the file:", error.message);
  }
};
```

### Success Response

```text
Status Code: 200 OK
```

### Error Response

You can receive the following error responses:

-   ```text 
    Status Code: 400 Bad Request
    Content: File path required
    ```

-   ```text
    Status Code: 400 Bad Request
    Content: Either name or path must be provided
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 404 Not Found
    Content: File not found
    ```

-   ```text
    Status Code: 409 Conflict
    Content: Cannot move or rename the file because a file with the same name already exists
    ```

## Deleting a File

To delete user images, you can use the **`~/api/Gallery/Private/{userId}/files/{filePath}`** endpoint. This DELETE request takes the `userId` and `filePath` required parameters.

### Request Example

```javascript title="Deleting a File"
const deleteFile = async (user, token, fileName) => {
  const url = `https://example.com{user}/files/${fileName}`;

  try {
    const response = await fetch(url, {
      method: "DELETE",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      // Log status text and try to log response body if available.
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    console.log("Success: The file was deleted.");
  } catch (error) {
    console.error("Failed to delete the file:", error.message);
  }
};
```

### Success Response

```text
Status Code: 200 OK
```

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 400 Bad Request
    Content: File path required
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 404 Not Found
    Content: File not found
    ```

## Creating a Folder

To create a folder in the private user gallery, you can use the **`~/api/Gallery/Private/{userId}/folders`** endpoint. This POST request takes the userId required parameter.

### URL Parameters

The `Gallery` controller can take the following parameters when creating a folder:

-   `Name` - the folder name to be created.
-   `Path` - the parent folder. If you omit this parameter, this controller creates the subfolder in the **..\\userdata\\\<userId>\\images\\** folder.

### Request Example

```javascript title="Creating a Folder"
const createFolder = async (user, token, folderName, path) => {
  const baseURL = "https://example.com";
  const url = `${baseURL}${user}/folders`;

  const params = new URLSearchParams();
  params.append("Name", folderName);
  if (path) {
    params.append("path", path);
  }

  try {
    const response = await fetch(`${url}?${params.toString()}`, {
      method: "POST",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    console.log("Success: The folder was created.");
  } catch (error) {
    console.error("Failed to create the folder:", error.message);
  }
};
```

### Success Response

```text
Status Code: 200 OK
```

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 400 Bad Request
    Content: The Name parameter is required
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 409 Conflict
    Content: Folder already exists
    ```

## Getting a Folder List

To get a list of folders, you can use the **`~/api/Gallery/Private/{userId}/folders`** endpoint. This GET request takes the `userId` required parameter. Also, you can pass the following optional parameters.

### URL Parameters

The `Gallery` controller can take the following parameters when retrieving the list of folders:

-   `Path` - the folder to retrieve subfolder names, relative to the **..\\userdata\\\<userId>\\images\\** folder. The default value is `null`, which means the controller returns subfolder names from the **\\images\\** folder.
-   `PageSize` - the number of subfolders that fit one page. The maximum value is `100`. The default value is `20`.
-   `Page` - the current page. The default value is `0`.
-   `OrderBy` - the sort order, either `Name` or `Date`. By default, this controller returns the subfolder list sorted by names.
-   `OrderByDescending` - enables the descending sort order. The default value is `false`.

### Request Example

```javascript title="Getting a Folder List"
const getFolderList = async (user, token, ...inputs) => {
  const searchParams = new URLSearchParams();

  inputs.forEach(input => {
    if (input.value !== "") {
      searchParams.append(input.id, input.value);
    }
  });

  const queryString = searchParams.toString();
  const url = `https://example.com{user}/folders${queryString ? '?' + queryString : ''}`;
  
  console.log("Request URL:", url);

  try {
    const response = await fetch(url, {
      method: "GET",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      // Log status text and response body if available.
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    const data = await response.json();
    console.log("Success:", data);
  } catch (error) {
    console.error("Failed to fetch folder list:", error.message);
  }
};
```

### Success Response

For example, you can get the following response for the case when the default user only has the **\\Europe\\** subfolder in **\\images\\**.

```text
Status Code: 200 OK
Content:
{
  "total": 1,
  "page": 0,
  "pageSize": 20,
  "folders": [{  
    "name": "Europe",
    "path": null,
    "date": "2020-01-23T05:43:40.000",
    "fileCount": 2
  }]
}
```

Here, `null` in the path property means that the parent folder of this subfolder is **\\images\\**. The `fileCount` property contains the number of files in this folder, and `date` is when this folder was created or updated.

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Key
    ```

-   ```text
    Status Code: 404 Not Found
    Content: Folder does not exist
    ```

## Renaming and Moving a Folder

To rename or move a folder in the private gallery, you can use the **`~/api/Gallery/Private/{userId}/folders/{folderPath}`** endpoint. This PUT request takes the `userId` and `folderPath` required parameters.

### URL Parameters

The `Gallery` controller can take the following optional parameters:

-   `Name` - the new folder name.
-   `Path` - the new folder path, relative to the **..\\userdata\\\<userId>\\images\\** folder.

### Request Example

```javascript title="Renaming and Moving a Folder"
const renameFolder = async (user, token, folderName, newFolderName, path) => {
  const baseURL = `https://example.com{user}/folders/${folderName}`;

  const params = new URLSearchParams();
  params.append("Name", newFolderName);
  if (path) {
    params.append("path", path);
  }

  const url = `${baseURL}?${params.toString()}`;

  try {
    const response = await fetch(url, {
      method: "PUT",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    console.log("Success: The folder was renamed or moved.");
  } catch (error) {
    console.error("Failed to rename or move the folder:", error.message);
  }
};
```

### Success Response

```text
Status Code: 200 OK
```

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 400 Bad Request
    Content: Folder path required
    ```

-   ```text
    Status Code: 400 Bad Request
    Content: Either name or path must be provided
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 404 Not Found
    Content: Folder does not exist
    ```

-   ```text
    Status Code: 404 Not Found
    Content: New folder name should not be the same as the old one
    ```

-   ```text
    Status Code: 409 Conflict
    Content: Folder with the same name already exists
    ```

## Deleting a Folder

To delete folders, you can use the **`~/api/Gallery/Private/{userId}/folders/{folderPath}`** endpoint. This DELETE request takes the `userId` and `folderPath` required parameters.

### Request Example

```javascript title="Deleting a Folder"
const deleteFolder = async (user, token, folderName) => {
  const baseURL = "https://example.com";
  const url = `${baseURL}${user}/folders/${folderName}`;

  try {
    const response = await fetch(url, {
      method: "DELETE",
      headers: { 
        "X-CCToken": token 
      }
    });

    if (!response.ok) {
      const errorText = await response.text();
      console.error("Status:", response.statusText);
      console.error("Response body:", errorText);
      throw new Error(`Server error: ${response.status}`);
    }

    console.log("Success: The folder was deleted.");
  } catch (error) {
    console.error("Failed to delete the folder:", error.message);
  }
};
```

### Success Response

```text
Status Code: 200 OK
```

### Error Response

You can receive the following error responses:

-   ```text
    Status Code: 400 Bad Request
    Content: The root folder cannot be deleted
    ```

-   ```text
    Status Code: 403 Forbidden
    Content: Invalid Security Token
    ```

-   ```text
    Status Code: 404 Not Found
    Content: Folder does not exist
    ```
