Answers.Body: <p>I am not sure yet what caused the issue, but it is probably somehow related to the fact that I also created the volume programmatically.
  At any rate, using an absolute path during volume creation solved the issue:</p>
  <pre class="lang-php prettyprint-override"><code>$volumesService-&gt;createVolume([
              'type' =&gt; Local::class,
              'name' =&gt; 'Assets Volume',
              'handle' =&gt; 'assetsVolumeHandle',
              'hasUrls' =&gt; 1,
              'url' =&gt; '/assets-url',
              'settings' =&gt; [
                  'path' =&gt; 'assetsFolder'
              ]
          ]);
  </code></pre>
  <p>Replace <code>assetsFolder</code> with <code>@webroot/assetsFolder</code> (and while you're at it replace <code>/assets-url</code> with <code>@web/assets-url</code>).</p>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2021-11-19T16:00:08.210Z
Answers.LastActivityDate: 2021-11-22T06:21:47.277Z
Answers.LastEditDate: 2021-11-22T06:21:47.277Z
Answers.UserDisplayName: Matanya
Body: <p>I am trying to seed some images to an existing volume programmatically.
  The code I'm using is taken (almost) verbatim from Craft's <code>AssetsController@actionUpload</code>:</p>
  <pre class="lang-php prettyprint-override"><code>        $filename = Assets::prepareAssetName($filename);
  
          $asset = new Asset();
          $asset-&gt;tempFilePath = $path;
          $asset-&gt;filename = $filename;
          $asset-&gt;newFolderId = $folder-&gt;id;
          $asset-&gt;setVolumeId($folder-&gt;volumeId);
          $asset-&gt;uploaderId = User::findOne()-&gt;id;
          $asset-&gt;avoidFilenameConflicts = true;
          $asset-&gt;setScenario(Asset::SCENARIO_CREATE);
          $res = Craft::$app-&gt;getElements()-&gt;saveElement($asset);
  </code></pre>
  <p>However, this creates a new folder on the root of my project, by the same name as the one that already exists under <code>web</code>, and saves the file there, which results in a broken asset in the CP.</p>
  <p>When I upload using Craft's CP the file is uploaded correctly to <code>web/{volumeFolder}</code>.</p>
  <p>I have dumped the <code>$asset</code> object both from my plugin code and from Craft's code and compared them. Everything matches (<code>volumeId</code>, <code>folderId</code>, <code>tempFilePath</code>).</p>
  <p>I couldn't track the place in the code where the folder is created. I'd appreciate some pointers.</p>
  <p>EDIT:</p>
  <p>To answer @MoritzLost comment here is the full context:</p>
  <pre class="lang-php prettyprint-override"><code>private function generateImages()
      {
          $assetVolume = Craft::$app-&gt;volumes-&gt;getVolumeByHandle(Handles::ASSETS);
          $assets = Asset::find()-&gt;volume($assetVolume)-&gt;all();
  
          // Generate new assets
          if (count($assets) === 0) {
              // Find folder
              $folder = VolumeFolder::findOne([
                  'volumeId' =&gt; $assetVolume-&gt;id
              ]);
  
              // images in plugin folder
              $dir = realpath(__DIR__ . '/../assets/site/images');
              $images = ['hero_1', 'img_1', 'img_2', 'img_3', 'img_4'];
  
              // temp folder in project
              $path = new Path();
              $tempDirPath = $path-&gt;getTempPath();
  
              foreach ($images as $image) {
                  // move file from plugin assets to project temp folder
                  $filename = $image . '.jpg';
                  $filenameUnique = $image . '_' . rand(100, 10000) . '.jpg';
                  $path = $dir . DIRECTORY_SEPARATOR . $filename;
                  $tempFilePath = $tempDirPath . DIRECTORY_SEPARATOR . $filenameUnique;
                  file_put_contents($tempFilePath, file_get_contents($path));
  
  
                  // Upload asset to permanent folder
                  // and create DB record
                  $result = $this-&gt;uploadNewAsset($folder, $tempFilePath, $filenameUnique);
  
                  $assets[] = $result;
              }
          }
  
          return array_map(function ($asset) {
              return $asset-&gt;id;
          }, $assets);
  
      }
  </code></pre>
  <pre class="lang-php prettyprint-override"><code> private function uploadNewAsset($folder, string $path, $filename)
      {
          $filename = Assets::prepareAssetName($filename);
  
          $asset = new Asset();
          $asset-&gt;tempFilePath = $path;
          $asset-&gt;filename = $filename;
          $asset-&gt;newFolderId = $folder-&gt;id;
          $asset-&gt;setVolumeId($folder-&gt;volumeId);
          $asset-&gt;uploaderId = User::findOne()-&gt;id;
          $asset-&gt;avoidFilenameConflicts = true;
          $asset-&gt;setScenario(Asset::SCENARIO_CREATE);
          $res = Craft::$app-&gt;getElements()-&gt;saveElement($asset);
  
          if (!$res) {
              blogify_log(&quot;Failed to save image&quot;);
          }
  
          return $asset;
      }
  </code></pre>
  <p>Checked <code>folderId</code> and it matches. <code>tempFilePath</code> is <code>{projectRoot}/storage/runtime/temp/{filename}</code>, which also matches Craft CP request.</p>
  
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2021-11-19T08:40:10.900Z
Comments.Text: How are you defining the `$path` and `$folder` variables? The error might be with those.
Comments.UserDisplayName: MoritzLost
Comments.ContentLicense: CC BY-SA 4.0
Comments.CreationDate: 2021-11-19T08:50:38.397Z
Comments.Text: @MoritzLost Added full context to question.
Comments.UserDisplayName: Matanya
Tags: <plugin-development><assets-field>
Title: Upload asset via API - Asset saved under root folder
ThreadText: # Upload asset via API - Asset saved under root folder
  
  (Asked by: username_0 on 2021-11-19)
  
  I am trying to seed some images to an existing volume programmatically.
  The code I'm using is taken (almost) verbatim from Craft's `AssetsController@actionUpload`:
  
  ```php
          $filename = Assets::prepareAssetName($filename);
  
          $asset = new Asset();
          $asset->tempFilePath = $path;
          $asset->filename = $filename;
          $asset->newFolderId = $folder->id;
          $asset->setVolumeId($folder->volumeId);
          $asset->uploaderId = User::findOne()->id;
          $asset->avoidFilenameConflicts = true;
          $asset->setScenario(Asset::SCENARIO_CREATE);
          $res = Craft::$app->getElements()->saveElement($asset);
  
  ```
  
  However, this creates a new folder on the root of my project, by the same name as the one that already exists under `web`, and saves the file there, which results in a broken asset in the CP.
  
  When I upload using Craft's CP the file is uploaded correctly to `web/{volumeFolder}`.
  
  I have dumped the `$asset` object both from my plugin code and from Craft's code and compared them. Everything matches (`volumeId`, `folderId`, `tempFilePath`).
  
  I couldn't track the place in the code where the folder is created. I'd appreciate some pointers.
  
  EDIT:
  
  To answer @username_1 comment here is the full context:
  
  ```php
  private function generateImages()
      {
          $assetVolume = Craft::$app->volumes->getVolumeByHandle(Handles::ASSETS);
          $assets = Asset::find()->volume($assetVolume)->all();
  
          // Generate new assets
          if (count($assets) === 0) {
              // Find folder
              $folder = VolumeFolder::findOne([
                  'volumeId' => $assetVolume->id
              ]);
  
              // images in plugin folder
              $dir = realpath(__DIR__ . '/../assets/site/images');
              $images = ['hero_1', 'img_1', 'img_2', 'img_3', 'img_4'];
  
              // temp folder in project
              $path = new Path();
              $tempDirPath = $path->getTempPath();
  
              foreach ($images as $image) {
                  // move file from plugin assets to project temp folder
                  $filename = $image . '.jpg';
                  $filenameUnique = $image . '_' . rand(100, 10000) . '.jpg';
                  $path = $dir . DIRECTORY_SEPARATOR . $filename;
                  $tempFilePath = $tempDirPath . DIRECTORY_SEPARATOR . $filenameUnique;
                  file_put_contents($tempFilePath, file_get_contents($path));
  
                  // Upload asset to permanent folder
                  // and create DB record
                  $result = $this->uploadNewAsset($folder, $tempFilePath, $filenameUnique);
  
                  $assets[] = $result;
              }
          }
  
          return array_map(function ($asset) {
              return $asset->id;
          }, $assets);
  
      }
  
  ```
  
  ```php
   private function uploadNewAsset($folder, string $path, $filename)
      {
          $filename = Assets::prepareAssetName($filename);
  
          $asset = new Asset();
          $asset->tempFilePath = $path;
          $asset->filename = $filename;
          $asset->newFolderId = $folder->id;
          $asset->setVolumeId($folder->volumeId);
          $asset->uploaderId = User::findOne()->id;
          $asset->avoidFilenameConflicts = true;
          $asset->setScenario(Asset::SCENARIO_CREATE);
          $res = Craft::$app->getElements()->saveElement($asset);
  
          if (!$res) {
              blogify_log("Failed to save image");
          }
  
          return $asset;
      }
  
  ```
  
  Checked `folderId` and it matches. `tempFilePath` is `{projectRoot}/storage/runtime/temp/{filename}`, which also matches Craft CP request.
  
  **Tags:** plugin-development, assets-field
  
  > **Comments:**
  > **username_1**: How are you defining the `$path` and `$folder` variables? The error might be with those.  
  > **username_0**: @username_1 Added full context to question.  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_0 on 2021-11-19. Score: 1 | Accepted answer
  
  I am not sure yet what caused the issue, but it is probably somehow related to the fact that I also created the volume programmatically.
  At any rate, using an absolute path during volume creation solved the issue:
  
  ```php
  $volumesService->createVolume([
              'type' => Local::class,
              'name' => 'Assets Volume',
              'handle' => 'assetsVolumeHandle',
              'hasUrls' => 1,
              'url' => '/assets-url',
              'settings' => [
                  'path' => 'assetsFolder'
              ]
          ]);
  
  ```
  
  Replace `assetsFolder` with `@webroot/assetsFolder` (and while you're at it replace `/assets-url` with `@web/assets-url`).
  
  
  ---
  
  