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 => ['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 — 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->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')->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> // @see \Drupal\file\Plugin\Field\FieldType\FileItem::defaultStorageSettings()<br> ->addConstraint(UriConstraint::PLUGIN_ID, ['allowReferences' => FALSE])<br> ->addConstraint(UriSchemeConstraint::PLUGIN_ID, [<br>- 'allowedSchemes' => ['public'],<br>+ 'allowedSchemes' => ['public', 's3', 'private'],<br> ]);<br> $properties['url']<br> ->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