Content Retention

Content retention lets an administrator set a per-virtual folder policy that automatically deletes files older than a chosen age. It is designed for the common regulatory pattern — transaction files purged after 90 days, log archives after 30, receipts kept for seven years. Retention is off by default; existing folders behave exactly as before the feature was introduced until an operator enables it explicitly.

Retention is available on every edition. It is a compliance feature, not a paid tier.

The four dimensions

A retention policy is defined by four values plus a master toggle. All five settings live on the virtual folder itself and are edited from the Virtual Folders tab on the Web Admin Console, the Swing Admin Console, or the CLI.

SettingValuesWhat it means
retention_enabledon / offMaster toggle. When off, the other four values are preserved but ignored, so a temporary pause does not lose the operator's settings.
retention_daysinteger, minimum 3Files older than this many days are staged for deletion.
retention_anchorCREATED or MODIFIEDWhich timestamp determines "old". CREATED uses the file's creation time; MODIFIED uses the last-modified time.
retention_scopeRECURSIVE or ROOT_ONLYWhether the whole tree under the folder is swept, or only the files directly under the folder's real path.
retention_patternJava regex, optionalIf set, only paths matching the pattern are candidates. The match is against the file's path relative to the folder root, with forward slashes on every OS.

Regex examples

.*\.log$                — only .log files
archive/.*              — everything under archive/
(?i).*/invoice-.*\.pdf  — invoices (case-insensitive)
.*\.tmp$                — cleanup for stray .tmp files

The pattern is a full-anchor match (Matcher.matches()) so you do not need a leading ^ or trailing $. Blank pattern matches every file.

The 24-hour grace bin

Deletion is a two-step operation. When a file becomes eligible, the retention worker moves it into a hidden .retention/ staging area sitting on the same volume as the folder's real path. The file stays there for 24 hours before it is finally deleted from disk. During that grace window an administrator can:

The grace bin is what makes retention safe. A typo in the policy — wrong day count, too broad a regex — does not delete anything for a full day. The bin also cannot be bypassed: even a force-delete goes through the same audit trail.

The staging area is a hidden .retention/ directory under the data folder (${STYLUS_SFTPSERVER_DATA}/.retention/). Each staged file mirrors its original physical path with the drive letter URL-encoded (F: becomes F%3A) and a millisecond-precision UTC timestamp appended to the leaf. Example:

Original:  F:\FTPRoots\uploads\acme\foo.pdf
Staged:    <data>\.retention\F%3A\FTPRoots\uploads\acme\foo.pdf.2026-07-22-14-30-15-123

Mirroring the physical layout means an operator browsing the staging area on disk can always tell which file came from where without a DB lookup. The timestamp suffix guarantees uniqueness even if a file with the same path is re-uploaded and re-retained later. The staging root uses the same drive-letter-encoding convention as the upload .staging/ directory.

End-user visibility in the File Portal

Retention is configured by administrators, but the users whose files it governs can see it too. The Web File Transfer Portal file grid includes an Expires column: for every file subject to a retention policy it shows the date the file is scheduled to be deleted — the file's created or last-modified time (per the policy's anchor) plus the retention period. The value is shown in the user's local time zone with a relative hint and a colour cue (red within 24 hours, amber within three days). Files not subject to any policy, and all folders, show a dash.

The Expires date always reflects the retention rule that actually applies to the file — the same basis the worker uses to decide what to delete — so it is shown correctly even when a user reaches the file by browsing a shared folder that sits above the folder carrying the policy. This gives end users a chance to download or refresh a file before it ages out.

What the worker skips

The retention worker never touches:

Files whose name ends in .tmp are not skipped — they are subject to the same policy as any other file. If you specifically want to target .tmp files for retention (e.g. to clean up abandoned third-party staging leftovers), set the file-path pattern to .*\.tmp$.

Editing a policy from the Web Admin Console

  1. Open the Virtual Folders tab.
  2. Select the folder in the top grid.
  3. Click Edit on the folder toolbar. The Content retention section is at the bottom of the dialog.
  4. Tick Enable content retention to unlock the four gated fields.
  5. Set the values and click Save.

The Grace bin (retention pending) tab, below the folders grid, shows the current staging area for the selected folder. Each row lists the original relative path, the file size, when it was staged, when it will be deleted, and the reason the worker picked it. Use the Restore or Delete Now buttons to act on a row.

Editing a policy from the Swing Admin Console

