Handling Entry Saves

As of Craft 3.2, drafts and revisions are elements just like published entries.

If your plugin or module was registering a listener for an entry’s Element::EVENT_BEFORE_SAVE or Element::EVENT_AFTER_SAVE, you may want to avoid multiple saves by first checking whether the element is a draft or revision as opposed to a published entry:

use craft\base\Element;
use craft\elements\Entry;
use craft\events\ModelEvent;
use craft\helpers\ElementHelper;
use yii\base\Event;

Event::on(
    Entry::class, 
    Element::EVENT_BEFORE_SAVE, 
    function(ModelEvent $e) {
        /* @var Entry $entry */
        $entry = $e->sender;

        if (ElementHelper::isDraftOrRevision($entry)) {
            // don’t do anything with drafts or revisions
            return;
        }
        
        // ...
    }
);

The same applies to Elements::EVENT_BEFORE_SAVE_ELEMENT and Elements::EVENT_AFTER_SAVE_ELEMENT.

Craft 3.2 consolidated drafts and revisions, previously with their own separate workflow and storage, intro entries with new draftId and revisionId properties and that ElementHelper::isDraftOrRevision() method.

Craft 3.7 overhauled entry authoring to be more intuitive, adding several concepts to clarify more nuanced underlying context.

Provisional Drafts #

The first of these concepts is a “provisional” draft, which consists of an author’s edits to an entry that weren’t explicitly saved or published. The author is prompted to discard or publish these changes, and that provisional draft stops being provisional when it’s explicitly saved as a draft, published, merged, or discarded.

Provisional draft

Authors are prompted to keep or discard unsaved changes in Craft 3.7 and higher. Behind the scenes, these implicit changes are considered provisional drafts.

Canonical Entries #

Craft 3.7 also introduced the concept of “canonical” entries.

Since an entry may represent implicit edits, an explicit draft or revision, or published content, it’s challenging to distinguish the most current, primary source of content from unpublished edits. That’s exactly what “canonical” entries are; if you don’t care about private edits but only entries that are live or pending, look for entries where the isCanonical property is true. The opposite of a canonical entry is any one of its ephemeral, derivative counterparts. For that reason, entries have an isDerivative property that will be true when it’s a draft or revision, and otherwise false.

Drafts

The “Current” entry is the canonical one. A canonical entry will never be a derivative draft or revision—though either may be saved or merged to become the canonical entry.

Fresh Entries #

An entry is considered “fresh” when it hasn’t been explicitly saved, but it doesn’t have any validation errors. In other words, when that entry’s content has first been edited, its isFresh property will be true.

New entry

Once this new entry form validates for the first time, it will be considered fresh.

Entries have a few boolean properties that describe newness:

  • isNew has been around longest and indicates whether the entry being saved is new. This became unreliable in Craft 3.2 since drafts and revisions were also entries that could be new—though these derivatives were usually unexpected.
  • isNewForSite was added more recently than isNew and is almost identical, but relative to current site instead of the Craft install as a whole.
  • firstSave indicates whether an entry is being saved for the first time in a canonical, non-derivative state. (This is most similar to isNew prior to Craft 3.2.)

Applies to Craft CMS 3.