<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>

CC BY-SA 4.0
2021-11-19T16:00:08.210Z
2021-11-22T06:21:47.277Z
2021-11-22T06:21:47.277Z
Matanya
<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>

CC BY-SA 4.0
2021-11-19T08:40:10.900Z
How are you defining the `$path` and `$folder` variables? The error might be with those.
MoritzLost
CC BY-SA 4.0
2021-11-19T08:50:38.397Z
@MoritzLost Added full context to question.
Matanya
<plugin-development><assets-field>
Upload asset via API - Asset saved under root folder
# 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:
        $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 @MoritzLost comment here is the full context:
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);

    }

 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**: @MoritzLost 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:
$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).


---

