0

The problem

WordPress supports the sticky option with some of its default post types and doesn't provide any option to extend this to other post types. It's hard-coded in the met-boxes.php file:

<?php if ( 'post' === $post_type && current_user_can( 'edit_others_posts' ) ) : ?>
    <span id="sticky-span"><input id="sticky" name="sticky" type="checkbox" value="sticky" <?php checked( is_sticky( $post_id ) ); ?> /> <label for="sticky" class="selectit"><?php _e( 'Stick this post to the front page' ); ?></label><br /></span>
<?php endif; ?>

Available solutions

I googled that too much and tried to find a clean solution that avoids adding extra custom fields or using 3rd party plugins, but I failed to find a satisfying answer!!

My solution

Simply, I found that inserting the same HTML tag of the native sticky option checkbox will do the job, and WordPress will recognize it automatically and handle all the related functions to save and get the value of that field.

I was about to answer some of the questions asking about the same topic, but I found it better to have it on a new question.

2
  • this looks like classic editor code, have you tried using the current editor?
    – Tom J Nowell
    Commented Apr 14, 2022 at 21:17
  • We use the classic editor on our websites, that's why I didn't think about the block editor. I will have a look and try to apply the same. But by using this code, users are still able to use the sticky option on the quick and bulk boxes. Commented Apr 14, 2022 at 23:26

3 Answers 3

3

First of all, we have to restrict this solution to run only with the custom post types that we target because running it globally will make the default post type have 2 sticky checkboxes.

So, I suggest adding a new support type to the custom post types that we want them to have the sticky option.

Suppose we have the custom post type news, we can run the following function to add a new support type for it, and we can do that with each custom post type that we need it to have the sticky option:

add_post_type_support('news', 'sticky');

Or we can add that directly the supports array in the custom post type register function:

add_action( 'init', 'register_post_type_news' );
function register_post_type_news() {
    $labels = [
        ...,
    ];
    $args = [
        ...,
        'supports' => ['title', ..., 'sticky'],
        ...,
        ],
    ];
    register_post_type( 'news', $args );
}

Now we are ready to run our function(s) safely.

Add the sticky option to the Post Edit page

Add the following code to your functions.php file:

function show_sticky_option__post_edit($post) {
    if ( current_user_can( 'edit_others_posts' ) && post_type_supports( $post->post_type, 'sticky' ) ) {
        $sticky_checkbox_checked = is_sticky( $post->post_id ) ? 'checked="checked"' : '';
        $sticky_span = '<span id="sticky-span" style="margin-left:0"><input id="sticky" name="sticky" type="checkbox" value="sticky" ' . $sticky_checkbox_checked . ' /> <label for="sticky" class="selectit">' . __( 'Stick this post to the front page' ) . '</label><br /></span>';
        echo $sticky_span . '<br>';
    }
}
add_action( 'post_submitbox_start', 'show_sticky_option__post_edit' );

And that's it! 🎉 🎉 🎉

Now you will see a new checkbox before the Publish button that works exactly like the native WordPress sticky checkbox.

Add sticky option to the custom post type edit page

NEXT is more enhancements to:

  • Show this field in the same place where WordPress shows it (under the Visibility section).
  • Add the sticky option to the Quick Edit box.
  • Add the sticky option to the Bulk Edit box.
  • Add a counter for the sticky posts in the subsubsub menu.

And all of that is done by using the same methodology; adding the same original HTML tag of the sticky option in the right place.

Show the sticky checkbox under the Visibility section

Instead of echoing the HTML directly in the Submit box, we can echo a Javascript code that inserts the HTML tag after the Public status in the Visibility section, like this:

function show_sticky_option__post_edit($post) {
    if ( current_user_can( 'edit_others_posts' ) && post_type_supports( $post->post_type, 'sticky' ) ) {
        $sticky_checkbox_checked = is_sticky( $post->post_id ) ? 'checked="checked"' : '';
        $sticky_span = '<span id="sticky-span"><input id="sticky" name="sticky" type="checkbox" value="sticky" ' . $sticky_checkbox_checked . ' /> <label for="sticky" class="selectit">' . __( 'Stick this post to the front page' ) . '</label><br /></span>';
        $is_sticky = is_sticky( $post->post_id ) ? 'true' : 'false';
        echo '
        <script>
        window.addEventListener("load", (event) => {
            document.getElementById("visibility-radio-password").insertAdjacentHTML("beforebegin", `' . $sticky_span . '`);
            if (' . $is_sticky . ')
                document.getElementById("post-visibility-display").innerHTML = "Public, Sticky";
        });
        </script>';
    }
}
add_action( 'post_submitbox_start', 'show_sticky_option__post_edit' );

And here's the result:

Add sticky option to the custom post type edit page in the visibility section

