I currently use Post name for the permalink structure. I want to add text from a field at the end of the permalink. Is there any hook for the permalink creation when a Post is published?

In my case, I use Advanced Custom Fields and every post has a Title and Subtitle. Currently, the permalink is /title/ but I wanted to be /title-subtitle/.

Edit: similar to this previous question, except that I only want to do it for post creation and not on future post editions, since this will modify the URL of the post which is a disaster for SEO.

  • After reading the answer from @stoopkid1 came some in mind, namely that there was/is a max of characters which can be used for titles (if I am correct that number is 200 when using utf8?). Maybe I am totally wrong but if correct, you have to take care for that (by setting max use characters and a msg for yourself/your users maybe?) or have to find a solution by using/rewriting sanitize_title, thinking of $utf8_string and $length or such.
    – Charles
    Feb 8, 2016 at 18:35
  • 1
    Permalinks are created on the fly, that makes possible to change permalink structure at any time if you wish. So, there is no way to "create permalinks when a post is published". I think, what you want is to change the slug of the post when it is saved. Possible duplicate of Rewriting post slug before post save
    – cybmeta
    Feb 8, 2016 at 19:36
  • @cybmeta Yes, I also thought slug was the way to go here. Is the slug used when Post name is selected for the permalink structure?
    – IvanRF
    Feb 8, 2016 at 20:08
  • 1
    Yes, the slug is the post_name field in the posts database and it is used when the permalink structure is set to %postname%.
    – cybmeta
    Feb 8, 2016 at 20:12

2 Answers 2


Here is what I did to implement this:

function slug_save_post_callback( $post_ID, $post, $update ) {
    // allow 'publish', 'draft', 'future'
    if ($post->post_type != 'post' || $post->post_status == 'auto-draft')

    // only change slug when the post is created (both dates are equal)
    if ($post->post_date_gmt != $post->post_modified_gmt)

    // use title, since $post->post_name might have unique numbers added
    $new_slug = sanitize_title( $post->post_title, $post_ID );
    $subtitle = sanitize_title( get_field( 'subtitle', $post_ID ), '' );
    if (empty( $subtitle ) || strpos( $new_slug, $subtitle ) !== false)
        return; // No subtitle or already in slug

    $new_slug .= '-' . $subtitle;
    if ($new_slug == $post->post_name)
        return; // already set

    // unhook this function to prevent infinite looping
    remove_action( 'save_post', 'slug_save_post_callback', 10, 3 );
    // update the post slug (WP handles unique post slug)
    wp_update_post( array(
        'ID' => $post_ID,
        'post_name' => $new_slug
    // re-hook this function
    add_action( 'save_post', 'slug_save_post_callback', 10, 3 );
add_action( 'save_post', 'slug_save_post_callback', 10, 3 );

It generates and updates the slug. Previous slug generated by WP can not be reused since it can have unique numbers if the title/slug was already used in another post. So, I sanitize the title. Then, wp_update_post makes sure there are no duplicates for the new slug with wp_unique_post_slug.

The only way I could find to only do this on publish is to compare the creation and the modified time. They are only equal when the post is created. The $update parameter is useless, since is true for the publish.

  • 1
    Maybe you should run some checks to be sure it does not run on every occasion? if ($new_slug['post_status'] !='published' && $new_slug['post_status'] !='auto-draft' && $_GET['action'] !='trash' && $_GET['action'] !='untrash') { //do your thing }
    – Charles
    Feb 9, 2016 at 5:37
  • Asking/mentioning this because I am working on almost the same to set a post_title (for a cpt) using get_the_date, and using a filter (wp_insert_post_data) but not the action save_post (not 100% sure why I did not use that action also).The function I am testing is working but my approach could be totally wrong. It will be very useful to see some input from the pro's here also.
    – Charles
    Feb 9, 2016 at 5:54
  • 1
    @Charles thanks for the tip about post_status. I did some testing and I finally found the way to call wp_update_post only one time, on creation.
    – IvanRF
    Feb 9, 2016 at 16:58
  • i was trying to implement this with some taxonomy terms, but i couldnt get this run, the terms where always empty on publish. i got it working on update, but i wanted it to work on publish. i guess, because the terms are not saved, yet. glad i came up with the idea of using $_POST variable. its all in there..
    – honk31
    Nov 20, 2018 at 20:13
  • This method doesn't seem to work for me, get_field (or get_post_meta) are not returning anything when the post is created. Are they getting set in the DB after this hook?
    – Louis W
    Jun 20, 2019 at 17:48

in your functions.php file, you should be able to accomplish your desired functionality with something like this:

function adjust_permalinks() {
    global $wp_rewrite;
    $title = get_field( 'title_field' );
    $subtitle = get_field( 'sub_title_field' );
    $wp_rewrite->set_permalink_structure( $title . '-' . $subtitle );
add_action('init', 'adjust_permalinks');

this will set your permalink structure to always use the $title & $subtitle

  • It seems this is applied always. I just want to hook on permalink creation for a new Post.
    – IvanRF
    Feb 8, 2016 at 18:31
  • you can hook into save_post() codex.wordpress.org/Plugin_API/Action_Reference/save_post
    – stoopkid1
    Feb 8, 2016 at 18:34
  • A permalink structure can not have a hardcoded values like you have done. And, by the way, I would use this code in a plugin, not in theme functions.php file. This functionality has nothing to do with the look and feel of the site and should remain active if the webmaster wants to change the theme.
    – cybmeta
    Feb 8, 2016 at 19:40
  • Also, flushing the rewrite rules on every page load performs unecessary and expensive database operations; it should not be done in any case.
    – cybmeta
    Feb 8, 2016 at 19:49

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.