8 – Custom element doesn’t appear in the UI

On my Drupal 8 dev instance (Acquia, PHP 7.315, Windows 10) I have the WebForm module installed.
I want to add a custom element, like moduleswebformmoduleswebform_example_element. I have successfully installed the default webform_example_element and this element can be added to a webform using the webform UI.

I have created a copy of the webform_example_element in modulescustommy_webform_example_element and changed webform_example_element to my_webform_example_element in the PHP and YAML files. I have successfully installed the my_webform_example_element module using Drupal extend (no errors in the drupal log), but the my_webform_example_element doesn’t show up in the webform UI.

Is there something extra that must be done to use custom elements from the modulecustom directory?

These are the files I used and their content.

my_webform_example_element.info.yml

name: 'My Webform Element Example'
type: module
description: 'My: Provides an example that shows how to create a Webform element.'
package: 'Webform example'
core_version_requirement: ^8.8
dependencies:
  - 'webform:webform'

# Information added by Drupal.org packaging script on 2021-02-04
version: '8.x-5.24'
project: 'webform'
datestamp: 1612453182

config/install/webform.webform.my_webform_example_element.yml

uuid: null
langcode: en
status: open
dependencies:
  enforced:
    module:
      - my_webform_example_element
open: null
close: null
weight: 0
uid: null
template: false
archive: false
id: my_webform_example_element
title: 'Example: My Webform Element'
description: 'MY example of a custom Webform element.'
category: Example
elements: |
  my_webform_example_element:
    '#type': my_webform_example_element
    '#title': 'My Webform Example Element'
  my_webform_example_element_multiple:
    '#type': my_webform_example_element
    '#title': 'My Webform Example Element Multiple'
    '#multiple': true
  
css: ''
javascript: ''
settings:
  ajax: false
  ajax_scroll_top: form
  ajax_progress_type: ''
  ajax_effect: ''
  ajax_speed: null
  page: true
  page_submit_path: ''
  page_confirm_path: ''
  page_theme_name: ''
  form_title: source_entity_webform
  form_submit_once: false
  form_exception_message: ''
  form_open_message: ''
  form_close_message: ''
  form_previous_submissions: true
  form_confidential: false
  form_confidential_message: ''
  form_remote_addr: true
  form_convert_anonymous: false
  form_prepopulate: false
  form_prepopulate_source_entity: false
  form_prepopulate_source_entity_required: false
  form_prepopulate_source_entity_type: ''
  form_reset: false
  form_disable_autocomplete: false
  form_novalidate: false
  form_disable_inline_errors: false
  form_required: false
  form_unsaved: false
  form_disable_back: false
  form_submit_back: false
  form_autofocus: false
  form_details_toggle: false
  form_access_denied: default
  form_access_denied_title: ''
  form_access_denied_message: ''
  form_access_denied_attributes: {  }
  form_file_limit: ''
  share: false
  share_node: false
  share_theme_name: ''
  share_title: true
  share_page_body_attributes: {  }
  submission_label: ''
  submission_log: false
  submission_views: {  }
  submission_views_replace: {  }
  submission_user_columns: {  }
  submission_user_duplicate: false
  submission_access_denied: default
  submission_access_denied_title: ''
  submission_access_denied_message: ''
  submission_access_denied_attributes: {  }
  submission_exception_message: ''
  submission_locked_message: ''
  submission_excluded_elements: {  }
  submission_exclude_empty: false
  submission_exclude_empty_checkbox: false
  previous_submission_message: ''
  previous_submissions_message: ''
  autofill: false
  autofill_message: ''
  autofill_excluded_elements: {  }
  wizard_progress_bar: true
  wizard_progress_pages: false
  wizard_progress_percentage: false
  wizard_progress_link: false
  wizard_progress_states: false
  wizard_auto_forward: true
  wizard_auto_forward_hide_next_button: false
  wizard_keyboard: true
  wizard_start_label: ''
  wizard_preview_link: false
  wizard_confirmation: true
  wizard_confirmation_label: ''
  wizard_track: ''
  wizard_prev_button_label: ''
  wizard_next_button_label: ''
  wizard_toggle: false
  wizard_toggle_show_label: ''
  wizard_toggle_hide_label: ''
  preview: 1
  preview_label: ''
  preview_title: ''
  preview_message: ''
  preview_attributes: {  }
  preview_excluded_elements: {  }
  preview_exclude_empty: true
  preview_exclude_empty_checkbox: false
  draft: none
  draft_multiple: false
  draft_auto_save: false
  draft_saved_message: ''
  draft_loaded_message: ''
  draft_pending_single_message: ''
  draft_pending_multiple_message: ''
  confirmation_type: page
  confirmation_title: ''
  confirmation_message: ''
  confirmation_url: ''
  confirmation_attributes: {  }
  confirmation_back: true
  confirmation_back_label: ''
  confirmation_back_attributes: {  }
  confirmation_exclude_query: false
  confirmation_exclude_token: false
  confirmation_update: false
  limit_total: null
  limit_total_interval: null
  limit_total_message: ''
  limit_total_unique: false
  limit_user: null
  limit_user_interval: null
  limit_user_message: ''
  limit_user_unique: false
  entity_limit_total: null
  entity_limit_total_interval: null
  entity_limit_user: null
  entity_limit_user_interval: null
  purge: none
  purge_days: null
  results_disabled: false
  results_disabled_ignore: false
  results_customize: false
  token_view: false
  token_update: false
  token_delete: false
  serial_disabled: false
