Posts in WordPress

In this article, let's talk about WordPress posts, or rather how their structure is arranged in the WordPress engine. There is a lot of information on the Internet about what posts in WordPress are and how they differ from static pages. Many of them is good, but designed for beginners, and if we dig deeper, there's more to talk about on this topic.

The first thing to do is to separate the concept of "Posts" into a narrow and a broad one.

Post in a Narrow sense

Is the basic WordPress posts - the post post type.

This is publishing an post with the post post type, which exists in WordPress immediately after installation. Such post can be attached to categories or tags. Such "posts" can be contrasted with "pages" (publishing an entry with the page post type).

Post in a Broad sense

It is a post of any post type: post, page, custom post type.

It is any element that contain site data (content). All such data is written to the database, in the wp_posts table.

Since all the elements are in the same table - they have the same data, and therefore technically they are not very different from each other. I would distinguish 3 main differences:

  1. Tree structure. One post can be a child or a parent to another. This is the structure of static pages.

  2. Name of post type. Each post type has its own name: posts are called post, pages page, user-created types are called as they are named, for example portfolio.

    I highlighted this distinction because the post_type field in the wp_posts table is indexed. So figuratively we can say that the type divides the table into separate smaller tables based on the type of post, i.e. during a query to select posts, WordPress very quickly "cuts down" the table to the specified post type and works only with that type.

  3. Built-in post types. This includes all non-standard post types: revisions, menu items (nav menu), attachments (media files). That is, these are posts that are used and processed in WordPress in a special way.

Default post Types (post_type)

The following post types are registered in WordPress by default.

Type name Title Description
post Posts Regular WP posts.
page Pages Static WP Pages.
custom_name Custom post type Entries created with register_post_type().
attachment Attachments WP media files: images, audio, video.
nav_menu_item Menu items WP's built-in type, for menus.
revision Revisions Built-in WP type, for post editing copies.
oembed_cache oEmbed Responses Responses for oEmbed requests.
custom_css Custom CSS CSS styles specified in the customizer. See wp_get_custom_css_post()
customize_changeset Changesets Customizer settings.
user_request User Requests Requests when changing personal data on the admin tools page. See. wp_create_user_request()
wp_block Reusable Blocks Gutenberg Blocks.
wp_template Templates Templates to include in your theme. (block editor)
wp_template_part Template Parts Template parts to include in your templates. (block editor)
wp_global_styles Global Styles Global styles to include in themes. (block editor)
wp_navigation Navigation Menus Navigation menus that can be inserted into your site. (block editor)

Default post types are registered by the function create_initial_post_types().

Default Posts Statuses (post_status)

The following post statuses are used in WordPress out of the box.

Status Description
publish Published post (page, post, custom post type).
pending Post under review before publication..
draft Post draft.
auto-draft Just created a post, not yet a title, content or other information.
future An post scheduled for publication in the future.
private A post is not available to unauthorized users.
inherit revision or attachment. See get_children().
trash Post that are in the trash bin.

Posts status related functions (full list see here):

Function Description
get_post_status_object() Retrieve a post status object by name.
is_post_status_viewable() Determines whether a post status is considered "viewable".
get_post_statuses() Retrieve all of the WordPress supported post statuses.

Each field of the wp_posts table:

Field Value Index
ID Post ID. primary_key
post_author Post author ID. post_author
post_date Date of post (in site range). type_status_date
post_date_gmt Date of post (GMT/UTC range). -
post_content Content of the post (post or page text). -
post_title Post title. -
post_excerpt A short text of the post (excerpt, extract, quote). -
post_status Post status (publish, inherit, trash). type_status_date
comment_status Whether comments are allowed (open, closed). -
ping_status Whether pings are allowed (open, closed). -
post_password Password for access to the post. -
post_name The post's slug. The name used in URLs. post_name
to_ping URLs to which a ping should be sent when publishing. -
pinged URL to which a ping was sent. -
post_modified The date the post was changed (in site range). -
post_modified_gmt The date the post was changed (in GMT/UTC range). -
post_content_filtered The temporary content of the post. Read more -
post_parent The parent post ID. post_parent
guid The unique identifier of the post. For feeds. -
menu_order Numerical order in the menu. -
post_type Post type: post, page. type_status_date
post_mime_type MIME type of the post. For attachments: image/jpeg, video/mp4. -
comment_count The number of comments on the post. -

