One of the very first things people tend to learn when writing plugins is how to attach custom HTML to the bottom of their WordPress posts using the filter called “the_content”. This is good, as it is an extremely useful filter, but rarely do tutorials really explain how to use this filter well, and how to avoid some potentially major problems when using it in your plugins. I’m going to walk you through the basics and also demonstrate some more advanced applications, and talk to you about problems that can be caused when using this filter.
The typical code that you will see intro plugin development tutorials show usually looks about like this:
1 2 3 4 5 6 | function pippin_filter_content_sample($content) { $new_content = '<p>This is added to the bottom of all post and page content</p>'; $content = $content . $new_content; return $content; } add_filter('the_content', 'pippin_filter_content_sample'); |
With this function active in your theme or plugin, any time the the_content() function is called, the text “This is added to the bottom of all post and page content” will be appended to the end.
There are a few minor and major issues with function. Let’s look at how we correct these.
Problem number 1: while there is nothing syntactically wrong with this function, we can improve it by cutting away some of the unnecessary code. There is really no reason for this line:
$content = $content . $new_content; |
Instead of doing that, we should just attach our $new_content variable directly to the $content variable in the return statement, like so:
return $content . $new_content; |
You could even go a little further and do just this:
return $content . '<p>This is added to the bottom of all post and page content</p>'; |
They will both result in the same thing, but I prefer the first of the two methods and it is just slightly easier to read, especially when you have much larger functions.
Problem number 2: the first problem was more of a good coding practice kind of problem, this second one is much more major. Have you ever seen a plugin, particularly social sharing plugins, that adds some form of content to posts and/or pages, and for some reason the content appears in weird places, such as sidebars or footers, and also on every single page, including archives? This is typically because the developer has not take the time to ensure that their extra content, be it social icons or something else, only gets added in the appropriate place. The most common place to append content is at the end of a post when viewing the single post.
To update our code and fix problem #2, we need to use some conditional tags. First let’s start with is_single().
1 2 3 4 5 6 7 8 | function pippin_filter_content_sample($content) { if(is_single()) { $new_content = '<p>This is added to the bottom of all post content</p>'; $content .= $new_content; } return $content; } add_filter('the_content', 'pippin_filter_content_sample'); |
By adding the is_single() conditional check, we ensure that out extra content only gets appended when we are on a single post page. But what if we want to add content to both single posts and pages? And perhaps custom post types? Then we need to use is_singular():
1 2 3 4 5 6 7 8 | function pippin_filter_content_sample($content) { if(is_singular()) { $new_content = '<p>This is added to the bottom of all post and page content, as well as custom post types.</p>'; $content .= $new_content; } return $content; } add_filter('the_content', 'pippin_filter_content_sample'); |
Looks pretty good right? Actually no, there is still a major problem with this code. The thing that you must understand and acknowledge is that the “the_content” filter may NOT only get applied when we call the_content(). Often times plugin and theme developers will use something like the following to help format their custom queries:
echo apply_filters('the_content', $custom_content_here); |
Doing this will pass whatever content is stored in the $custom_content_here variable and perform all of the nice formatting that we expect with the the_content() function. This also means that our pippin_filter_content_sample() function will attach its custom content to this other text as well, and that’s not good.
In order to solve this problem, we can use one more conditional tag. The is_main_query() function was introduced in WordPress 3.3 and allows us to check whether we are currently inside of the main post query. The main post query can be thought of as the primary post loop that displays the main content for the post/page or archive. By using this tag, our filter will NOT get applied to any custom loops in sidebars, footers, or elsewhere, even if they use this:
apply_filters('the_content' . . . |
Our final function, which should play well with everything, looks like this:
1 2 3 4 5 6 7 8 | function pippin_filter_content_sample($content) { if( is_singular() && is_main_query() ) { $new_content = '<p>This is added to the bottom of all post and page content, as well as custom post types.</p>'; $content .= $new_content; } return $content; } add_filter('the_content', 'pippin_filter_content_sample'); |
When building your own plugins and themes, do not forget to think about what other developer’s plugins and themes may be doing. You should always try to eliminate as many possible conflicts before they even happen, and making sure that you use these kind of filters correctly is a great place to start.
It’s probably worth noting that the ‘the_content’ filter is NOT actually applied to get_the_content().
Ah yes, you’re right. Updating now.
thanks Pippin!
I’m guilty of doing_it_wrong π But from now on, I’ll use the improved code.
how’s the baby?
Lots of people are. I’ve definitely done it wrong far more than I’d care to admit.
The baby is doing well π
I think, if you want to add something below the content, (for eg: social button). It should be added after wp_link_pages.?
Because wp link pages already have filter to enable this.
The wp_link_pages() function does not actually have a filter that you can use to attach additional content too, and even if it did, it actually would not be a good idea to use, as it is up to the theme to implement wp_link_pages() in the template files. Obviously yes, it is up to the theme to add the_content() as well, but that function is almost guaranteed to be included in every theme.
There’s a hook there. It was one of my contributions to the core code. Andrew Nacin has an awesome solution that uses it in his Simple Footnotes plugin where he checks if
wp_link_pages()
is used. It’s brilliant stuff.Oh, thanks for the correction!
Thanks for this tutorial π I was wondering if you could advise the best practise for say – if one built an options page with checkboxes so users could ‘tick’ boxes to show content after Single / Page/ other post types? Thanks!
Are you looking for help on creating those checkboxes, or how to detect if they are enabled and then adding the appropriate content?
Thanks for the excellent explanation on how this filter works. I have written a plugin that allows you to display the contents of a custom post type called content block in a widget and often get the complaint that social media buttons are displayed in the widget area.
From now on I will point them to this post!
Social media plugins tend to be the largest culprit for sure.
I use it withing my theme single.php file but this code is cleaner will definitely try it thanks for sharing
As a user, not a plugin developer, I’m trying do remove a filter from a plugin. I’m using Secondary Content Blocks and it adds extra content beneath each block. like social media icons etc. This means that with 3 extra content blocks, I also get three extra sets of social media icons. The writer of the plugin told me this: “You have to the remove the filter (remove_filter) that adds the extra content via the “the_content” filters before your secondary HTML functions.” but that’s were the help stops ;).
How should my code look like then in my templates?
You’re going to use something like this:
You will have to find the appropriate function name to replace “name_of_the_function_from_the_plugin”.
Unfortunately the is_main_query function doesn’t work OK inside a custom loop:/
Do you mean it doesn’t work to detect a custom loop? If so, that’s correct, it’s for the main query that is performed when the page is loaded, not any sub-queries that happen throughout the page.
Yes, exactly. And unfortunately that’s why the pippin_filter_content_sample filter will be also applied inside a custom loop (unless your custom query overwrites $GLOBALS[‘wp_query’] for a moment)
Yes, you’re right, though it largely depends on how the query is setup actually. The snippet I gave could actually be improved even further to help mitigate affecting sub queries.
I’m having a problem filtering the content in my Sermon plugin. The content will repeat in the header, but only using certain themes with JetPack installed. None of the themes I’ve developed have this issue. Twenty Twelve does cause the issue if JetPack is active. Here’s an example using the Suffusion theme with JetPack: http://stlukeswatford.org/sermons/image/
You can view the code to filter the content here on lines 961-972 : https://github.com/wpforchurch/sermon-manager-for-wordpress/blob/master/sermons.php
Any ideas? I’d really appreciate any help you could offer.
What’s the issue in the header exactly? Can you give me an example?
Sorry, there was an example at the first url, but they understandably took it down. Here’s another one: http://demo.wpforchurch.com/plugins/sermons/rooted-in-love/
Whoa, that’s bizarre. Have you tried deactivating all of the social modules? I suspect getting rid of those will fix it.
Pippin your site helps me a lot, but his time I can’t figure out what is happening, I need to ask you:
When using the filter
the_content
, the defaults themes (2010 and 2011) outputs only raw content in archive listings π Even your example turns into raw text.I think there’s something to do with this themes excerpt functions but I can’t find a clue on how to bypass they filters/functions.
Thanks for any help.
What do you mean exactly by raw text? Can you give me an example of what you were hoping for and what you’re getting?
I want place some special content in posts, just like in your example, but some themes like 2010 and 2011 just strips HTML if the filter is intend to affect on archives pages too.
If you try your example on archive pages, (not only single), those themes will strip out any HTML, e.g.
if you set
$new_content= 'Test';
in your example, 2010 and 2011 themes will remove he HTML tags πThe 2012 theme and some other don’t do this, but 2010 and 2011 does;
You’ll want to filter
the_excerpt
Thanks @Jack! That solved!
I just added the the_excerpt as well and works ok now!. Thanks!
Hi Pippin,
I have a question. I am using a plugin that inserts content at the of the post automatically. Now I am using your code to insert custom content & it works well. The problem is that the content inserted by the plugin appears before my custom content. I am trying to reverse this. I am trying to show my content first, followed by the content inserted by the plugin. I tried to use 10 for the priority in the add_filter line of my theme but it does not seem to fix it.
So do you know how I can fix this? Any help is appreciated.
Thanks.
Try setting the priority to “-1”.
Should I do this for my theme for the plugin? If I edit the source code of the plugin, I am afraid that it will break when the plugin upgrades. So should I do this for theme?
In your theme yes.
Hello Pipin..
I am using:
1. a social media plugin that appends it’s social buttons after the_content
2. a metadata plugin that also appends it’s metadata after the content
3. An author box plugin that also appends the author box after the_content
Now I want that The 2nd plugin(metadata plugin) should put it’s data after the social plugin or after both 1st and 3rd plugins data.
Looked in plugin’s files. all 3 of them are using filter and returning something like this:
the_content . “plugin data”
through the function they hook.
Can you help me solving this one?
It can be taken as prioritizing the filters or something like that.
Thank you π
Do you want to disable the filters globally or just in certain contexts?
I Don’t want to disable them.
Just asking what to do when 3 plugins are using the_content filter and I want to make sure which plugin puts it content after post content first.
Got it. Then you should be using the priority options for the filters. The higher the priority, the later the function will run. Make sense?
Hello Pippin,
Nice post, thanks a lot.
I just tried to take help of your code and was able to successfully implement it with the following piece of code.
function belowcontent($content) {
global $post;
if( is_singular() && is_main_query() ) {
$variable = get_post_meta($post->ID, 'metadata', true);
if ($variable == 'ap')
{
$new_content = 'xxxxxxxxxxxxxxxxxxxxxxx';
return $content . $new_content;
}
else
{
return $content;
}
}
}
add_filter('the_content', 'belowcontent');
I am observing that the conditional tag is successfully operating on single post pages. However, on there is no data on the main page (http://loancalculators.in/articles/), where all the posts are chronologically listed.
Appreciate any help in this regard.
Thanks
Just wanted to add that I am using a theme, which is using custom page for the main page. I have shared the code of this page on Google Drive here at…
https://docs.google.com/file/d/0B9VMYc0qEDtpUXZMcmxrRDhId00/edit?usp=sharing.
Thanks for any help.
That’s because the archive pages uses the_excerpt() instead of the_content().
Thanks for looking.
Yes, I understand that archive pages are using the_excerpt(). But I do not know if the_excerpt() and the_content() are related.
Could you help me how I can get the excerpt back on the archive pages.
Sorry, I may be sounding too naive to ask such simple question.
Silki
They are related but only the_content() uses the filter called “the_content”.
So,
How can I fix up the issue?
Silki
Modify the theme template files and replace the_excerpt() with the_content().
Pippin. could show me how to add
Try setting the priority to β-1β³.
I have no idea how much to add is to give an example
Instead of this (for example):
Thanks Pippin, you helped me get through a road block I was having!
YES YES YES. THANK YOU, HOLY CRAP.
Seriously, this was frustrating me, very much. Incredibly helpful.
Thanks.
is_main_query() didn’t make any difference for me. I had a basic echo apply_filters(‘the_content’, …) in the footer and it still got stuff attached to it.
I added “in_the_loop()” as well, and that fixed things up for me. Seems like it could be good to use both if you really want to narrow things down to the main page/post content.
in_the_loop worked. Thanks
is_main_query() is a simple test for equality, and should probably be first in the expression, followed by is_single/is_singular, then any custom tests e.g; for post type. This is also a nice logical progression of specificity.
Hi,
I am totally green in coding.
how i can add the link to all posts, which will repeat after each 200 characters in the post.
Hi pippin! I want my plugin to add a banner just above/before the post list at the front page. Is it possible?
The width of banner size may flexible following all the width.
Thanks you for ur great tutorial.
That will be dependent upon your theme. What theme are you using?
Owh ok. Currently im using twentytwelve.
So if its dependent upon my theme, then its impossible to make it appear automatically for all theme. What should i do?
Or is there any add filter that can we use here?
Very nice!! I had it wrong for months. Only after revisiting part of my plugin.. and noticing this issue… did I find your post. Easy to read, easy to understand, well explained from all perspectives… AND… solved my problems! Thanks!!
That is a super super cool snippet!
I am using it now to add the breadcrumb from Yoast’s SEO plugin, but the breadcrumb ends up on the top of the content, and a figure “1” is shown on the bottom of each page and post…. any idea what I’m doing wrong? This is what I put in my functions.php file:
function wvr_add_breadcrumb_filter( $content) {
if ( is_singular() && is_main_query() && function_exists(‘yoast_breadcrumb’) ) {
$new_content = yoast_breadcrumb();
}
return $content . $new_content;
}
add_filter( ‘the_content’, ‘wvr_add_breadcrumb_filter’);
My guess is that yoast_breadcrumb() is echoing the content of the breadcrumbs instead of returning them. Ty using: yoast_breadcrumb( ”, ”, false )
That worked perfectly, thanks!
(allthough I did install another plugin and used the same fix)
Thank you sir! I released my first plugin yesterday only to find out it was breaking WordPress Captions on Single posts. This post helped me figure it out:)
Thanks for this tutorial.
I am just started learning WordPress theming and this is very useful for me.
Could you please explain me how I can break down the_content to 2 parts and include some custom content between it ?
Thanks
That’s certainly possible, though a bit trickier.
How do you want to split it? Do you want to split it exactly in half? Split it after a specific word? After a certain number of characters or words?
As a follow-up to this great advice, you can also use:
in_the_loop()
combined with is_single(), is_main_query(). The main reason for this, is to be precautions and make sure that the first query is in the actual loop itself.
I was trying to prevent my content filter function from being called multiple times on a single WordPress Page. I tried using in_the_loop(), is_singular() and is_main_query() but for some reason with some themes, my content filter was still getting called multiple times per page load. This was not good as I was making a request to an external API inside my content filter and the request was getting processed twice, slowing down the page load.
I think I solved this by defining a PHP constant inside my filter function. I’ve posted a Gist here:
https://gist.github.com/datafeedr/500b2eba17b6d39ad09e225889754228
Now my filter function is called just once.
Hope this helps!
Eric
Hi Pippin, great article, as always. Do you know of any way to let my code NOT hook into the_content when the blog post is being embedded? It’s quite a headache if the post content is short and my stuff gets printed with HTML stripped at the end inside the embed box:
https://www.dropbox.com/s/4w5y0g1pecnq2jj/Screenshot%202017-03-23%2011.07.45.png?dl=0
You should be able to use the is_embed() function to detect when you’re on an embed view: https://developer.wordpress.org/reference/functions/is_embed/