---
title: Custom Previews for File Upload Questions
product: Form Library
description: With SurveyJS form builder tool, you can create custom file previews for File Upload questions. This free demo for JavaScript offers a step-by-step guide on how to override the built-in preview functionality by adjusting the showPreview property and how to render personalized previews using the onAfterRenderQuestion event handler. 
framework: Vue 3
source: https://surveyjs.io/form-library/examples/custom-file-previews/vue3js
index: https://surveyjs.io/form-library/examples/overview.md
---

# Custom Previews for File Upload Questions (Vue 3)

SurveyJS File Upload questions support the file preview functionality out of the box (see the [File Upload](/form-library/examples/file-upload/) demo). This example demonstrates how you can override this functionality and implement custom file previews if required.

To implement custom file previews, follow the steps below:

1. Disable built-in file previews.      
Set the File Upload question's [`showPreview`](/form-library/documentation/api-reference/file-model#showPreview) property to `false`.

1. Implement a custom file preview component.       
In this demo, a custom component adds a `<div>` element to the bottom of the question. When users select one or multiple files for upload, this `<div>` displays "Download file" and "Remove file" buttons per uploaded file.

1. Register your custom component under the `sv-file-preview` name.      
    In HTML/CSS/JavaScript projects, register the component in `ReactElementFactory` as shown in the `index.js` file.           
    In React, register the component in `ReactElementFactory` as shown in the `FilePreviewComponent.jsx` file.          
    In Angular, register the component in `AngularComponentFactory` as shown in the `file-preview.component.ts` file.              
    In Vue.js, register the component in `ComponentFactory` as shown in the `main.ts` file.

> In this demo, files are uploaded to SurveyJS servers from where they are then deleted after a predefined period of time. We strongly encourage you to use your own servers for file uploads when you use SurveyJS to collect sensitive respondent data in your application.

## Files

### `public/index.html`

```html
<div id="app" style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; height:100%"></div>
```

### `src/FilePreviewComponent.vue`

```html
<style>
.my-preview-container {
    margin-top: 2em;
    max-width: calc(100% - 64px);
    margin-bottom: 1em;
    padding-left: 1em;
    padding-right: 1em;
    box-sizing: border-box;
    position: absolute;
    left: 50%;
    transform: translateX(-50%);
    bottom: 0;
}

.btn {
    appearance: none;
    border: 1px solid lightgray;
    cursor: pointer;
    padding: 4px 8px;
    color: var(--sjs2-color-fg-brand-on-primary, #fff);
    font-weight: 600;
    white-space: nowrap;
    text-overflow: ellipsis;
}

.button-container {
    width: 100%;
    display: flex;
    gap: 8px;
}

.btn--choose {
    background-color: var(--sjs2-color-project-brand-600, #19b394);
    overflow: hidden;
    flex: 1 1 auto;
}

.btn--choose:hover {
    background-color: var(--sjs2-color-bg-brand-primary-dim, rgb(20, 164, 139));
}

.btn--remove {
    background-color: var(--sjs2-color-bg-alert-primary, #e60a3e);
}

.btn--remove:hover {
    background-color: rgb(217, 4, 49);
}

.btn--remove.disabled {
    cursor: not-allowed;
    opacity: 0.5;
    pointer-events: none;
}
</style>

<template>
    <div class="my-preview-container">
        <div class="files-container">
            <div class="button-container" v-for="fileItem in question.value" :key="fileItem.name">
                <div class="btn btn--choose" @click="downloadFile(fileItem)">
                    Download {{ fileItem.name }}
                </div>
                <div class="btn btn--remove"
                     :class="{ disabled: isReadOnly }"
                     :title="isReadOnly ? 'Read-only mode' : 'Remove file'"
                     @click="!isReadOnly && removeFile(fileItem.name)">
                    X
                </div>
            </div>
        </div>
    </div>
</template>

<script lang="ts" setup>
import { QuestionFileModel } from "survey-core";
import { computed, defineProps } from 'vue';

const props = defineProps<{ question: QuestionFileModel }>();

const isReadOnly = computed(() => props.question.isReadOnly || (props.question.survey && props.question.survey.readOnly));

const downloadFile = (fileItem: any) => {
    fetch(fileItem.content)
        .then((response) => response.blob())
        .then((blob) => {
            const file = new File([blob], fileItem.name, {
                type: fileItem.type,
            });
            const url = URL.createObjectURL(file);
            const a = document.createElement("a");
            a.href = url;
            a.download = fileItem.name;
            a.click();
            URL.revokeObjectURL(url);
        })
        .catch((error) => {
            console.error("Error:", error);
        });
};

const removeFile = (fileName: string) => {
    if (!isReadOnly.value) {
        props.question.removeFile(fileName);
    }
};
</script>
```

### `src/App.vue`

