Developer documentation for Default Permalink: hooks, options and customization.
How slug replacement works
The plugin hooks into the wp_unique_post_slug filter (priority 10, 5 arguments) and replaces the slug only when all of the following conditions are met:
- The
df_enableoption is"Enable"(enabled on the settings page). - The post type is targeted (all public post types when the option has never been saved).
- The slug matches the regular expression
/(%[0-9a-f]{2})+/(contains URL-encoded multi-byte characters). - The user has not explicitly specified a slug (no
post_namein the saved input; recorded at the start of the save via thewp_insert_post_empty_contentfilter).
For new posts created without an ID yet (REST API, WP-CLI, direct wp_insert_post() calls), an empty slug is returned first and the slug is replaced when wp_insert_post() regenerates it with the new ID after inserting. The generated slug is made unique with wp_unique_post_slug() (a suffix such as -2 is added on conflicts).
When a slug is replaced on an actual save, the new slug is stored in the _dp_slug_replaced post meta for the notice (permalink previews do not record it). In the block editor, it is included in the REST response of the save as dp_slug_replaced, the meta is deleted and the editor shows the notice. In the classic editor, the notice is shown on the next load of the edit screen.
The notice is recorded only for saves from the admin screens (except admin-ajax) and the REST API (since 1.9.2). Saves from WP-CLI, imports (WP_IMPORTING), cron (DOING_CRON) and ajax requests such as Quick Edit do not record it (the slug is still replaced). Change this with the default_permalink_record_notice filter (bool). In the block editor, the notice is shown only when the record matches the current slug of the post (since 1.10.2: an old record is not reported when the slug was changed in the save being made).
With the “post date and ID” format (date), saves of existing posts use the date being saved (for example the scheduled date; the current time for a draft without a fixed date) (since 1.10.1). A slug typed in the permalink box of the classic editor is not replaced in the preview either.
Options
| Option | Values | Description |
|---|---|---|
df_enable | "Enable" / "" | Whether the plugin’s behavior is enabled |
df_slug_format | post_type / date / random | Slug format (default: post_type) |
df_post_types | Array of post type names | Targeted post types. All public post types when never saved (installs older than v1.4) |
df_taxonomies | Array of taxonomy names | Taxonomies whose term slugs are replaced (since 1.7.0). Empty (none) by default. post_format cannot be selected |
The default_permalink_slug filter
The generated slug is passed through the default_permalink_slug filter, so themes and other plugins can freely customize the format.
/**
* Example: change the generated slug to "item-{post_ID}".
*
* @param string $slug The generated slug.
* @param int $post_id The post ID.
* @param string $post_type The post type.
*/
add_filter( 'default_permalink_slug', function ( $slug, $post_id, $post_type ) {
return 'item-' . $post_id;
}, 10, 3 );
The default_permalink_term_slug filter
Slugs of new terms in the targeted taxonomies ({taxonomy}-{term_id} by default, with _ replaced by -) are passed through the default_permalink_term_slug filter. The result is made unique with sanitize_title() and wp_unique_term_slug().
/**
* Example: change term slugs to "topic-{term_ID}".
*
* @param string $slug The generated slug (e.g. category-12).
* @param int $term_id The term ID.
* @param string $taxonomy The taxonomy name.
*/
add_filter( 'default_permalink_term_slug', function ( $slug, $term_id, $taxonomy ) {
return 'topic-' . $term_id;
}, 10, 3 );
The default_permalink_record_notice filter
Decides whether a save that replaced the slug leaves the notice record (_dp_slug_replaced) behind (since 1.9.2). The argument is the default decision (true for saves from the admin screens or the REST API, false for WP-CLI, imports, cron and ajax). To record it for every save, as in 1.9.1 and earlier:
add_filter( 'default_permalink_record_notice', '__return_true' );
WP-CLI commands
wp default-permalink status: shows whether the plugin is enabled, the slug format, the targeted post types and taxonomies, and the number of posts to convert.wp default-permalink convert [--dry-run]: runs the same conversion as the settings screen without the 50-post limit.--dry-runlists the posts and their new slugs without changing anything. It moves on past posts that cannot be converted and exits with code 1 if any failed.- Options of
convert(since 1.10.0):--post_type=<types>(comma-separated; non-targeted types are skipped with a warning),--limit=<number>(stops after that many posts and prints the remaining count and the--aftervalue to continue),--after=<id>(since 1.10.1; processes only posts after that ID). - The bulk conversion (settings screen and WP-CLI) does not change the status or the publish schedule of a post. A scheduled post whose time has passed stays scheduled; only its slug is converted (since 1.10.2).
Site Health
The plugin adds the direct test default_permalink through the site_status_tests filter (since 1.10.0, WordPress 5.2 or later). It is “recommended” (with a link to the settings page) when df_enable is not "Enable", and “good” when enabled. Remaining posts with a multi-byte slug keep it “good” and add their number and a link to the bulk conversion to the description (since 1.10.1).
Uninstall
Deleting the plugin runs uninstall.php, which removes all of the options above and the _dp_slug_replaced notice post meta. On multisite, it removes them from every site of the network (since 1.9.2). Converted post slugs are part of the post data itself and remain unchanged.