Modify default Slug for WordPress Posts

The default slug is generated from the initial title. If the title is long, this can be annoying. I prefer short URLs without common filler words like “the,” “and,” or “I.” This setup also limits slugs to a maximum of six words. You can always edit the slug manually afterward.

For example; if you start with this Title:

The small fox jumps through a simple set of 9 new burning hoops of flame

Instead of a default slug of:

the-small-fox-jumps-through-a-simple-set-of-9-new-burning-hoops-of-flame

You will get:

small-fox-jumps-through-simple-set-9-new-burning-hoops-flame

And because I further limit to 6 terms the final slug is:

small-fox-jumps-through-simple-set

First, the system checks whether the post already has a slug. If it does, we don’t modify it. Otherwise, the slug is generated on the first save (not on subsequent updates) using the following steps:

  • Take the current title
  • Remove all non-alphanumeric characters
  • Remove all single-letter words (numbers are allowed)
  • Remove any words in the stop-word list
  • Limit the slug to the first six remaining words
/*
	Alter default Post slug upon first save
*/
function slug_save_post_callback( $post_id, $post, $update ) {

	if (empty($post->post_title) || !empty($post->post_name)) {
		return;
	}

	if ($post->post_type != 'post' || $post->post_status == 'auto-draft') {
		return;
	}

	$slug = preg_replace("/[^A-Za-z0-9 ]/", '', $post->post_title);
	$slug = trim(preg_replace("/(^|\s+)(\D(\s+|$))+/", ' ', $slug));

	$remove_list = array(
		'after', 'an', 'and', 'are', 'around', 'as', 'at', 'back', 'be', 'behind', 'being', 'big', 'biggest', 'but', 'by', 
		'can', 'cant', 'changed', 'did', 'do', 'drive', 'far', 'for', 'from', 'get', 'going', 'got', 'grab', 'great', 
		'had', 'has', 'hasnt', 'have', 'heres', 'his', 'how', 'in', 'into', 'is', 'its', 'just', 
		'knows', 'like', 'means', 'more', 'most', 'need', 'needs', 'new', 'not', 
		'of', 'off', 'on', 'only', 'or', 'ok', 'says', 'see', 'set', 'stay', 'still', 'story', 
		'than', 'that', 'the', 'their', 'theyre', 'this', 'to', 'use', 'used', 'using', 
		'want', 'we', 'what', 'whats', 'when', 'where', 'who', 'whom', 'why', 'will', 'with', 'wont', 'you'
	);

	foreach($remove_list as $word) {
		$slug = preg_replace("/\b$word\b/i", "", $slug);
	}

	$slug = explode("-", sanitize_title($slug));
	$slug = implode("-", array_slice($slug, 0, 6));

	if ($slug == $post->post_name) {
		return;
	}

	// Unhook this function to prevent infinite looping, restore it later
	remove_action( 'save_post', 'slug_save_post_callback', 5, 3 );

	wp_update_post(array(
		'ID'        => $post_id,
		'post_name' => $slug
	));

	add_action( 'save_post', 'slug_save_post_callback', 5, 3 );
	clean_post_cache($post_id);
}
add_action( 'save_post', 'slug_save_post_callback', 5, 3 );

Gist: https://gist.github.com/donohoe/4766b8a20cc6573de3dd3cb59df9fc32