WikiUsing it

Files

MarkdownSource

On this page

Anything you upload — a PDF, an image, a spreadsheet, a workspace logo — is stored as a file on disk beside the database, referenced from the page it was added to, and listed in an index you can browse and search. This page covers how a file gets in, where it ends up, who can read it, what happens to the text inside a PDF, and what removing something does and does not do.

Where a file lives

Uploads go into the files folder of the server’s data directory, next to the SQLite database. Each one is stored under a random id plus its extension — 4f2c…a1.pdf, never Offer Acme.pdf.

The extension is cleaned first: lower case, letters and digits only. An upload through the browser also drops the extension entirely when it is longer than twelve characters counting the dot, so a twelve-letter extension is thirteen characters and does not survive. An upload from an agent keeps whatever it is given, cleaned of everything but letters, digits and the dot.

The name a person recognises is kept separately — in the block on the page and in the file index — which is why a file can be downloaded under its real name while the byte on disk stays anonymous.

Files are served from /files/<stored name> and that path requires a signed-in account or an API token. Three things follow from how it is served:

  • There is no directory listing. Asking for /files/ returns “not found”, so the random names cannot be enumerated.
  • Every file is delivered with a sandbox policy and X-Content-Type-Options: nosniff. An uploaded HTML or SVG file can never run a script on the application’s origin.
  • An anonymous visitor to a publicly shared page is not signed in, so images uploaded into that page do not load for them. Shared pages are text and structure; the uploads behind them stay behind the sign-in.

Adding a file to a page

In the editor

Drag a file from your desktop onto an open document. A drop that lands on the text is placed exactly where the pointer is. A drop anywhere else on the page — the wide margins, the empty space below the last block, the title — is caught by Salt, shows a Drop to add to this page overlay, and appends the file at the end of the document.

Dropping several files at once works; they upload one after another, and one failure does not stop the rest — you get the ones that worked plus a message saying why the other one failed. The message names the reason, not the file, so two oversized files in one drop produce two identical notices. More than one file added at once is confirmed with “2 files added”.

To put a file at a specific place without dragging, type / where you want it. The Media group of the slash menu carries four entries — Image, Video, Audio and File — and each inserts an empty block with its own panel. The panel has two tabs:

  • Upload opens a file picker. What you choose is stored by Salt exactly as a dropped file is: same size cap, same index, same PDF text extraction.
  • Embed takes an address instead (Enter URL, then Embed image / Embed file). Nothing is uploaded. The block points at somebody else’s server, the file appears in no file list, no backup contains it, no PDF text is indexed from it, and it never opens in the built-in viewer. If the far side goes away, so does the content.

What a dropped file becomes depends on its type: an image, video or audio block for those, and a file block for everything else, which renders as a download row showing the name. While an upload runs, a thin progress bar sits at the top of the window.

Uploading into a page requires write access to that page.

From an agent

Over MCP, upload_file(file_name, data_base64, page_id) uploads a file from base64 data. With a page_id the file is attached to that page as a block — an image block for .png, .jpg, .jpeg, .gif and .webp, a file block for everything else — and a PDF’s text is extracted for search. Without a page_id the file is stored and given back as a URL, but it belongs to no page and appears in no file list.

See Agents for connecting one.

From a script

POST /api/upload takes an ordinary multipart form with one field named file. Add ?page=<page id> to attach it — without that query parameter the upload is stored but belongs to no page, exactly as over MCP. The answer is JSON: {"url":"/files/<stored name>"}, which is the address to put in a block or a property. The request needs a session cookie or an API token like every other call; see API.

Size limits

Default cap per file50 MB
Range an admin can set1–2048 MB, under Max. file size per upload (MB) in Administration
What the browser refuses on its ownanything over 50 MB, before sending — “File too large (…) — 50 MB max.”
What an agent may send in one MCP requestthe file cap plus a third (base64 costs a third) plus 1 MB

The cap is enforced on the whole request body with one megabyte of slack for the multipart wrapping around the file, so a file a few hundred kilobytes over the stated number is accepted rather than refused. The slack is for the envelope, not a second limit to plan around.

The browser’s own 50 MB check does not follow the admin setting. Raising the limit above 50 MB takes effect for agents and for direct calls to /api/upload, but the editor will still refuse a larger file before it sends one. Lowering the limit works everywhere: the server answers 413 and the editor shows the server’s own message, “file too large — max 10 MB”, naming the limit this instance is set to.

If you run the instance yourself and put a reverse proxy in front of it, its own body limit has to be at least as large — the Administration dialog prints a matching client_max_body_size line for exactly that reason.

