WordPress Function to use Media Library Items as the Primary Post Type

I’ll admit it, it’s hard to find time to blog — especially if you’re just goofing off and posting photos for your own vanity, like I often do. We have to make this as easy as possible! The way we’re going to do that is to eliminate all annoying steps from our workflow, until we’re left with a simple drag’n’drop upload, as opposed to the normal Posts > Add New > type some stuff > upload some stuff > grab the uploads > position some stuff > publish post > edit post to fix some stuff > call it good enough.

In other words, the end goal is this: Have WordPress grab your photos instead of your posts. Here’s the gist:

Let’s break that down, line by line.

Escaping the Guard

The first part of our function is a series of guards to make sure the query only gets altered on the home page:

	// we don't want to alter the query in wp-admin
	if ( is_admin() ) {return;}
	// we don't want to alter the query if it's not the main query
   	if ( !$query->is_main_query() ) { return; }
	// furthermore, we only want to do this on the home page
	if( !is_home() ){return;}
  1. There’s a check to make sure we don’t mess with the query in the admin area. Funny story: Some years ago, I was debugging a client site wherein the client was unable to perform image searches in the admin area. It was because the theme dev (…me…) had altered the main query, but had not scoped it to avoid the admin area.
  2. Next is a check to make sure we only alter the query if it’s the main query. is_main_query() is a little different from most of the conditional tags we’re used to in that it’s a method of the WP_Query class. That’s why we pass $query into it.

    Another funny story: Some years ago, I was debugging a client site wherein the archive pages were set to show more posts per page than other views. But that posts_per_page value also seemed to apply to sidebar widgets. Odd, right? It’s because the theme dev was not scoping his function to only hit the main query.

  3. Finally, a check to make sure we only alter the query if it’s the home page. If you have any uncertainty about this concept, get familiar with is_home() vs is_front_page().

Get Random

After the guards have been passed, we finally start altering the query. First, by changing the orderby param:

   	// I happen to be a big fan of the random, in all things.
	$query->set( 'orderby', 'rand' );

I’ve blogged about this in the past: Random is cool. You could leave this condition out, and WordPress would obey it’s default reverse-cron behavior.

Let’s Not Over-share

There’s an old photographer saying about how you should only show your best photos, that way everyone will think you always take great photos. But we still want to be able to upload all of our photos to our WordPress blog — we wouldn’t want to burden ourselves by keeping our great photos on our blog and all our other photos in some other spot. We want a one-stop-shop for stroking our ego with our own photography!

To that end, we’ll upload all our photos, but come up with a way to curate our best images onto the homepage. We’ll do that via a tax_query:

   	// curate our images such that only our best shots make the cover
	$tax_query = array(
    		// we've tagged our best photos
		'taxonomy' => 'post_tag',
		// the tag we applied to our best photos was "Cover Shot"
		'terms' => 'cover-shot',
		// we grab by slug since slugs seem to get broken or changed less frequently than titles or IDs
		'field' => 'slug',
		// this is the wp default, but I often like to be verbose/redundant so as to help code readability
		'operator'=> 'IN'
	// add our tax query to the array of tax_query queries
	$query->tax_query->queries[] = $tax_query;
	// add the tax_query queries to the current query
	$query->query_vars['tax_query'] = $query->tax_query->queries;

Tax queries are confusing because it’s hard to understand how or why there can be queries nested inside of queries. And why would you ever need to have an array of tax queries? There are some fantastic examples of how and why this is the case on the codex. For our purposes, understand this: We’ll tag an image as “Cover Shot” to curate it onto the cover.


You might notice that by default, tags don’t work with attachments. We’re going to dig into attachments & taxonomies a bit later in this vague-torial, but for now, here’s the deal:

register_taxonomy_for_object_type( 'post_tag', 'attachment' );

That line of code is not in our gist above — we’ll deal with all our taxonomy stuff in detail a bit later, but I wanted to at least clarify what’s happening for now.

Just Our Type

Next we deal with post_type. When you make a normal post in WordPress, it gets a post_type of ‘post’. When you make a page, it gets a post_type of ‘page’. When you make a menu item, it gets a post_type of ‘nav_menu_item’. And when you upload a media item, it gets a post_type of ‘attachment’. Therefore, we alter the query thusly:

	// tell wordpress to only grab posts of the attachment post_type
	$query->set( 'post_type', 'attachment' );

You could also pass an array of allowed post types, but for our purposes we only want attachments.

Status Update

Next we address the post_status param. By default WordPress grabs posts of the post_status ‘publish’. But attachments are a little different. They get a post_status of ‘inherit’.

	// because attachments work a little differently than normal posts, we have to tell wordpress to ignore their post_status
	$query->set( 'post_status', 'any' );

Just One Post

Since we’re showing one large image, we only need to query for one post. This also frees us up to use the wp-admin > settings > reading > posts per page setting elsewhere on our project, perhaps if we also want to publish a more traditional text-focused blog.

	// since we're just doing one large image, we might as well only query 1
	$query->set( 'posts_per_page', '1' );

Passing by Reference

I’m in the habit of always having a function return a value, but in this case it’s not necessary and could even impede our understanding of how WP_Query works:

	// there's no need to return a value because $query is altered by reference

Hook it Up

It can be easy to forget to hook our functions, so many developers prefer to do add_action() before the function definition. My habit is to put it after the function because I find it more readable in the context of phpdoc commenting. A brief survey of wp core files reveals that I’m not alone in that regard. I also have the benefit of having forgotten to hook hundreds of times, so I know exactly where to look if something doesn’t work.

add_action( 'pre_get_posts', 'sjf_deh_alter_main_query' );

What Have We Done?

So far we’ve accomplished approximately one thing: Alter the main query such that WordPress grabs your photos instead of your posts, with a few additional specs thrown in.

What Else Might We Do?

Because of our tax query, we’re guaranteed only to grab the images we want. However, I can imagine a scenario where it would be helpful to only grab jpegs:

	// only grab jpegs
	$query->set( 'post_mime_type', 'image/jpeg' );

What Else Must We Do?

As noted above, we need to do some work to make sure tags work with attachments. I’ve dug into that here.