Show the Sticky Posts Count at the top of the posts list table

Before we can hook into the Quick/Bulk Edit boxes, we need to create a new admin column, and I used the same function to print the sticky posts count that shows the number of the sticky posts of the current post type with a link to filter the posts list to show only those sticky posts.

I have no idea if there's a better hook to show the sticky posts count at the top of the posts list table. If you know, please suggest.

And I added some Javascript code that will hide the new custom admin column because it's useless to show it in the posts list table.

And here's the code:

function add_sticky_column_and_counter($columns, $post_type)
{
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        $columns['sticky'] = 'Sticky';
        $args = [
            'post__in'    => get_option('sticky_posts'),
            'post_type'   => $post_type,
        ];
        $sticky_posts = new WP_Query($args);
        $sticky_count = $sticky_posts->found_posts;
        $sticky_count_li = '
        <li class="sticky">
            | <a href="edit.php?post_type=' . $post_type . '&amp;show_sticky=1">Sticky <span class="count">(' . $sticky_count . ')</span></a>
        </li>';
        echo '
        <script>
            window.addEventListener("load", (event) => {
                let toggle_sticky_column  = document.getElementById("sticky-hide");
                if (toggle_sticky_column.checked) toggle_sticky_column.click();

                let sticky_count_listitem = document.getElementsByClassName("subsubsub")[0];
                sticky_count_listitem.insertAdjacentHTML("beforeend", `' . $sticky_count_li . '`);
            });
        </script>';
    }
    return $columns;
}
add_filter('manage_posts_columns', 'add_sticky_column_and_counter', 10, 2);

Now, we can see the sticky posts count at the top of the posts list table.

Add the sticky posts count

And the new custom admin column is always hidden:

Add a new custom admin column to show the sticky option on the Quick and Bulk edit boxes

We are ready now to add the sticky option to the Quick/Bulk Edit boxes.

Add the sticky option to the Quick Edit box

Now we can hook into the quick_edit_custom_box and insert the HTML of the sticky option in the Quick Edit box, using this code:

function show_sticky_option__quick_edit($column, $post_type) {
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        $sticky_span = '
        <label id="quickedit-sticky" class="alignleft">
            <input type="checkbox" name="sticky" value="sticky" />
            <span class="checkbox-title">' . __( 'Make this post sticky' ) . '</span>
        </label>';
        echo '
        <script>document.getElementsByClassName("inline-edit-status")[0].insertAdjacentHTML("afterend", `' . $sticky_span . '`);</script>';
    }
}
add_action('quick_edit_custom_box', 'show_sticky_option__quick_edit', 10, 2);

And here's the result:

Add the sticky option to the Quick Edit box

Add the sticky option to the Bulk Edit box

In the same way, we will hook into the bulk_edit_custom_box and insert the HTML of the sticky option in the Bulk Edit box, using this code:

function show_sticky_option__bulk_edit($column, $post_type) {
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        $sticky_span = '
        <label id="quickedit-sticky" class="alignright">
            <span class="title">' . __( 'Sticky' ) . '</span>
            <select name="sticky">
                <option value="-1">' . __( '&mdash; No Change &mdash;' ) . '</option>
                <option value="sticky">' . __( 'Sticky' ) . '</option>
                <option value="unsticky">' . __( 'Not Sticky' ) . '</option>
            </select>
        </label>';
        echo '
        <script>document.querySelectorAll("#bulk-edit .inline-edit-status")[0].insertAdjacentHTML("afterend", `' . $sticky_span . '`);</script>';
    }
}
add_action('bulk_edit_custom_box', 'show_sticky_option__bulk_edit', 10, 2);

And the result is:

Add the sticky option to the Bulk Edit box

The full solution code

If you only need to show the sticky option checkbox on the post edit page, you can add the first piece of code to your functions.php file. But if you need to run the full solution, you can add the rest pieces of code, or create a new php file that holds all these functions and, then, include it in the functions.php file.

Here's the full solution code that you need with some comments to understand what's going on:

/**
 * This function shows the sticky option checkbox for the post types that supports 'sticky'.
 * So, to show this checkbox with a certain post type, just add 'sticky' to the 'supports' 
 * array in the post type register function.
 *
 * Wordpress will handle saving the value of this field automatically because we used
 * the same HTML structure of the sticky <input> field that WordPress uses.
 *
 * @return void
 */
