Add the Meta Box
// Register the meta box function wp_custom_meta_box() { add_meta_box( 'custom_meta_box', // ID 'Custom Meta Box', // Title 'wp_custom_meta_box_html', // Callback function 'post', // Post type 'normal', // Context 'high' // Priority ); } add_action('add_meta_boxes', 'wp_custom_meta_box'); function wp_custom_meta_box_html($post) { wp_nonce_field('save_custom_meta_box', 'custom_meta_box_nonce'); // Get existing data if available $meta_data = get_post_meta($post->ID, '_custom_meta_box', true); echo '<div id="repeatable-fieldset-one">'; if ($meta_data) { foreach ($meta_data as $index => $data) { wp_custom_repeatable_field($index, $data); } } else { wp_custom_repeatable_field(0); } echo '</div>'; echo '<button id="add-row" class="button">Add Another</button>'; // JS to handle adding/removing repeatable fields ?> <script> jQuery(document).ready(function($) { let i = <?php echo count($meta_data) ? count($meta_data) : 1; ?>; $('#add-row').on('click', function(e) { e.preventDefault(); let row = <?php ob_start(); wp_custom_repeatable_field('INDEX'); echo json_encode(ob_get_clean()); ?>; $('#repeatable-fieldset-one').append(row.replace(/INDEX/g, i)); i++; }); $(document).on('click', '.remove-row', function(e) { e.preventDefault(); $(this).closest('.repeatable-row').remove(); }); }); </script> <?php } function wp_custom_repeatable_field($index = 0, $data = []) { $image = isset($data['image']) ? $data['image'] : ''; $url = isset($data['url']) ? $data['url'] : ''; $text = isset($data['text']) ? $data['text'] : ''; ?> <div class="repeatable-row"> <div> <label>Upload Image</label><br/> <input type="text" name="custom_meta_box[<?php echo $index; ?>][image]" id="image_url_<?php echo $index; ?>" value="<?php echo esc_attr($image); ?>" class="image-url-field"> <button class="button upload-image-button">Upload</button> </div> <div> <label>Link URL</label><br/> <input type="text" name="custom_meta_box[<?php echo $index; ?>][url]" value="<?php echo esc_attr($url); ?>" class="widefat"> </div> <div> <label>Link Text</label><br/> <input type="text" name="custom_meta_box[<?php echo $index; ?>][text]" value="<?php echo esc_attr($text); ?>" class="widefat"> </div> <button class="remove-row button">Remove</button> </div> <?php }
2. Save Meta Box Data
function wp_save_custom_meta_box($post_id) { if (!isset($_POST['custom_meta_box_nonce']) || !wp_verify_nonce($_POST['custom_meta_box_nonce'], 'save_custom_meta_box')) { return $post_id; } if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return $post_id; } if (!current_user_can('edit_post', $post_id)) { return $post_id; } $meta_data = isset($_POST['custom_meta_box']) ? $_POST['custom_meta_box'] : []; update_post_meta($post_id, '_custom_meta_box', $meta_data); } add_action('save_post', 'wp_save_custom_meta_box');
3. Handle Image Upload (JavaScript)
jQuery(document).ready(function($) { var file_frame; $(document).on('click', '.upload-image-button', function(e) { e.preventDefault(); var inputField = $(this).prev('.image-url-field'); if (file_frame) { file_frame.open(); return; } file_frame = wp.media.frames.file_frame = wp.media({ title: 'Select an Image', button: { text: 'Use this Image' }, multiple: false }); file_frame.on('select', function() { var attachment = file_frame.state().get('selection').first().toJSON(); inputField.val(attachment.url); }); file_frame.open(); }); });