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.0Register 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/BlobsFor 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 typeStream. Use it when a later step must consume stored contents.Save file — accepts
Data(object),Path(string), andAppend(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:
Pathwith a provider path, preferably derived from controlled workflow data.For
Save file,Datawith a literal, expression, stream-producing activity result, byte array, form file, string, or supported collection.For
Save file,Appendexplicitly 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
Pathas 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