I have created a form where users update their profile. When a profile is created or updated it creates a CPT called course. The url is the permalink + /course/ + the title of the course


function create_course_page( $user_id = '' ) {
   $user = new WP_User($user_id);
   if ( ! $user->ID ) return '';

   // check if the user whose profile is updating has already a post
   global $wpdb;
   $course_post_exists = $wpdb->get_var( $wpdb->prepare(
   "SELECT ID FROM $wpdb->posts WHERE post_name = %s AND post_type = 'course' and post_status = 'publish'", $user->course_name
   ) );

  if ( ! in_array('instructor', $user->roles) ) return '';

  $user_info = array_map( function( $a ){ return $a[0]; }, get_user_meta( $user->ID ) );
  $title = $user_info['course_name'];
  // of course create the content as you want
  $content = 'This is the page for: ';
  $content .= $user_info['description_course'];
  $post = array(
    'post_title' => $title,
    'post_name' => $user->course_name,
    'post_content' => $content,
    'post_status' => 'publish',
    'post_type' => 'course'
  if ( $course_post_exists ) {
    $post['ID'] = $course_post_exists;
    wp_update_post( $post );
  } else {
    wp_insert_post( $post );

add_action( 'personal_options_update', 'create_course_page' );
add_action( 'edit_user_profile_update', 'create_course_page' );

The problem that I'm facing is that when someone changes the title of the course it creates a new post with this url.

So I want to delete the old post when a title change is done on the course.

NOTE: When changing title of the course it changes the URL, that is why I think it takes like a new post.

I have customised this code a bit to do the job:

add_action( 'admin_init', 'removing_older_posts' );
function removing_older_posts() {
    $args = array (
        'post_type'         => 'course',
        'author'            => get_current_user_id(),
        'orderby'           => 'date',
    $query = new WP_Query( $args );

    if ( $query->have_posts() ) { //if the current user has posts under his/her name
        $i = 0; //create post counter
        while ( $query->have_posts() ) { //loop through each post
            $query->the_post(); //get the current post
            if ($i > 0) { //if you're not on the first post
                wp_delete_post( $query->post->ID, true ); //delete the post
            $i++; //increment the post counter

But it isn't working.

Appreciate any suggestion :)

  • Have you considered just updating the title instead so that no new course post is created?
    – Tom J Nowell
    Aug 4 '20 at 15:51
  • Thanks for your response. We'll, that would be a solution... But don't know how 😅 Because hen the title updates. It changes the url.
    – Zaesar
    Aug 4 '20 at 19:37
  • why do you create a post based off of a user meta value? would it not be easier to create a course post then set its author to the user?
    – Tom J Nowell
    Aug 5 '20 at 9:56
  • Thanks for your time. Really appreciate. Courses are more like channels in YouTube. On them users upload diferent videos. The form where they create the "channel" is based on their profile user where they access on the courses dashboard. It is a bit complex. Of course it would be easier as you said creating a cpt putting user meta values from author. But here it isn't possible 😕
    – Zaesar
    Aug 5 '20 at 10:01
  • I disagree, it is possible, don't store the course name in user meta, store it in the course posts title. There are lots of alternatives, such as storing the channel post ID in user meta instead, then displaying its title in your profile editor. That would avoid the need for the SQL query, and completely avoid your problem ( as well as 1 or 2 you haven't realised involving -2 postfixes and clashing slugs ). You could even search for the channel post that whos author is the current user and render it as an option
    – Tom J Nowell
    Aug 5 '20 at 10:04

I think it would be helpful if you could explain why you're generating courses out of edits that a user's doing on his profile. The simplest solution seems to be a frontend form that just allows a user to create new posts or edit existing ones rather than going for this alternative solution where you're using the data from the profile page to create new posts.

If you want to stick to the current implementation, I believe it would be useful for you to look into status transitions: https://developer.wordpress.org/reference/hooks/transition_post_status/

Here's some example code that I used to create a post in a the category with the ID 1 (= news) after a custom post type had been created:

// Automatically create a news when a wiki post has been published
function nxt_create_news($new_status, $old_status, $nxt_wiki_post) {
    if('publish' === $new_status && 'publish' !== $old_status && $nxt_wiki_post->post_type === 'wiki') {
        $nxt_post_author = $nxt_wiki_post->post_author;
        $nxt_wiki_permalink = get_post_permalink($nxt_wiki_id);
        $nxt_wiki_title = $nxt_wiki_post->post_title;
        $nxt_postarr = array(
            'post_author' => $nxt_post_author,
            'post_content' => 'The wiki entry ' . $nxt_wiki_title . ' has just been published.<br /><br /><a href="' . $nxt_wiki_permalink . '" title="' . $nxt_wiki_title . '">Check out wiki entry</a>.',
            'post_title' => 'The wiki entry ' . $nxt_wiki_title . ' has been published',
            'post_status' => 'publish',
            'post_category' => array(1),
        $nxt_post_id = wp_insert_post($nxt_postarr);
        set_post_thumbnail(get_post($nxt_post_id), '1518');
add_action( 'transition_post_status', 'nxt_create_news', 10, 3 );

In your specific case, you can compare parts of the saved posts to the new post that would be created and then prevent the default action from happening (a new post being created) and instead just edit the old post.

The main question would be how would the system know which post someone's trying to edit? I can't really answer that as I couldn't make out why you're using the profile edits to create new posts I'm afraid, but I hope this is helpful regardless. :-)

  • Thanks for your response. But will your code not rewrite the url? It seems it stays with the old one...
    – Zaesar
    Aug 4 '20 at 15:54
  • My code when the course title is changed it changes the url. That's why it creates a new post and it doesn't update the post. But, in my case this is crucial. That's why when there is a new course (thay just can have one) should delete the old one and stay with the new course title and url
    – Zaesar
    Aug 4 '20 at 15:57

You don't need to do any of that, and can sidestep the problem completely, and make the code faster and simpler!

This is what we're going to do:

  • We're going to use the course post author, not the name, it's more reliable
  • We're going to avoid the raw SQL query so we can use caches and use WP_Query
  • We'll use wp_insert_post for both updating and creating the post
  • Eliminate a useless array_map call
  • Actually check if wp_insert_post was succesful or not
  • We'll apply the WP coding standards to make it easier to read and nicer to work with, e.g. modern PHP arrays [] rather than array(), strict in_array checks, no global variables
  • The code that figures out the course ID will be moved to its own function

This way, when the course name changes, the original course post has its title and URL updated/changed, rather than creating a brand new course that requires cleanup of old courses.

Here's something close to what should work:

function get_course_by_author( $user_id ) : int {
    // if we don't find anything then a value of 0 will create a new post rather than update an existing post.
    $course_id = 0;
    $args = [
        'post_type'      => 'course',
        'posts_per_page' => 1,
        'post_author'    => $user_id,
    $query     = new WP_Query( $args );
    if ( $query->have_posts() ) {
        while ( $query->have_posts() ) {
            $course_id = get_the_ID();
            // we only want a single post ID
        // cleanup after the query
    } else {
        // perhaps the course was created before post authors were set? Fall back to looking for the post by its slug/name
        return 0;
    return $course_id;

function get_course_by_slug( string $slug ) : int {
    $course_id = 0;
    $args = [
        'post_type'      => 'course',
        'posts_per_page' => 1,
        'post_name'      => $slug,
    ... etc etc see get_course_by_author ...
    return $course_id;

function handle_course_post( $user_id = '' ) {
    // First, is this user an instructor?
    $user = get_user_by( 'ID', $user_id );
    if ( ! $user || ! in_array( 'instructor', $user->roles, true ) ) {

    // get the course name
    $course_name = get_user_meta( $user_id, 'course_name', true );
    if ( empty( $course_name ) {
        error_log( "the user does not have a course_name!" );

    // Then lets find this users course if it exists.
    // if we don't find anything then a value of 0 will create a new post rather than update an existing post.
    $course_id = get_course_by_author( $user_id );
    if ( $course_id === 0 ) {
        // we didn't find a course with that author, try the course name.
        $course_id = get_course_by_slug( $course_name );

    // Finally, save/update the course!
    $content  = 'This is the page for: ';
    $content .= get_user_meta( $user_id, 'description_course', true );
    $args     = [
        'ID'           => $course_id,
        'post_title'   => $course_name,
        'post_name'    => $course_name, // set the slug/permalink
        'post_content' => $content,
        'post_status'  => 'publish',
        'post_type'    => 'course',
    $post_id  = wp_insert_post( $args );

    // check if we were succesful.
    if ( is_wp_error( $post_id ) ) {
        // it didn't work!
        error_log( $post_id->get_error_message() );
    } else if ( empty( $post_id ) ) {
        // it didn't work!
        error_log( "post_id is empty/false" );


add_action( 'personal_options_update', 'handle_course_post' );
add_action( 'edit_user_profile_update', 'handle_course_post' );

Remember this is untested and will require some understanding, it can't be copy pasted as is ( although if it does work as is then yay! ).

Notice that at the end we check if the call to wp_insert_post worked or not. We can't assume it was succesful, what if another user has already chosen that course name? What if the user is no longer an instructor?

Also keep in mind that your existing course posts do not have the right author, they will need to be re-saved. Once this is done all cases will be handled. The code above will try to fetch the course post via its slug if it can't find it via the author, allowing a partial migration path.

This does mean your original problem will still happen, but only to users who have never updated their profile, and immediatley change their course name. Those who save their profiles then change the name will be handled, as will all future users.

  • Thanks Tom. Have tested and changed a bit trying to figure out how to make it work. When I update the form with new course name it changes the value on user profile. But it doesn't create any new custom post type. And in consequence it doesn't delete any old post.
    – Zaesar
    Aug 5 '20 at 13:31
  • It updates the old post, no new post is created, there is no deletion necessary. The original course is renamed and its URL changed to the new course name/URL
    – Tom J Nowell
    Aug 5 '20 at 13:39
  • Have also created directly a course post on add new and it publish it but it doesn't delete the old ones. Nope. it doesn't update posts. It just creates new ones.
    – Zaesar
    Aug 5 '20 at 13:39
  • I can't see on the code where it changes the URL.
    – Zaesar
    Aug 5 '20 at 14:08
  • By changing the post_name the URL is changed, the post_name is the posts slug that determines its permalink/URL. WP doesn't store a full URL in the database, but rather it uses the URL to figure out what to load. Keep in mind that your old code did not set the post author, so it won't pick up old course posts, but newly created ones do get picked up. I've adjusted the code to move finding the course into separate functions and made one or two extra comments that should help. I also added basic type hinting to the functions
    – Tom J Nowell
    Aug 5 '20 at 14:51

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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