Stop building WordPress post links from slugs
A safer way to link related articles when the site's permalink structure contains a post ID
If a WordPress site's post URLs include %post_id%, generate internal links with get_permalink($post_id). A slug alone cannot supply the post ID, and string concatenation can miss other parts of the site's permalink structure. WordPress resolves the URL from the stored post and the current permalink rules.
Consider a published game guide at 9Puz. Its path begins with 5705-. A content script that derives /creator-chronicles-beginner-guide/ from the title would produce a different path. Whether the site redirects that path is a separate question; it is not the URL WordPress generated for the post.
Use the post ID at render time
For a known related post, check that it is published, request its permalink, and escape the result when writing HTML:
$related_post_id = 5705;
if ( 'publish' === get_post_status( $related_post_id ) ) {
$related_url = get_permalink( $related_post_id );
if ( false !== $related_url ) {
printf(
'<a href="%s">%s</a>',
esc_url( $related_url ),
esc_html( 'Creator Chronicles beginner guide' )
);
}
}
get_permalink() accepts a post ID or post object and returns the full permalink, or false when the post does not exist. WordPress replaces tokens such as %post_id% and %postname% using the stored post and the site's permalink structure. Passing the ID explicitly also avoids depending on whichever post happens to be current in the loop. The WordPress function reference documents those behaviors.
The status check prevents this example from linking to a draft or a deleted post as though it were public. get_post_status() returns the post status or false if the post cannot be found. esc_url() and esc_html() prepare the URL and label for HTML output. These functions have separate jobs: a correct permalink still needs escaping when inserted into markup.
Resolve links after the target exists
A publishing batch often knows the intended title and slug before WordPress assigns a post ID. That is too early to construct a final URL for a structure containing %post_id%. Keep a stable identifier for the intended target, create or locate the target post, and store the ID returned by WordPress. Then generate related links after the target is published.
For two articles published in one batch, this can be a two-pass operation:
- Create both posts and record their WordPress IDs.
- Publish the target posts and call
get_permalink()for each ID. - Insert the resulting URLs into the related-article text.
- Read back the rendered page and inspect the actual
hrefvalues.
If a target is still a draft, omit the public link until it is published. A preview URL or a guessed slug is not a substitute for the final public permalink.
Check the published output
A script can run without errors while producing an incorrect link. After publishing, compare every generated internal href with get_permalink() for its target ID. Check the published HTML, because a template, importer, or editor can change the final markup after the script writes it. Also confirm that the destination is publicly reachable.
For a small batch, a simple audit table is enough: source post ID, target post ID, generated href, current get_permalink() value, and result. A mismatch identifies the exact link to repair. This check is especially useful after permalink settings change or when older content was assembled from slug-based templates.
WordPress documentation: get_permalink(), get_post_status(), esc_url(), and esc_html().



