Binary Storage
Fire Arrow Server integrates with Azure Blob Storage to store binary files (images, documents, PDFs, etc.) alongside your FHIR resources. Binary data is referenced in FHIR resources using firearrow:// URLs, which the server automatically resolves to time-limited, pre-signed Azure Blob URLs when clients read the resources.
How It Works
- Upload a file via
$binary-upload, receiving afirearrow://URL - Store the URL in any FHIR resource field (e.g.,
Patient.photo.url,DocumentReference.content.attachment.url) - Read the resource - Fire Arrow automatically replaces
firearrow://URLs with time-limited pre-signed Azure Blob URLs
Uploading Files
Multipart Upload
The most common approach for uploading files from client applications:
curl -X POST http://localhost:8080/fhir/\$binary-upload \
-H "Authorization: Bearer <your-token>" \
-F "resourceReference=Patient/123" \
-F "[email protected]"
resourceReference- the FHIR resource this binary belongs to (used for authorization)file- the binary file to upload
JSON Upload (Base64)
For programmatic uploads or when multipart isn't convenient:
curl -X POST http://localhost:8080/fhir/\$binary-upload \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceReference": "Patient/123",
"data": "iVBORw0KGgoAAAANSUhEUg...",
"contentType": "image/jpeg"
}'
Upload Response
Both upload methods return a FHIR Parameters resource:
{
"resourceType": "Parameters",
"parameter": [
{ "name": "url", "valueUrl": "firearrow://my-container/a1b2c3d4-photo.jpg" },
{ "name": "contentType", "valueString": "image/jpeg" },
{ "name": "size", "valueInteger": 204800 }
]
}
Use the url value in your FHIR resources:
curl -X PUT http://localhost:8080/fhir/Patient/123 \
-H "Content-Type: application/fhir+json" \
-H "Authorization: Bearer <your-token>" \
-d '{
"resourceType": "Patient",
"id": "123",
"name": [{ "family": "Smith", "given": ["Jane"] }],
"photo": [{
"contentType": "image/jpeg",
"url": "firearrow://my-container/a1b2c3d4-photo.jpg"
}]
}'
Starting with Fire Arrow Server 2.0.0, uploads are content-addressed: the stored blob is named from the server-computed hash of the uploaded bytes. Retrying an upload of identical bytes for the same resource returns the same firearrow:// URL instead of creating a second copy, so a client retry after a network error is safe. Existing firearrow:// URLs (including the earlier UUID-style names) continue to work unchanged, and no blob migration is required.
Content-Type Validation
Up to and including 1.x, $binary-upload accepted any content type by default. Starting with Fire Arrow Server 2.0.0, uploads are validated against an allow-list, and for sniffable types the declared content type is checked against the file's magic bytes. The default allow-list is:
image/png, image/jpeg, image/gif, image/webp, application/pdf, text/plain
An upload whose content type is not on the list (or whose bytes do not match the declared type) is rejected. Deployments that need to accept other content types must either extend the allow-list or set allow-all-content-types: true to restore the previous accept-everything behavior.
fire-arrow:
binary-storage:
allowed-content-types:
- image/png
- image/jpeg
- application/pdf
- application/dicom
allow-all-content-types: false # set true to accept any content type (legacy behavior)
FHIR Binary Resources
The firearrow:// mechanism above covers binary content referenced from Attachment.url fields. Starting with Fire Arrow Server 2.0.0, large FHIR Binary resources are also offloaded to Azure Blob Storage (rather than stored inline in the database), using HAPI FHIR's binary-storage extension point. This activates when fire-arrow.binary-storage.enabled: true and HAPI's own binary storage is left off (hapi.fhir.binary_storage_enabled: false, the default). Binary resources created before the upgrade continue to read normally; only new content above the size threshold is written to Azure. No migration is required.
Reading Resources with Binary URLs
When you read a resource containing firearrow:// URLs, Fire Arrow Server automatically replaces them with time-limited, pre-signed Azure Blob Storage URLs:
curl http://localhost:8080/fhir/Patient/123 \
-H "Authorization: Bearer <your-token>"
Response (note the resolved URL):
{
"resourceType": "Patient",
"id": "123",
"name": [{ "family": "Smith", "given": ["Jane"] }],
"photo": [{
"contentType": "image/jpeg",
"url": "https://myaccount.blob.core.windows.net/my-container/a1b2c3d4-photo.jpg?sv=2021-06-08&se=2026-01-15T10%3A02%3A00Z&sig=..."
}]
}
The pre-signed URL is valid for a limited time (default: 120 seconds). After expiration, clients need to re-read the resource to get a fresh URL.
Pre-signed URLs are generated on every read, so clients should fetch the resource just before they need to access the binary content rather than caching the URL long-term.
Authorization
Binary upload is governed by a single binary-upload authorization rule per client role. The validator named on the rule supplies the per-resource scope, so the same rule both permits the $binary-upload operation and decides whether the caller may attach a binary to the resource named in resourceReference. A separate update rule is not required for upload authorisation.
Example rules:
fire-arrow:
authorization:
validation-rules:
- client-role: "Patient"
resource: "Binary"
operation: "binary-upload"
validator: "PatientCompartment"
- client-role: "Practitioner"
resource: "Binary"
operation: "binary-upload"
validator: "LegitimateInterest"
Up to and including Fire Arrow Server 1.9.1, the resource-scope check for $binary-upload was not consistently applied — the binary-upload rule alone effectively decided access regardless of which resource was named in resourceReference. The validator on the binary-upload rule now governs both the operation permission and the per-resource scope. Re-check your binary-upload rules' validators to confirm they reflect the access you actually intend each client role to have.
Starting with Fire Arrow Server 2.0.0, a binary-upload rule with resource: "*" is rejected at startup, as it is for token-generation rules. Enumerate the concrete resource types that binaries may be attached to as separate rules.
See the Media and Binary Storage how-to for a complete end-to-end example with authorization rules, uploads, and file management. For the full $binary-upload operation reference, see Custom Operations.
Configuration
Configure binary storage under the fire-arrow.binary-storage key in your application.yaml:
fire-arrow:
binary-storage:
enabled: true
azure:
connection-string: "${AZURE_STORAGE_CONNECTION_STRING}"
container-name: "fhir-binaries"
max-file-size: 10485760 # 10 MB
pre-signed-url-expiration-seconds: 120
| Property | Description | Default |
|---|---|---|
enabled | Enable binary storage | false |
azure.connection-string | Azure Blob Storage connection string | -- |
azure.container-name | Blob container name | -- |
azure.max-file-size | Maximum upload size in bytes | 10485760 (10 MB) |
azure.pre-signed-url-expiration-seconds | How long pre-signed URLs remain valid | 120 |
allow-all-content-types | Accept any content type on upload (legacy behavior). When false, uploads are restricted to allowed-content-types. (2.0.0+) | false |
allowed-content-types | Allow-list of accepted upload content types when allow-all-content-types is false (2.0.0+) | image/png, image/jpeg, image/gif, image/webp, application/pdf, text/plain |
Additional options (2.0.0+)
Fire Arrow Server 2.0.0 adds several optional settings under fire-arrow.binary-storage; all have safe defaults and require no change for existing deployments:
| Property | Description | Default |
|---|---|---|
azure.client.request-timeout-seconds | Per-request timeout for Azure Blob calls | 30 |
azure.client.retry-max-attempts | Bounded retry attempts for Azure Blob calls | 4 |
presigned-url.delegation-key-refresh-seconds | How often the cached Azure User Delegation Key is refreshed (managed-identity deployments). Must be ≥ pre-signed-url-expiration-seconds. | 300 |
presigned-url.bind-to-ip | Bind issued pre-signed URLs to the requesting client IP | false |
gc.enabled | Enable the scheduled orphan-blob garbage collector | false |
gc.dry-run | When the GC is enabled, log candidate deletions without deleting | true |
Using Managed Identity
For production deployments on Azure, you can use managed identity instead of connection strings. Replace the connection-string with your storage account's endpoint:
fire-arrow:
binary-storage:
enabled: true
azure:
endpoint: "https://myaccount.blob.core.windows.net"
container-name: "fhir-binaries"
When endpoint is provided instead of connection-string, Fire Arrow Server uses Azure's DefaultAzureCredential chain, which supports managed identity, environment variables, Azure CLI credentials, and other standard authentication methods.
Environment Variable Overrides
| YAML Path | Environment Variable |
|---|---|
fire-arrow.binary-storage.enabled | FIRE_ARROW_BINARY_STORAGE_ENABLED |
fire-arrow.binary-storage.azure.connection-string | FIRE_ARROW_BINARY_STORAGE_AZURE_CONNECTION_STRING |
fire-arrow.binary-storage.azure.container-name | FIRE_ARROW_BINARY_STORAGE_AZURE_CONTAINER_NAME |
fire-arrow.binary-storage.azure.max-file-size | FIRE_ARROW_BINARY_STORAGE_AZURE_MAX_FILE_SIZE |