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.
In this article, we will talk about WordPress posts, specifically about how their structure is organized in the engine. There is information on the web about what posts are and how they differ from pages. For example, here is an article on this topic - the article is excellent, but for beginners, and if we dig deeper, there is more to discuss on this topic.
The first thing to do — is to divide the concept of "Post" into narrow and broad. A post can be both specific posts of the post
post type and, in a broader sense, any post, for example, when we register our post type - an element of this post type can also be called a post.
See also:
- register_post_type() - registers a post type.
- get_post_type_object() - retrieves post type data.
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:
-
Tree structure. One post can be a child or a parent to another. This is the structure of static pages.
-
Name of post type. Each post type has its own name: posts are called
post
, pagespage
, user-created types are called as they are named, for exampleportfolio
.I highlighted this distinction because the
post_type
field in thewp_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. - 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.
Post Types
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 | Cached 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 | Theme settings that are applied on top of the basic ones, see theme.json, wp_get_global_settings(). (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().
Posts Statuses
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() | Gets the data object of the specified post status. |
is_post_status_viewable() | Determines whether a post status is considered "viewable". |
get_post_statuses() | Retrieve all of the WordPress supported post statuses. |
wp_posts
Fields
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. Read more | - |
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.
Field post_content_filtered
post_content_filtered
- the place where temporary content (post_content) is stored. WordPress does not use this field at all; it is intended for plugins.
WordPress assigns an empty string to post_content_filtered with each post update (if it is not specified in the parameter). Therefore, the field is cleared with any changes: quick editing, publishing scheduled posts, changing status, bulk edits, external edits, etc.
If the field is specified when calling update functions, for example, wp_update_post(), it will be written to the database, but if it is not specified during the next update, it will be erased.
How to preserve the field during updates
Without custom code, the field will always be reset. Therefore, to preserve the value, you can use the filter wp_insert_post_data, where you return the current value if a new one has not been specified:
add_filter( 'wp_insert_post_data', 'preserve_content_filtered', 999, 2 ); function preserve_content_filtered( $data, $postarr ) { // only during update if ( empty( $postarr['ID'] ) ) { return $data; } $before = get_post( $postarr['ID'] ); if( empty( $data['post_content_filtered'] ) ){ $data['post_content_filtered'] = $before->post_content_filtered; } return $data; }
More details: http://wordpress.stackexchange.com/questions/113387/when-is-the-post-content-filtered-column-in-database-cleared-by-wordpress
Field guid
Created to store a unique value — the identifier of the post. It is needed for identifying the post in the RSS feed. GUID stands for Globally Unique Identifier. Based on this field, RSS parsers determine whether they have processed the post or not.
It is not recommended to change this field, ever, even if your site has moved to another domain. If this field is changed, your RSS readers may receive a bunch of already published materials.
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:
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 thewp_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:

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 thewp_posts
table.
Notes
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: