# Handling Files in Frends

File-based data exchange is one of the most common integration patterns — systems drop files into directories, other systems pick them up, transform them, and route them onward. Frends has strong support for this pattern, from watching directories and triggering Processes automatically, to reading, transforming, writing, and uploading files to remote servers. This guide walks through a typical file handling flow end to end: starting a Process when a file arrives, finding and reading the file, and finally writing and uploading the result. It also covers how to handle binary files, such as images or PDFs, where text-based reading and writing is not appropriate.

## Prerequisites

To follow this guide you need a Frends Tenant with at least one Agent that has access to the file system path you intend to monitor. For remote server transfers you also need valid credentials or SSH keys for the target SFTP or FTP server. The Tasks referenced here belong to the `Frends.Files`, `Frends.SFTP`, and `Frends.FTP` packages — make sure these are available in your environment.

## Triggering a Process on a New File

The most natural way to start a file handling Process is with the File Trigger. It monitors a local directory accessible by the Agent and starts a new Process Instance whenever one or more matching files appear and remain stable on disk. Configure the **Directory** to watch, a **File filter** wildcard pattern such as `*.xml` or `order_*.csv` to narrow which files are picked up, and a **Quiet period in seconds** to ensure the file is fully written before the Process reads it. The trigger output provides `#trigger.data.filePaths` with an array of full file paths and `#trigger.data.files` with just the file names, both of which you can pass directly to file-reading Tasks downstream.

When you need richer logic before starting the Process — such as waiting until both a data file and a corresponding control file are present, or you need to monitor an SFTP location — a Conditional Trigger is the better choice. A Conditional Trigger runs a Subprocess on a schedule and starts the main Process only when the Subprocess returns a non-empty result, giving you full control over the start condition. Refer to the Conditional Trigger reference documentation for full details.

## Finding and Reading the File

Once the Process has started, the next step is locating and reading the file content.

### Finding Files Programmatically

When you need to search for files as part of the Process logic rather than relying solely on the trigger output, use the `Frends.Files.Find` Task. Its **Directory** parameter sets the root to search from, and **Pattern** supports a glob-style syntax where `*` matches within a single path segment, `?` matches one character, and `**` matches across multiple directory levels. For example, `**\output\*\*.xml` matches any XML file inside any `output` subfolder anywhere in the tree. The Task returns a list of file objects, each with properties like `FullPath`, `FileName`, `Extension`, `SizeInMegaBytes`, `CreationTimeUtc`, and `LastWriteTimeUtc`.

Note that `Frends.Files.Find` accepts only one pattern per Task call. If you need to match multiple patterns, use separate Find Tasks and combine their results downstream.

### Downloading Files from a Remote Server

If the files reside on an SFTP server, use `Frends.SFTP.DownloadFiles` to transfer them to the Agent's local file system first. Set the **Address**, **Port**, and authentication parameters, then specify **SourceDirectory** and **SourceFileMask** to target the right files.

For the local destination, use a dedicated temporary folder that is not monitored by any File Trigger, to avoid the Process accidentally picking up its own intermediate files. On PaaS Agents, the appropriate locations are the shared Agent Group storage mounted as `F:\` on Windows or `/frends-shared-data/` on Linux, or the Agent-local storage mounted as `G:\` on Windows or `/frends-local-data/` on Linux. On self-hosted Agents, use a dedicated subfolder on a local or network-mounted path that the Agent service account has write access to.

The **SourceOperation** parameter controls what happens to the remote file after a successful download — common choices are `Delete` (remove from the server) or `Move` (archive to another remote folder). For FTP servers, the equivalent is `Frends.FTP.DownloadFiles`, which additionally has a **TransportType** setting of `Ascii` or `Binary`; use `Binary` for any file that is not plain text.

### Reading a Text File

To read the content of a local text file, use `Frends.Files.Read`. Set **Path** to the file's full path — typically from `#trigger.data.filePaths[0]` or the `FullPath` property of a Find result. Choose the correct **FileEncoding** to match the file's actual encoding; options include `UTF8`, `ASCII`, `Unicode`, and `Windows1252`. If you need a less common encoding such as `iso-8859-1`, select `Other` and provide the encoding name in **EncodingInString**. The Task returns an object whose `Content` property holds the file text as a string, ready for transformation, parsing, or mapping in subsequent Tasks.

For files that live directly on an SFTP server without a prior download step, `Frends.SFTP.ReadFile` reads the file in a single Task and returns its content as either `TextContent` or `BinaryContent` depending on the **ContentType** setting you choose.

## Writing and Uploading the Result

After your Process has processed or transformed the data, write the output to a file and optionally push it to a remote server.

### Writing a Text File Locally

`Frends.Files.Write` Task writes a string to a local file. Provide the output string in **Content**, a destination path in **Path**, and pick a **FileEncoding** that matches what the downstream system expects. When writing an intermediate file before uploading it onward, write it to a dedicated temporary folder rather than a monitored inbox or the final outbox directly — this prevents half-written files from being picked up prematurely by another Process or a File Trigger.