```html
<template>
    <SurveyComponent :model="survey" />
</template>
<script setup lang="ts">
    import { Model } from "survey-core";
    import { SurveyComponent } from "survey-vue3-ui";
    import "survey-core/survey-core.min.css";
    import "./index.css";
    import { json } from "./json";

    const survey = new Model(json);
    survey.onComplete.add((sender, options) => {
        console.log(JSON.stringify(sender.data, null, 3));
    });
    survey.onUploadFiles.add((_, options) => {
        const formData = new FormData();
        options.files.forEach((file) => {
            formData.append(file.name, file);
        });
    
        fetch("https://api.surveyjs.io/private/Surveys/uploadTempFiles", {
            method: "POST",
            body: formData
        })
            .then((response) => response.json())
            .then((data) => {
                options.callback(
                    options.files.map((file) => {
                        return {
                            file: file,
                            content: "https://api.surveyjs.io/private/Surveys/getTempFile?name=" + data[file.name]
                        };
                    })
                );
            })
            .catch((error) => {
                console.error("Error: ", error);
                options.callback([], [ "An error occurred during file upload." ]);
            });
    });
    async function deleteFile(fileURL) {
        try {
            const name = fileURL.split("=")[1];
            const apiUrl = `https://api.surveyjs.io/private/Surveys/deleteTempFile?name=${name}`;
            const response = await fetch(apiUrl, { method: "DELETE" });
    
            if (response.status === 200) {
                console.log(`File ${name} was deleted successfully`);
                return "success";
            } else {
                console.error(`Failed to delete file: ${name}`);
                return "error";
            }
        } catch (error) {
            console.error("Error while deleting file: ", error);
            return "error";
        }
    }
    
    survey.onClearFiles.add(async (_, options) => {
        if (!options.value || options.value.length === 0) {
            return options.callback("success");
        }
    
        const filesToDelete = options.fileName
            ? options.value.filter((item) => item.name === options.fileName)
            : options.value;
    
        if (filesToDelete.length === 0) {
            console.error(`File with name ${options.fileName} is not found`);
            return options.callback("error");
        }
    
        const results = await Promise.all(
            filesToDelete.map((file) => deleteFile(file.content))
        );
    
        if (results.every((res) => res === "success")) {
            options.callback("success");
        } else {
            options.callback("error");
        }
    });
</script>
```

### `src/index.css`

```css
/* You can add your custom CSS here. */
```

### `src/json.ts`

```ts
export const json = {
  "elements": [
    {
      "type": "file",
      "title": "Please upload your files",
      "name": "files",
      "storeDataAsText": false,
      "allowMultiple": true,
      "showPreview": false,
      "maxSize": 102400
    }
  ]
};
```

### `src/main.ts`

```ts
import { createApp } from "vue";
import App from "./App.vue";
import FilePreviewComponent from "./FilePreviewComponent.vue";
import { ComponentFactory } from "survey-vue3-ui";

const app = createApp(App);
ComponentFactory.Instance.registerComponent(
  "sv-file-preview",
  FilePreviewComponent
);
app.mount("#app");
```

### `src/shims-vue.d.ts`

```ts
/* eslint-disable */
declare module "*.vue" {
    import type { DefineComponent } from "vue"
    const component: DefineComponent<{}, {}, any>
    export default component
}
```

### `src/theme.ts`

```ts
export const themeJson = {};
```

### `.eslintrc.js`

```js
module.exports = {
    root: true,
    env: {
        node: true
    },
    extends: [
        "plugin:vue/vue3-essential",
        "eslint:recommended",
        "@vue/typescript/recommended",
        "@vue/prettier",
        "@vue/prettier/@typescript-eslint"
    ],
    parserOptions: {
        ecmaVersion: 2020
    },
    rules: {
        "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
        "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off"
    },
    overrides: [
        {
            files: [
                "**/__tests__/*.{j,t}s?(x)",
                "**/tests/unit/**/*.spec.{j,t}s?(x)"
            ],
            env: {
                jest: true
            }
        }
    ]
};
```

### `babel.config.js`

```js
module.exports = {
  presets: ["@vue/cli-plugin-babel/preset"]
};
```

### `package.json`

```json
{
  "name": "surveyjs-library-vue3",
  "version": "0.1.0",
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
    "lint": "vue-cli-service lint"
  },
  "dependencies": {
    "core-js": "^3.6.5",
    "tslib": "2.6.1",
    "vue": "^3.4.1",
    "survey-core": "latest",
    "survey-vue3-ui": "latest",
    "vue-router": "^4.0.0-0",
    "vuex": "^4.0.0-0"
  },
  "devDependencies": {
    "@typescript-eslint/eslint-plugin": "^2.33.0",
    "@typescript-eslint/parser": "^2.33.0",
    "@vue/cli-plugin-babel": "~4.5.0",
    "@vue/cli-plugin-eslint": "~4.5.0",
    "@vue/cli-plugin-pwa": "~4.5.0",
    "@vue/cli-plugin-router": "~4.5.0",
    "@vue/cli-plugin-typescript": "~4.5.0",
    "@vue/cli-plugin-vuex": "~4.5.0",
    "@vue/cli-service": "~4.5.0",
    "@vue/compiler-sfc": "^3.0.0",
    "@vue/eslint-config-prettier": "^6.0.0",
    "@vue/eslint-config-typescript": "^5.0.2",
    "@vue/test-utils": "^2.0.0-0",
    "eslint": "^6.7.2",
    "eslint-plugin-prettier": "^3.1.3",
    "eslint-plugin-vue": "^7.0.0-0",
    "node-sass": "^4.12.0",
    "prettier": "^1.19.1",
    "sass-loader": "^8.0.2",
    "typescript": "~3.9.3"
  }
}
```

### `tsconfig.json`

```json
{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env",
      "jest"
    ],
    "paths": {
      "@/*": [
        "src/*"
      ]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts",
    "tests/**/*.tsx"
  ],
  "exclude": [
    "node_modules"
  ]
}
```

## Other Frameworks

- [Angular](https://surveyjs.io/form-library/examples/custom-file-previews/angular.md)
- [React](https://surveyjs.io/form-library/examples/custom-file-previews/reactjs.md)
- [jQuery](https://surveyjs.io/form-library/examples/custom-file-previews/jquery.md)
- [Vanilla JS](https://surveyjs.io/form-library/examples/custom-file-previews/vanillajs.md)
