WordPress ends every automatic excerpt with […], which tells the reader there is more but gives them nothing to click. This snippet changes the WordPress default excerpt […] to an ellipsis followed by a Read more link to the post.
The link always points at the post the excerpt belongs to, also in widgets and custom queries outside the main loop, and screen readers hear which post it opens.
The snippet
<?php
/**
* Replace the WordPress excerpt "[…]" with a "Read more" link to the post.
*/
// ===== Config =====
function dpc_excerpt_more_text() {
return __( 'Read more', 'dpc' ); // Link text.
}
// ===== End of config =====
// Remember which post the excerpt is being built for; excerpt_more itself does not get it.
add_filter( 'get_the_excerpt', 'dpc_excerpt_more_remember_post', 9, 2 );
function dpc_excerpt_more_remember_post( $excerpt, $post = null ) {
$GLOBALS['dpc_excerpt_more_post'] = $post;
return $excerpt;
}
add_filter( 'excerpt_more', 'dpc_excerpt_more' );
function dpc_excerpt_more( $more ) {
$post = get_post( $GLOBALS['dpc_excerpt_more_post'] ?? null );
if ( ! $post ) {
return $more;
}
$text = dpc_excerpt_more_text();
$title = html_entity_decode( wp_strip_all_tags( get_the_title( $post ) ), ENT_QUOTES, get_bloginfo( 'charset' ) );
return sprintf(
'… <a class="dpc-read-more" href="%1$s" aria-label="%2$s">%3$s</a>',
esc_url( get_permalink( $post ) ),
/* translators: 1: link text, 2: post title. */
esc_attr( sprintf( __( '%1$s: %2$s', 'dpc' ), $text, $title ) ),
esc_html( $text )
);
}
How it works
- When a post has no hand-written excerpt,
wp_trim_excerpt()cuts the content and appends the value of theexcerpt_morefilter. The snippet returns…and the link instead of[…]. excerpt_moredoes not say which post is being trimmed, andget_the_permalink()without an argument reads the global post. Outside the loop that can be a different post. Theget_the_excerptcallback at priority 9 remembers the real post first, and the link is built for that post.- The URL goes through
esc_url()and the text throughesc_html(). - Many “Read more” links in a list sound the same to a screen reader. The
aria-labelstarts with the visible text and adds the post title, for example “Read more: Hello world”. - The link has the class
dpc-read-morefor styling. It no longer hasrel="nofollow": these are links to your own posts, and search engines should follow them.
Add it with Scripts Organizer
In Scripts Organizer go to Code Blocks → Add New and give the block a name. Set Trigger location to Everywhere, tick PHP under Script location, paste the code into the PHP editor, edit the config and publish. Leave Action/Hook empty.
The same code also works unchanged in a small plugin or an mu-plugin.
Customise it
- Other wording: change the string in
dpc_excerpt_more_text(). It is translatable with thedpctext domain. - No ellipsis: remove
…at the start of the format string, but keep a space before<a. - Shorter or longer excerpts: see Change the Default Excerpt Length in WordPress.
Gotchas
- Hand-written excerpts are not trimmed, so they get no link. Show a separate link in your template if you need one there too.
- The block themes’ Post Excerpt block has its own More text setting. When it is filled in, the block hides this link so only one is shown.
- Themes that already add a Read more button below the excerpt will now show two links; remove one of them.
- The link also appears in RSS feeds that use excerpts, which is usually what you want.
The older version of this post printed get_the_permalink() unescaped, linked to the global post, had no space before the link and added rel="nofollow".
In the Scripts Organizer library Import it with one click from Scripts Organizer.