Title: [BUG] Fields button duplication
Last modified: March 16, 2026

---

# [BUG] Fields button duplication

 *  [Bartek20](https://wordpress.org/support/users/bartek20/)
 * (@bartek20)
 * [1 month ago](https://wordpress.org/support/topic/bug-fields-button-duplication/)
 * Since I’m using the Lite version of WPForms, I had to implement the field myself,
   which is normally a paid field.
   After implementing custom field, during testing,
   I discovered that the button for adding a field to the form (in the builder) 
   was duplicated. After reviewing the code, I found a bug in the official implementation
   of button generation.I can’t confirm whether the error also occurs in the paid
   version of the plugin (I assume it does), but in the Lite version, it is visible
   when creating a field of the same type as one of the default fields.
 * Bug Location:
   File: src/Lite/Admin/Education/Builder/Fields.phpFunction: add_fieldsDescription:
   In the code, the search for a field of type is incorrectly performed on the field
   group, rather than on the field list.
 *     ```wp-block-code
       // What is in code// Skip if in the current group already exist field of this type.if ( ! empty( wp_list_filter( $group_data, [ 'type' => $edu_field['type'] ] ) ) ) {	continue;}// What should be// Skip if in the current group already exist field of this type.if ( ! empty( wp_list_filter( $group_data[ 'fields' ], [ 'type' => $edu_field['type'] ] ) ) ) {	continue;}
       ```
   

Viewing 3 replies - 1 through 3 (of 3 total)

 *  Plugin Support [Ralden Souza](https://wordpress.org/support/users/rsouzaam/)
 * (@rsouzaam)
 * [1 month ago](https://wordpress.org/support/topic/bug-fields-button-duplication/#post-18854163)
 * Hi [@bartek20](https://wordpress.org/support/users/bartek20/),
 * Thanks for reaching out and for the detailed bug report! We really appreciate
   you taking the time to dig into the code and share your findings!
 * Before I pass this along to our development team for review, it would help to
   have some additional context. Could you share a copy of the custom field code
   you implemented? That will give the team what they need to investigate the issue
   more thoroughly.
 * When you have a chance, please share the code and let me know if you have any
   additional questions.
 * Thanks!
 *  Thread Starter [Bartek20](https://wordpress.org/support/users/bartek20/)
 * (@bartek20)
 * [1 month ago](https://wordpress.org/support/topic/bug-fields-button-duplication/#post-18854607)
 * Hi, [@rsouzaam](https://wordpress.org/support/users/rsouzaam/) ,
 * Here is my custom field – it is basic text field modified to fit my needs (that’s
   why for example LOCALE is hard coded).
 *     ```wp-block-code
       <?phpnamespace Customizations;class DateTimeField extends \WPForms_Field {	/**	 * Primary class constructor.	 *	 * @since 1.0.0	 */	public function init() {		// Define field type information.		$this->name  = esc_html__( 'Date / Time', 'wpforms-lite' );		$this->type  = 'date-time';		$this->icon  = 'fa-calendar-o';		$this->order = 60;		$this->group = 'fancy';		// Define additional field properties.		add_filter( 'wpforms_field_properties_date-time', [ $this, 'field_properties' ], 5, 3 );        add_action('init', [$this, 'register_frontend_js']);		add_action( 'wpforms_frontend_js', [ $this, 'frontend_js' ] );	}	/**	 * Convert mask formatted for jquery.inputmask into  the format used by amp-inputmask.	 *	 * Note that amp-inputmask does not yet support all of the options that jquery.inputmask provides.	 * In particular, amp-inputmask doesn't provides:	 *  - Upper-alphabetical mask.	 *  - Upper-alphanumeric mask.	 *  - Advanced Input Masks with arbitrary repeating groups.	 *	 * @link https://amp.dev/documentation/components/amp-inputmask	 * @link https://wpforms.com/docs/how-to-use-custom-input-masks/	 *	 * @param string $mask Mask formatted for jquery.inputmask.	 * @return array {	 *     Mask and placeholder.	 *	 *     @type string $mask        Mask for amp-inputmask.	 *     @type string $placeholder Placeholder derived from mask if one is not supplied.	 * }	 */	protected function convert_mask_to_amp_inputmask( $mask ) {		$placeholder = '';		// Convert jquery.inputmask format into amp-inputmask format.		$amp_mask            = '';		$req_mask_mapping    = [			'9' => '0', // Numeric.			'a' => 'L', // Alphabetical (a-z or A-Z).			'A' => 'L', // Upper-alphabetical (A-Z). Note: AMP does not have an uppercase-alphabetical mask type, so same as previous.			'*' => 'A', // Alphanumeric (0-9, a-z, A-Z).			'&' => 'A', // Upper-alphanumeric (A-Z, 0-9). Note: AMP does not have an uppercase-alphanumeric mask type, so same as previous.			' ' => '_', // Automatically insert spaces.		];		$opt_mask_mapping    = [			'9' => '9', // The user may optionally add a numeric character.			'a' => 'l', // The user may optionally add an alphabetical character.			'A' => 'l', // The user may optionally add an alphabetical character.			'*' => 'a', // The user may optionally add an alphanumeric character.			'&' => 'a', // The user may optionally add an alphanumeric character.		];		$placeholder_mapping = [			'9' => '0',			'a' => 'a',			'A' => 'a',			'*' => '_',			'&' => '_',		];		$is_inside_optional  = false;		$last_mask_token     = null;		for ( $i = 0, $len = strlen( $mask ); $i < $len; $i++ ) {			if ( '[' === $mask[ $i ] ) {				$is_inside_optional = true;				$placeholder       .= $mask[ $i ];				continue;			} elseif ( ']' === $mask[ $i ] ) {				$is_inside_optional = false;				$placeholder       .= $mask[ $i ];				continue;			} elseif ( isset( $last_mask_token ) && preg_match( '/^\{(?P<n>\d+)(?:,(?P<m>\d+))?\}/', substr( $mask, $i ), $matches ) ) {				$amp_mask    .= str_repeat( $req_mask_mapping[ $last_mask_token ], $matches['n'] );				$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['n'] );				if ( isset( $matches['m'] ) ) {					$amp_mask    .= str_repeat( $opt_mask_mapping[ $last_mask_token ], $matches['m'] );					$placeholder .= str_repeat( $placeholder_mapping[ $last_mask_token ], $matches['m'] );				}				$i += strlen( $matches[0] ) - 1;				$last_mask_token = null; // Reset.				continue;			}			if ( '\\' === $mask[ $i ] ) {				$amp_mask .= '\\';				$i++;				if ( ! isset( $mask[ $i ] ) ) {					continue;				}				$amp_mask .= $mask[ $i ];			} else {				// Remember this token in case it is a mask.				if ( isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {					$last_mask_token = $mask[ $i ];				}				if ( $is_inside_optional && isset( $opt_mask_mapping[ $mask[ $i ] ] ) ) {					$amp_mask .= $opt_mask_mapping[ $mask[ $i ] ];				} elseif ( isset( $req_mask_mapping[ $mask[ $i ] ] ) ) {					$amp_mask .= $req_mask_mapping[ $mask[ $i ] ];				} else {					$amp_mask .= '\\' . $mask[ $i ];				}			}			if ( isset( $placeholder_mapping[ $mask[ $i ] ] ) ) {				$placeholder .= $placeholder_mapping[ $mask[ $i ] ];			} else {				$placeholder .= $mask[ $i ];			}		}		return [ $amp_mask, $placeholder ];	}	/**	 * Define additional field properties.	 *	 * @since 1.4.5	 *	 * @param array $properties Field properties.	 * @param array $field      Field settings.	 * @param array $form_data  Form data and settings.	 *	 * @return array	 */	public function field_properties( $properties, $field, $form_data ) {		// Add class that will trigger custom mask.		$properties['inputs']['primary']['class'][] = 'wpforms-masked-input';		$properties['inputs']['primary']['class'][] = 'wpforms-datepicker';		if ( wpforms_is_amp() ) {			return $this->get_amp_input_mask_properties( $properties, $field );		}		$properties['inputs']['primary']['data']['rule-inputmask-incomplete'] = true;        $mask = $field['format'];        $properties['inputs']['primary']['data']['inputmask-alias']       = 'datetime';        $properties['inputs']['primary']['data']['inputmask-inputformat'] = $mask;        /**         * Some datetime formats include letters, so we need to switch inputmode to text.         * For instance:         * – tt is am/pm         * – TT is AM/PM         */        $properties['inputs']['primary']['data']['inputmask-inputmode'] = preg_match( '/[tT]/', $mask ) ? 'text' : 'numeric';        return $properties;	}	/**	 * Define additional field properties for the inputmask on AMP pages.	 *	 * @since 1.7.6	 *	 * @param array $properties Field properties.	 * @param array $field      Field settings.	 *	 * @return array	 */	private function get_amp_input_mask_properties( $properties, $field ) {		list( $amp_mask, $placeholder ) = $this->convert_mask_to_amp_inputmask( 'date:' . $field['format'] );		$properties['inputs']['primary']['attr']['mask'] = $amp_mask;		if ( empty( $properties['inputs']['primary']['attr']['placeholder'] ) ) {			$properties['inputs']['primary']['attr']['placeholder'] = $placeholder;		}		return $properties;	}	/**	 * Field options panel inside the builder.	 *	 * @since 1.0.0	 *	 * @param array $field Field settings.	 */	public function field_options( $field ) {		/*		 * Basic field options.		 */		// Options open markup.		$this->field_option(			'basic-options',			$field,			[				'markup' => 'open',			]		);		// Label.		$this->field_option( 'label', $field );		// Description.		$this->field_option( 'description', $field );		// Required toggle.		$this->field_option( 'required', $field );		// Options close markup.		$this->field_option(			'basic-options',			$field,			[				'markup' => 'close',			]		);		/*		 * Advanced field options.		 */		// Options open markup.		$this->field_option(			'advanced-options',			$field,			[				'markup' => 'open',			]		);		// Size.		$this->field_option( 'size', $field );        // Date format        $lbl = $this->field_element('label', $field, [            'slug'    => 'format',            'value'   => esc_html__( 'Date format', 'wpforms-lite' ),        ], false);        $fld = $this->field_element('select', $field, [            'slug' => 'format',            'value' => empty($field['format']) ? 'dd/mm/yyyy' : $field['format'],            'options' => [                'dd/mm/yyyy' => 'dd/mm/yyyy'            ],        ], false);        $args = [            'slug' => 'format',            'content' => $lbl . $fld,        ];        $this->field_element('row', $field, $args, true);		// Placeholder.		$this->field_option( 'placeholder', $field );		// Default value.		$this->field_option( 'default_value', $field );		// Custom CSS classes.		$this->field_option( 'css', $field );		// Hide label.		$this->field_option( 'label_hide', $field );		// Options close markup.		$this->field_option(			'advanced-options',			$field,			[				'markup' => 'close',			]		);	}	/**	 * Field preview inside the builder.	 *	 * @since 1.0.0	 *	 * @param array $field Field settings.	 */	public function field_preview( $field ) {		// Define data.		$placeholder   = ! empty( $field['placeholder'] ) ? $field['placeholder'] : '';		$default_value = ! empty( $field['default_value'] ) ? $field['default_value'] : '';		// Label.		$this->field_preview_option( 'label', $field );		// Primary input.		echo '<input type="text" placeholder="' . esc_attr( $placeholder ) . '" value="' . esc_attr( $default_value ) . '" class="primary-input" readonly>';		// Description.		$this->field_preview_option( 'description', $field );	}	/**	 * Field display on the form front-end.	 *	 * @since 1.0.0	 *	 * @param array $field      Field settings.	 * @param array $deprecated Deprecated.	 * @param array $form_data  Form data and settings.	 */	public function field_display( $field, $deprecated, $form_data ) {		// Define data.		$primary = $field['properties']['inputs']['primary'];		// Primary field.		printf(			'<input type="text" %s %s>',			wpforms_html_attributes( $primary['id'], $primary['class'], $primary['data'], $primary['attr'] ),			$primary['required'] // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped		);	}	public function register_frontend_js() {        wp_register_script('picker-popper', 'https://cdnjs.cloudflare.com/ajax/libs/popper.js/2.11.8/umd/popper.min.js', null, false, true);        wp_register_script('picker-airdatepicker', 'https://cdn.jsdelivr.net/npm/air-datepicker@3.5.3/air-datepicker.js', ['picker-popper'], false, true);        wp_register_style('picker-airdatepicker-css', 'https://cdn.jsdelivr.net/npm/air-datepicker@3.5.3/air-datepicker.min.css', null, false);    }        /**	 * Enqueue frontend datepicker js.	 *	 * @since 1.5.6	 *	 * @param array $forms Forms on the current page.	 */	public function frontend_js( $forms ) {        // Get fields.		$fields = array_map(			function( $form ) {				return empty( $form['fields'] ) ? [] : $form['fields'];			},			(array) $forms		);		// Make fields flat.		$fields = array_reduce(			$fields,			function( $accumulator, $current ) {				return array_merge( $accumulator, $current );			},			[]		);		// Leave only fields with limit.		$fields = array_filter(			$fields,			function( $field ) {				return $field['type'] === $this->type;			}		);		if ( count( $fields ) ) {			wp_enqueue_script('picker-popper');            wp_enqueue_script('picker-airdatepicker');            wp_enqueue_style('picker-airdatepicker-css');            wp_add_inline_script('picker-airdatepicker', <<<JS            const PICKER_LOCALE = {                days: ['Niedziela', 'Poniedziałek', 'Wtorek', 'Środa', 'Czwartek', 'Piątek', 'Sobota'],                daysShort: ['Nie', 'Pon', 'Wto', 'Śro', 'Czw', 'Pią', 'Sob'],                daysMin: ['Nd', 'Pn', 'Wt', 'Śr', 'Czw', 'Pt', 'So'],                months: ['Styczeń', 'Luty', 'Marzec', 'Kwiecień', 'Maj', 'Czerwiec', 'Lipiec', 'Sierpień', 'Wrzesień', 'Październik', 'Listopad', 'Grudzień'],                monthsShort: ['Sty', 'Lut', 'Mar', 'Kwi', 'Maj', 'Cze', 'Lip', 'Sie', 'Wrz', 'Paź', 'Lis', 'Gru'],                today: 'Dzisiaj',                clear: 'Wyczyść',                dateFormat: 'dd/MM/yyyy',                timeFormat: '',                firstDay: 1            }            document.querySelectorAll('.wpforms-datepicker').forEach(input => {                new AirDatepicker(input, {                    container: input.parentElement,                    selectedDates: [],                    onSelect: () => {                        input.dispatchEvent(new Event('dateSelected'));                    },                    locale: PICKER_LOCALE,                    autoClose: true,                    toggleSelected: false,                    position({ \$datepicker, \$target, \$pointer, done }) {                        let popper = Popper.createPopper(\$target, \$datepicker, {                            placement: 'bottom',                            modifiers: [                                {                                    name: 'flip',                                    options: {                                        padding: {                                            top: 64                                        }                                    }                                },                                {                                    name: 'offset',                                    options: {                                        offset: [0, 20]                                    }                                },                                {                                    name: 'arrow',                                    options: {                                        element: \$pointer                                    }                                }                            ]                        });                        return function completeHide() {                            popper.destroy();                            done();                        };                    }                });            })        JS);		}    }	/**	 * Format and sanitize field.	 *	 * @since 1.5.6	 *	 * @param int   $field_id     Field ID.	 * @param mixed $field_submit Field value that was submitted.	 * @param array $form_data    Form data and settings.	 */	public function format( $field_id, $field_submit, $form_data ) {		$field = $form_data['fields'][ $field_id ];		$name  = ! empty( $field['label'] ) ? sanitize_text_field( $field['label'] ) : '';		// Sanitize.		$value = sanitize_text_field( $field_submit );		wpforms()->obj( 'process' )->fields[ $field_id ] = [			'name'  => $name,			'value' => $value,			'id'    => wpforms_validate_field_id( $field_id ),			'type'  => $this->type,		];	}}
       ```
   
 * The bug is caused by the use of the “date-time” type in the init() function (
   date-time as an example). This is a pro field from WPForms, but according to 
   the comment in the previously mentioned function, using an existing type should
   exclude the original field, as a field with that name already exists. However,
   this doesn’t happen, and duplication occurs.
 *  Plugin Support [Ralden Souza](https://wordpress.org/support/users/rsouzaam/)
 * (@rsouzaam)
 * [1 month ago](https://wordpress.org/support/topic/bug-fields-button-duplication/#post-18855415)
 * Hi [@bartek20](https://wordpress.org/support/users/bartek20/),
 * Thanks for sharing your custom field code, and that was really helpful for our
   team to review!
 * I’ve passed this along and the issue has been flagged. I apologize that I don’t
   have an ETA on when a fix will be released just yet.
 * In the meantime, here’s a workaround: use a **type slug that doesn’t match any
   existing WPForms Pro field** in your `init()` function. Since the deduplication
   check compares against existing field types, using a unique slug will prevent
   it from triggering and avoid the duplicate button until the fix is in place.
 * For example, instead of `'type' => 'date-time'` (which matches an existing Pro
   field), use something like `'type' => 'custom-date-time'` or any other slug that
   isn’t already used by WPForms.
 * When you have a chance, please try that and let me know if you have any additional
   questions.
 * Thanks!

Viewing 3 replies - 1 through 3 (of 3 total)

You must be [logged in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fwordpress.org%2Fsupport%2Ftopic%2Fbug-fields-button-duplication%2F%3Foutput_format%3Dmd&locale=en_US)
to reply to this topic.

 * ![](https://ps.w.org/wpforms-lite/assets/icon.svg?rev=3254748)
 * [WPForms - Easy Form Builder for WordPress - Contact Forms, Payment Forms, Surveys, & More](https://wordpress.org/plugins/wpforms-lite/)
 * [Frequently Asked Questions](https://wordpress.org/plugins/wpforms-lite/#faq)
 * [Support Threads](https://wordpress.org/support/plugin/wpforms-lite/)
 * [Active Topics](https://wordpress.org/support/plugin/wpforms-lite/active/)
 * [Unresolved Topics](https://wordpress.org/support/plugin/wpforms-lite/unresolved/)
 * [Reviews](https://wordpress.org/support/plugin/wpforms-lite/reviews/)

 * 3 replies
 * 2 participants
 * Last reply from: [Ralden Souza](https://wordpress.org/support/users/rsouzaam/)
 * Last activity: [1 month ago](https://wordpress.org/support/topic/bug-fields-button-duplication/#post-18855415)
 * Status: not resolved