Skip to main content

Downloading & uploading files


In order to offer a file download to the user, the following steps can be taken:

  1. Add a DownloadButton in the Parametrization class of the corresponding entity type:

    from viktor.parametrization import ViktorParametrization, DownloadButton

    class MyParametrization(ViktorParametrization):
    download = DownloadButton('Download', method='download_file')
  2. Add the download method (in this case download_file) on the corresponding Controller class of the entity type to link the DownloadButton, and return the file (content) as DownloadResult:

    from viktor import ViktorController
    from viktor.result import DownloadResult

    class Controller(ViktorController):

    def download_file(self, params, **kwargs):
    return DownloadResult('file content', 'filename.txt')

Downloading a File object

DownloadResult directly accepts a File object as input. This type is often returned by VIKTOR functions such as utils' document conversions and external analyses. This allows for easy handling of result files:

from viktor import ViktorControllerfrom viktor.result import DownloadResultclass Controller(ViktorController):    ...    def download_spreadsheet(self, params, **kwargs):        ...        filled_spreadsheet = render_spreadsheet(...)        return DownloadResult(filled_spreadsheet, 'filled_spreadsheet.xlsx')    def download_word_file(self, params, **kwargs):        ...        word_file = render_word_file(...)        return DownloadResult(word_file, 'word_file.docx')    def download_scia_result(self, params, **kwargs):        ...        result_file = scia_analysis.get_xml_output_file(as_file=True)        return DownloadResult(result_file, 'scia_result.xml')

Downloading other file types

It is also possible to download any other type of document (e.g. .txt, .xml, .json, etc.). In these cases, the DownloadResult should be passed the file content as type str or bytes. Since VIKTOR does not offer specific template for these file types, you will have to write the logic that generates the desired file content yourself.

Downloading multiple files

New in v12.1.0

Downloading multiple files in one go is easy when using the zipped_files argument in a DownloadResult. The files will be bundled in a zip-file with given file_name:

DownloadResult(zipped_files={'my_file_1.txt': my_file_1, 'my_file_2.txt': my_file_2}, file_name="")

Alternatively, you can use the zipfile module from Python's standard library to create your own zip-file manually:

import zipfile

class Controller(ViktorController):

def download_zip(self, params, **kwargs):

file = File()
with zipfile.ZipFile(file.source, 'w', zipfile.ZIP_DEFLATED) as z:
z.writestr('my_file_1.txt', my_file_1_content)
z.writestr('my_file_2.txt', my_file_2_content)

return DownloadResult(file, file_name="")


There are two ways for the user to upload a file in the VIKTOR interface:

  1. By defining a FileField in the parametrization, a user can upload a file through an upload modal and directly attach it to a specific field. This way, the developer can easily use the file (content) which will be present in the params of a job. This method is encouraged due to its simplicity and the possibility for users to stay within the current entity in which the FileField resides.
  2. By creating a file-like entity type. When a user creates a new entity of this type, he or she will be prompted with an upload modal instead of just the entity name. This method is a bit more cumbersome for users, since it requires more actions (clicks) to be performed and navigation between entities. This method can be used to isolate a file with, for example, attached views.

Upload using FileField

New in v13.0.0

Adding a FileField (or MultiFileField) is as easy as adding any other field:

from viktor.parametrization import ViktorParametrization, FileField, MultiFileFieldclass MyParametrization(ViktorParametrization):    file = FileField('CPT file')    files = MultiFileField('CPT files')

These fields will be generated in the VIKTOR interface as dropdown fields, with the possibility to upload one or multiple files. When the user has made a selection, the file resource will be available in the params of every job as FileResource object:

  • Obtain the name of the file by calling the filename property (will perform API call!)
  • Obtain a File object by calling the file property

FileResource objects originating from a FileField or MultiFileField are memoizable

Upload using file-like entity type

In short, the following steps should be implemented. A more elaborate example is shown in the following sections.

  1. Add a new entity type by defining a controller.

  2. Implement a process method (can be named arbitrarily) on the controller class, decorated by ParamsFromFile.

    from viktor import ViktorController, File, ParamsFromFile

    class Controller(ViktorController):

    def process_file(self, file: File, **kwargs):
    return {} # nothing to store

In the example above, the process_file method does not contain any logic. However, this method can also be used to parse certain information from the file, which can be stored in the parametrization of the entity. See the example below if this is desired.

File processing


Prevent storing the complete file content on the properties if the file is large, as this may cause speed and/or stability issues. The file content can be retrieved at all times using the API whenever necessary.

The processed content can only be stored on fields that are defined in the entity's parametrization. These can be input fields (e.g. NumberField), but also read-only (OutputField) or even a HiddenField. Let's assume the following parametrization and text file to be uploaded:

class Parametrization(ViktorParametrization):
number_of_entries = NumberField('Number of entries')
project_name = TextField('Project')
Project=Super nice project

The decorated process method should return the data in a dictionary format. The structure of this dictionary should match with the structure of the parametrization fields:

class Controller(ViktorController):    ...    @ParamsFromFile()    def process_file(self, file: File, **kwargs):  # viktor.core.File        # app specific parse logic        number_of_entries = 0        project_name = ''        with'utf-8') as f:            for lineno, line in enumerate(f):                if lineno == 0:                    _, project_name = line.split('=')                elif line.startswith('Entry'):                    number_of_entries += 1        # linking the parsed output to the parametrization fields (names in database)        return {            'number_of_entries': number_of_entries,            'project_name': project_name        }

In case of a nested parametrization structure, the return value is a nested dictionary:

class Controller(ViktorController):    ...    @ParamsFromFile()    def process_file(self, file: File, **kwargs):        ...        return {            'tab': {                'section' : {                    'number_of_entries': number_of_entries,                    'project_name': project_name                }            }        }

By using the API module, the file entity can be retrieved and the file (content) can be extracted (from either the current entity or another).

File restrictions

A limit is set on the file size of an upload to protect the developer for potential memory issues and/or instability of the application when handled incorrectly. Sometimes however, it is required to deal with large files. In this case the limit can be increased using the max_size argument:

file = FileField('CPT file', max_size=100_000_000)  # 100 MB
@ParamsFromFile(max_size=100_000_000)  # 100 MB

Keep in mind that reading the complete content of a large file in memory will lead to memory issues. We therefore recommend reading the file in chunks (see File).

Similarly, you are able to restrict the file type(s) that may be uploaded, by means of file_types:

file = FileField('CPT file', file_types=['.png', '.jpg', '.jpeg'])
@ParamsFromFile(file_types=['.png', '.jpg', '.jpeg'])

Excel vs CSV

If you want to upload an Excel file, the preferred method is to convert it to a plain-text CSV (comma-separated values) format (extension ".csv"). Excel files (".xls", ".xlsx") are not plain-text and special characters are not always imported correctly. An additional advantage of a CSV file is that, being plain-text, they can be compared using version control software (e.g. Git).


A CSV file does not support 'sheets' as an Excel file does, meaning that a CSV file must be created for each sheet in a multi-sheet Excel file.