access:
  create:
    roles:
      - anonymous
      - authenticated
    users: {  }
    permissions: {  }
  view_any:
    roles: {  }
    users: {  }
    permissions: {  }
  update_any:
    roles: {  }
    users: {  }
    permissions: {  }
  delete_any:
    roles: {  }
    users: {  }
    permissions: {  }
  purge_any:
    roles: {  }
    users: {  }
    permissions: {  }
  view_own:
    roles: {  }
    users: {  }
    permissions: {  }
  update_own:
    roles: {  }
    users: {  }
    permissions: {  }
  delete_own:
    roles: {  }
    users: {  }
    permissions: {  }
  administer:
    roles: {  }
    users: {  }
    permissions: {  }
  test:
    roles: {  }
    users: {  }
    permissions: {  }
  configuration:
    roles: {  }
    users: {  }
    permissions: {  }
handlers: {  }
variants: {  }

src/Element/MyWebformExampleElement.php

namespace Drupalmy_webform_example_elementElement;

use DrupalCoreRenderElement;
use DrupalCoreRenderElementFormElement;
use DrupalCoreFormFormStateInterface;

/**
 * Provides a 'my_webform_example_element'.
 *
 * Webform elements are just wrappers around form elements, therefore every
 * webform element must have correspond FormElement.
 *
 * Below is the definition for a custom 'my_webform_example_element' which just
 * renders a simple text field.
 *
 * @FormElement("my_webform_example_element")
 *
 * @see DrupalCoreRenderElementFormElement
 * @see https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Core%21Render%21Element%21FormElement.php/class/FormElement
 * @see DrupalCoreRenderElementRenderElement
 * @see https://api.drupal.org/api/drupal/namespace/Drupal%21Core%21Render%21Element
 * @see Drupalmy_webform_example_elementElementMyWebformExampleElement
 */
class MyWebformExampleElement extends FormElement {

  /**
   * {@inheritdoc}
   */
  public function getInfo() {
    $class = get_class($this);
    return (
      '#input' => TRUE,
      '#size' => 60,
      '#process' => (
        ($class, 'processWebformElementExample'),
        ($class, 'processAjaxForm'),
      ),
      '#element_validate' => (
        ($class, 'validateMyWebformExampleElement'),
      ),
      '#pre_render' => (
        ($class, 'preRenderMyWebformExampleElement'),
      ),
      '#theme' => 'input__my_webform_example_element',
      '#theme_wrappers' => ('form_element'),
    );
  }

  /**
   * Processes a 'my_webform_example_element' element.
   */
  public static function processWebformElementExample(&$element, FormStateInterface $form_state, &$complete_form) {
    // Here you can add and manipulate your element's properties and callbacks.
    return $element;
  }

  /**
   * Webform element validation handler for #type 'my_webform_example_element'.
   */
  public static function validateMyWebformExampleElement(&$element, FormStateInterface $form_state, &$complete_form) {
    // Here you can add custom validation logic.
  }

  /**
   * Prepares a #type 'email_multiple' render element for theme_element().
   *
   * @param array $element
   *   An associative array containing the properties of the element.
   *   Properties used: #title, #value, #description, #size, #maxlength,
   *   #placeholder, #required, #attributes.
   *
   * @return array
   *   The $element with prepared variables ready for theme_element().
   */
  public static function preRenderMyWebformExampleElement(array $element) {
    $element('#attributes')('type') = 'text';
    Element::setAttributes($element, ('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
    static::setAttributes($element, ('form-text', 'my-webform-example-element'));
    return $element;
  }

}

Plugin/MyWebformElement/MyWebformExampleElement.php

namespace Drupalmy_webform_example_elementPluginWebformElement;

use DrupalCoreFormFormStateInterface;
use DrupalwebformPluginWebformElementBase;
use DrupalwebformWebformSubmissionInterface;

/**
 * Provides a 'my_webform_example_element' element.
 *
 * @WebformElement(
 *   id = "my_webform_example_element",
 *   label = @Translation("My Webform example element"),
 *   description = @Translation("Provides MY webform element example."),
 *   category = @Translation("Example elements"),
 * )
 *
 * @see Drupalmy_webform_example_elementElementMyWebformExampleElement
 * @see DrupalwebformPluginWebformElementBase
 * @see DrupalwebformPluginWebformElementInterface
 * @see DrupalwebformAnnotationWebformElement
 */
class MyWebformExampleElement extends WebformElementBase {

  /**
   * {@inheritdoc}
   */
  protected function defineDefaultProperties() {
    // Here you define your webform element's default properties,
    // which can be inherited.
    //
    // @see DrupalwebformPluginWebformElementBase::defaultProperties
    // @see DrupalwebformPluginWebformElementBase::defaultBaseProperties
    return (
      'multiple' => '',
      'size' => '',
      'minlength' => '',
      'maxlength' => '',
      'placeholder' => '',
    ) + parent::defineDefaultProperties();
  }

  /****************************************************************************/

  /**
   * {@inheritdoc}
   */
  public function prepare(array &$element, WebformSubmissionInterface $webform_submission = NULL) {
    parent::prepare($element, $webform_submission);

    // Here you can customize the webform element's properties.
    // You can also customize the form/render element's properties via the
    // FormElement.
    //
    // @see Drupalmy_webform_example_elementElementMyWebformExampleElement::processWebformElementExample
  }

  /**
   * {@inheritdoc}
   */
  public function form(array $form, FormStateInterface $form_state) {
    $form = parent::form($form, $form_state);
    // Here you can define and alter a webform element's properties UI.
    // Form element property visibility and default values are defined via
    // ::defaultProperties.
    //
    // @see DrupalwebformPluginWebformElementBase::form
    // @see DrupalwebformPluginWebformElementTextBase::form
    return $form;
  }

}