How WP determines archive page vs single page?

TL;DR:

WordPress uses URL query string variables to select a template based on the Template Hierarchy.


General template selection steps

  1. Permalink structures are used to create rewrite rules.
  2. Rewrite rules take a pretty permalink URL and convert it into a plain URL with a query string.
  3. Parameters from the query string are used to choose a template type.
  4. A template file is chosen based on the Template Hierarchy for the template type.

Example

A permalink structure like this:

/%category%/%postname%/

Step 1

could be converted into a rewrite rule that looks like this:
(The regular expression is left of the =>. The query string is to the right, after index.php.)

(.+?)/([^/]+)(?:/([0-9]+))?/?$ => index.php?category_name=$matches[1]&name=$matches[2]&page=$matches[3]

WordPress uses the $matches array for substitution of regular expression capture groups. This is for URLs targeting the local WordPress site. If targeting an external site (or using an absolute URL), use the standard regular expression capture group substitution sequence: $1, $2, etc.

rewrite_rules_array

This rewrite rule will only be used if it is high enough in the priority order. It must match the URL with its regular expression before another rewrite rule that could also match the same URL. You can see the order of the full list of rules by using the rewrite_rules_array filter hook or the global $wp_rewrite object. Both methods are demonstrated in this comment from WordPress documentation on rewrite_rules_array. See a sample rewrite rule array on the same documentation page.

Step 2

The rewrite rule would take a URL like this:

https://example.com/sports/jai-alai-championship

and convert it into this:

https://example.com/index.php?category_name=sports&name=jai-alai-championship&page=

(The page parameter is added automatically by WordPress to handle pagination on a front page.)

Step 3

The presence of the parameter category_name in the query string results in the selection of a category archive index page. The template file chosen is based on the Template Hierarchy.

The presence of the parameter name (the post slug) in the query string results in the selection of a single post template. The template file chosen is based on the Template Hierarchy.

If both category_name and name are in the same plain URL, which template type would be used?

WP_Query class

The WP_Query class uses conditional logic to resolve combinations of parameters. In this example, name is given priority, category_name is ignored, and a single post template is chosen.

The value returned by Conditional Tags (e.g. is_singular()) is set by the WP_Query class. For example, if the query parameter name is used, WP_Query sets the value of WP_Query->is_singular to true. When WP_Query->is_singular is true, category_name is ignored. If you dare to look, you can see this logic in the WordPress source code for WP_Query. In the code, parts of the SQL WHERE clause for taxonomies are omitted when WP_Query->is_singular is true.

The list of public query parameters (also known as query variables) is long, and so is the list of parameter combinations. I’m not aware of any documentation outlining how different combinations of query variables are resolved. You might just have to test the combinations you intend to use to see what happens.

Some combinations don’t make much sense.

For example:

https://example.com/index.php?name=jai-alai-championship&s=football

name is a query variable that selects the Single Post template.
s is a query variable that selects the Search Result template.
Why search for football while simultaneously targeting an individual jai-alai-championship post?

This combination of name and s is confusing. WordPress resolves this by selecting the 404 (not found) template.

Step 4

Since name is given priority for the converted URL from Step 2, a single post template is chosen using the Template Hierarchy. If the template file exists, it would be chosen based on this priority order (first in the list is highest priority):

(Modified from the WordPress documentation)

  1. templates/single-{post-type}-{slug}.html – (Since 4.4) First, WordPress looks for a template for the specific post. For example, if post type is biography and the post slug is jai-alai-championship, WordPress would look for single-biography-jai-alai-championship.html.
  2. templates/single-{post-type}.html – If the post type is biography, WordPress would look for single-biography.html.
  3. templates/single.html – WordPress then falls back to single.html.
  4. templates/singular.html – Then it falls back to singular.html.
  5. templates/index.html – Finally, as mentioned above, WordPress ultimately falls back to index.html.

When permalinks are not used

When permalinks are not used (i.e. “pretty” permalinks are not used; but, “plain” permalinks are used), query string variables (if any) are taken directly from the URL. For example: http://example.com?p=9, where p represents a post database ID. The ID uniquely identifies the post, and results in selecting a single post template based on the Template Hierarchy (e.g. single.html).

Custom permalink structures

Permalink structures are involved in:

  1. generating links for posts (and other items such as pages and attachments), and
  2. parsing URLs and resolving them to target posts (or pages, etc.).

The example presented above explains parsing and resolving. Here, a permalink structure such as /%category%/%postname%/ only uses the permalink structure tag %postname% (via the name query parameter) to find a post. The permastructure tag %category% seems redundant. But, for the purpose of generating the post link, %category% inserts the post’s category taxonomy term slug into the URL. This could be good for SEO and usabilty since it informs the user about the hierarchy of web site content. If target template pages are designed appropriately for each path segment, the URL could act like a breadcrumb allowing users to remove URL path segments to move upward through the hierarchy.

Useful links

You can read more about WordPress functions and hooks that affect link generation and URL parsing with these useful links.

  1. Linking Posts Pages and Categories
  2. add_rewrite_rule() function
    • Add custom rewrite rules with high or low priority using the $after parameter.
  3. add_rewrite_tag() function
    • Add custom permalink structure tags.
  4. post_link and post_type_link filter hooks
    • When using custom permalink structure tags in the admin Settings > Permalinks > Custom Structure field, use these hooks to tell WordPress how to process your custom permastructure tag.
  5. add_rewrite_endpoint() function
    • Add custom endpoints (custom terminating URL path segments) for various content types for your posts (e.g. json, xml, pdf, text-only, etc.).
  6. register_post_type rewrite[‘slug’] parameter
    • Change the slug used for the custom post type in generated URLs.
  7. rewrite_rules_array filter hook
    • Get the full list of active rewrite rules (regular expressions and conversion query string patterns), and add/update/delete the rules and/or their order produced by the WP_Rewrite::generate_rewrite_rules() function.
  8. parse_request action hook
    • Great for debugging and validating query variables passed to WP_Query. Passes current (not global) $wp instance. Use $wp->query_vars to get query variables.
  9. posts_clauses filter hook
    • Great for inspecting and updating clauses from the SQL query produced by WP_Query before the query is executed on the database. Add extra terms to the WHERE and JOIN clauses for custom filtering.
  10. flush_rewrite_rules() function
    • Programmatically generate a refreshed list of rewrite rules. Use after updates to permalink structures, rewrite rules or rewrite options (e.g. register\_post\_type rewrite\['slug'\]).
  11. redirect_canonical filter hook
    • Terminate 301 redirects used to traverse the rewrite rule array. Useful when your custom rewrite rule fails to match and you deliberately want to generate a 404 (not found).