What the Loop is
When someone visits /, /category/news/ or /hello-world/, WordPress works out which posts that address needs and runs a database query for them. That is the main query. By the time your template file is loaded, the posts are already waiting. The Loop is the few lines of PHP that go through them one at a time.
This lesson uses a small classic theme. First the theme files that stay the same:
/*
Theme Name: Loop Demo
Version: 1.0.0
*/<!doctype html>
<html <?php language_attributes(); ?>>
<head><meta charset="<?php bloginfo( 'charset' ); ?>"><?php wp_head(); ?></head>
<body <?php body_class(); ?>><?php wp_footer(); ?>
</body>
</html>The basic Loop
<?php get_header(); ?>
<main>
<?php if ( have_posts() ) : ?>
<?php while ( have_posts() ) : the_post(); ?>
<article>
<h2><a href="<?php the_permalink(); ?>"><?php the_title(); ?></a></h2>
<p class="date"><?php the_date( 'j F Y' ); ?></p>
</article>
<?php endwhile; ?>
<?php else : ?>
<p class="empty">Nothing found.</p>
<?php endif; ?>
</main>
<?php get_footer(); ?>wp theme activate loop-demoSuccess: Switched to 'Loop Demo' theme.Read it from the inside out:
have_posts()istruewhile there are posts left to show.the_post()takes the next post and makes it the current post. This is what makes the "template tags" work:the_title(),the_permalink()andthe_date()have no arguments because they read the current post.- When the Loop ends, there are no posts left, and the
elsebranch runs only if there were none at all.
The home page shows the one sample post:
GET /Hello world!Template tags: print or return
Most template tags come in two forms. the_title() prints; get_the_title() returns the value so you can use it in PHP. The same goes for the_permalink() and get_permalink(), the_ID() and get_the_ID().
<?php
global $post;
$post = get_post( 1 );
setup_postdata( $post ); // what the_post() does for each post
echo get_the_title(), "\n";
echo get_permalink(), "\n";
echo get_the_date( 'Y-m-d' ), "\n";
echo get_the_author(), "\n";
echo wp_trim_words( get_the_content(), 5, '...' ), "\n";
wp_reset_postdata();Hello world!
http://localhost:8080/hello-world/
2026-01-15
admin
Welcome to WordPress. This is...Useful tags inside the Loop:
| Tag | Prints |
|---|---|
the_title() |
the title |
the_content() |
the full content, with blocks rendered and shortcodes run |
the_excerpt() |
the excerpt, or a trimmed start of the content |
the_permalink() |
the post's address |
the_date() / the_time() |
the publication date or time |
the_author() |
the author's display name |
the_post_thumbnail() |
the featured image |
the_category() / the_tags() |
the categories or tags as links |
Anything printed from user content is already prepared for output by WordPress. When you print values you built yourself, escape them (see Writing secure WordPress code).
Different template, same Loop
A single post template shows one post, but the Loop is the same, and this one prints the whole content:
<?php get_header(); ?>
<main>
<?php while ( have_posts() ) : the_post(); ?>
<article>
<h1><?php the_title(); ?></h1>
<div class="entry"><?php the_content(); ?></div>
</article>
<?php endwhile; ?>
</main>
<?php get_footer(); ?>GET /hello-world/Hello world!Search results and the empty case
The search template uses the same Loop. A query that matches nothing shows the else branch of index.php:
GET /?s=zebraNothing found.GET /?s=welcomeHello world!Never replace the main query yourself
A common beginner mistake is to call query_posts() or to create a new query at the top of a template to change what a page shows. It runs the database query twice and breaks pagination. To change the main query, use the pre_get_posts hook (covered in Custom queries with WP_Query); for an extra list somewhere on a page, make a separate WP_Query.
Common mistakes
- Calling template tags outside the Loop without a post set up; they print nothing or the wrong post.
- Forgetting
the_post(), which gives an endless loop or the same post repeated. - Using
echo the_title();.the_title()already prints. - No
elsebranch, so an empty search shows a blank page. - Using
query_posts().
Exercise
Change index.php so that it prints the number of posts found above the list, for example "1 post found". Hint: the main query is in the global $wp_query, and $wp_query->found_posts holds the total.
Show one possible answer
<?php global $wp_query; ?>
<p class="count"><?php echo esc_html( sprintf( _n( '%d post found', '%d posts found', $wp_query->found_posts ), $wp_query->found_posts ) ); ?></p>_n() chooses the singular or plural text. Put the line inside <main>, before the Loop.
Key points
- Before your template runs, WordPress has already run the main query; the Loop walks through the posts it found.
- while ( have_posts() ) : the_post(); ... endwhile; makes each post the current one, so template tags such as the_title() and the_content() work.
- Use the_title() and the_permalink() to print, get_the_title() and get_permalink() to return a value.
- Always handle the empty case with if ( have_posts() ) ... else ..., and never write your own query for the main content of a template.