Answers.Body: <p>I'm not certain what the issue is but a few things that have got me in the past that I can note:</p>
  <p>While you can use <code>EVENT_DEFINE_NATIVE_FIELDS</code> to register native fields, if you control the element and are the only one deciding if the native field should exist on the element, you can also just do this in the <code>Element::getFieldLayout()</code> method. Your current code might be adding your field to all field layouts as mandatory. You can check the Event sender to only add it where you want, or just lean on the Element to only act in the context you want.</p>
  <p>I believe fields may also need to pass validation before being added to the model, so you might need to add your field to the validation rules. A simple way to do that is to just define your field as <code>safe</code>:</p>
  <pre><code>protected function defineRules(): array
  {
      $rules = parent::defineRules();
  
      $rules[] = [['myCustomField'], 'safe'];
   
      return $rules;
  }
  </code></pre>
  
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-09-26T19:14:54.777Z
Answers.Text: Ben is exactly right—Yii will only see "safe" attributes as mass-assignable, which is how Craft passes incoming request params to your model in an element-agnostic way!
Answers.UserDisplayName: August Miller
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-09-27T06:30:24.240Z
Answers.Text: Thanks that did the trick
Answers.UserDisplayName: user14370
Answers.ContentLicense: CC BY-SA 4.0
Answers.CreationDate: 2023-09-26T18:55:51.413Z
Answers.LastActivityDate: 2023-09-26T19:08:41.820Z
Answers.LastEditDate: 2023-09-26T19:08:41.820Z
Answers.UserDisplayName: Ben Parizek
Body: <p>Following the docs on <a href="https://craftcms.com/docs/4.x/extend/element-types.html" rel="nofollow noreferrer">the offical CraftCMS website</a> i'm not able to get &quot;Native Layout Elements&quot; saved. My field <code>body</code> is always empty. Any idea what i'm missing here?</p>
  <p><strong>Relevant Part of Plugin base class</strong></p>
  <pre><code>&lt;?php
  
  namespace tkf\craftfirebasemessaging;
  
  use Craft;
  use craft\base\Model;
  use craft\base\Plugin;
  use craft\events\RegisterComponentTypesEvent;
  use craft\events\RegisterUrlRulesEvent;
  use craft\services\Elements;
  use craft\web\UrlManager;
  use craft\models\FieldLayout;
  use craft\events\DefineFieldLayoutFieldsEvent;
  use craft\fieldlayoutelements\TitleField;
  use craft\fieldlayoutelements\TextareaField;
  use tkf\craftfirebasemessaging\elements\Notification;
  use tkf\craftfirebasemessaging\models\Settings;
  use tkf\craftfirebasemessaging\services\Messaging;
  use tkf\craftfirebasemessaging\services\Tokens;
  use yii\base\Event;
  
  class FirebaseMessaging extends Plugin
  {
      public string $schemaVersion = '1.0.0';
      public bool $hasCpSettings = true;
  
      public static function config(): array
      {
          return [
              'components' =&gt; ['tokens' =&gt; Tokens::class, 'messaging' =&gt; Messaging::class],
          ];
      }
  
      public function init(): void
      {
          parent::init();
  
          // Defer most setup tasks until Craft is fully initialized
          Craft::$app-&gt;onInit(function() {
              $this-&gt;attachEventHandlers();
              // ...
          });
      }
  
      protected function createSettingsModel(): ?Model
      {
          return Craft::createObject(Settings::class);
      }
  
      protected function settingsHtml(): ?string
      {
          return Craft::$app-&gt;view-&gt;renderTemplate('firebase-messaging/_settings.twig', [
              'plugin' =&gt; $this,
              'settings' =&gt; $this-&gt;getSettings(),
          ]);
      }
  
      private function attachEventHandlers(): void
      {
          Event::on(
              UrlManager::class,
              UrlManager::EVENT_REGISTER_SITE_URL_RULES,
              function (RegisterUrlRulesEvent $event) {
                  $event-&gt;rules['api/v1/firebase-token'] = 'firebase-messaging/tokens/index';
              }
          );
          Event::on(
              Elements::class,
              Elements::EVENT_REGISTER_ELEMENT_TYPES,
              function (RegisterComponentTypesEvent $event) {
                  $event-&gt;types[] = Notification::class;
              }
          );
          Event::on(
              UrlManager::class,
              UrlManager::EVENT_REGISTER_CP_URL_RULES,
              function (RegisterUrlRulesEvent $event) {
                  $event-&gt;rules['notifications'] = ['template' =&gt; 'firebase-messaging/notifications/_index.twig'];
                  $event-&gt;rules['notifications/&lt;elementId:\\d+&gt;'] = 'elements/edit';
              }
          );
  
          Event::on(
              FieldLayout::class,
              FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
              function(DefineFieldLayoutFieldsEvent $event) {
                  /** @var FieldLayout $fieldLayout */
                  $fieldLayout = $event-&gt;sender;
  
                  $event-&gt;fields[] = new TextareaField([
                      'attribute' =&gt; 'body',
                      'required' =&gt; true,
                      'mandatory' =&gt; true,
                      'label' =&gt; 'Body',
                      'instructions' =&gt; 'Enter a description.',
                  ]);
              }
          );
      }
  }
  </code></pre>
  <p><strong>Relevant Part of my Element Class</strong></p>
  <pre><code>&lt;?php
  
  namespace tkf\craftfirebasemessaging\elements;
  
  use Craft;
  use craft\base\Element;
  use craft\elements\User;
  use craft\elements\conditions\ElementConditionInterface;
  use craft\elements\db\ElementQueryInterface;
  use craft\helpers\UrlHelper;
  use craft\helpers\Db;
  use craft\web\CpScreenResponseBehavior;
  use tkf\craftfirebasemessaging\elements\conditions\NotificationCondition;
  use tkf\craftfirebasemessaging\elements\db\NotificationQuery;
  use yii\web\Response;
  use craft\models\FieldLayout;
  
  /**
   * Notification element type
   */
  class Notification extends Element
  {
      public static function displayName(): string
      {
          return Craft::t('firebase-messaging', 'Notification');
      }
  
      public static function lowerDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'notification');
      }
  
      public static function pluralDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'Notifications');
      }
  
      public static function pluralLowerDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'notifications');
      }
  
      public static function refHandle(): ?string
      {
          return 'notification';
      }
  
      public static function trackChanges(): bool
      {
          return false;
      }
  
      public static function hasContent(): bool
      {
          return false;
      }
  
      public static function hasTitles(): bool
      {
          return true;
      }
  
      public static function hasUris(): bool
      {
          return false;
      }
  
      public static function isLocalized(): bool
      {
          return false;
      }
  
      public static function hasStatuses(): bool
      {
          return true;
      }
  
      public ?string $body = null;
  
      public function getFieldLayout(): FieldLayout
      {
          return \Craft::$app-&gt;getFields()-&gt;getLayoutByType(self::class);
      }
  
      public static function find(): ElementQueryInterface
      {
          return Craft::createObject(NotificationQuery::class, [static::class]);
      }
  
      public static function createCondition(): ElementConditionInterface
      {
          return Craft::createObject(NotificationCondition::class, [static::class]);
      }
  
      protected static function defineSources(string $context): array
      {
          return [
              [
                  'key' =&gt; '*',
                  'label' =&gt; Craft::t('firebase-messaging', 'All notifications'),
              ],
          ];
      }
  
      protected static function defineActions(string $source): array
      {
          // List any bulk element actions here
          return [];
      }
  
      protected static function includeSetStatusAction(): bool
      {
          return true;
      }
  
      protected static function defineSortOptions(): array
      {
          return [
              'title' =&gt; Craft::t('app', 'Title'),
              [
                  'label' =&gt; Craft::t('app', 'Date Created'),
                  'orderBy' =&gt; 'elements.dateCreated',
                  'attribute' =&gt; 'dateCreated',
                  'defaultDir' =&gt; 'desc',
              ],
              [
                  'label' =&gt; Craft::t('app', 'Date Updated'),
                  'orderBy' =&gt; 'elements.dateUpdated',
                  'attribute' =&gt; 'dateUpdated',
                  'defaultDir' =&gt; 'desc',
              ],
              [
                  'label' =&gt; Craft::t('app', 'ID'),
                  'orderBy' =&gt; 'elements.id',
                  'attribute' =&gt; 'id',
              ],
          ];
      }
  
      protected static function defineTableAttributes(): array
      {
          return [
              'id' =&gt; ['label' =&gt; Craft::t('app', 'ID')],
              'dateCreated' =&gt; ['label' =&gt; Craft::t('app', 'Date Created')],
              'dateUpdated' =&gt; ['label' =&gt; Craft::t('app', 'Date Updated')],
          ];
      }
  
      protected static function defineDefaultTableAttributes(string $source): array
      {
          return [
              'link'
          ];
      }
  
      protected function defineRules(): array
      {
          return array_merge(parent::defineRules(), [
              // ...
          ]);
      }
  
      public function getUriFormat(): ?string
      {
          // If notifications should have URLs, define their URI format here
          return null;
      }
  
      protected function previewTargets(): array
      {
          $previewTargets = [];
          $url = $this-&gt;getUrl();
          if ($url) {
              $previewTargets[] = [
                  'label' =&gt; Craft::t('app', 'Primary {type} page', [
                      'type' =&gt; self::lowerDisplayName(),
                  ]),
                  'url' =&gt; $url,
              ];
          }
          return $previewTargets;
      }
  
      protected function route(): array|string|null
      {
          // Define how notifications should be routed when their URLs are requested
          return [
              'templates/render',
              [
                  'template' =&gt; 'site/template/path',
                  'variables' =&gt; ['notification' =&gt; $this],
              ]
          ];
      }
  
      public function canView(User $user): bool
      {
          if (parent::canView($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user-&gt;can('viewNotifications');
      }
  
      public function canSave(User $user): bool
      {
          if (parent::canSave($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user-&gt;can('saveNotifications');
      }
  
      public function canDuplicate(User $user): bool
      {
          if (parent::canDuplicate($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user-&gt;can('saveNotifications');
      }
  
      public function canDelete(User $user): bool
      {
          if (parent::canSave($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user-&gt;can('deleteNotifications');
      }
  
      public function canCreateDrafts(User $user): bool
      {
          return true;
      }
  
      protected function cpEditUrl(): ?string
      {
          return sprintf('notifications/%s', $this-&gt;getCanonicalId());
      }
  
      public function getPostEditUrl(): ?string
      {
          return UrlHelper::cpUrl('notifications');
      }
  
      public function prepareEditScreen(Response $response, string $containerId): void
      {
          /** @var Response|CpScreenResponseBehavior $response */
          $response-&gt;crumbs([
              [
                  'label' =&gt; self::pluralDisplayName(),
                  'url' =&gt; UrlHelper::cpUrl('notifications'),
              ]
          ]);
      }
  
      public function afterSave(bool $isNew): void
      {
          if (!$this-&gt;propagating) {
              Db::upsert('{{%firebase-messaging_notifications}}', [
                  'id' =&gt; $this-&gt;id,
              ], [
                  'body' =&gt; $this-&gt;body,
              ]);
          }
  
          parent::afterSave($isNew);
      }
  }
  </code></pre>
  
Tags: <plugin-development><element>
Title: Custom Element Type Documentation not working or confusing
ThreadText: # Custom Element Type Documentation not working or confusing
  
  (Asked by: username_0 on 2023-09-26)
  
  Following the docs on [the offical CraftCMS website](https://craftcms.com/docs/4.x/extend/element-types.html) i'm not able to get "Native Layout Elements" saved. My field `body` is always empty. Any idea what i'm missing here?
  
  **Relevant Part of Plugin base class**
  
  ```
  <?php
  
  namespace tkf\craftfirebasemessaging;
  
  use Craft;
  use craft\base\Model;
  use craft\base\Plugin;
  use craft\events\RegisterComponentTypesEvent;
  use craft\events\RegisterUrlRulesEvent;
  use craft\services\Elements;
  use craft\web\UrlManager;
  use craft\models\FieldLayout;
  use craft\events\DefineFieldLayoutFieldsEvent;
  use craft\fieldlayoutelements\TitleField;
  use craft\fieldlayoutelements\TextareaField;
  use tkf\craftfirebasemessaging\elements\Notification;
  use tkf\craftfirebasemessaging\models\Settings;
  use tkf\craftfirebasemessaging\services\Messaging;
  use tkf\craftfirebasemessaging\services\Tokens;
  use yii\base\Event;
  
  class FirebaseMessaging extends Plugin
  {
      public string $schemaVersion = '1.0.0';
      public bool $hasCpSettings = true;
  
      public static function config(): array
      {
          return [
              'components' => ['tokens' => Tokens::class, 'messaging' => Messaging::class],
          ];
      }
  
      public function init(): void
      {
          parent::init();
  
          // Defer most setup tasks until Craft is fully initialized
          Craft::$app->onInit(function() {
              $this->attachEventHandlers();
              // ...
          });
      }
  
      protected function createSettingsModel(): ?Model
      {
          return Craft::createObject(Settings::class);
      }
  
      protected function settingsHtml(): ?string
      {
          return Craft::$app->view->renderTemplate('firebase-messaging/_settings.twig', [
              'plugin' => $this,
              'settings' => $this->getSettings(),
          ]);
      }
  
      private function attachEventHandlers(): void
      {
          Event::on(
              UrlManager::class,
              UrlManager::EVENT_REGISTER_SITE_URL_RULES,
              function (RegisterUrlRulesEvent $event) {
                  $event->rules['api/v1/firebase-token'] = 'firebase-messaging/tokens/index';
              }
          );
          Event::on(
              Elements::class,
              Elements::EVENT_REGISTER_ELEMENT_TYPES,
              function (RegisterComponentTypesEvent $event) {
                  $event->types[] = Notification::class;
              }
          );
          Event::on(
              UrlManager::class,
              UrlManager::EVENT_REGISTER_CP_URL_RULES,
              function (RegisterUrlRulesEvent $event) {
                  $event->rules['notifications'] = ['template' => 'firebase-messaging/notifications/_index.twig'];
                  $event->rules['notifications/<elementId:\\d+>'] = 'elements/edit';
              }
          );
  
          Event::on(
              FieldLayout::class,
              FieldLayout::EVENT_DEFINE_NATIVE_FIELDS,
              function(DefineFieldLayoutFieldsEvent $event) {
                  /** @var FieldLayout $fieldLayout */
                  $fieldLayout = $event->sender;
  
                  $event->fields[] = new TextareaField([
                      'attribute' => 'body',
                      'required' => true,
                      'mandatory' => true,
                      'label' => 'Body',
                      'instructions' => 'Enter a description.',
                  ]);
              }
          );
      }
  }
  
  ```
  
  **Relevant Part of my Element Class**
  
  ```
  <?php
  
  namespace tkf\craftfirebasemessaging\elements;
  
  use Craft;
  use craft\base\Element;
  use craft\elements\User;
  use craft\elements\conditions\ElementConditionInterface;
  use craft\elements\db\ElementQueryInterface;
  use craft\helpers\UrlHelper;
  use craft\helpers\Db;
  use craft\web\CpScreenResponseBehavior;
  use tkf\craftfirebasemessaging\elements\conditions\NotificationCondition;
  use tkf\craftfirebasemessaging\elements\db\NotificationQuery;
  use yii\web\Response;
  use craft\models\FieldLayout;
  
  /**
   * Notification element type
   */
  class Notification extends Element
  {
      public static function displayName(): string
      {
          return Craft::t('firebase-messaging', 'Notification');
      }
  
      public static function lowerDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'notification');
      }
  
      public static function pluralDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'Notifications');
      }
  
      public static function pluralLowerDisplayName(): string
      {
          return Craft::t('firebase-messaging', 'notifications');
      }
  
      public static function refHandle(): ?string
      {
          return 'notification';
      }
  
      public static function trackChanges(): bool
      {
          return false;
      }
  
      public static function hasContent(): bool
      {
          return false;
      }
  
      public static function hasTitles(): bool
      {
          return true;
      }
  
      public static function hasUris(): bool
      {
          return false;
      }
  
      public static function isLocalized(): bool
      {
          return false;
      }
  
      public static function hasStatuses(): bool
      {
          return true;
      }
  
      public ?string $body = null;
  
      public function getFieldLayout(): FieldLayout
      {
          return \Craft::$app->getFields()->getLayoutByType(self::class);
      }
  
      public static function find(): ElementQueryInterface
      {
          return Craft::createObject(NotificationQuery::class, [static::class]);
      }
  
      public static function createCondition(): ElementConditionInterface
      {
          return Craft::createObject(NotificationCondition::class, [static::class]);
      }
  
      protected static function defineSources(string $context): array
      {
          return [
              [
                  'key' => '*',
                  'label' => Craft::t('firebase-messaging', 'All notifications'),
              ],
          ];
      }
  
      protected static function defineActions(string $source): array
      {
          // List any bulk element actions here
          return [];
      }
  
      protected static function includeSetStatusAction(): bool
      {
          return true;
      }
  
      protected static function defineSortOptions(): array
      {
          return [
              'title' => Craft::t('app', 'Title'),
              [
                  'label' => Craft::t('app', 'Date Created'),
                  'orderBy' => 'elements.dateCreated',
                  'attribute' => 'dateCreated',
                  'defaultDir' => 'desc',
              ],
              [
                  'label' => Craft::t('app', 'Date Updated'),
                  'orderBy' => 'elements.dateUpdated',
                  'attribute' => 'dateUpdated',
                  'defaultDir' => 'desc',
              ],
              [
                  'label' => Craft::t('app', 'ID'),
                  'orderBy' => 'elements.id',
                  'attribute' => 'id',
              ],
          ];
      }
  
      protected static function defineTableAttributes(): array
      {
          return [
              'id' => ['label' => Craft::t('app', 'ID')],
              'dateCreated' => ['label' => Craft::t('app', 'Date Created')],
              'dateUpdated' => ['label' => Craft::t('app', 'Date Updated')],
          ];
      }
  
      protected static function defineDefaultTableAttributes(string $source): array
      {
          return [
              'link'
          ];
      }
  
      protected function defineRules(): array
      {
          return array_merge(parent::defineRules(), [
              // ...
          ]);
      }
  
      public function getUriFormat(): ?string
      {
          // If notifications should have URLs, define their URI format here
          return null;
      }
  
      protected function previewTargets(): array
      {
          $previewTargets = [];
          $url = $this->getUrl();
          if ($url) {
              $previewTargets[] = [
                  'label' => Craft::t('app', 'Primary {type} page', [
                      'type' => self::lowerDisplayName(),
                  ]),
                  'url' => $url,
              ];
          }
          return $previewTargets;
      }
  
      protected function route(): array|string|null
      {
          // Define how notifications should be routed when their URLs are requested
          return [
              'templates/render',
              [
                  'template' => 'site/template/path',
                  'variables' => ['notification' => $this],
              ]
          ];
      }
  
      public function canView(User $user): bool
      {
          if (parent::canView($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user->can('viewNotifications');
      }
  
      public function canSave(User $user): bool
      {
          if (parent::canSave($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user->can('saveNotifications');
      }
  
      public function canDuplicate(User $user): bool
      {
          if (parent::canDuplicate($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user->can('saveNotifications');
      }
  
      public function canDelete(User $user): bool
      {
          if (parent::canSave($user)) {
              return true;
          }
          // todo: implement user permissions
          return $user->can('deleteNotifications');
      }
  
      public function canCreateDrafts(User $user): bool
      {
          return true;
      }
  
      protected function cpEditUrl(): ?string
      {
          return sprintf('notifications/%s', $this->getCanonicalId());
      }
  
      public function getPostEditUrl(): ?string
      {
          return UrlHelper::cpUrl('notifications');
      }
  
      public function prepareEditScreen(Response $response, string $containerId): void
      {
          /** @var Response|CpScreenResponseBehavior $response */
          $response->crumbs([
              [
                  'label' => self::pluralDisplayName(),
                  'url' => UrlHelper::cpUrl('notifications'),
              ]
          ]);
      }
  
      public function afterSave(bool $isNew): void
      {
          if (!$this->propagating) {
              Db::upsert('{{%firebase-messaging_notifications}}', [
                  'id' => $this->id,
              ], [
                  'body' => $this->body,
              ]);
          }
  
          parent::afterSave($isNew);
      }
  }
  
  ```
  
  **Tags:** plugin-development, element
  
  
  ---
  
  ## 1 answers
  
  ### Answer by username_1 on 2023-09-26. Score: 2 | Accepted answer
  
  I'm not certain what the issue is but a few things that have got me in the past that I can note:
  
  While you can use `EVENT_DEFINE_NATIVE_FIELDS` to register native fields, if you control the element and are the only one deciding if the native field should exist on the element, you can also just do this in the `Element::getFieldLayout()` method. Your current code might be adding your field to all field layouts as mandatory. You can check the Event sender to only add it where you want, or just lean on the Element to only act in the context you want.
  
  I believe fields may also need to pass validation before being added to the model, so you might need to add your field to the validation rules. A simple way to do that is to just define your field as `safe`:
  
  ```
  protected function defineRules(): array
  {
      $rules = parent::defineRules();
  
      $rules[] = [['myCustomField'], 'safe'];
   
      return $rules;
  }
  
  ```
  
  > **Comments:**
  > **username_2**: Ben is exactly right—Yii will only see "safe" attributes as mass-assignable, which is how Craft passes incoming request params to your model in an element-agnostic way!  
  > **username_0**: Thanks that did the trick  
  
  ---
  
  