EVENT_BEFORE_SAVE_ELEMENT Saving element loop in task/queue I'm calling EVENT_BEFORE_SAVE_ELEMENT to trigger a task that then updates the entry, but it's causing a loop?

The event is triggered when a specific entry type is saved:

Event::on(
  Elements::class,
  Elements::EVENT_BEFORE_SAVE_ELEMENT,
  function(Event $event) {
    $entry = $event-&gt;sender;
    if ($event-&gt;element instanceof \craft\elements\Entry) {
      $entry = $event-&gt;element;
      if (ElementHelper::isDraftOrRevision($entry)) {
        return;
      }
      if(($entry-&gt;sectionId == 18) &amp;&amp; ($entry-&gt;typeId == 26)) {
        $signpostURL = $entry-&gt;getFieldValue('signpostUrl');
        if(($signpostURL) &amp;&amp; ($signpostURL !== '')) {
          Craft::$app-&gt;queue-&gt;push(new BrowshotTask([
            'description' =&gt; 'Grabbing screenshot for '.$signpostURL,
            'url' =&gt; $signpostURL,
            'entryId' =&gt; $entry-&gt;id
          ]));
        }
      }
    }
  }
);


BrowshotTask then performs some API calls to a third-party service, gets a screenshot, saves an asset and saves the asset in the asset field of the entry.

But this triggers the event again and causes a loop:

$entry-&gt;setFieldValue('browsershotUrl', [$assetId]);
if(Craft::$app-&gt;elements-&gt;saveElement($entry)) {


Sometimes BrowshotTask can take 20 seconds due to the third-party service.. so I thought putting it in a task would be a great idea, but now it's causing me so many problems :(
The easiest solution for this would be either a special fieldvalue or a property in a behavior that you set in your job.

class MyJob extends BaseJob
{
    public function execute($queue): bool
    {
        // do some api calls here
        // ....
        $entry-&gt;isSavedViaApi = true;
        $entry-&gt;setFieldValue('browsershotUrl', [$assetId]);
        Craft::$app-&gt;elements-&gt;saveElement($entry)
        // ....
    }
}



and in your event, you can check against this property

Event::on(
  Elements::class,
  Elements::EVENT_BEFORE_SAVE_ELEMENT,
  function(Event $event) {
    $entry = $event-&gt;sender;
    if ($event-&gt;element instanceof \craft\elements\Entry) {
      $entry = $event-&gt;element;
      if (ElementHelper::isDraftOrRevision($entry)) {
        return;
      }

      if(($entry-&gt;sectionId == 18) &amp;&amp; ($entry-&gt;typeId == 26)) {

        // make sure to check the section first, because otherwise the entry
        // may not have this property of field
        if($entry-&gt;isSavedViaApi === true){
            return;
        }
        //


        $signpostURL = $entry-&gt;getFieldValue('signpostUrl');
        if(($signpostURL) &amp;&amp; ($signpostURL !== '')) {
          Craft::$app-&gt;queue-&gt;push(new BrowshotTask([
            'description' =&gt; 'Grabbing screenshot for '.$signpostURL,
            'url' =&gt; $signpostURL,
            'entryId' =&gt; $entry-&gt;id
          ]));
        }
      }
    }
  }
);


If you want to use a behavior instead of a field value for this

use yii\base\Behavior;

class MyBehavior extends Behavior
{
    public $isSavedViaApi = false;
}


and in your main module file

use craft\elements\Entry;
use craft\events\DefineBehaviorsEvent;

Event::on(
    Entry::class,
    Entry::EVENT_DEFINE_BEHAVIORS,
    static function(DefineBehaviorsEvent $event) {
        //so every element gets a isSubscribed attached
        $event-&gt;behaviors[] = MyBehavior::class;
    }
);