Same shape as the Web version. Select the folder in the left list, scroll to the Content retention section on the right, tick the master checkbox, adjust the values, and click Save folder. The grace bin is a sibling tab of Groups granted access under the folder detail pane.

Editing a policy from the CLI

admin folder-retention /releases/ --enable --days 30 --anchor MODIFIED --scope RECURSIVE
admin folder-retention /releases/ --enable --days 90 --pattern ".*\.log$"
admin folder-retention /releases/ --disable

When --disable is used the days / anchor / scope / pattern values are preserved, so re-enabling later picks up right where it left off. That is by design.

Grace bin actions:

admin folder-retention-pending /releases/
admin folder-retention-restore /releases/ 42
admin folder-retention-delete  /releases/ 42

The integer argument in the last two commands is the retention_id shown in the pending list.

The Content Retention admin page

Both admin consoles carry a dedicated Content Retention page (between Virtual Folders and Audit). It gives an at-a-glance view of the worker's health, the current schedule, and everything in the grace bin across every VD.

Summary

The summary block at the top of the page shows:

Recent runs

A history table lists the last 50 worker ticks with started/ended timestamps, duration, and the number of files staged, reaped, and warnings per tick. The Origin column tags each row as Scheduled or On demand so a Run-Now action is distinguishable from a normal timer tick.

Run Now

The Run Now button next to Next run fires one tick immediately. It does not shift the scheduled cadence — the next scheduled tick still fires at its original time. The row it writes to the run history is marked ON_DEMAND so the summary card's Last-run / Next-run values keep reflecting the timer's real cadence.

Run Now is useful for testing a freshly-changed policy (no need to wait up to an hour to see whether a file gets staged), for clearing a backlog that has piled up during off-hours, or for QA workflows on non-production installs.

Global Grace Bin

The Grace Bin tab is a global view of every staged file across every VD, sorted so the most-urgent rows appear first. Columns: Folder, Path, Size, Staged at, Delete after, Countdown, and Reason.

The Countdown column is the key operator-facing signal. It is colored:

A Show filter above the table restricts the list to items that will be reaped within a chosen horizon (1 h / 6 h / 24 h / All). Server-side paging keeps payloads bounded regardless of backlog size; the default page holds 500 rows.

The row toolbar carries the same Restore and Delete Now actions as the per-VD grace bin: Restore atomically moves the file back to its original location, Delete Now force-reaps before the grace window ends. Both actions are audited.

CLI equivalents

admin retention-status                     # summary + last 20 runs
admin retention-run                        # Run Now — one ON_DEMAND tick
admin retention-pending-all                # global Grace Bin, terminal-formatted
admin retention-pending-all --reaps-in 6   # filter: reaps in the next 6 h
admin retention-pending-all --limit 200    # up to 200 rows

Audit

Four event types are emitted:

EventWhenTarget
FOLDER_RETENTION_UPDATEDPolicy changedFolder path
FILE_STAGED_FOR_RETENTIONWorker moved a file into the grace binOriginal absolute path
FILE_RESTORED_FROM_RETENTIONAdmin restored a staged fileRestored path
FILE_DELETED_BY_RETENTIONFile removed from disk (grace window expired or admin force-delete)Staged path

Every event records the actor (the RETENTION_WORKER for automatic actions, the administrator's username for manual ones). See the Audit & Logging chapter for the full list of audit event types.

Scheduling

The retention worker runs every hour by default. Setting a policy or changing one takes effect on the next tick; there is no need to restart the server. If the grace bin has entries whose 24-hour window has expired, they are reaped on the same tick.

The interval can be shortened for testing with the JVM system property stylus.sftpserver.retention-worker.disabled=true (disables the worker entirely). No knob is exposed for a shorter interval in production; the once-per-hour cadence is fine even for folders with millions of files because the walker skips the whole tree if the policy is off.

Edge cases

ScenarioBehaviour
Invalid regex saved when disabledRejected. The regex is compiled even in the off state so a broken pattern cannot lie in wait.
retention_days below 3Rejected. Enforced by the server and the UI. The floor guards against typos like 0 or 1 deleting an entire folder immediately.
Grace bin is on a different volume than the folderThe worker requires the .retention/ directory to share a filesystem with the folder's real path so the move can be atomic. If that is not possible (rare on Windows with symlinked drives), the worker logs a warning and skips the file until the layout is fixed.
Folder deleted while items are in the grace binThe delete is refused until the pending items are restored or reaped. The admin console surfaces this as an error.
Two admins edit the same policy at onceLast write wins. No lock is taken; the whole edit is a single row update.

See also