Title: wp_unique_post_slug
Published: April 25, 2014
Last modified: February 24, 2026

---

# wp_unique_post_slug( string $slug, int $post_id, string $post_status, string $post_type, int $post_parent ): string

## In this article

 * [Parameters](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#parameters)
 * [Return](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#return)
 * [Source](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#source)
 * [Hooks](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#hooks)
 * [Related](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#related)
 * [Changelog](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#changelog)

[ Back to top](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#wp--skip-link--target)

Computes a unique slug for the post, when given the desired slug and some post details.

## 󠀁[Parameters](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#parameters)󠁿

 `$slug`stringrequired

The desired slug (post_name).

`$post_id`intrequired

Post ID.

`$post_status`stringrequired

No uniqueness checks are made if the post is still draft or pending.

`$post_type`stringrequired

Post type.

`$post_parent`intrequired

Post parent ID.

## 󠀁[Return](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#return)󠁿

 string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)

## 󠀁[Source](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#source)󠁿

    ```php
    function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_parent ) {
    	if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
    		|| ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
    	) {
    		return $slug;
    	}

    	/**
    	 * Filters the post slug before it is generated to be unique.
    	 *
    	 * Returning a non-null value will short-circuit the
    	 * unique slug generation, returning the passed value instead.
    	 *
    	 * @since 5.1.0
    	 *
    	 * @param string|null $override_slug Short-circuit return value.
    	 * @param string      $slug          The desired slug (post_name).
    	 * @param int         $post_id       Post ID.
    	 * @param string      $post_status   The post status.
    	 * @param string      $post_type     Post type.
    	 * @param int         $post_parent   Post parent ID.
    	 */
    	$override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_id, $post_status, $post_type, $post_parent );
    	if ( null !== $override_slug ) {
    		return $override_slug;
    	}

    	global $wpdb, $wp_rewrite;

    	$original_slug = $slug;

    	$feeds = $wp_rewrite->feeds;
    	if ( ! is_array( $feeds ) ) {
    		$feeds = array();
    	}

    	if ( 'attachment' === $post_type ) {
    		// Attachment slugs must be unique across all types.
    		$check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
    		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_id ) );

    		/**
    		 * Filters whether the post slug would make a bad attachment slug.
    		 *
    		 * @since 3.1.0
    		 *
    		 * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
    		 * @param string $slug     The post slug.
    		 */
    		$is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );

    		if ( $post_name_check
    			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
    			|| $is_bad_attachment_slug
    		) {
    			$suffix = 2;
    			do {
    				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
    				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_id ) );
    				++$suffix;
    			} while ( $post_name_check );
    			$slug = $alt_post_name;
    		}
    	} elseif ( is_post_type_hierarchical( $post_type ) ) {
    		if ( 'nav_menu_item' === $post_type ) {
    			return $slug;
    		}

    		/*
    		 * Page slugs must be unique within their own trees. Pages are in a separate
    		 * namespace than posts so page slugs are allowed to overlap post slugs.
    		 */
    		$check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
    		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id, $post_parent ) );

    		/**
    		 * Filters whether the post slug would make a bad hierarchical post slug.
    		 *
    		 * @since 3.1.0
    		 *
    		 * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
    		 * @param string $slug        The post slug.
    		 * @param string $post_type   Post type.
    		 * @param int    $post_parent Post parent ID.
    		 */
    		$is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );

    		if ( $post_name_check
    			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
    			|| preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
    			|| $is_bad_hierarchical_slug
    		) {
    			$suffix = 2;
    			do {
    				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
    				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id, $post_parent ) );
    				++$suffix;
    			} while ( $post_name_check );
    			$slug = $alt_post_name;
    		}
    	} else {
    		// Post slugs must be unique across all posts.
    		$check_sql       = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
    		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id ) );

    		$post = get_post( $post_id );

    		// Prevent new post slugs that could result in URLs that conflict with date archives.
    		$conflicts_with_date_archive = false;
    		if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
    			$slug_num = (int) $slug;

    			if ( $slug_num ) {
    				$permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
    				$postname_index = array_search( '%postname%', $permastructs, true );

    				/*
    				* Potential date clashes are as follows:
    				*
    				* - Any integer in the first permastruct position could be a year.
    				* - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
    				* - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
    				*/
    				if ( 0 === $postname_index ||
    					( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
    					( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
    				) {
    					$conflicts_with_date_archive = true;
    				}
    			}
    		}

    		/**
    		 * Filters whether the post slug would be bad as a flat slug.
    		 *
    		 * @since 3.1.0
    		 *
    		 * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
    		 * @param string $slug      The post slug.
    		 * @param string $post_type Post type.
    		 */
    		$is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );

    		if ( $post_name_check
    			|| in_array( $slug, $feeds, true ) || 'embed' === $slug
    			|| $conflicts_with_date_archive
    			|| $is_bad_flat_slug
    		) {
    			$suffix = 2;
    			do {
    				$alt_post_name   = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
    				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id ) );
    				++$suffix;
    			} while ( $post_name_check );
    			$slug = $alt_post_name;
    		}
    	}

    	/**
    	 * Filters the unique post slug.
    	 *
    	 * @since 3.3.0
    	 *
    	 * @param string $slug          The post slug.
    	 * @param int    $post_id       Post ID.
    	 * @param string $post_status   The post status.
    	 * @param string $post_type     Post type.
    	 * @param int    $post_parent   Post parent ID
    	 * @param string $original_slug The original post slug.
    	 */
    	return apply_filters( 'wp_unique_post_slug', $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug );
    }
    ```

[View all references](https://developer.wordpress.org/reference/files/wp-includes/post.php/)
[View on Trac](https://core.trac.wordpress.org/browser/tags/6.9.4/src/wp-includes/post.php#L5464)
[View on GitHub](https://github.com/WordPress/wordpress-develop/blob/6.9.4/src/wp-includes/post.php#L5464-L5635)

## 󠀁[Hooks](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#hooks)󠁿

 [apply_filters( ‘pre_wp_unique_post_slug’, string|null $override_slug, string $slug, int $post_id, string $post_status, string $post_type, int $post_parent )](https://developer.wordpress.org/reference/hooks/pre_wp_unique_post_slug/)

Filters the post slug before it is generated to be unique.

 [apply_filters( ‘wp_unique_post_slug’, string $slug, int $post_id, string $post_status, string $post_type, int $post_parent, string $original_slug )](https://developer.wordpress.org/reference/hooks/wp_unique_post_slug/)

Filters the unique post slug.

 [apply_filters( ‘wp_unique_post_slug_is_bad_attachment_slug’, bool $bad_slug, string $slug )](https://developer.wordpress.org/reference/hooks/wp_unique_post_slug_is_bad_attachment_slug/)

Filters whether the post slug would make a bad attachment slug.

 [apply_filters( ‘wp_unique_post_slug_is_bad_flat_slug’, bool $bad_slug, string $slug, string $post_type )](https://developer.wordpress.org/reference/hooks/wp_unique_post_slug_is_bad_flat_slug/)

Filters whether the post slug would be bad as a flat slug.

 [apply_filters( ‘wp_unique_post_slug_is_bad_hierarchical_slug’, bool $bad_slug, string $slug, string $post_type, int $post_parent )](https://developer.wordpress.org/reference/hooks/wp_unique_post_slug_is_bad_hierarchical_slug/)

Filters whether the post slug would make a bad hierarchical post slug.

## 󠀁[Related](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#related)󠁿

| Uses | Description | 
| [_truncate_post_slug()](https://developer.wordpress.org/reference/functions/_truncate_post_slug/)`wp-includes/post.php` |

Truncates a post slug.

  | 
| [is_post_type_hierarchical()](https://developer.wordpress.org/reference/functions/is_post_type_hierarchical/)`wp-includes/post.php` |

Determines whether the post type is hierarchical.

  | 
| [apply_filters()](https://developer.wordpress.org/reference/functions/apply_filters/)`wp-includes/plugin.php` |

Calls the callback functions that have been added to a filter hook.

  | 
| [get_option()](https://developer.wordpress.org/reference/functions/get_option/)`wp-includes/option.php` |

Retrieves an option value based on an option name.

  | 
| [get_post()](https://developer.wordpress.org/reference/functions/get_post/)`wp-includes/post.php` |

Retrieves post data given a post ID or post object.

  | 
| [wpdb::get_var()](https://developer.wordpress.org/reference/classes/wpdb/get_var/)`wp-includes/class-wpdb.php` |

Retrieves one value from the database.

  | 
| [wpdb::prepare()](https://developer.wordpress.org/reference/classes/wpdb/prepare/)`wp-includes/class-wpdb.php` |

Prepares a SQL query for safe execution.

  |

[Show 5 more](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#)
[Show less](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#)

| Used by | Description | 
| [WP_REST_Posts_Controller::create_item()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/create_item/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Creates a single post.

  | 
| [WP_REST_Posts_Controller::update_item()](https://developer.wordpress.org/reference/classes/wp_rest_posts_controller/update_item/)`wp-includes/rest-api/endpoints/class-wp-rest-posts-controller.php` |

Updates a single post.

  | 
| [get_sample_permalink()](https://developer.wordpress.org/reference/functions/get_sample_permalink/)`wp-admin/includes/post.php` |

Returns a sample permalink based on the post name.

  | 
| [wp_ajax_inline_save()](https://developer.wordpress.org/reference/functions/wp_ajax_inline_save/)`wp-admin/includes/ajax-actions.php` |

Handles Quick Edit saving a post from a list table via AJAX.

  | 
| [wp_insert_post()](https://developer.wordpress.org/reference/functions/wp_insert_post/)`wp-includes/post.php` |

Inserts or update a post.

  |

## 󠀁[Changelog](https://developer.wordpress.org/reference/functions/wp_unique_post_slug/?output_format=md#changelog)󠁿

| Version | Description | 
| [2.8.0](https://developer.wordpress.org/reference/since/2.8.0/) | Introduced. |

## User Contributed Notes

You must [log in](https://login.wordpress.org/?redirect_to=https%3A%2F%2Fdeveloper.wordpress.org%2Freference%2Ffunctions%2Fwp_unique_post_slug%2F)
before being able to contribute a note or feedback.