octobercms/october · warning · ApplicationException

Cannot delete this template because it is locked

Error message

Cannot delete this template because it is locked

What it means

MailLayout::beforeDelete() blocks deletion of locked mail layouts. Layouts seeded from registration carry is_locked = 1 (e.g. the built-in 'default' layout that system and plugin emails extend), and deleting such a row throws 'Cannot delete this template because it is locked' before the delete query runs.

Source

Thrown at modules/system/models/MailLayout.php:62

    /**
     * @var array jsonable attribute names that are json encoded and decoded from the database
     */
    protected $jsonable = [
        'options'
    ];

    /**
     * @var array codeCache
     */
    public static $codeCache;

    /**
     * beforeDelete
     */
    public function beforeDelete()
    {
        if ($this->is_locked) {
            throw new ApplicationException('Cannot delete this template because it is locked');
        }
    }

    /**
     * listCodes
     */
    public static function listCodes()
    {
        if (self::$codeCache !== null) {
            return self::$codeCache;
        }

        return self::$codeCache = self::lists('id', 'code');
    }

    /**
     * getIdFromCode
     */

View on GitHub (pinned to b608633a7e)

Solutions

  1. Delete a non-locked (custom) layout instead — locked ones are system-managed
  2. If you truly must remove it, unlock first: set is_locked to false, save, then delete
  3. In bulk scripts, skip locked rows: iterate MailLayout::where('is_locked', false)->get() and delete each

Example fix

// before
$layout->delete(); // throws: is_locked

// after — deliberate removal of a system layout
$layout->is_locked = false;
$layout->save();
$layout->delete();
Defensive patterns

Strategy: type-guard

Validate before calling

if ($layout->is_locked) {
    // refuse deletion in the UI, or explicitly unlock first if removal is intended
}

Try / catch

try { $layout->delete(); } catch (\ApplicationException $ex) { /* keep the row and inform the user it is system-managed */ }

Prevention

When it happens

Trigger: $layout->delete() on a row with is_locked true; the backend mail layout delete action on a locked layout; bulk deletion iterating all layouts without filtering is_locked.

Common situations: Trying to remove the default mail layout during cleanup; import/sync scripts deleting every mail_layouts row.

Related errors


AI-assisted analysis of octobercms/october@b608633a7e (2026-08-21). Data as JSON: /api/errors/868f4b587c9f4148. Report an issue: GitHub.