# File Upload and Download Patterns in OutSystems

- Tool: OutSystems
- Difficulty: Intermediate
- Time required: 30-40 min
- Compatibility: OutSystems 11 and ODC
- Last updated: March 2026

## TL;DR

Use the Upload widget to capture a file into FileContent (Binary Data) and FileName (Text) local variables, then store them in an entity attribute of type Binary Data via a Server Action. To download, call the built-in Download action in a Server Action, passing the Binary Data and MIME type. Both patterns work identically in O11 and ODC.

## File Upload and Download in OutSystems

Many business applications need document management — contracts, invoices, profile images. OutSystems handles files as Binary Data, the same data type used for Excel exports and PDF generation. This tutorial walks through a complete document upload/download cycle: adding the Upload widget, capturing the file into variables, validating type and size, persisting to the database, and offering it back as a download.

## Before you start

- A Reactive Web App with at least one screen open in Service Studio
- A Document entity in the Data tab with attributes: Id (Long Integer), Name (Text), Content (Binary Data), MimeType (Text), UploadedAt (DateTime)
- Basic knowledge of Server Actions and how to call them from Client Actions

## Step-by-step guide

### 1. Add the Upload widget and bind file variables

Open your screen in the Screen Editor. In the Toolbox, search for 'Upload' and drag it onto the canvas inside a Form widget. In the Properties Panel:
- FileContent: create a new Local Variable 'FileContent' of type Binary Data
- FileName: create a new Local Variable 'FileName' of type Text
- Accept: set to '.pdf,.doc,.docx' to restrict file types at the browser level (this is advisory only — always validate server-side too)
- MaxFileSize: set to 10485760 (10 MB, expressed in bytes)

After the user picks a file, OutSystems automatically populates FileContent and FileName from the browser file picker.

**Expected result:** An Upload widget is visible on the screen. Opening the app and clicking the widget opens the OS file picker filtered to PDF and Word documents.

### 2. Create the Document entity in the Data tab

If you have not already created the entity: Data tab → Entities → Database → right-click → Add Entity. Name it 'Document'. Right-click the entity → Add Entity Attribute for each field:
- Name: Text, Is Mandatory: Yes
- Content: Binary Data, Is Mandatory: Yes
- MimeType: Text, Is Mandatory: Yes, Length: 100
- UploadedAt: DateTime, Is Mandatory: Yes

The Id attribute (Long Integer, auto-increment) is created automatically. TrueChange will show no errors after this step.

**Expected result:** Document entity appears in the Entity Diagram with five attributes. OutSystems auto-creates CreateDocument, UpdateDocument, DeleteDocument, GetDocument Server Actions.

### 3. Create a Server Action to save the uploaded file

Logic tab → Server Actions → right-click → Add Server Action. Name it 'SaveDocument'. Add input parameters:
- FileContent: Binary Data
- FileName: Text
- MimeType: Text

Action flow: Start → CreateDocument → Assign DocumentId = CreateDocument.Id → End

For the CreateDocument call (drag from Toolbox or Logic tab), set the entity record attributes:
- Document.Name = FileName
- Document.Content = FileContent
- Document.MimeType = MimeType
- Document.UploadedAt = CurrDateTime()

Add an Output Parameter 'DocumentId' (Long Integer) to return the new record Id.

```
/* CreateDocument node attribute mapping */
Document.Name        = FileName
Document.Content     = FileContent
Document.MimeType    = MimeType
Document.UploadedAt  = CurrDateTime()
```

**Expected result:** SaveDocument Server Action compiles with no TrueChange errors. It accepts a binary file and creates a Document record.

### 4. Validate file type and size in the submit Client Action

Add an Upload button to the screen. Right-click → Add OnClick Client Action. In the Action Flow Editor:

