Over 05 years we help companies reach their financial and branding goals. webtechguru is a values-driven technology agency dedicated.

Gallery

Contacts

support@webtechguru.in

How to Create Custom Testimonial in WordPress

Introduction

Hello friends,

In this tutorial, I will show you how to create a custom testimonial in WordPress through a custom WordPress plugin.

In this plugin, we will build a carousel slider and add a settings submenu under the Testimonials menu. From this settings page, you can set the carousel options like autoplay, loop, dots, and timing for the slider.

We will also add a copy button for the shortcode so you can easily copy it and paste it anywhere in WordPress to display the testimonial slider.

In addition, we will add dummy content so that if no real testimonial data exists, dummy data will be displayed until it is replaced with real data. More settings will also be added as needed.

Let’s get started

Requirements

We’ll first create a simple folder structure for our plugin:

1. Folder Structure

We first need the plugin folder and main file inside the plugins directory.

-- plugins (folder)
   -- custom-testimonial (folder)
      -- custom-testimonial.php (file)

After creating this folder and file, we need to add the plugin header details so WordPress can register it.

/*
Plugin Name: Custom Testimonials with Slider & Ratings
Description: Manage testimonials with Owl Carousel slider, role/company, and star ratings. Shortcode: [testimonial_slider]
Version: 1.3
Author: Your Name
*/

2. Steps for the Plugin Custom Testimonial

We will build this plugin step by step:

  1. Register Testimonial CPT + thumbnails
  2. Add Metabox: Role, Company, Rating (1–5)
  3. Register Front-end assets (CSS/JS)
  4. Create Shortcode: [testimonial_slider]
  5. Add Admin submenu: Slider Settings

3. Register Testimonial CPT + Thumbnails

We will create a custom post type for testimonials.

This allows you to manage testimonials from the WordPress admin area. You can add, edit, update, and delete testimonials. It also supports thumbnails (featured images).

// =====================================================
// 1) Register Testimonial CPT + thumbnails
// =====================================================
function ct_register_testimonial_cpt() {
    $labels = array(
        'name'               => 'Testimonials',
        'singular_name'      => 'Testimonial',
        'menu_name'          => 'Testimonials',
        'name_admin_bar'     => 'Testimonial',
        'add_new'            => 'Add New',
        'add_new_item'       => 'Add New Testimonial',
        'new_item'           => 'New Testimonial',
        'edit_item'          => 'Edit Testimonial',
        'view_item'          => 'View Testimonial',
        'all_items'          => 'All Testimonials',
        'search_items'       => 'Search Testimonials',
    );
    $args = array(
        'labels'       => $labels,
        'public'       => true,
        'show_ui'      => true,
        'show_in_menu' => true,
        'menu_icon'    => 'dashicons-testimonial',
        'supports'     => array('title', 'editor', 'thumbnail'),
    );
    register_post_type('testimonial', $args);
}
add_action('init', 'ct_register_testimonial_cpt');

function ct_enable_testimonial_thumbs() {
    add_theme_support('post-thumbnails', array('testimonial'));
}
add_action('after_setup_theme', 'ct_enable_testimonial_thumbs');

 

4. Metabox: Role, Company, Rating (1–5)

Next, we add extra information fields to each testimonial using metaboxes.

For example:

  • Role
  • Company
  • Rating (1–5 stars)

This will allow you to store structured information along with the testimonial text.

// =====================================================
// 2) Metabox: Role, Company, Rating (1–5)
// =====================================================
function ct_add_testimonial_metabox() {
    add_meta_box(
        'ct_testimonial_details',
        'Testimonial Details',
        'ct_testimonial_metabox_html',
        'testimonial',
        'normal',
        'default'
    );
}
add_action('add_meta_boxes', 'ct_add_testimonial_metabox');

function ct_testimonial_metabox_html($post) {
    wp_nonce_field('ct_testimonial_save_meta', 'ct_testimonial_nonce');

    $role    = get_post_meta($post->ID, '_testimonial_role', true);
    $company = get_post_meta($post->ID, '_testimonial_company', true);
    $rating  = get_post_meta($post->ID, '_testimonial_rating', true);
    if ($rating === '' || $rating === null) { $rating = 5; }
    ?>
    <p>
        <label><strong>Role / Designation</strong></label><br>
        <input type="text" name="testimonial_role" value="<?php echo esc_attr($role); ?>" style="width:100%">
    </p>
    <p>
        <label><strong>Company</strong></label><br>
        <input type="text" name="testimonial_company" value="<?php echo esc_attr($company); ?>" style="width:100%">
    </p>
    <p>
        <label><strong>Rating (1–5)</strong></label><br>
        <select name="testimonial_rating">
            <?php for ($i=1; $i<=5; $i++): ?>
                <option value="<?php echo $i; ?>" <?php selected(intval($rating), $i); ?>>
                    <?php echo $i; ?> <?php echo $i>1 ? 'stars' : 'star'; ?>
                </option>
            <?php endfor; ?>
        </select>
    </p>
    <?php
}