function show_sticky_option__post_edit($post) {
    if ( current_user_can( 'edit_others_posts' ) && post_type_supports( $post->post_type, 'sticky' ) ) {
        $sticky_checkbox_checked = is_sticky( $post->post_id ) ? 'checked="checked"' : '';
        $sticky_span = '<span id="sticky-span"><input id="sticky" name="sticky" type="checkbox" value="sticky" ' . $sticky_checkbox_checked . ' /> <label for="sticky" class="selectit">' . __( 'Stick this post to the front page' ) . '</label><br /></span>';
        $is_sticky = is_sticky( $post->post_id ) ? 'true' : 'false';
        /**
         * The following JS code does the following (after DOM is ready):
         * - Inserts the sticky option checkbox after Public status item in the
         * Visibility section
         */
        echo '
        <script>
        window.addEventListener("load", (event) => {
            document.getElementById("visibility-radio-password").insertAdjacentHTML("beforebegin", `' . $sticky_span . '`);
            if (' . $is_sticky . ')
                document.getElementById("post-visibility-display").innerHTML = "Public, Sticky";
        });
        </script>';
    }
}
add_action( 'post_submitbox_start', 'show_sticky_option__post_edit' );



/**
 * This function adds a new custom column 'sticky' that's mandatory to hook into
 * the quick and bulk edit modes.
 * This will not be applied only to the post types that support 'sticky'.
 *
 * @param $columns   : The admin columns of the current screen
 * @param $post_type : The post type of the current screen
 * @return $columns  : The new set of columns
 */
function add_sticky_column_and_counter($columns, $post_type)
{
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        // Add a new custom column
        $columns['sticky'] = 'Sticky';
        // Get the count of the sticky posts of the current post type
        $args = [
            'post__in'    => get_option('sticky_posts'),
            'post_type'   => $post_type,
        ];
        $sticky_posts = new WP_Query($args);
        $sticky_count = $sticky_posts->found_posts;
        // The HTML tag of the sticky posts count
        $sticky_count_li = '
        <li class="sticky">
            | <a href="edit.php?post_type=' . $post_type . '&amp;show_sticky=1">Sticky <span class="count">(' . $sticky_count . ')</span></a>
        </li>';
        /**
         * The following JS code does the following (after DOM is ready):
         * - Hides the new custom column 'sticky' if visible on the posts list table
         * - Appends the sticky count <li> element to the 'subsubsub' <ul>
         */
        echo '
        <script>
            window.addEventListener("load", (event) => {
                let toggle_sticky_column  = document.getElementById("sticky-hide");
                if (toggle_sticky_column.checked) toggle_sticky_column.click();

                let sticky_count_listitem = document.getElementsByClassName("subsubsub")[0];
                sticky_count_listitem.insertAdjacentHTML("beforeend", `' . $sticky_count_li . '`);
            });
        </script>';
    }
    return $columns;
}
add_filter('manage_posts_columns', 'add_sticky_column_and_counter', 10, 2);



/**
 * This function adds the sticky option checkbox to the Quick Edit panel.
 * This will not be applied only to the post types that support 'sticky'.
 *
 * @param $columns   : The admin columns of the current screen
 * @param $post_type : The post type of the current screen
 * @return void
 */
function show_sticky_option__quick_edit($column, $post_type) {
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        // The HTML tag of the sticky option checkbox
        $sticky_span = '
        <label id="quickedit-sticky" class="alignleft">
            <input type="checkbox" name="sticky" value="sticky" />
            <span class="checkbox-title">' . __( 'Make this post sticky' ) . '</span>
        </label>';
        /**
         * The following JS code does the following (after DOM is ready):
         * - Appends the sticky option checkbox element after the post 'status' field
         */
        echo '
        <script>document.getElementsByClassName("inline-edit-status")[0].insertAdjacentHTML("afterend", `' . $sticky_span . '`);</script>';
    }
}
add_action('quick_edit_custom_box', 'show_sticky_option__quick_edit', 10, 2);



/**
 * This function adds the sticky option menu to the Bulk Edit panel.
 * This will not be applied only to the post types that support 'sticky'.
 *
 * @param $columns   : The admin columns of the current screen
 * @param $post_type : The post type of the current screen
 * @return void
 */
function show_sticky_option__bulk_edit($column, $post_type) {
    if ( post_type_supports( $post_type, 'sticky' ) ) {
        // The HTML tag of the sticky option menu
        $sticky_span = '
        <label id="quickedit-sticky" class="alignright">
            <span class="title">' . __( 'Sticky' ) . '</span>
            <select name="sticky">
                <option value="-1">' . __( '&mdash; No Change &mdash;' ) . '</option>
                <option value="sticky">' . __( 'Sticky' ) . '</option>
                <option value="unsticky">' . __( 'Not Sticky' ) . '</option>
            </select>
        </label>';
        /**
         * The following JS code does the following:
         * - Appends the sticky option menu element after the post 'status' field
         */
        echo '
        <script>document.querySelectorAll("#bulk-edit .inline-edit-status")[0].insertAdjacentHTML("afterend", `' . $sticky_span . '`);</script>';
    }
}
add_action('bulk_edit_custom_box', 'show_sticky_option__bulk_edit', 10, 2);

