<p>It's because those element actions happen over an AJAX and you have to <a href="https://www.google.com/search?q=download%20a%20file%20to%20browser%20over%20ajax" rel="nofollow noreferrer">jump through a few hoops</a> to send a file to the browser over an AJAX request.</p>

CC BY-SA 3.0
2017-01-22T20:07:54.067Z
2017-01-22T20:07:54.067Z
Brad Bell
<p>Element actions take place over AJAX requests, so the <code>performAction</code> method will not work as you are expecting. </p>

<p>Here's how I solved it in Craft 3.</p>

<p>In the element action class, create a redirect to a controller action in the <code>activate</code> setting of the <code>ElementActionTrigger</code>:</p>

<pre class="lang-php prettyprint-override"><code>public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = &lt;&lt;&lt;EOD
(function()
{
var trigger = new Craft.ElementActionTrigger({
    type: {$type},
    activate: function() {
        Craft.redirectTo(Craft.getActionUrl('my-plugin/my-controller/say-hello'));
    }
});
})();
EOD;

    Craft::$app-&gt;getView()-&gt;registerJs($js);

    return null;
}
</code></pre>

<p>Then perform the download in the controller:</p>

<pre class="lang-php prettyprint-override"><code>public function actionSayHello(): Response
{
    return Craft::$app-&gt;getResponse()-&gt;sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' =&gt; 'application/pdf'
    ]);
}
</code></pre>

<p>You'll usually want to do something with the selected elements, which you can do by joining the selected element IDs:</p>

<pre class="lang-php prettyprint-override"><code>public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = &lt;&lt;&lt;EOD
(function()
{
var trigger = new Craft.ElementActionTrigger({
    type: {$type},
    activate: function(\$selectedItems) {
        Craft.redirectTo(Craft.getActionUrl('my-plugin/my-controller/say-hello', 'elementIds=' + Craft.elementIndex.getSelectedElementIds().join('|')));
    }
});
})();
EOD;

    Craft::$app-&gt;getView()-&gt;registerJs($js);

    return null;
}
</code></pre>

<p>You can then explode the <code>elementIds</code> in the controller:</p>

<pre class="lang-php prettyprint-override"><code>public function actionSayHello(): Response
{
    $elementIds = Craft::$app-&gt;getRequest()-&gt;getRequiredParam('elementIds');
    $elementIds = explode('|', $elementIds);

    // Do something with elementIds

    return Craft::$app-&gt;getResponse()-&gt;sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' =&gt; 'application/pdf'
    ]);
}
</code></pre>

CC BY-SA 4.0
2020-04-08T15:23:38.800Z
Brilliant solution - I just implemented this in a module for my use-case and it worked a treat!
Josh Angell
CC BY-SA 4.0
2020-02-06T08:08:19.223Z
2020-02-06T08:08:19.223Z
Ben Croker
<p>I'm trying to download a CSV file from a custom created element action</p>

<p>Here's the performAction function</p>

<pre><code>    public function performAction(ElementCriteriaModel $criteria) {
        $contents = 'row1column1,row2column2'.PHP_EOL.'row2column1,row2column2'.PHP_EOL;

        craft()-&gt;request-&gt;sendFile('export.csv', $contents, array('forceDownload' =&gt; true), false);

        return true;  
    }
</code></pre>

<p>No error is thrown, the code passes right through the function. When I check the craft logs, there's no sign of any error</p>

<p>And yet, there's no download occuring. When I try this same code in a controller or a service, it works just fine. But in this element action class, nothing happens</p>

<plugin-development>
Downloading file from custom element action
# Downloading file from custom element action

(Asked by: username_0 on 2017-01-22)

I'm trying to download a CSV file from a custom created element action
Here's the performAction function
    public function performAction(ElementCriteriaModel $criteria) {
        $contents = 'row1column1,row2column2'.PHP_EOL.'row2column1,row2column2'.PHP_EOL;

        craft()->request->sendFile('export.csv', $contents, array('forceDownload' => true), false);

        return true;  
    }

No error is thrown, the code passes right through the function. When I check the craft logs, there's no sign of any error
And yet, there's no download occuring. When I try this same code in a controller or a service, it works just fine. But in this element action class, nothing happens

**Tags:** plugin-development

---

## 2 answers

### Answer by username_1 on 2017-01-22. Score: 2 | Accepted answer

It's because those element actions happen over an AJAX and you have to [jump through a few hoops](https://www.google.com/search?q=download%20a%20file%20to%20browser%20over%20ajax) to send a file to the browser over an AJAX request.


---

### Answer by username_2 on 2020-02-06. Score: 3

Element actions take place over AJAX requests, so the performAction method will not work as you are expecting. 
Here's how I solved it in Craft 3.
In the element action class, create a redirect to a controller action in the activate setting of the ElementActionTrigger:
public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = <<getView()->registerJs($js);

    return null;
}

Then perform the download in the controller:
public function actionSayHello(): Response
{
    return Craft::$app->getResponse()->sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' => 'application/pdf'
    ]);
}

You'll usually want to do something with the selected elements, which you can do by joining the selected element IDs:
public function getTriggerHtml()
{
    $type = Json::encode(static::class);

    $js = <<getView()->registerJs($js);

    return null;
}

You can then explode the elementIds in the controller:
public function actionSayHello(): Response
{
    $elementIds = Craft::$app->getRequest()->getRequiredParam('elementIds');
    $elementIds = explode('|', $elementIds);

    // Do something with elementIds

    return Craft::$app->getResponse()->sendContentAsFile('Hello!', 'hello.pdf', [
        'mimeType' => 'application/pdf'
    ]);
}

> **Comments:**
> **username_3**: Brilliant solution - I just implemented this in a module for my use-case and it worked a treat!  

---