function ct_save_testimonial_meta($post_id) {
    if (!isset($_POST['ct_testimonial_nonce']) || !wp_verify_nonce($_POST['ct_testimonial_nonce'], 'ct_testimonial_save_meta')) {
        return;
    }
    if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; }
    if (isset($_POST['post_type']) && 'testimonial' === $_POST['post_type']) {
        if (!current_user_can('edit_post', $post_id)) { return; }
    } else {
        return;
    }

    if (isset($_POST['testimonial_role'])) {
        update_post_meta($post_id, '_testimonial_role', sanitize_text_field($_POST['testimonial_role']));
    }
    if (isset($_POST['testimonial_company'])) {
        update_post_meta($post_id, '_testimonial_company', sanitize_text_field($_POST['testimonial_company']));
    }
    if (isset($_POST['testimonial_rating'])) {
        $r = max(1, min(5, intval($_POST['testimonial_rating'])));
        update_post_meta($post_id, '_testimonial_rating', $r);
    }
}
add_action('save_post', 'ct_save_testimonial_meta');

Custom Testimonial

 

5. Front-end Assets

We need to load the front-end CSS and JS files required for the slider.

For this, we register assets like Owl Carousel, CSS files, and JavaScript. You can also include Bootstrap or other libraries if needed.

// =====================================================
// 3) Front-end assets
// =====================================================
function ct_enqueue_owl_assets() {
    wp_enqueue_style('owl-carousel', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.carousel.min.css', array(), '2.3.4');
    wp_enqueue_style('owl-theme', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/assets/owl.theme.default.min.css', array('owl-carousel'), '2.3.4');
    wp_enqueue_script('owl-carousel', 'https://cdnjs.cloudflare.com/ajax/libs/OwlCarousel2/2.3.4/owl.carousel.min.js', array('jquery'), '2.3.4', true);

    $css = '
    .ct-testimonial-card { background:#fff; border:1px solid #eee; border-radius:12px; padding:24px; box-shadow:0 4px 12px rgba(0,0,0,0.06); text-align:center; }
    .ct-testimonial-card .ct-photo { border-radius:50%; object-fit:cover; display:block; margin:0 auto 12px; }
    .ct-testimonial-card .ct-name { font-weight:700; margin:6px 0 0; }
    .ct-testimonial-card .ct-meta { color:#6c757d; font-size:12px; margin-bottom:8px; }
    .ct-testimonial-card .ct-stars { color:#f5b50a; font-size:18px; margin:6px 0; letter-spacing:1px; }
    .ct-testimonial-card .ct-text { color:#444; font-size:14px; margin-top:8px; }
    ';
    wp_add_inline_style('owl-carousel', $css);
}
add_action('wp_enqueue_scripts', 'ct_enqueue_owl_assets');

6. Shortcode: [testimonial_slider]

We will create the shortcode [testimonial_slider].

By using this shortcode, you can display the testimonial slider anywhere on your site (posts, pages, or widgets). Simply copy and paste the shortcode where you want the testimonials to appear.

This makes the plugin very flexible and easy to use.

// =====================================================
// 4) Shortcode: [testimonial_slider]
// =====================================================
function ct_testimonial_slider_shortcode() {
    $defaults = array(
        'items'      => 3,
        'autoplay'   => true,
        'loop'       => true,
        'speed'      => 3000,
        'nav'        => true,
        'dots'       => true,
        'image_size' => 80,
    );
    $settings = wp_parse_args(get_option('ct_slider_settings', array()), $defaults);

    $q = new WP_Query(array(
        'post_type'      => 'testimonial',
        'posts_per_page' => -1,
        'post_status'    => 'publish',
    ));

    // Dummy testimonials if no posts exist
    $dummy_testimonials = array(
        array(
            'name' => 'John Doe',
            'text' => 'This product is absolutely amazing! Highly recommended.',
            'role' => 'CEO',
            'company' => 'TechCorp',
            'rating' => 5,
            'image' => plugins_url('images/testimonial1.png', __FILE__),
        ),
        array(
            'name' => 'Jane Smith',
            'text' => 'Fantastic support and great features. Love it!',
            'role' => 'Marketing Manager',
            'company' => 'BizWorld',
            'rating' => 4,
            'image' => plugins_url('images/testimonial2.png', __FILE__),
        ),
        array(
            'name' => 'Michael Lee',
            'text' => 'Very easy to use and setup. Five stars from me.',
            'role' => 'Freelancer',
            'company' => '',
            'rating' => 5,
            'image' => plugins_url('images/testimonial3.png', __FILE__),
        ),
    );

    ob_start();
    ?>
    <div class="owl-carousel ct-testimonial-slider">
        <?php if ($q->have_posts()): while ($q->have_posts()): $q->the_post();
            $img = get_the_post_thumbnail_url(get_the_ID(), 'thumbnail');
            if (!$img) {
                $img = 'https://via.placeholder.com/' . intval($settings['image_size']);
            }
            $role    = get_post_meta(get_the_ID(), '_testimonial_role', true);
            $company = get_post_meta(get_the_ID(), '_testimonial_company', true);
            $rating  = intval(get_post_meta(get_the_ID(), '_testimonial_rating', true));
            if ($rating < 1) { $rating = 5; }
            ?>
            <div class="ct-testimonial-card">
                <img class="ct-photo" src="<?php echo esc_url($img); ?>"
                     alt="<?php echo esc_attr(get_the_title()); ?>"
                     style="width:<?php echo intval($settings['image_size']); ?>px!important;height:<?php echo intval($settings['image_size']); ?>px!important;object-fit: cover;object-position: center;border-radius: 50px!important;">

                <div class="ct-stars">
                    <?php for ($i = 1; $i <= 5; $i++) echo $i <= $rating ? '★' : '☆'; ?>
                </div>

                <div class="ct-text"><?php echo wp_kses_post(get_the_content()); ?></div>

                <div class="ct-name"><?php echo esc_html(get_the_title()); ?></div>
                <?php if ($role || $company): ?>
                    <div class="ct-meta">
                        <?php
                        $parts = array();
                        if ($role)    { $parts[] = esc_html($role); }
                        if ($company) { $parts[] = esc_html($company); }
                        echo implode(' at ', $parts);
                        ?>
                    </div>
                <?php endif; ?>
            </div>
        <?php endwhile; wp_reset_postdata();
        else:
            foreach ($dummy_testimonials as $d): ?>
                <div class="ct-testimonial-card">
                    <img class="ct-photo" src="<?php echo esc_url($d['image']); ?>"
                         alt="<?php echo esc_attr($d['name']); ?>"
                         style="width:<?php echo intval($settings['image_size']); ?>px!important;height:<?php echo intval($settings['image_size']); ?>px!important;object-fit: cover;object-position: center;border-radius: 50px!important;">

                    <div class="ct-stars">
                        <?php for ($i = 1; $i <= 5; $i++) echo $i <= $d['rating'] ? '★' : '☆'; ?>
                    </div>

                    <div class="ct-text"><?php echo esc_html($d['text']); ?></div>

                    <div class="ct-name"><?php echo esc_html($d['name']); ?></div>
                    <?php if ($d['role'] || $d['company']): ?>
                        <div class="ct-meta">
                            <?php
                            $parts = array();
                            if ($d['role'])    { $parts[] = esc_html($d['role']); }
                            if ($d['company']) { $parts[] = esc_html($d['company']); }
                            echo implode(' at ', $parts);
                            ?>
                        </div>
                    <?php endif; ?>
                </div>
            <?php endforeach;
        endif; ?>
    </div>

    <script>
    jQuery(document).ready(function($){
        $(".ct-testimonial-slider").owlCarousel({
            items: <?php echo intval($settings['items']); ?>,
            autoplay: <?php echo !empty($settings['autoplay']) ? 'true' : 'false'; ?>,
            loop: <?php echo !empty($settings['loop']) ? 'true' : 'false'; ?>,
            nav: <?php echo !empty($settings['nav']) ? 'true' : 'false'; ?>,
            dots: <?php echo !empty($settings['dots']) ? 'true' : 'false'; ?>,
            autoplayTimeout: <?php echo intval($settings['speed']); ?>,
            margin: 20,
            responsive:{
                0:{items:1},
                768:{items:Math.min(2, <?php echo intval($settings['items']); ?>)},
                1024:{items:<?php echo intval($settings['items']); ?>}
            }
        });
    });
    </script>
    <?php
    return ob_get_clean();
}
add_shortcode('testimonial_slider', 'ct_testimonial_slider_shortcode');

7. Admin Submenu: Slider Settings

We will add a settings submenu under the Testimonials menu in the WordPress admin panel.

From here, you can configure the slider options, such as:

  • Enable/disable autoplay
  • Enable/disable loop
  • Show/hide dots
  • Set time interval for the carousel

This makes the plugin customizable and user-friendly.

// =====================================================
// 5) Admin submenu: Slider Settings
// =====================================================
function ct_register_slider_settings_page() {
    add_submenu_page(
        'edit.php?post_type=testimonial',
        'Slider Settings',
        'Slider Settings',
        'manage_options',
        'ct-slider-settings',
        'ct_slider_settings_page'
    );
}
add_action('admin_menu', 'ct_register_slider_settings_page');

function ct_slider_settings_page() {
    if (isset($_POST['save_ct_settings'])) {
        if (!current_user_can('manage_options')) { return; }

        $settings = array(
            'items'      => isset($_POST['items']) ? intval($_POST['items']) : 3,
            'autoplay'   => !empty($_POST['autoplay']),
            'loop'       => !empty($_POST['loop']),
            'speed'      => isset($_POST['speed']) ? intval($_POST['speed']) : 3000,
            'nav'        => !empty($_POST['nav']),
            'dots'       => !empty($_POST['dots']),
            'image_size' => isset($_POST['image_size']) ? max(40, intval($_POST['image_size'])) : 80,
        );
        update_option('ct_slider_settings', $settings);
        echo '<div class="updated"><p>Settings Saved.</p></div>';
    }

    $defaults = array(
        'items'      => 3,
        'autoplay'   => true,
        'loop'       => true,
        'speed'      => 3000,
        'nav'        => true,
        'dots'       => true,
        'image_size' => 80,
    );
    $settings = wp_parse_args(get_option('ct_slider_settings', array()), $defaults);
    ?>
    <div class="wrap">
        <h1>Testimonial Slider Settings</h1>
        <form method="post">
            <table class="form-table">
                <tr>
                    <th scope="row">Items Visible</th>
                    <td><input type="number" name="items" min="1" max="10" value="<?php echo esc_attr($settings['items']); ?>"></td>
                </tr>
                <tr>
                    <th scope="row">Autoplay</th>
                    <td><input type="checkbox" name="autoplay" <?php checked($settings['autoplay'], true); ?>></td>
                </tr>
                <tr>
                    <th scope="row">Loop</th>
                    <td><input type="checkbox" name="loop" <?php checked($settings['loop'], true); ?>></td>
                </tr>
                <tr>
                    <th scope="row">Show Navigation Arrows</th>
                    <td><input type="checkbox" name="nav" <?php checked($settings['nav'], true); ?>></td>
                </tr>
                <tr>
                    <th scope="row">Show Dots</th>
                    <td><input type="checkbox" name="dots" <?php checked($settings['dots'], true); ?>></td>
                </tr>
                <tr>
                    <th scope="row">Autoplay Speed (ms)</th>
                    <td><input type="number" name="speed" min="1000" step="100" value="<?php echo esc_attr($settings['speed']); ?>"></td>
                </tr>
                <tr>
                    <th scope="row">Image Size (px)</th>
                    <td><input type="number" name="image_size" min="40" max="300" value="<?php echo esc_attr($settings['image_size']); ?>"></td>
                </tr>
            </table>
            <p><input type="submit" name="save_ct_settings" class="button-primary" value="Save Settings"></p>
        </form>

        <h2>Use this Shortcode</h2>
        <p>
            <input type="text" value="[testimonial_slider]" id="ct-shortcode" readonly style="width:260px;">
            <button type="button" class="button"
                onclick="navigator.clipboard.writeText(document.getElementById('ct-shortcode').value)">Copy</button>
        </p>
    </div>
    <?php
}

 

Conclusion

That’s it! We have created a Custom Testimonial Plugin in WordPress that includes:

  • A custom post type (CPT) for testimonials
  • Extra fields (role, company, rating) via metaboxes
  • Front-end slider powered by Owl Carousel
  • A shortcode [testimonial_slider] to display anywhere
  • A settings page to configure slider options

If you want to learn more about WordPress customization, you can check my related posts.

If you need any help, feel free to connect with me — I will be available for you.

Thank you.

Author

Admin

Leave a comment

Your email address will not be published. Required fields are marked *