Skip to content

Truly Random Post Slugs

I’ve again changed the way my notes’ permalinks are being generated. I’ve said this before, but I simply don’t like how URLs to untitled WordPress entries typically end in just the unique post ID.

So, I used to use a bit of code that obfuscates these IDs, and that was that, until I started using the Micropub plugin. Now, this plugin works great; it’s my blog setup that’s weird.

My “notes,” you see, unlike regular blog posts, are a Custom Post Type. Makes for a tidier back end, I find. I’d tried Post Formats—WordPress’s built-in way to tell articles from, let’s say, quotes or image posts—before. Post Kinds—a sort of IndieWeb variant of Post Formats—too. Still, I wanted to separate things just a little more.

Micropub, then—the plugin, not the protocol. As there’s no filter hook to specify Post Types for, e.g., notes or likes, I’m using the low-level wp_insert_post_data hook instead, as illustrated below. (Post content, luckily, can be programmatically modified.)

Anyway, because wp_insert_post_data typically runs after wp_unique_post_slug, the latter’s $post_type argument will always be 'post'—we’ve yet to override the Post Type!—and my previous “slug generation function” won’t do anything. (Note: this obviously doesn’t apply to anything posted through WP Admin.)

Now, I could’ve maybe found another way around this—e.g., interacting with the database directly—but decided to just set a fully random slug instead, right after having determined the Post Type. Like so:

add_filter( 'wp_insert_post_data', function( $data, $postarr ) {
  if ( ! empty( $postarr['ID'] ) ) {
    // Not a new post.
    return $data;

  if ( ! empty( $postarr['meta_input']['mf2_like-of'][0] ) ) {
    // Like.
    $data['post_type'] = 'like'; // Likes get their own Post Type and admin menu.
  } elseif ( ! empty( $postarr['meta_input']['mf2_bookmark-of'][0] ) ) {
    // Bookmark.
    $data['post_type'] = 'note'; // That's right! Bookmarks are notes, too!
  } elseif ( ! empty( $postarr['meta_input']['mf2_repost-of'][0] ) ) {
    // Repost.
    $data['post_type'] = 'note';
  } elseif ( ! empty( $postarr['meta_input']['mf2_content'][0] ) && empty( $data['post_title'] ) ) {
    // Note.
    $data['post_type'] = 'note';

  if ( in_array( $data['post_type'], array( 'like', 'note' ), true ) ) {
    global $wpdb;

    do {
      // Generate random slug.
      $slug = bin2hex( openssl_random_pseudo_bytes( 5 ) );

      // Check uniqueness.
      $result = $wpdb->get_var( $wpdb->prepare( "SELECT post_name FROM $wpdb->posts WHERE post_name = %s LIMIT 1", $slug ) );
    } while ( $result );

    $data['post_name'] = $slug;

  return $data;
}, 10, 2 );

Note: We’re first checking if a post is being newly inserted or updated—we don’t want to change slugs on every update, or our blog’d be full of 404s. There’s an explicit “slug uniqueness” check, too.