Getting the current post or page title in a WordPress template is one of the first things you’ll need when writing theme PHP. WordPress provides two functions for this: get_the_title(), which returns the title as a string, and the_title(), which echoes it directly. They look almost identical, but the distinction between returning and echoing matters — using the wrong one is a common source of blank output or unexpected double-printing.
This guide covers both functions, when to use each, how to get titles outside the loop, archive page titles, and how to escape correctly when using a title in an HTML attribute. If you’re deciding between writing custom templates and using a page builder, see page builders vs. custom code in WordPress — the functions here are only relevant when you’re working in PHP templates.
get_the_title()
get_the_title( int|WP_Post $post = 0 )
Returns the post title as a string. The $post parameter is optional — omit it to get the current post’s title (inside the loop), or pass an integer post ID or a WP_Post object to get any post’s title from anywhere.
Because it returns a value rather than printing it, you control how it’s used:
// Echo it directly
echo get_the_title();
// Store it in a variable
$title = get_the_title();
// Use it in an attribute — always escape when going into HTML attributes
<img src="..." alt="<?php echo esc_attr( get_the_title() ); ?>">
// Use it in a link title
<a href="<?php the_permalink(); ?>" title="<?php echo esc_attr( get_the_title() ); ?>">
Read more
</a>
Return value: get_the_title() returns the post title with protected characters decoded (e.g. & → &) but without any HTML tags — post titles are stored as plain text in the database. If the post is password-protected, WordPress prepends “Protected: ” to the title; for private posts, it prepends “Private: “.
Getting a specific post’s title by ID
// Inside or outside the loop — pass any post ID
$page_title = get_the_title( 42 );
echo $page_title;
// Or pass a WP_Post object
$post_obj = get_post( 42 );
echo get_the_title( $post_obj );
This is the function to use when you need a title outside the main loop — for example, pulling the parent page’s title in a hierarchical template, or displaying a “related posts” heading next to another post’s thumbnail.
the_title()
the_title( string $before = '', string $after = '', bool $echo = true )
Displays or retrieves the current post title with optional wrapper markup. Inside the loop, it uses the current post — like get_the_title() with no arguments, but echoed rather than returned.
$before(string, optional)- Markup to prepend to the title. Default:
''(empty string). $after(string, optional)- Markup to append to the title. Default:
''(empty string). $echo(bool, optional)- When
true(the default), echoes the output. Passfalseto return the string instead.
Examples
// Simple echo — most common form inside a template loop
the_title();
// Wrap in heading tags
the_title( '<h1 class="entry-title">', '</h1>' );
// Wrap with a permalink
the_title(
'<h2><a href="' . get_permalink() . '">',
'</a></h2>'
);
// Return instead of echo (useful for string concatenation)
$title_string = the_title( '', '', false );
The $before/$after approach is common in loop templates where every post needs a heading with the same markup structure. It keeps the template tidy compared to manually opening and closing a heading tag around every echo get_the_title() call.
Which Function to Use When
- Inside the loop, echoing directly: Either works.
the_title()is slightly more idiomatic for just printing the title;echo get_the_title()is equally correct. - Inside the loop, wrapping in heading tags:
the_title( '<h2>', '</h2>' )is the cleaner option. - Inside an HTML attribute: Always
esc_attr( get_the_title() )—the_title()should not be used in attributes since it echoes without attribute escaping context. - Outside the loop, with a specific post ID:
get_the_title( $post_id )—the_title()does not accept a post ID argument. - Storing the title in a variable:
get_the_title()—the_title()withfalsealso works but is less readable.
Template Examples
single.php — single post template
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
?>
<article id="post-<?php the_ID(); ?>">
<?php the_title( '<h1 class="entry-title">', '</h1>' ); ?>
<div class="entry-content">
<?php the_content(); ?>
</div>
</article>
<?php
endwhile;
endif;
archive.php — post loop
if ( have_posts() ) :
while ( have_posts() ) :
the_post();
?>
<article>
<?php
the_title(
'<h2><a href="' . get_permalink() . '">',
'</a></h2>'
);
?>
<?php the_excerpt(); ?>
</article>
<?php
endwhile;
endif;
Custom WP_Query loop — getting titles outside the main loop
$recent_posts = new WP_Query([
'post_type' => 'post',
'posts_per_page' => 3,
'orderby' => 'date',
'order' => 'DESC',
]);
if ( $recent_posts->have_posts() ) :
echo '<ul>';
while ( $recent_posts->have_posts() ) :
$recent_posts->the_post();
echo '<li><a href="' . get_permalink() . '">' . get_the_title() . '</a></li>';
endwhile;
echo '</ul>';
wp_reset_postdata(); // always reset after a custom query
endif;
Getting a title by ID — outside any loop
// In a widget, sidebar template, or functions.php callback:
$parent_id = wp_get_post_parent_id( get_the_ID() );
$parent_title = $parent_id ? get_the_title( $parent_id ) : '';
echo '<p>Section: <a href="' . get_permalink( $parent_id ) . '">'
. esc_html( $parent_title )
. '</a></p>';
Archive and Taxonomy Page Titles
On category, tag, custom taxonomy, author, and date archive pages, the_title() and get_the_title() return the title of the current post in the loop — not the archive page name. To get the archive page’s own descriptive title, use get_the_archive_title():
// In archive.php, above the loop:
echo '<h1>' . get_the_archive_title() . '</h1>';
// Output examples: "Category: News", "Tag: WordPress", "Author: Ajay Khandal"
// Strip the "Category: " prefix if you want just the name:
echo single_cat_title( '', false ); // category archives
echo single_tag_title( '', false ); // tag archives
echo single_term_title( '', false ); // any taxonomy term
For page templates on static front pages or custom pages, the_title() and get_the_title() work exactly as they do in single.php — they return the page’s title, not the blog name or site tagline.
The HTML <title> Tag Is Different
The post title functions covered here output the post’s editorial title — the same text that appears in the page’s <h1> heading. The HTML <title> element (what appears in browser tabs and search engine results) is separate, controlled by wp_get_document_title() and typically managed by an SEO plugin like Rank Math or Yoast.
Do not use the_title() or get_the_title() in the <head><title> block — WordPress’s wp_head() hook handles that, and an SEO plugin should be setting the exact title tag content. The two are independent.
Escaping Title Output
Post titles are stored as plain text in WordPress, but they can contain characters that need escaping depending on where you use the output:
// Outputting in page body — escape as HTML
echo esc_html( get_the_title() );
// Inside an HTML attribute (alt, title, aria-label)
echo esc_attr( get_the_title() );
// Inside a URL (query string value)
echo esc_url( add_query_arg( 'title', get_the_title(), $base_url ) );
// the_title() applies esc_html internally when $echo is true
// so plain the_title() in body copy is safe
the_title();
The safe pattern: use the_title() when printing in body copy, and esc_attr( get_the_title() ) whenever the title goes into an HTML attribute. The title can legitimately contain characters like ", <, or & — a post titled get_the_title() & Related Functions will break your markup if you don’t escape it in an attribute context.
The the_title Filter
Both the_title() and get_the_title() pass through the the_title filter before returning. This lets you modify title output sitewide without editing template files:
// Add a "(Draft)" suffix to draft posts in the admin or on the frontend
add_filter( 'the_title', function( $title, $id ) {
if ( get_post_status( $id ) === 'draft' ) {
$title .= ' (Draft)';
}
return $title;
}, 10, 2 );
// Strip emoji from all titles
add_filter( 'the_title', function( $title ) {
return preg_replace( '/[\x{1F600}-\x{1F64F}]/u', '', $title );
} );
The filter receives the title string and the post ID. Return the modified string — don’t echo inside the filter callback.
Related Template Functions
Title functions are typically used alongside other template tag functions in the same file. The complete picture of common template tags:
get_the_ID()/the_ID()— current post IDget_permalink()/the_permalink()— post URLget_the_excerpt()/the_excerpt()— post excerptget_the_date()/the_date()— published dateget_the_author()/the_author()— post author display namethe_content()— full post body (no get_ equivalent — always echoes)
For displaying images in templates, see how to use wp_get_attachment_image() — it’s the paired function to these title/permalink tags, covering featured images, ACF image fields, and srcset handling in custom loops.
For enqueueing the scripts and styles that your custom templates depend on, see the right way to enqueue scripts and styles in WordPress — using wp_enqueue_script() correctly is the template setup step that comes alongside writing the PHP itself.
If you need to output post content through non-standard means — rendering a post’s content as part of another template — WordPress shortcodes are the bridge between template PHP and content-embedded functionality.
Common Mistakes
- Using
the_title()outside the loop without a post ID.the_title()doesn’t accept a post ID — it uses the global$post. Outside the loop,$postis whatever was last set, which may not be the post you want. Useget_the_title( $post_id )explicitly. - Not echoing
get_the_title().get_the_title()returns a string — it doesn’t print anything. A common template bug is callingget_the_title()on its own line and seeing nothing, because the return value was discarded. - Skipping
esc_attr()in HTML attributes. A title like “The <b>Basics</b>” or “Breadcrumbs & Navigation” inside an unescaped attribute will break your HTML. Always escape:esc_attr( get_the_title() ). - Forgetting
wp_reset_postdata()after a customWP_Query. After a custom query loop, the global$postis set to the last post in that query. Anyget_the_title()orthe_title()call after the custom loop — beforewp_reset_postdata()— returns the wrong post’s title. - Confusing the post title with the
<title>tag.get_the_title()andthe_title()return the post’s editorial title (the h1). The browser tab/SEO title in<head>is set bywp_get_document_title()and modified by SEO plugins — they’re separate.