Start
  → If: FileContent = NullBinaryData() → [True] show Message 'Please select a file' → End
  → If: not (Index(ToLower(FileName), ".pdf", 0, False, True) >= 0
          or Index(ToLower(FileName), ".doc", 0, False, True) >= 0
          or Index(ToLower(FileName), ".docx", 0, False, True) >= 0)
    → [True] set UploadWidget.Valid = False, UploadWidget.ValidationMessage = 'Only PDF and Word files are accepted' → End
  → Assign: MimeType = If(Index(ToLower(FileName), ".pdf", 0, False, True) >= 0, "application/pdf", "application/vnd.openxmlformats-officedocument.wordprocessingml.document")
  → SaveDocument (Server Action) with FileContent, FileName, MimeType
  → Message: 'File uploaded successfully' (Success)
  → End

```
/* File type check expression */
not (
  Index(ToLower(FileName), ".pdf", 0, False, True) >= 0
  or Index(ToLower(FileName), ".doc", 0, False, True) >= 0
  or Index(ToLower(FileName), ".docx", 0, False, True) >= 0
)

/* MIME type assignment */
MimeType = If(
  Index(ToLower(FileName), ".pdf", 0, False, True) >= 0,
  "application/pdf",
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
)
```

**Expected result:** Uploading a disallowed file type shows the validation error inline. Uploading a valid PDF or Word document creates a Document record in the database.

### 5. Build the Download Server Action

Logic tab → Server Actions → right-click → Add Server Action. Name it 'DownloadDocument'. Add input parameter: DocumentId (Long Integer).

Action flow:
Start
  → GetDocument (use the auto-created GetDocument entity action) with Id = DocumentId
  → If: GetDocument.Document.Id = NullIdentifier() → [True] raise User Exception 'Document not found'
  → Download node (drag from Toolbox → System category)
      FileContent: GetDocument.Document.Content
      FileName: GetDocument.Document.Name
      MimeType: GetDocument.Document.MimeType
  → End

The Download node triggers the browser's native download dialog. It does not return — execution ends after the browser receives the file.

```
/* Download node properties */
FileContent: GetDocument.Document.Content
FileName:    GetDocument.Document.Name
MimeType:    GetDocument.Document.MimeType
```

**Expected result:** Calling DownloadDocument with a valid DocumentId triggers a browser file download with the stored filename and correct MIME type.

### 6. Add a Download button to the document list

On a screen showing a list of documents (Table or List widget bound to a GetDocuments Aggregate), add a Button or Link widget in each row. Set its OnClick to a new Client Action. In that Client Action:

Start → DownloadDocument (call Server Action) with DocumentId = GetDocuments.List.Current.Document.Id → End

Note: Download Server Actions must be called from Client Actions. You cannot call a Download action directly from a Server Action that is part of a navigation flow — it must be triggered by a user interaction.

**Expected result:** Clicking the Download button on a list row triggers the browser download dialog with the correct filename.

## Complete code example

File: `FileUploadDownload_actions.txt`

```outsystems-pseudocode
/* ============================================================
   ENTITY: Document
   Attributes:
     Id          : Long Integer (auto PK)
     Name        : Text (mandatory)
     Content     : Binary Data (mandatory)
     MimeType    : Text (mandatory, length 100)
     UploadedAt  : DateTime (mandatory)
   ============================================================ */

/* ============================================================
   SERVER ACTION: SaveDocument
   Input:  FileContent (Binary Data), FileName (Text), MimeType (Text)
   Output: DocumentId (Long Integer)
   ============================================================ */
Start
  --> CreateDocument:
        Document.Name       = FileName
        Document.Content    = FileContent
        Document.MimeType   = MimeType
        Document.UploadedAt = CurrDateTime()
  --> Assign: DocumentId = CreateDocument.Id
  --> End

/* ============================================================
   SERVER ACTION: DownloadDocument
   Input:  DocumentId (Long Integer)
   ============================================================ */
Start
  --> GetDocument: Id = DocumentId
  --> If: GetDocument.Document.Id = NullIdentifier()
       [True] --> Raise UserException: "Document not found"
       [False]--> Download:
                    FileContent = GetDocument.Document.Content
                    FileName    = GetDocument.Document.Name
                    MimeType    = GetDocument.Document.MimeType
  --> End

/* ============================================================
   CLIENT ACTION: ButtonUploadOnClick
   ============================================================ */
Start
  --> If: FileContent = NullBinaryData()
       [True] --> Message: "Please select a file" (Warning) --> End
  --> If: not valid extension
       [True] --> Assign: UploadWidget.Valid = False
                          UploadWidget.ValidationMessage = "Only PDF and Word files accepted"
             --> End
  --> Assign: MimeType = (extension-based expression)
  --> SaveDocument: FileContent, FileName, MimeType
  --> Message: "File uploaded successfully" (Success)
  --> End
```

