# File

> An uploaded file of any type: the file goes on disk under dirBase, the filename goes in the column. The same machinery as image minus the image processing — …

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/technical/property-types/file. Last updated 2026-07-27.

---
An uploaded file of any type: the file goes on disk under `dirBase`, the **filename** goes in the
column. The same machinery as [`image`](https://docs.configbox.at/docs/technical/property-types/image) minus the image processing — no dimension checks,
no mutations.

**Storage kind:** `column` · **Column:** `varchar(255)`

Use it for assets that are stored and served as-is: a PDF datasheet, a CAD file, a connector's
certificate, a downloadable attached to an answer. Reach for [`image`](https://docs.configbox.at/docs/technical/property-types/image) instead whenever
the file is a picture, even if you do not need mutations today — the dimension checks and the option
of adding mutations later come free, and switching type afterwards means re-uploading.

## Settings

| Setting | Meaning |
|---|---|
| `dirBase` | **Required.** Filesystem directory written to. Created (recursively, 0777) if missing |
| `urlBase` | URL base served from. Without it, reads expose no `_href` |
| `filename` | Fixed base filename instead of the uploaded one |
| `appendSerial` | Append a random 4-digit serial to the base name — a cache-buster |
| `allowedExtensions` | Array of permitted extensions, lower-case, without the dot |
| `allowedMimeTypes` | Array of permitted MIME types — checked as well as the extension |
| `maxFileSizeKb` | Upload ceiling in KB — **this is what `size` meant on a file property** |
| `options` | `FILENAME_TO_RECORD_ID`, `NODELETEFILE`, `NO_SAVE_FILENAME` |

## The filename is persisted by default

As with [`image`](https://docs.configbox.at/docs/technical/property-types/image), `store()` calls `updateFilenameBaseTable()` unless the definition opts
out with `NO_SAVE_FILENAME`.

This used to be opt-in, behind a `SAVE_FILENAME` tag. Omitting it uploaded the file and never touched
the column, and because `prepareForStorage()` has already put the *previous* filename into the data
object, the record simply kept pointing at the old file — no error, nothing in a log. Every shipped
file definition set the tag, so it was never really optional.

`FILENAME_TO_RECORD_ID` names the stored file after the record id; `NODELETEFILE` suppresses the
"delete file at save" checkbox. The checkbox posts `<propertyName>-delete=1`, and it only renders
when a file is stored, `NODELETEFILE` is absent, the property is not `required`, and the record id
is not `0`. A delete flag arriving when the property is not deletable makes `store()` **throw**.

## Validation

`check()` refuses on the **first** problem it finds, unlike `image`, which collects them all:

| Checked | Condition |
|---|---|
| Presence | Only when `required`, the field applies, and the record is **new** (`empty($data->id)`) |
| `maxFileSizeKb` | `filesize()` of the temp file against the ceiling |
| `allowedMimeTypes` | `KenedoFileHelper::getMimeType()`. **Skipped silently if the system cannot determine one** |
| `allowedExtensions` | The uploaded name's extension |

Declare both `allowedExtensions` and `allowedMimeTypes`. An extension is a claim by the client; the
MIME check looks at what was actually sent — but it is also the check that can silently not run, so
neither alone is enough.

Note that the required-file check only fires on an insert. On an update, an existing record with a
missing file is not re-flagged.

## How the filename is built

Same order as `image`: `FILENAME_TO_RECORD_ID` → the definition's `filename` → the uploaded name,
lower-cased and stripped of its extension. Then `appendSerial` appends `-` plus a random 4-digit
number, and the uploaded extension is put back.

On a **copy** there is one difference from `image`: without `appendSerial`, `file` avoids a
collision by probing `-1`, `-2`, … until the name is free on disk. `image` has no such loop, so an
`image` copy without `appendSerial` can overwrite — though not in the shipped configuration, where
every image property sets either `appendSerial` or `FILENAME_TO_RECORD_ID`, and the new record's id
cannot collide.

## What reads produce

| Key | Holds |
|---|---|
| `<name>` | the filename |
| `<name>_href` | `urlBase` + `/` + filename, when `urlBase` is set |
| `<name>_path` | `dirBase` + `/` + filename, when `dirBase` is set |

Unlike `image`, these keys are added whenever the bases are configured — they are set to `''` for a
record with no file rather than being left off.

## `size` meant two different things

On a `file` property `size` was a kilobyte ceiling; on a text property it was an input's `maxlength`.
One key, two unrelated meanings, disambiguated only by the type. They are now `maxFileSizeKb` and
`maxLength`, with a shim in `modernizeOldPropertySettings()` routing an old `size` by type.

## Notes

- **Deleting a record does delete the file.** `KenedoPropertyFile::delete()` unlinks it before the
  row goes, and a failure to unlink is logged rather than raised — the record still goes. You do not
  need an `afterDelete()` for this.
- The upload is `rename()`d out of the temp directory, not copied the way `image` does it, then
  chmod'ed `0775`. On hosts where the temp dir is on a different filesystem that is the faster path.
- Replacing a file unlinks the previous one, but only when the new name differs. With a fixed
  `filename` and no `appendSerial` the name is the same and the file is simply overwritten.
- Copying a record whose source file is missing logs a warning and continues with an empty value —
  it still writes the row, because a `storeExternally` file property may be the only column in its
  foreign table and a row has to exist. `image` throws in the same situation.
- `storeExternally` moves the **filename column** to another table; the property implements the
  branch itself. The files always live under `dirBase`.

@see image.md
@see ../com_configbox_property_types.md §3 for `storeExternally`
