This is a tutorial on how to add a custom setting to WordPress’ admin page “Settings > Permalinks” to define the slug for your custom post type. If you are developing a theme or plugin for others that has a built-in custom post type, adding this setting will be appreciated by its user for allowing them to decide the custom post type URL structure.

I won’t go through in detail how to add a custom post type; if you are unsure I recommend taking a look at how to add a custom post type.

Assume you have a code for registering your custom post type like this:

add_action('init', function() {
	register_post_type('reference', [
		'label' => __('References', 'txtdomain'),
		'public' => true,
		'menu_position' => 5,
		'menu_icon' => 'dashicons-book',
		'supports' => ['title', 'editor', 'thumbnail', 'author', 'custom-fields', 'revisions'],
		'show_in_rest' => true,
		'rewrite' => [
			'slug' => 'reference',
			'with_front' => false
		],
		'labels' => [
			'singular_name' => __('Reference', 'txtdomain'),
			'add_new_item' => __('Add new reference', 'txtdomain'),
			'new_item' => __('New reference', 'txtdomain'),
			'view_item' => __('View reference', 'txtdomain'),
			'not_found' => __('No references found', 'txtdomain'),
			'not_found_in_trash' => __('No references found in trash', 'txtdomain'),
			'all_items' => __('All references', 'txtdomain'),
			'insert_into_item' => __('Insert into reference', 'txtdomain')
		],		
	]);
});

The part we’re interested is the rewrite argument. This is where we define what slug the post type will get in front of all their posts. Your code for registering a custom post type might be slightly different, e.g. without ‘with_front‘, but what we’re interested in here is ‘slug‘:

'rewrite' => [
	'slug' => 'reference',
	'with_front' => false
]

With this code you are practically hardcoding what the posts’s permalink slug will be. A post type for references might be used for many types of content and theme users; e.g. project, portfolio, customer references, company references etc. To make your theme flexible you might want to allow the theme user to decide the post type’s slug themselves.

So let’s take a look at how to do that!

Adding and saving the field

Using WordPress’ Settings API simplifies this somewhat for us. All we need to is add a settings field and tell WordPress which admin page we want it to be displayed at – in our case the permalinks settings page. Keep in mind that we need to write a function which actually saves the value as well, which we’ll do at the end.

We hook our function onto the admin_init hook and register our setting. When we register a setting we define which function it should run for outputting the setting:

add_action('admin_init', function() {
	add_settings_field('mytheme_reference_slug', __('References base', 'txtdomain'), 'mytest_reference_slug_output', 'permalink', 'optional');
});

Setting argument four of add_settings_field() to 'permalink' tells WordPress to add the setting to the Permalinks Settings page. Argument number three is the function WordPress should run when outputting the setting – a function we define next (outside the hooked function):

function mytest_reference_slug_output() {
	?>
	<input name="mytheme_reference_slug" type="text" class="regular-text code" value="<?php echo esc_attr(get_option('mytheme_reference_slug')); ?>" placeholder="<?php echo 'reference'; ?>" />
	<?php
}

In this function we simply output a basic form text <input>. For its value attribute we fetch the value of the setting using get_option(). The placeholder attribute is optional, but it’s a good practice to put in your default slug base so the theme user knows whether they want to change its value it or not.

If you save your code and go to your WordPress Permalinks page now, you should see your field at the very end:

At the moment the setting is not saving its value, that’s the next step.

We need to add another function hooked to admin_init that checks whether or not our setting was submitted in permalink structure form. And if so, it should be saved with update_option().

add_action('admin_init', function() {
    if (isset($_POST['permalink_structure'])) {
        update_option('mytheme_reference_slug', trim($_POST['mytheme_reference_slug']));
    }
});

When saving Permalinks settings a form is submitted, so we are able to get the values via PHP’s $_POST global variable. The submitted values from the form is stored as an array with the input’s name attributes as keys. In our case we set a name attribute ‘mytheme_reference_slug‘ on our input, so we can access the submitted value with $_POST['mytheme_reference_slug'].

Now the setting should be functioning fully in Permalink Settings page. The final part is attaching the value of this setting onto our custom post type.

Making our custom post type use our setting

In order to make our custom post type use the custom value of our setting, we return to the register_post_type() function call and modify the rewrite argument into something like this:

'rewrite' => [
	'slug' => (!empty(get_option('mytheme_reference_slug'))) ? get_option('mytheme_reference_slug') : 'reference',
	'with_front' => false
]

All we do here is fetching the value of our new option. If it’s empty the default ('reference') will be used.

And that’s it! Now your custom post type will use whatever slug base is set in Permalinks Settings page. You can add more settings in the same way, for example for custom taxonomies.

The full code

All together now.

// Register custom post type
add_action('init', function() {
	register_post_type('reference', [
		'label' => __('References', 'txtdomain'),
		'public' => true,
		'menu_position' => 5,
		'menu_icon' => 'dashicons-book',
		'supports' => ['title', 'editor', 'thumbnail', 'author', 'custom-fields', 'revisions'],
		'show_in_rest' => true,
		'rewrite' => [
			'slug' => (!empty(get_option('mytheme_reference_slug'))) ? get_option('mytheme_reference_slug') : 'reference',
			'with_front' => false
		],
		'labels' => [
			'singular_name' => __('Reference', 'txtdomain'),
			'add_new_item' => __('Add new reference', 'txtdomain'),
			'new_item' => __('New reference', 'txtdomain'),
			'view_item' => __('View reference', 'txtdomain'),
			'not_found' => __('No references found', 'txtdomain'),
			'not_found_in_trash' => __('No references found in trash', 'txtdomain'),
			'all_items' => __('All references', 'txtdomain'),
			'insert_into_item' => __('Insert into reference', 'txtdomain')
		],		
	]);
});

// Add setting
add_action('admin_init', function() {
	add_settings_field('mytheme_reference_slug', __('References base', 'txtdomain'), 'mytest_reference_slug_output', 'permalink', 'optional');
});

// Setting output
function mytest_reference_slug_output() {
	?>
	<input name="mytheme_reference_slug" type="text" class="regular-text code" value="<?php echo esc_attr(get_option('mytheme_reference_slug')); ?>" placeholder="<?php echo 'reference'; ?>" />
	<?php
}

// Save setting
add_action('admin_init', function() {
    if (isset($_POST['permalink_structure'])) {
        update_option('mytheme_reference_slug', trim($_POST['mytheme_reference_slug']));
    }
});