Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 3.0
Answers.CreationDate: 2017-01-22T20:07:54.067Z
Answers.LastActivityDate: 2017-01-22T20:07:54.067Z
Answers.UserDisplayName: Brad Bell
Answers.Body: <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>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-04-08T15:23:38.800Z
Answers.Text: Brilliant solution - I just implemented this in a module for my use-case and it worked a treat!
Answers.UserDisplayName: Josh Angell
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2020-02-06T08:08:19.223Z
Answers.LastActivityDate: 2020-02-06T08:08:19.223Z
Answers.UserDisplayName: Ben Croker
Body: <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>
  
Tags: <plugin-development>
Title: Downloading file from custom element action
ThreadText: # 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`:
  
  ```php
  public function getTriggerHtml()
  {
      $type = Json::encode(static::class);
  
      $js = <<<EOD
  (function()
  {
  var trigger = new Craft.ElementActionTrigger({
      type: {$type},
      activate: function() {
          Craft.redirectTo(Craft.getActionUrl('my-plugin/my-controller/say-hello'));
      }
  });
  })();
  EOD;
  
      Craft::$app->getView()->registerJs($js);
  
      return null;
  }
  
  ```
  
  Then perform the download in the controller:
  
  ```php
  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:
  
  ```php
  public function getTriggerHtml()
  {
      $type = Json::encode(static::class);
  
      $js = <<<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->getView()->registerJs($js);
  
      return null;
  }
  
  ```
  
  You can then explode the `elementIds` in the controller:
  
  ```php
  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!  
  
  ---
  
  