File Sharing and Storage
File Sharing and Storage is currently in alpha. Functionality may change, and some features are incomplete. Use in production at your own risk.
Stirling PDF can store files on the server and let users share them with each other. Files can be shared directly with specific users or via shareable links. Admins can set storage quotas to control disk usage.
Basic local-disk storage and sharing need no license - just turn on security.enableLogin and storage.enabled. Only the database and s3 storage providers require a Pro/Enterprise license. See Modes for what each deploy mode includes; self-hosted instances never use credits.
If your admin has turned storage on and you just want to use it, skip ahead to The My Files Page and Sharing Files. The setup sections in between (storage providers, S3, quotas) are for whoever runs the server.
What You Can Do
- Store files server-side -- upload PDFs and other files that persist across sessions
- Share with specific users -- grant other registered users access to your files with configurable permissions
- Share via link -- generate a shareable link that any logged-in user with the link can access
- Control access levels -- assign Editor, Commenter, or Viewer roles to shared users
- Set storage quotas -- limit storage per user, per file, or system-wide
- Audit access -- see who accessed your shared links and when
- Automatic cleanup -- expired share links and orphaned files are cleaned up daily
Prerequisites
- Authentication must be enabled (
security.enableLogin: true) - For share links:
system.frontendUrlmust be set to your instance URL - For email notifications when sharing:
mail.enabled: truewith valid SMTP configuration
Enabling File Storage
- Settings File
- Environment Variables
storage:
enabled: true
provider: local # 'local', 'database', or 's3'
local:
basePath: './storage' # Filesystem path (local provider only)
STORAGE_ENABLED=true
STORAGE_PROVIDER=local
STORAGE_LOCAL_BASEPATH=./storage
Storage Providers
| Provider | Config Value | License | Description | Best For |
|---|---|---|---|---|
| Local Filesystem | local | Free | Files stored on disk under basePath | Most deployments, large files |
| Database | database | Pro/Enterprise | Files stored as BLOBs in the database | Simple setups where you want everything in one place |
| S3-Compatible | s3 | Pro/Enterprise | Files stored in an S3-compatible object store | Multi-node clusters, cloud object storage |
The local provider is recommended for most deployments and requires no license. It handles large files well and keeps database size manageable. The database provider is convenient but uses more memory with large files. The s3 provider is for object-storage and multi-node deployments. The database and s3 providers require a Pro/Enterprise license.
Docker Volume Mount
When using the local provider with Docker, mount the storage directory so files persist across container restarts:
volumes:
- ./stirling-storage:/storage
S3-Compatible Object Storage
The s3 storage provider requires a valid Pro or Enterprise license.
Set storage.provider: s3 to store user uploads in any S3-compatible object store. The same storage.s3.* block is also used by the cluster artifact store (see below).
- Settings File
- Environment Variables
storage:
enabled: true
provider: s3
s3:
endpoint: "" # blank = AWS regional default; otherwise full URL incl. https://
bucket: my-bucket # required
region: us-east-1
accessKey: "" # blank = fall back to AWS DefaultCredentialsProvider (env / profile / IMDS)
secretKey: ""
pathStyleAccess: false # true for MinIO and Supabase; false for AWS/R2/most CDNs
allowPrivateEndpoints: false # SSRF guard - see below
requestChecksumCalculation: WHEN_SUPPORTED # WHEN_SUPPORTED | WHEN_REQUIRED | DISABLED
responseChecksumValidation: WHEN_SUPPORTED # WHEN_SUPPORTED | WHEN_REQUIRED | DISABLED
STORAGE_ENABLED=true
STORAGE_PROVIDER=s3
STORAGE_S3_ENDPOINT=
STORAGE_S3_BUCKET=my-bucket
STORAGE_S3_REGION=us-east-1
STORAGE_S3_ACCESSKEY=
STORAGE_S3_SECRETKEY=
STORAGE_S3_PATHSTYLEACCESS=false
STORAGE_S3_ALLOWPRIVATEENDPOINTS=false
STORAGE_S3_REQUESTCHECKSUMCALCULATION=WHEN_SUPPORTED
STORAGE_S3_RESPONSECHECKSUMVALIDATION=WHEN_SUPPORTED
S3 Configuration Keys
| Key | Default | Description |
|---|---|---|
endpoint | (blank) | Blank uses the AWS regional default. For other vendors, the full URL including https://. |
bucket | (blank) | Required. The bucket that holds the stored files. |
region | us-east-1 | Region of the bucket. |
accessKey | (blank) | Static access key. Blank falls back to the AWS DefaultCredentialsProvider (env vars, profile, or IMDS). |
secretKey | (blank) | Static secret key. Used together with accessKey. |
pathStyleAccess | false | Use path-style (endpoint/bucket/key) instead of virtual-host addressing. true for MinIO and Supabase. |
allowPrivateEndpoints | false | SSRF guard. When false, an endpoint that resolves to a loopback, link-local, or private (RFC1918) address is rejected at startup. Set true to opt in (for example in-cluster MinIO). Leave false for any internet-facing vendor. |
requestChecksumCalculation | WHEN_SUPPORTED | WHEN_SUPPORTED, WHEN_REQUIRED, or DISABLED. Set WHEN_REQUIRED if your vendor rejects auto-added x-amz-checksum-* headers (older Backblaze B2, some R2 corner cases). |
responseChecksumValidation | WHEN_SUPPORTED | WHEN_SUPPORTED, WHEN_REQUIRED, or DISABLED. Set WHEN_REQUIRED if you see false-positive checksum-mismatch errors on GET from a vendor that never returns checksum headers. |
allowPrivateEndpoints defaults to false. The server resolves the configured endpoint host and refuses to start if it points at a loopback, link-local, or private IP. This blocks an admin-supplied endpoint from being pointed at the cloud metadata service (for example http://169.254.169.254/) to exfiltrate instance-role credentials. Only set it to true for a trusted in-cluster store such as MinIO.
Per-Vendor Cheat Sheet
| Vendor | endpoint | region | pathStyleAccess | Notes |
|---|---|---|---|---|
| AWS S3 | (blank) | your region | false | Uses the AWS regional default. |
| MinIO (in-cluster) | http://minio:9000 | us-east-1 | true | Also set allowPrivateEndpoints: true. |
| Cloudflare R2 | https://<acct>.r2.cloudflarestorage.com | auto | false | If uploads fail with unsupported header x-amz-checksum-*, set requestChecksumCalculation: WHEN_REQUIRED. |
| Supabase Storage | https://<project>.supabase.co/storage/v1/s3 | your project region | true | Non-ASCII display filenames are fine - the storage key is opaque. |
| Backblaze B2 | https://s3.<region>.backblazeb2.com | your region | false | On B2 deployments older than July 2025, if uploads return Unsupported header x-amz-checksum-crc32, set requestChecksumCalculation: WHEN_REQUIRED. |
| DigitalOcean Spaces | https://<region>.digitaloceanspaces.com | your region | false | 5 GB per-object cap (regardless of multipart). |
Sharing Credentials with the Cluster Artifact Store
The storage.s3.* block is used in two places: the s3 storage provider (persistent user uploads) and the cluster artifact store when cluster.artifactStore: s3 (transient multi-node job artifacts). When both use S3 they reuse the same credentials and bucket. The cluster store writes under a separate key prefix (cluster.s3.keyPrefix, default transient/) so a single bucket can host both persistent uploads and transient artifacts without collisions. Multi-node deployments must set cluster.artifactStore: s3.
Enabling File Sharing
- Settings File
- Environment Variables
storage:
enabled: true
sharing:
enabled: true # Master switch for all sharing
linkEnabled: true # Enable shareable links
emailEnabled: true # Send email notifications when sharing
linkExpirationDays: 3 # Days until share links expire
STORAGE_SHARING_ENABLED=true
STORAGE_SHARING_LINKENABLED=true
STORAGE_SHARING_EMAILENABLED=true
STORAGE_SHARING_LINKEXPIRATIONDAYS=3
Feature Dependencies
| Feature | What It Needs |
|---|---|
| File Storage | security.enableLogin: true |
| File Sharing | Storage enabled |
| Shareable Links | Sharing enabled + system.frontendUrl set |
| Email Notifications | Sharing enabled + mail.enabled: true |
| Shared Signing | Storage enabled + storage.signing.enabled: true |
Storage Quotas
Control how much storage space is available.
- Settings File
- Environment Variables
storage:
quotas:
maxStorageMbPerUser: -1 # Per-user cap in MB (-1 = unlimited)
maxStorageMbTotal: -1 # Total system cap in MB (-1 = unlimited)
maxFileMb: -1 # Max size per upload in MB (-1 = unlimited)
STORAGE_QUOTAS_MAXSTORAGEMBPERUSER=500
STORAGE_QUOTAS_MAXSTORAGEMBTOTAL=10000
STORAGE_QUOTAS_MAXFILEMB=100
Quotas are checked before a file is stored. When replacing an existing file, only the size difference counts against the quota.
Access Roles
When sharing a file, you choose what level of access to grant:
| Role | Can View/Download | Can Replace File | In Signing Workflows |
|---|---|---|---|
| Editor | Yes | Yes | Can sign |
| Commenter | Yes | No | Can sign |
| Viewer | Yes | No | Read-only |
The file owner always has full access. The default role is Editor.
The difference between Commenter and Viewer only matters in Shared Signing workflows -- both are read-only for regular file sharing.
The My Files Page
When your admin has turned storage on, you get a My Files page (find it at /files once you are logged in). It is your personal space on the server for keeping documents that stick around between sessions, instead of living only in your browser tab.
On the My Files page you can:
- Upload documents to keep them on the server
- Organize them into folders, including folders inside folders, and drag files between them
- Preview a stored file right in the browser without downloading it
- Rename, move, and delete files and folders
- Jump around quickly using the folder sidebar on the left
- Personalize folders with colors and thumbnails so they are easy to spot
- See where each file lives - every item shows a small badge telling you whether it is in your current browser session or saved on the server
Your folders are private to you. Folders themselves are not shared; you share individual files instead (see Sharing Files below).
Sharing Files
You can share a file from the My Files page, or from the Share button in the top bar of the editor workbench while a file is open.
Share with a Specific User
From the file manager, select a file and share it with another user by their username or email address. You can choose the access role when sharing.
If you enter an email address for someone who doesn't have an account, the system will create a share link and email it to them (if email notifications are enabled).
Share via Link
Generate a shareable link for any file you own. Anyone who is logged in and has the link can access the file. Links expire automatically based on your linkExpirationDays setting (default: 3 days).
You can revoke a share link at any time, which immediately removes access and deletes all access records for that link.
The share link URL follows the format:
https://your-stirling-instance.com/share/{token}
Share links require the recipient to be logged in. There is no anonymous or public access -- the link is an additional credential on top of authentication.
Access History
For any share link you've created, you can view who accessed it, whether they viewed or downloaded the file, and when.
Security
- All endpoints require authentication -- there is no anonymous file access
- Owner-only controls -- only the file owner can update, delete, or manage sharing
- Random tokens -- share link tokens are cryptographically random UUIDs
- Automatic expiration -- expired links return an error and are cleaned up daily
- Revocation -- owners can revoke any share link immediately
- Access auditing -- every share link access is recorded with user, action type, and timestamp
Known Limitations
- Share links require
system.frontendUrlto be configured - Share links require the user to be logged in -- there is no public/anonymous access
- The database storage provider uses more memory with large files (use the local provider for large deployments)
Troubleshooting
"Storage is disabled"
- Verify
storage.enabled: truein your settings - Verify
security.enableLogin: true
"Share links are disabled"
- Verify
storage.sharing.linkEnabled: true - Verify
system.frontendUrlis set to your instance URL
"Email sharing is disabled"
- Verify
storage.sharing.emailEnabled: true - Verify
mail.enabled: truewith valid SMTP settings
Share link returns 404 Not Found
- The link has expired or does not exist. Expired and missing links both return 404 Not Found. The file owner needs to create a new one.
Quota exceeded (HTTP 413)
- A file or upload that exceeds a configured quota is rejected with 413 Payload Too Large (per-file, per-user, and total-storage caps all use this status)
- Increase
maxStorageMbPerUser,maxStorageMbTotal, ormaxFileMb, or delete unused files to free up space
Developer Reference: Storage API
This section is for developers and admins automating storage outside the web app. If you just want to upload, organize, and share files, everything above is done from the My Files page - you do not need any of this.
The full storage and sharing endpoints are listed below. See API Documentation for authentication and general usage.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/storage/files | Upload file |
| PUT | /api/v1/storage/files/{id} | Update file (owner only) |
| GET | /api/v1/storage/files | List accessible files |
| GET | /api/v1/storage/files/{id} | Get file metadata |
| GET | /api/v1/storage/files/{id}/download | Download file |
| DELETE | /api/v1/storage/files/{id} | Delete file (owner only) |
| POST | /api/v1/storage/files/{id}/shares/users | Share with user |
| DELETE | /api/v1/storage/files/{id}/shares/users/{username} | Revoke user share |
| DELETE | /api/v1/storage/files/{id}/shares/self | Leave shared file |
| POST | /api/v1/storage/files/{id}/shares/links | Create share link |
| DELETE | /api/v1/storage/files/{id}/shares/links/{token} | Revoke share link |
| GET | /api/v1/storage/share-links/{token} | Access via share link |
| GET | /api/v1/storage/share-links/{token}/metadata | Get share link info |
| GET | /api/v1/storage/share-links/accessed | List your accessed links |
| GET | /api/v1/storage/files/{id}/shares/links/{token}/accesses | Access history (owner only) |
Folder Endpoints
These are the endpoints behind the folders on the My Files page. All operations are scoped to the authenticated user.
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/storage/folders | List your folders |
| POST | /api/v1/storage/folders | Create a folder |
| PATCH | /api/v1/storage/folders/{folderId} | Update a folder (name, appearance) |
| DELETE | /api/v1/storage/folders/{folderId} | Delete a folder |
| PATCH | /api/v1/storage/files/{fileId}/folder | Move a single file to a folder (or to root when folderId is null) |
| PATCH | /api/v1/storage/files/folder | Bulk-move files to a folder (up to 1000 per request) |
Related
- Shared Signing -- Collaborative multi-participant document signing
- Certificate Signing -- Individual certificate signing
- System and Security Settings -- JWT, sessions, server certificates