Add support for file fields using custom stream wrappers (currently only `public` is supported)
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3577155. --> Reported by: [darrellhq](https://www.drupal.org/user/356505) >>> <h3 id="overview">Overview</h3> <p><code>FileUriItemOverride::propertyDefinitions()</code> adds a <code>UriSchemeConstraint</code> to the <code>file_uri</code> field type's <code>value</code> property with a hardcoded <code>allowedSchemes =&gt; ['public']</code>. Because this override is applied <strong>globally</strong> via <code>hook_field_info_alter()</code> in <code>ShapeMatchingHooks</code>, it affects <strong>all</strong> file entities &mdash; not just those used within Canvas components.</p> <p>This causes entity validation failures for any site that stores files using a non-<code>public://</code> stream wrapper (e.g., <code>s3://</code> via the S3FS module, or <code>private://</code>).</p> <p>The code already contains a <code>@todo</code> on line 31 of <code>FileUriItemOverride.php</code> acknowledging this:</p> <p><code>// @todo should respect the `uri_scheme` field storage setting of \Drupal\file\Plugin\Field\FieldType\FileItem</code></p> <h4>Steps to reproduce</h4> <ol> <li>Install Canvas (<code>drupal/canvas ^1.1</code>) alongside S3FS (<code>drupal/s3fs ^3.9</code>) or configure any media field storage to use <code>uri_scheme: private</code></li> <li>Upload an image via JSON:API (or any mechanism that creates a file entity with a non-<code>public://</code> URI)</li> </ol> <h4>Actual result</h4> <p>Upload fails with a 422 Unprocessable Entity error:</p> <p><code>'s3' is not allowed, must be one of the allowed schemes: public.</code></p> <h4>Expected result</h4> <p>File uploads succeed with any stream wrapper scheme that is registered on the site.</p> <h4>Root cause</h4> <ol> <li><code>ShapeMatchingHooks::fieldInfoAlter()</code> (line 107) globally replaces the <code>file_uri</code> field type class with <code>FileUriItemOverride</code></li> <li><code>FileUriItemOverride::propertyDefinitions()</code> (line 34) adds <code>UriSchemeConstraint</code> with <code>['public']</code> to the <code>value</code> property</li> <li>When a file is uploaded (e.g., via <code>\Drupal\file\Upload\FileUploadHandler::handleFileUpload()</code>), the file is first moved to its destination (e.g., <code>s3://2026-03/image.png</code>), then <code>$file-&gt;validate()</code> is called</li> <li><code>UriSchemeConstraintValidator::validate()</code> checks the URI scheme against the allowlist and rejects <code>s3</code></li> <li>The validator special-cases <code>temporary://</code> (allows it through), which is why the initial temp file creation succeeds, but the final validation after the file is moved to its permanent location fails</li> </ol> <h4>Environment</h4> <ul> <li>Drupal 11</li> <li>Canvas 1.1.x</li> <li>S3FS 3.9.x (provides <code>s3://</code> stream wrapper)</li> <li><code>field.storage.media.field_media_image</code> configured with <code>uri_scheme: s3</code></li> </ul> <h3 id="proposed-resolution">Proposed resolution</h3> <p>The <code>@todo</code> on line 31 already states the intent. Rather than hardcoding <code>['public']</code>, the constraint should dynamically include all registered stream wrappers on the site. For example, querying <code>\Drupal::service('stream_wrapper_manager')-&gt;getWrappers()</code> to build the allowlist, or at minimum expanding the hardcoded list to include <code>private</code> which is a core stream wrapper.</p> <h4>Workaround</h4> <p>Apply the following patch:</p> <pre>--- a/src/Plugin/Field/FieldTypeOverride/FileUriItemOverride.php<br>+++ b/src/Plugin/Field/FieldTypeOverride/FileUriItemOverride.php<br>@@ -31,7 +31,7 @@<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // @see \Drupal\file\Plugin\Field\FieldType\FileItem::defaultStorageSettings()<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;addConstraint(UriConstraint::PLUGIN_ID, ['allowReferences' =&gt; FALSE])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;addConstraint(UriSchemeConstraint::PLUGIN_ID, [<br>-&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'allowedSchemes' =&gt; ['public'],<br>+&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 'allowedSchemes' =&gt; ['public', 's3', 'private'],<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ]);<br>&nbsp;&nbsp;&nbsp;&nbsp; $properties['url']<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; -&gt;setClass(ComputedFileUrlOverride::class)</pre><h3 id="ui-changes">User interface changes</h3> <p>None.</p> > Related issue: [Issue #3530351](https://www.drupal.org/node/3530351)
issue