File API
Drupal's services for saving, copying, moving and deleting files by stream wrapper URI, plus file entities and usage records that track each managed file.
The File API is the group of core services that handle files on a Drupal site. Code addresses files by stream wrapper URI, such as public://reports/export.csv, instead of a server path. Two layers exist: file_system works on the disk only, and file.repository also keeps a file entity in the database.
How the File API works in Drupal 11
The file_system service (FileSystemInterface) copies, moves, deletes and writes files and checks directories with prepareDirectory(). It makes no database change and invokes no hooks. Its API documentation says to use delete() for a file that has no row in the files table.
The file.repository service (FileRepositoryInterface) does the same jobs for files that have a record: writeData(), copy(), move() and loadByUri(). It creates or updates the file entity. A file written by writeData() is marked permanent.
The file.usage service (FileUsageInterface) records which module uses a file for which object, for example a node. listUsage() returns those references, and the cron cleanup of temporary files skips any file that still has one.
The URI scheme decides where a file lives. public:// is for files served directly by the web server. private:// is for files served by Drupal; core registers that wrapper only when $settings['file_private_path'] is set. temporary:// is described in core as temporary local files for uploads and previews.
When a destination already exists, the methods take a FileExists enum with the cases Rename, Replace and Error. The integer constants on FileSystemInterface were deprecated in Drupal 10.3.0 and are removed from Drupal 12.0.0.
Example
This code stores a generated CSV string so that it shows up as a file entity:
use Drupal\Core\File\FileExists;
use Drupal\Core\File\FileSystemInterface;
$directory = 'public://reports';
\Drupal::service('file_system')->prepareDirectory($directory, FileSystemInterface::CREATE_DIRECTORY);
$file = \Drupal::service('file.repository')->writeData($csv, $directory . '/export.csv', FileExists::Replace);
Common mistakes
- Using
file_systemfor a file that has an entity. Deleting or moving it there leaves a file record pointing at nothing. Move it withfile.repositoryand delete the file entity itself. - Hard-coding a server path. A URI such as
public://reportsstill resolves when the files directory is configured elsewhere. - Writing a custom upload without validation. The core upload topic says to always validate against a list of allowed extensions. A
managed_fileelement with such a list also blocks insecure extensions by default. - Turning on
allow_insecure_uploads. Potentially insecure files are then no longer renamed, and core documents the setting as not recommended.
For marketing and content teams
Files that must not be public, such as paid downloads, need the private file system, which a developer configures in settings.php. The File system settings page at /admin/config/media/file-system has a "Delete temporary files after" option. Its description warns that deleted temporary files may not be recoverable. Related ideas: entities, cron and image styles.
Sources
- FileSystemInterface, Drupal API
- FileRepositoryInterface, Drupal API
- FileUsageInterface, Drupal API
- File interface topic (upload security), Drupal API
- FileRepository, Drupal 11.4.10 source
- File module cron hook (temporary file cleanup), Drupal 11.4.10 source
- CoreServiceProvider (private stream wrapper), Drupal 11.4.10 source
- FileSystemForm, Drupal 11.4.10 source
- Change record: FileSystemInterface constants replaced with the FileExists enum