As you can see, there are common fields for all posts types, e.g: post_type, post_title. And there are special fields which used only for specific post types. For example, the field post_mime_type is used only for attachments, the field post_parent is used for hierarchical (tree-type) posts, the fields to_ping and pinged are used for posts with content, for example: posts, pages, custom post types.

Indexed fields play a big role in this table: post_name, post_parent, post_author, post_type + post_status + post_date (composite index). Indexes are needed to speed up queries on selecting posts. I won't go into details of indexes. You can read about them in the excellent article Indexes in MySQL.

At this point, I think we can finish with the concept of "posts in WordPress" and move on to other important knowledge.

Difference between post_content_filtered and post_content

post_content_filtered - place where temporary content is stored. WordPress doesn't use this field at all, it's for plugins. It's set as empty every time you update a post, except when it's specified. That is, if this field is specified when the post is updated, it will be written, but if it is not specified on another update, it will be erased.

Detailed answer on this topic here:

Metadata — Additional Post Data

Tasks during development can be very different and the available fields in the wp_posts table is always not enough. Therefore, to expand capabilities, any post in WordPress, ie, any string in the `wp_posts table can have additional data. Such data are called: metadata, they are also called meta-fields, custom fields.

Any of metadata are stored in the wp_postmeta DB table:

wp_postmeta Table

Field Value Index
meta_id Meta-data ID. Internal field used by WP core. PRIMARY
post_id Post ID from wp_posts table. post_id
meta_key The key (name) of the metadata. meta_key
meta_value The value of the metadata. Always a string, arrays are stored in serialized form. -

For post, page meta-fields can be controlled on the post edit page:

How enable the Custom Fields block in the Gutenberg Block Editor?

Notes about meta-fields (metadata, custom fields)

  • Revisions have no metadata.

  • Meta-field whose name starts with underscore (_) is perceived by WP as hidden and is not displayed in admin by default.

    So for example in the WP there are the following hidden meta-fields for posts:

    • _wp_page_template - saved name of the php file template, if the page was specified as a template file.
    • _edit_lock - records the timestamp and ID of the user who edits the post.
    • _edit_last - ID of the user who last edited the post.
    • _thumbnail_id - ID of the post thumbnail (attachment).
  • To manage metadata I have a small class Kama_Post_Meta_Box. There are also more powerful plugins:

  • More about WordPress metadata

Post meta functions

To manage metadata in the code of a theme or plugin, there are special functions:

get_post_meta() Gets the value of the specified custom field of the post. Also can get an array of all post meta fields.
add_post_meta() Adds meta field to the specified post.
update_post_meta() Updates specified meta field of specified post. Or adds it, if it's not exists.
delete_post_meta() Remove metadata matching criteria from a post.
get_post_custom() Retrieve post meta fields, based on post ID.
get_post_custom_keys() Retrieve meta field names for a post.
get_post_custom_values() Retrieve values for a custom post field.
the_meta() Display list of post custom fields.

Posts relations with parent posts and taxonomies

Since any content needs to be structured for easy reading, posts can be linked together:

  • Hierarchical posts are linked to each other as parent and child. And they are normally not linked to headings (taxonomies). The tree-like link is written in the post_parent field of the wp_posts table.

  • Non-tree-like (linear) entries are linked via headings, labels and arbitrary taxonomies. Their post_parent field is always 0 and is not used.

Detailed explanation of what taxonomies in WordPress are.

Schematically it looks like this:

Hierarchical and Taxonomy Structure Schema
Hierarchical and Taxonomy Structure Schema

Relation in non-standard posts types:

  • Menu items - use the logic of tree entries to link between them.
  • Revisions and attachments - are attached to the parent post through the post_parent field of the wp_posts table.


Post content Cleaning / Sanitizing

Post content and post excerpt are cleaned/sanitized with wp_filter_post_kses() before saving it to the database. The function is hung on (field_no_prefix)_save_pre hook:

add_filter( 'content_save_pre', 'wp_filter_post_kses' );
add_filter( 'excerpt_save_pre', 'wp_filter_post_kses' );

These hooks are triggered from: