File Sharing and Storage

Warning

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.

Already set up?

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.frontendUrl must be set to your instance URL
  • For email notifications when sharing: mail.enabled: true with valid SMTP configuration

Enabling File Storage#

yaml
storage:
  enabled: true
  provider: local          # 'local', 'database', or 's3'
  local:
    basePath: './storage'  # Filesystem path (local provider only)
bash
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
Tip

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:

yaml
volumes:
  - ./stirling-storage:/storage

S3-Compatible Object Storage#

Info

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).

yaml
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
bash
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.
Warning

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#

yaml
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
bash
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.

yaml
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)
bash
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).

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}
Info

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.frontendUrl to 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: true in your settings
  • Verify security.enableLogin: true
  • Verify storage.sharing.linkEnabled: true
  • Verify system.frontendUrl is set to your instance URL

"Email sharing is disabled"#

  • Verify storage.sharing.emailEnabled: true
  • Verify mail.enabled: true with valid SMTP settings
  • 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, or maxFileMb, 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)