File (Field) Paths 8.x-1.0; stable and back under maintenance
File (Field) Paths is stable. 8.x-1.0 is the first stable release since the Drupal 7 one (long may it live), and it does what it has always done: sorts and renames your uploads with token patterns, so you get a filesystem you can read.
If you've been holding off while it sat in RC, you don't need to. It has 215 automated tests and 1287 assertions where beta8 had 21 tests, a safer default for where uploads wait, and a plan for what comes next. It's under active maintenance again.
If you haven't used it
Core file fields let you choose a directory, and that setting takes tokens. Drupal ships [date:custom:Y]-[date:custom:m] as the default. What it can't take is a token about the entity you're attaching the file to, because at the moment the file is saved those values don't exist yet. The file name isn't touched at all.
It puts the upload somewhere else first, waits for the save, then moves and renames it once the tokens mean something. You get two patterns per field. Here's what I run on an article's image field:
File path [node:url:path] File name [file:ffp-name-only-original].[file:ffp-extension-original]
Upload FFP Showcase Hero Shot.PNG to this post and it ends up at writing/file-field-paths-8x-10-stable-and-back-under-maintenance-20261001/ffp-showcase-hero-shot.png. The directory is the post's own URL, the name gets transliterated and cleaned up on the way through, and the original filename stays on the file record. You'll want Pathauto installed for the cleaning, and Cleanup using Pathauto ticked on the file name, or the raw value goes straight in. Building the path out of the entity the file belongs to is the part core has never done.
Changing a pattern doesn't touch anything you've already uploaded. New files follow it, old ones stay put until you ask, either with Retroactive update on the field or drush filefield_paths:update. Do that on a copy first. It moves every file at once, and it'll break links to them unless Create Redirect is on, which wants the Redirect module.
A path and a name built from the entity the file is attached to. That is the whole trick.
What's new in 8.x-1.0
The image widget no longer shows the wrong thumbnail when two uploads share a name (#3277844). A new upload no longer creates a redirect every time (#3494240), and the PHP 8.2 that rc2 already needed is finally declared (#3622262). Being stable also means security advisory coverage.
I've been putting all of my modules on Alex Skrypnyk's Drupal Extension Scaffold, running Drupal's own CI templates on my own GitLab with GitHub beside it. Most of my time on this release went into the tests that pipeline runs: 9 test files at rc1, 36 now. That's what tells me the module still works when I come back to it after months on something else.
Where uploads wait
Because the tokens only resolve once the entity is saved, every upload waits somewhere first. If that staging directory sits under public://, a file headed for a private field is in the web root until the save finishes. The module's been saying so on the status report since beta6 in December 2022, as a partial fix for SA-CONTRIB-2022-065:
File (Field) Paths temporary path Insecure! This site supports private files but the File (Field) Paths temporary path is under public:// which could lead to private files being temporarily exposed publicly. Change the temporary path to be under temporary:// or private:// in order to secure your files.
That release also added an update hook, so database updates move a public:// staging path onto temporary://, or private:// if temporary:// isn't writable. If you've run updates any time since 2022 it's already happened:
drush config:get filefield_paths.settings temp_locationSince rc1 a field can set its own staging location, and the update hook and the status report don't check that override yet. If you've set one, have a look at it. It's on the 1.1 list. temporary:// has been the recommended scheme since 2022, and now that #3121826 in rc2 builds image derivatives on demand, private:// is only for the case it was always for: several web servers with no shared temporary directory.
The site-wide setting, at Configuration > Media > File system > File (Field) Paths.
What's next
Field Tokens, Custom Formatters and JSON:API Views have each had this treatment this year. This is the fourth, and the one with the longest queue. The code is modern and the tests are in. The queue is next.
The milestones carry that plan. 1.0 takes bug fixes only, 1.1 is where the queue catch-up goes, 1.2 is the long tail, and breaking changes wait for 2.0.0. The oldest open issue, #1115740 from April 2011, is on the 1.1 list, with a kernel test on its issue fork that reproduces it. The patch is still to write.
If you want to take something on, #3069511, replacing existing files, is the most-watched issue in the queue and needs a design decision more than it needs code. #3182718, taxonomy hierarchy in paths, has a community patch waiting on review.
If you write code against this one, I've deprecated six procedural functions in 8.x-1.0 and they go in 2.0.0. Each has a #[\Deprecated] attribute naming what replaced it, so Upgrade Status will tell you if you're calling them.
A big thanks to the community
Big thanks to Oleh Vehera, who kept this module alive while my focus was elsewhere. The Drupal 9 port, then Drupal 10, the move to Drush 12, the hooks lifted into classes and the services behind them are all his. So is the status report warning above. He has commit access.
And to everyone else who kept it moving. rpayanm, dunebaud and Paulino Michelazzo cut releases. Bryan Sharpe, Peter Wolanin, Jeremy Stoller, Chandansha Fakir, Youri van Koppen and solideogloria got rc1 out. dshields, xamount, Sakshi Sharma and Francesco Maria Battaglia fixed the last things standing between rc2 and today. Jeffrey Clark, Aidan Lister and Magnus Gunnarsson were doing this back in the Drupal 6 days. If you have ever put a patch on this module, your name is in the changelog.
I'm independent now, working on open source full time, across Drupal and Druxt. There's no client work paying for it, so sponsorship is what makes the time possible. That's what got 8.x-1.0 out, and it's what keeps the rest of them moving.
Tens of thousands of sites run this one, and several thousand are still on 7.x-1.x. If any of this is useful to you, 8.x-1.0 is out now:
composer require 'drupal/filefield_paths:^1.0'Composer maps the tag to 1.0.0, so ^1.0 is the constraint you want. If you're stuck on PHP 8.1, 1.0.0-rc1 is the last release that runs there, but it carries bugs rc2 fixed and the security team doesn't cover release candidates. Moving to 8.2 is the better answer.
Source on GitHub, with the project page, the issue queue and the full changelog on Drupal.org. Patches welcome in the queue, and if your sites lean on this one, sponsoring is what keeps it maintained.
So what's in your path pattern? Mine is the post's own URL. I'd like to know what people are doing with the entity tokens I never thought to try.