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();
});
});