On PaaS Agents, use the shared storage at `F:\` (Windows) or `/frends-shared-data/` (Linux) for files that need to be accessible across multiple Agents in the same group, or the Agent-local storage at `G:\` (Windows) or `/frends-local-data/` (Linux) for files only needed by the current Agent. On self-hosted Agents, use a dedicated subfolder on a path the Agent service account controls.

The **WriteBehaviour** parameter determines what happens if a file already exists at that path: `Overwrite` replaces it, `Append` adds to the end, and `Throw` raises an error so you can handle the conflict explicitly.

### Uploading to an SFTP Server

`Frends.SFTP.UploadFiles` Task transfers one or more local files to an SFTP server. Point **SourceFilePaths** at the local files — you can feed `#trigger.data.filePaths` directly here if you want to forward the incoming files unchanged. Set **TargetDirectory** to the remote destination path. The **TargetFileName** parameter supports macros: `%SourceFileName%` inserts the original file name without extension, and `%SourceFileExtension%` inserts the extension including the dot, making it straightforward to preserve the original file name on the remote side.

**SourceOperation** applies to the local source file after a successful upload and supports `Delete`, `Move`, `Rename`, or `Nothing`. Enable **CreateTargetDirectories** if the remote directory might not exist yet. For reliable transfers, **RenameTargetFileDuringTransfer** uploads the file under a temporary name and renames it only once the transfer completes, preventing downstream systems from picking up a partial file.

For FTP, the equivalent Task is `Frends.FTP.UploadFiles`. Set **TransportType** to `Binary` when uploading anything other than plain ASCII text, including UTF-8 encoded files.

## Handling Binary Files

Not all files are text. Images, PDFs, Office documents, and compressed archives need to be treated as raw byte sequences — reading them as text would corrupt the content.

### Reading Binary Files

Use `Frends.Files.ReadBytes` Task instead of `Frends.Files.Read` when working with binary content. It shares the same **Path** parameter but returns a `ContentBytes` property of type `byte[]` rather than a string. Downstream Tasks receive the byte array and can pass it on, transform it, or write it to another file without any encoding conversion taking place.

For large binary files it is good practice to enable **Skip logging results and parameters** and **Dispose at end of scope** in the Task's advanced settings. This keeps the byte array out of the Process log and releases memory as soon as the Task result is no longer needed.

### Writing Binary Files

The counterpart to `ReadBytes` is `Frends.Files.WriteBytes`. Its **ContentBytes** parameter accepts a `byte[]` directly. It also supports the same **WriteBehaviour** options as the text-based Write Task: `Overwrite`, `Append`, or `Throw`. There is no encoding parameter, because binary data is written exactly as-is.

### Working with Base64-Encoded Binary Data

Some integration points — particularly API Triggers configured to receive raw request bodies, or REST APIs that exchange file data as JSON — represent binary content as a Base64-encoded string rather than a native byte array. Frends handles both conversion directions using standard .NET expressions in any expression field.

To decode a Base64 string into a `byte[]` for use with `Frends.Files.WriteBytes` or any Task expecting binary input, use `Convert.FromBase64String`:

```
Convert.FromBase64String(#trigger.data.httpContentBytesInBase64)
```

To go the other direction — for example when you have read a file with `Frends.Files.ReadBytes` and need to pass its content as a Base64 string to an API or include it in a JSON payload — use `Convert.ToBase64String`:

```
Convert.ToBase64String(#result.ContentBytes)
```

Both expressions can be used directly in Task input fields or inside a larger expression, such as building a JSON string that embeds the encoded file.

### Converting Between Strings and Byte Arrays

Sometimes you need to convert a plain text string into a `byte[]` — for instance, when you have built an XML or JSON string in the Process and need to pass it to `Frends.Files.WriteBytes`, or to a Task that expects binary input. Use `System.Text.Encoding` with the appropriate encoding for the content:

```
System.Text.Encoding.UTF8.GetBytes(#result.Content)
```

To go the other direction and decode a `byte[]` back into a readable string — for example after reading a text-based file with `Frends.Files.ReadBytes` — use `GetString`:

```
System.Text.Encoding.UTF8.GetString(#result.ContentBytes)
```

Replace `UTF8` with `ASCII`, `Unicode`, or `Latin1` as needed to match the actual encoding of the content. Choosing the wrong encoding here will produce garbled characters, so make sure it matches whatever the originating system wrote.

### Uploading Binary Files

Binary files are uploaded with the same `Frends.SFTP.UploadFiles` or `Frends.FTP.UploadFiles` Tasks described earlier. For FTP, ensure **TransportType** is set to `Binary`. For SFTP, the protocol handles binary transfer by default, so no extra configuration is required beyond pointing the Task at the correct source file path.

### Handling Binary Data over HTTP

Binary files often move over HTTP — either received as an incoming request body or fetched from a remote API. Frends provides two dedicated Tasks for this: `Frends.HTTP.SendBytes` for sending binary data and `Frends.HTTP.RequestBytes` for fetching binary data from a remote endpoint.

When sending a binary file as an HTTP request body, use `Frends.HTTP.SendBytes`. Set **Content** to the `byte[]` to send and configure the **Uri**, **Method**, and any required **Headers** such as `Content-Type: application/pdf` or `Content-Type: application/octet-stream` for a generic binary. The Task sends the raw bytes without any encoding transformation.

When fetching binary data from a remote API, use `Frends.HTTP.RequestBytes`. It behaves like a regular HTTP request Task but returns the response body as a `byte[]` in the result's `BodyBytes` property. You can pass that value directly to `Frends.Files.WriteBytes` to save the file locally, or convert it to a Base64 string with `Convert.ToBase64String` for embedding in a downstream payload. When your Process receives binary data through an HTTP Trigger or API Trigger configured to accept a raw request body, the incoming bytes are available as a Base64-encoded string via `#trigger.data.httpContentBytesInBase64`. Decode it back to a `byte[]` with `Convert.FromBase64String` before writing or processing the file.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.frends.com/guides/development/handling-files-in-frends.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