Finding files again

The structure panel

Open the structure panel beside a document (Pages). Its Files section lists every file on this page and every page under it, with a coloured extension badge, the size, and — when the file hangs off a sub-page rather than the page you are looking at — the name of the page it came from. Empty, it says “No files”.

Clicking a PDF opens it in a viewer. Anything else opens in a new tab.

The file list

Two ways in, both opening the same dialog:

  1. Workspace settings → Data → Files (“Every uploaded file in this workspace, with the page carrying it”). Workspace settings are open to workspace admins.
  2. The menu on any page in the sidebar → Files in this subtree. The dialog is then headed Files below “…” and covers that page and its descendants only. Right-clicking the page row opens the same menu, which saves hunting for a three-dot button that appears on hover.

The dialog shows, newest first: the file name as a link, its size, the date it was added, and a button carrying the title of the page it belongs to — pressing it opens that page. A file whose page is gone shows “no page” instead. With nothing to show it says “No files here yet.”

Above the list sit a filter box (Filter by file or page…, which matches both the file name and the page title) and a type dropdown (All types, then every extension actually present — .pdf, .png, and so on, with the leading dot, because that is how they are stored). The dropdown is built from everything in scope, not from what the filter box currently leaves visible, so the choices do not shift under you as you type. Under them is a running count and the total size of what is currently shown, so filtering to .pdf answers “how much of this is PDFs” without any arithmetic.

The dialog always covers the workspace you are currently in and offers no workspace picker — including when you open it from Workspace settings. To see another workspace’s files, switch to it first.

Over MCP and the API

list(kind: "files") returns the files of a workspace — name, extension, size, the page carrying each one and its /files/ URL. Pass under with a page id for one subtree. Over HTTP the same answer comes from /api/files, with ?workspace= or ?under=.

Both go through two permission checks: first the workspaces you can reach, then the individual page carrying each file. The second is what keeps a file attached to somebody’s private page out of your list even though you are in the same workspace. Files on pages in the trash drop out of the list until the page is restored.

Previewing and downloading

Clicking the name of a file block in a document normally sends it to your downloads folder. PDFs open in a viewer instead — a bar with the file’s name, a Download button and Close, and the document itself filling the rest. Escape closes it, as does clicking outside.

The viewer is the browser’s own PDF renderer, which means it costs no conversion on the server and nothing leaves the machine. It applies only to PDFs, and only to files stored in this instance: a file block made with the Embed tab points at a foreign address and keeps opening the normal way.

Office formats — .docx, .xlsx, .pptx — deliberately have no preview. No browser reads them natively, and the two ways to change that are shipping an office suite inside the server or handing your documents to somebody else’s online viewer. Neither is worth it.

Download keeps the readable file name rather than the random id it is stored under.

When a PDF is uploaded with a page, Salt extracts its text and indexes it under that page. Searching for a phrase that appears only inside the PDF finds the page carrying it. See Search. Agents reach the same text: the search tool covers titles, page content and indexed PDF attachments in one query.

Two limits keep one large document from taking the server down, and both are sized to the memory the server believes it has:

Memory availableLargest PDF whose text is extractedExtractions at the same time
not detectable10 MB1
512 MB5 MB1
2 GB20 MB1
8 GB50 MB2
16 GB and above50 MB3

The extraction limit is one hundredth of available memory, never below 5 MB and never above 50 MB. Parsing a PDF costs several times the file’s own size in memory, and the server has to keep answering everything else meanwhile. Extractions beyond the slot count wait their turn instead of running together.

Getting this wrong never costs you an upload. A PDF over the limit is still stored, still listed, still downloadable, and still shows in the file list — only its text stays out of the search index. The server log says so plainly: pdf extract …: skipped for indexing, N bytes is over the M byte limit (the file itself is stored and listed as usual).

At most 500,000 bytes of text are kept per PDF; a whole book is not worth the database weight.

The startup log prints what the instance decided: memory: 16000 MB available, soft limit 12800 MB, PDF indexing up to 50 MB, 3 extraction(s) at a time. If that figure is wrong — which happens in nested setups, a container inside a virtual machine — set SALT_MEMORY_MB to the real number of megabytes. See Self-hosting.

Extraction runs for PDFs only. No other format’s contents are read.

Covers, icons, logos and pictures

These are uploads too, and they behave slightly differently:

WhatWhere it is setCounted in the file list
Page cover (CoverUpload image)on the pageno
Page icon (icon picker → UploadUpload picture)on the pageyes
Workspace logo (Workspace pictureChoose a picture…)on the workspaceno
Account picture (ProfileUpload picture)on the accountno