Conclusion

As you noticed, adding the same original HTML tag of the sticky option field to the right place will make WordPress use it natively. So no more custom fields or plugins are needed to add the sticky option field to the custom posts types.

AND DON'T FORGET to exclude the default post type in any way to avoid the sticky checkbox duplication. You can add a new support type as suggested above.

0
// Handle the ajax request
add_action( 'wp_ajax_process_sticky_post', 'process_ajax_sticky_post' );
/*
* AJAX Call
* Handle the ajax request and set/unset the post sticky
*/
function process_ajax_sticky_post() {
    // Nonce security check
    if(!check_ajax_referer('sticky-post-nonce')) {
        wp_send_json_error(__('An error has occurred. Please reload the page and try again.'));
    }

    $handle = sanitize_text_field($_POST['handle']);
    $post_id = absint($_POST['post_id']);
    $post_obj = get_post($post_id);
    $post_type_object = get_post_type_object($post_obj->post_type);

    // Check capabilities
    if (!current_user_can( $post_type_object->cap->edit_others_posts ) || !current_user_can( $post_type_object->cap->publish_posts ) ) {
        wp_send_json_error(__('Sorry, you are not allowed to edit this item.'));
    }

    // Mark the post as currently being edited by the current user
    wp_set_post_lock( $post_id );

    // Sticky posts are not available on password or private posts
    $sticky_available = true;
    if(post_password_required($post_obj) || $post_obj->post_status == 'private')
    {
        unstick_post( $post_id );
        $sticky_available = false;
    }
    else
    {
        set_the_post_sticky($post_id, $handle);
    }

    // Get all post states
    ob_start();
    _post_states(get_post($post_id));
    $post_states = ob_get_clean();

    // Ajax output response
    wp_send_json_success(array(
        'sticky'    => is_sticky($post_id),
        'states'    => $post_states,
        'available' => $sticky_available
    ));
}
1
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jul 24, 2023 at 13:33
0

ADD JS ADNIN .... admin-script.js

(function($) {
    /*
     * Attach a click event handler to the sticky post icon link
     */
    $('table.wp-list-table').on('click', 'td.column-sticky > a.sticky', function(event) {
        toggle_sticky_post($(this));
        event.preventDefault();
    });

    /*
     * Process a ajax request and set/unset the post sticky
     */
    function toggle_sticky_post(element)
    {
        // Init variables
        var postID = element.data('id'),
            postNonce = element.data('nonce'),
            postHandle = !element.hasClass('active') ? 'sticky' : 'unsticky';

        element.children('span.dashicons').addClass('load');

        // Queue up the sticky ajax request
        $.ajaxQueue({
            type: 'POST',
            dataType: 'json',
            url: stickyPostObject.ajaxUrl,
            data: {
                _ajax_nonce: postNonce,
                action: stickyPostObject.action,
                handle: postHandle,
                post_id: postID
            }
        }).done(function(response) {

            if(response.success) {
                // Toggle link state/icon and set post states to title
                set_sticky_icon(element, response.data.sticky, postID);
                set_sticky_post_states(postID, response.data.states, response.data.available);
            } else {
                alert(response.data);
            }

            element.children('span.dashicons').removeClass('load');

        }).fail(function(jqXHR, textStatus) {
            console.log(jqXHR.responseText);
            element.children('span.dashicons').removeClass('load');
        });
    }

    /*
     * Set the dashboard star icon filled/empty
     */
    function set_sticky_icon(element, sticky, postID) {
        if(sticky) {
            element.addClass('active');
            element.children('span.dashicons').removeClass('dashicons-star-empty').addClass('dashicons-star-filled');
            $('.wp-list-table #post-' + postID ).addClass('status-sticky');

        } else {
            element.removeClass('active');
            element.children('span.dashicons').removeClass('dashicons-star-filled').addClass('dashicons-star-empty');
            $('.wp-list-table #post-' + postID ).removeClass('status-sticky');

        }
    }

    /*
     * Set the post states to the title cell
     */
    function set_sticky_post_states(postID, states, available) {
        var postTitleLink = $('tr#post-' + postID).find('a.row-title');

        // Post title available
        if(postTitleLink.length) {

            // Reset the post link element, to delete the post-states
            postTitleLink.parent().html(postTitleLink);

            if(states !== '') {
                postTitleLink.after(states);

                // Change the post state color to red, if the sticky feature is not available
                if(!available) {
                    postTitleLink.parent().find('span.post-state').css('color', '#a00');
                }
            }
        }
    }
})(jQuery);
1
  • Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Jul 24, 2023 at 13:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.