For the complete documentation index, see llms.txt. This page is also available as Markdown.

File Storage

Read and write files from Elsa 3.8.0 workflows with a configurable storage provider.

The Elsa.Storage.Files extension adds Open file and Save file to the Elsa / Storage category. Use them when a workflow needs to read or write a file through a configured FluentStorage.Blobs.IBlobStorage provider.

These activities run on the Elsa Server. Elsa Studio only edits their inputs; it does not read the browser's filesystem or move the data itself. The file storage provider is separate from Elsa's workflow-instance persistence: a file written by Save file is not automatically a workflow variable or a workflow-definition artifact.

Install and register the extension

Install the package in the application that executes workflows:

dotnet add package Elsa.Storage.Files --version 3.8.0

Register the module during Elsa configuration:

using Elsa.Extensions;

builder.Services.AddElsa(elsa =>
{
    elsa.UseFileStorage();
});

UseFileStorage() registers the two activities and an IBlobStorageProvider. The provider wrapper keeps the file-storage registration distinct from any other IBlobStorage services in the host.

Choose the storage backend

With no configuration, the feature creates a directory-backed provider rooted at:

{temporary-directory}/Elsa/Storage/Blobs

For a different FluentStorage backend, use the overload that supplies a factory. This example keeps the same directory provider but chooses an application-controlled root:

The factory receives the application's IServiceProvider, so it can select or construct a provider from host configuration. UseFileStorage only wires the provider used by these activities; it does not provision directories, cloud containers, credentials, or an application-specific retention policy. Those responsibilities belong to the selected storage backend and host.

Choose an activity

  • Open file — accepts Path (string) and returns an activity result of type Stream. Use it when a later step must consume stored contents.

  • Save file — accepts Data (object), Path (string), and Append (bool), and has no output. Use it to write one value or a collection of values.

Path is the blob or provider path passed to IBlobStorage; its exact mapping to a physical or remote location is defined by the selected provider. It is not an Elsa workflow-instance ID and it is not automatically made unique.

Open a file

Open file resolves Path and calls the provider's OpenReadAsync method. Its result is an open Stream that downstream activities can read. The activity does not convert the result to text, JSON, or a byte array.

For example, a programmatic workflow can bind the result to a later activity:

The underlying IBlobStorage contract allows a provider to return no stream when the path does not exist, and providers can surface their own storage errors. Treat a missing or unreadable path as an expected failure case in workflow design rather than assuming that every path is present.

Save a file

Save file resolves Data, converts it to a stream, and calls IBlobStorage.WriteAsync(Path, stream, Append, cancellationToken). The release supports these Data shapes:

Data value

Conversion in 3.8.0

Stream

Passed directly to the storage provider.

byte[]

Wrapped in a MemoryStream.

IFormFile

Read through OpenReadStream().

string

Encoded as UTF-8 bytes. It is treated as content, not as a path.

IEnumerable with one item

The item is converted recursively.

IEnumerable with zero or multiple items

Written as a generated ZIP stream.

Unsupported values cause the activity to throw NotSupportedException. Prefer a stream for large content when the producing activity already has one; the byte-array and generated-ZIP paths allocate additional in-memory representations.

Single file example

This example writes UTF-8 text to the configured provider:

When the activity receives a Stream, the stream is what the provider reads. Make sure a stream produced by an earlier activity is still open and positioned for reading when Save file runs. The activity does not expose a saved URI, byte count, ETag, or other provider metadata as an output.

Multiple values become a ZIP

When Data is an enumerable with more than one item, Save file creates a ZIP archive in memory and writes that archive to the single Path. Entries are named file-0.bin, file-1.bin, and so on; the original item filenames are not retained by this implementation. An empty enumerable follows the same archive path. A one-item enumerable is unwrapped and written using that item's normal conversion rules.

There is an important 3.8.0 limitation: the implementation does not dispose or otherwise finalize its ZipArchive before returning the MemoryStream. The generated collection archive can therefore be incomplete when the storage provider reads it. Do not use this collection form for a portable ZIP contract without verifying the resulting archive. When archive validity or entry names matter, build and finalize the archive in a dedicated/custom step, then pass the completed stream or byte array to Save file.

Use an explicit archive-building activity when entry names, compression settings, archive layout, or archive validity are part of the business contract. Use Save file's collection behavior only when its 3.8.0 generated archive limitation is acceptable.

Append behavior

Append defaults to false when it is not supplied. The value is passed to the provider as the append argument: false requests a normal write and true requests append behavior where the selected provider supports it. Confirm the provider's overwrite and append semantics before relying on them for audit logs, retries, or concurrent workflow instances.

Use the activities in Elsa Studio

After the server is configured and restarted, connect Studio to that server and search the activity picker for Open file or Save file in Storage. Configure:

  1. Path with a provider path, preferably derived from controlled workflow data.

  2. For Save file, Data with a literal, expression, stream-producing activity result, byte array, form file, string, or supported collection.

  3. For Save file, Append explicitly when the workflow depends on its behavior.

Studio gets activity descriptors from the connected server. Installing the package only in a Studio host does not make these activities executable; the Elsa Server must register the extension and provide the storage provider.

Security and operations

  • Treat Path as untrusted when it is derived from HTTP input, messages, or user-entered workflow data. The Elsa activities do not add authorization or path validation; validate allowed prefixes, names, and lengths at the workflow boundary and choose a provider with suitable isolation.

  • The default root is under the process temporary-directory path. It is useful for development, but it is not a durable or shared-storage policy for a multi-node deployment. Select a durable, shared provider when instances can resume on another node or when files must survive host replacement.

  • Storage access happens on the server. Keep local paths, provider endpoints, and credentials in server configuration; do not put secrets into workflow definitions or expose them through Studio inputs.

  • A stream returned by Open file is an execution-time resource. If a workflow waits or dispatches work, persist a provider path or durable file identifier rather than assuming the open stream remains usable.

  • Plan for retries and duplicate execution. Choose deterministic or unique paths deliberately, and decide whether overwriting or appending is safe for the workflow's idempotency requirements.

Troubleshooting

The activities are missing from Studio

Confirm that Elsa.Storage.Files is installed in the execution host, that UseFileStorage() runs during Elsa configuration, and that Studio is connected to that server.

A file cannot be opened

Check the resolved provider path, the server's access to the selected backend, and the provider's missing-file behavior. A path in Studio is not a path on the Studio user's computer.

The saved file is a ZIP or has unexpected content

Check the runtime type of Data. Multiple enumerable items intentionally become a generated ZIP, and a string is written as UTF-8 content rather than being interpreted as a source filename. In 3.8.0, the generated ZIP is not explicitly finalized; use a dedicated/custom archive step when the consumer requires a valid portable ZIP.

A retry changes the file unexpectedly

Review Append, path uniqueness, and the selected provider's write semantics. The activity does not add a separate deduplication or transaction policy.

Release source

The behavior described here is based on release/3.8.0 in the following repositories:

Last updated