Only the icon upload names a page, so only it appears in that workspace’s file list. The others belong to a workspace or an account rather than to a page, so the index treats them as unattached and no workspace list shows them. They are still stored, still backed up, and still protected from clean-up: the routine that removes unreferenced uploads checks page content, page properties, covers, old page versions, comments, workspace logos and account pictures before deleting anything.

Each of them can be taken off again, and each has its own control: Remove icon in the page’s icon picker, Remove picture in the Profile dialog next to Upload picture, and Remove in the workspace picture dialog. Removing clears the reference; the stored file itself stays, like every other upload.

A workspace has one identity at a time: setting an emoji clears the logo and uploading a logo clears the emoji. Remove clears both.

Removing files

No button deletes a single stored file. Only deleting a whole workspace or a whole account reaches the bytes on disk. What the per-file actions actually do:

  • Deleting the file block from a page removes the reference, not the byte. The file stays on disk, stays downloadable to anyone signed in who has the link, and stays in the file list until the index is next rebuilt. This is deliberate: a block removed by accident, or by a collaborative edit landing badly, would otherwise destroy the only copy.
  • Moving the page to the trash takes its files out of every list. Restoring the page brings them back. See Trash and recovery.
  • Deleting the page permanently removes the extracted PDF text from the search index. The file itself stays, and shows in the workspace’s file list with “no page” — until the index is next rebuilt, at which point it is recorded with no page and no workspace and drops out of every workspace list. It is still on disk and still in every backup.
  • Deleting a workspace or an account is where uploads are removed from disk — and only those that nothing anywhere references any more.

Workspace settings → Data → Delete workspace (“Takes every page in it along”) is the common one. It is open to workspace admins, asks you to type the workspace name as confirmation, and is refused if it would leave you with no workspace at all. It deletes the pages and then removes from disk every upload nothing else points at.

Deleting an account does the same for the personal spaces that go with it. Shared workspaces are handed over or left behind instead; a workspace left with no members can be deleted from the instance owner’s clean-up view, again after typing its name, and that path clears its files too. See Account and Administration.

“Nothing else points at it” is the full check listed above — content, properties, covers, old versions, comments, logos and account pictures. The same upload can live on in a copy or a moved page, and a missing image in somebody else’s workspace would be worse than a leftover on disk.

Unattached files are not lost track of. When the index is rebuilt, everything in the files folder that no page mentions is recorded without a page, and the startup log counts them: file index: built (version 2, 128 files on 40 pages, 3 unreferenced).

What the index is, and why it can be thrown away

The file list is a derived index. The truth is the block on the page plus the byte on disk; the index only makes “every document for this customer” a question the server can answer in one query instead of a walk through every page’s content.

That is why it can be rebuilt whenever the rules for reading it change — the rebuild deletes the whole index and reads it back out of the pages and the files folder. Files are found by looking for a URL pointing into /files/, not by block type: the editor writes image, video, audio and file blocks, an agent’s upload writes its own, and the list of types keeps growing, while a /files/ URL is the one signal that stays stable.

If a count ever looks wrong, that is the repair. It costs one thing: the upload dates are recorded nowhere else, so a rebuild stamps every file with the moment of the rebuild, and the list’s “newest first” order collapses into a fallback sort by stored name until new uploads arrive. Nothing else in the index is authoritative.

Backups and exports

  • The instance backup (Maintenance → Download backup (.tar.gz) in Administration, or /api/admin/backup) contains the database and every file, including the unattached ones. The same section shows the total size under Uploads. Only the instance owner may take it — an admin asking for it is refused, because the archive holds every workspace, every upload, password hashes and session tokens.
  • The native workspace archive (Workspace settings → Data → Export workspace, or /api/workspaces/{id}/export) carries every upload the workspace refers to — from page content, from properties, from covers and the workspace logo — inside the ZIP under files/. Importing it on another instance restores them.
  • The Markdown export (/api/export) writes text. It links to files but does not carry them.

See Workspaces for import and export of a whole workspace, and Self-hosting for backing up the machine.

Limits, in one place

Per-file upload cap50 MB by default, 1–2048 MB configurable
Slack on top of the cap, for the multipart envelope1 MB
Editor’s own refusal50 MB, not configurable
PDF text extractedup to 50 MB of file, 500,000 bytes of text
Parallel PDF extractions1 to 3, by memory
Extension length kept (browser upload)12 characters including the dot
Depth of a “files in this subtree” walk20 levels
Formats with a built-in previewPDF
Formats whose text is indexedPDF