## Common mistakes

- **Calling the Download action from a Server Action inside a navigation flow** — undefined Fix: The Download action must be called from a Client Action triggered by a user interaction (button click). Calling it from a background Server Action will not reach the browser. Structure: Button OnClick → Client Action → calls DownloadDocument Server Action.
- **Storing only the FileName without the MimeType in the entity** — undefined Fix: Without MimeType, you cannot serve the correct Content-Type header, so the browser may not know how to handle the file. Always store MimeType in a separate entity attribute and pass it to the Download action.
- **Checking file type using FileName = '.pdf' instead of Index()** — undefined Fix: Simple equality checks on FileName will only match files literally named '.pdf'. Use Index(ToLower(FileName), '.pdf', 0, False, True) >= 0 to check if the extension appears anywhere in the filename.
- **Forgetting to set the Upload widget's FileContent and FileName Variable properties** — undefined Fix: If Variable is not set, the picked file is not bound to any local variable and FileContent remains NullBinaryData(). Always bind both FileContent and FileName in the Upload widget Properties Panel.

## Best practices

- Always validate file type server-side in addition to the browser-level Accept property — the Accept filter is bypassed by drag-and-drop in many browsers.
- Store MIME type alongside the binary content so downloads are served with the correct Content-Type header.
- For large files (over 5 MB), consider chunked upload via a REST API instead of the standard Upload widget, which holds the entire file in memory.
- Set a maximum upload size both in the Upload widget's MaxFileSize property and in Service Center → Administration → Server Configuration → Max Request Content Length.
- Use NullBinaryData() to check for an empty upload rather than checking the length of the binary variable.
- Consider adding a file size display after upload by reading the Length(FileContent) expression to give users feedback before they submit.

## Frequently asked questions

### Is there a file size limit for the Upload widget in OutSystems?

By default OutSystems Platform Server allows requests up to 28.6 MB. You can increase this in Service Center → Administration → Server Configuration → Max Request Content Length. The Upload widget's MaxFileSize property enforces a client-side limit in bytes before the file is sent to the server. Set both to be consistent.

### How do I display an uploaded image on screen instead of downloading it?

Bind an Image widget's URL property to a server-provided URL that streams the binary content, or use the Image widget's Content property to bind a Binary Data expression directly (supported for screen-resident data). For images stored in the database, the easiest approach is to set Image.Content = Document.Content after fetching the record.

### Can I upload multiple files at once with the Upload widget?

The standard Upload widget supports single file selection only. For multi-file upload, use the OutSystems UI FileUpload pattern from Forge, or implement a loop where the user selects and uploads files one at a time. The FilePicker Forge component provides drag-and-drop multi-file support.

### Does file upload work the same way in ODC?

Yes, the Upload widget, Binary Data type, and Download action are identical in ODC. The only difference is where you configure the maximum request size — in ODC this is managed via ODC Portal settings rather than Service Center.

---

Source: https://www.rapidevelopers.com/outsystems-tutorial/outsystems-file-upload-download
© RapidDev — https://www.rapidevelopers.com/outsystems-tutorial/outsystems-file-upload-download
