How We Built It: WP Podcasts
It was late June, 2021, and Cate and I were driving home from our anniversary weekend. We were trying to come up with ideas of things we could offer back to the WordPress community. We had a few criteria.
- It couldn’t be expensive or too time consuming to build. We wanted to raise funding, but not until AFTER we released, which doesn’t help pay for a build. We also didn’t want this build to drag on for months.
- It needed to be easy to maintain. If it took a lot of work or money to maintain, it would probably die, especially if we didn’t get funding.
- Ideally it would use off-the-shelf products. This would decrease build time, and put future maintenance costs on the vendor, rather than me.
I had recently built Topher.How for fun, and was delighted that it populates itself as new content is created out on the internet. It uses RSS feeds to pull in content from all over.
I knew that WordPress does a wonderful job making RSS feeds, so we started thinking about what kind of content could offer substantially more value if it were gathered all in one place.
That’s when the idea for WP Podcasts struck, and my mind couldn’t let it go. At first I wasn’t sure it was a good idea. I didn’t want to steal traffic from podcasters. Then I remembered with Topher.How I’m not actually hosting the content, I’m linking directly back to the source, which actually increases traffic for the content creator.
So let’s take a look at how we fulfilled all the requirements and built something really cool.
Thinking About Data Types
Podcast RSS feeds really only have two types of data; multiple Episodes (“items” in RSS parlance), and podcast meta data in the header. The meta data holds things like the title of the podcast, the source URL, the contact information, categories for the whole feed, etc.
It seemed extremely logical to use the default Post type for episodes. They are the core of the site, the primary content. I chose to use tags for the categories and tags that are attached to each episode because I wanted a tag cloud, and that was the path of least resistance.
But where to store Podcasts? I thought about a custom post type, but that wouldn’t magically create an archive of Episodes for each one, and I didn’t want to code it out (simplicity first!). After thinking about it I decided the easiest way to get what I wanted was to make each Podcast a Category, the default taxonomy attached to posts. This meant that every episode that gets imported attaches to its category and we magically get very nicely named and organized archives.
That’s how it stands now, Episodes are Post, and Podcasts are Categories. Episode “categories” are Tags. I didn’t have to build anything at all. Simple!
Thinking About Custom Fields
For Episodes, I didn’t make any custom fields at all myself. I have a plugin that makes two, and we’ll talk about that in a few minutes. Episode title maps to Post title, Episode description goes in the content, Episode publish date becomes Post datetime, so ours matches the original. Tags we’ve talked about, and that’s about all there is to it.
Except for the URL of the original post. I started out with a custom field for it, but switched to something else, which like I said, we’ll talk about in a bit.
For Categories on the other hand, I knew I’d need some custom fields. I wanted the Category page to look nice and represent the Podcast well. I used the Category name as the Podcast name, and the Category description as the Podcast description. That meant I needed three extra custom fields; logo, RSS URL, and Site URL.
I’m traditionally one to use code libraries for custom fields. I really like Metabox.io, but I knew that if I used Advanced Custom Fields then I could add the fields and move on in about 5 minutes. Simplicity called to me, so that’s what I did. There’s another reason too, but we’ll talk about that in a few minutes.
I thought about using a plain text field for the logo url, since it’s in the RSS, but that would have made my pages hit the source server every time, which COULD be a bunch of times per page. I didn’t want that. So an Image field actually imports the image into the WP Podcasts website.
So here’s what a category looks like:
Final Data Storage Adjustments
Podcast Slugs
The default slug for the Category taxonomy is “category”, but I didn’t want that. The items held therein are podcasts. I didn’t immediately think of it, but the solution was as simple as changing it in the Permalink Settings page.
And that magically gives me category URLs like this: https://wppodcasts.com/podcast/delicious-brain-waves/
Episode Links
I mentioned above that there were a couple of custom fields created by a plugin. I started down this path myself, and then remembered that Mark Jaquith had already made a wonderful plugin for it. By default, WordPress Posts appear in Archives, and each have a Single, where you see the item itself.
I didn’t want that single to exist. If someone searches for something and sees a result, or finds it in an archive, I want them to link directly to the source page, hosted by the podcast owner. This means the podcast owners gets all those visitors looking at their ads, sponsored spots, and the owner gets to control the experience of listening to the episode, and guiding people toward subscribing.
So I grabbed Page Links To and dropped it in. It has a field for the URL and another one for whether it should open in a new tab.
Finally, Importing!
For the importing I looked at WP All Import. For Topher.how I had used Feedzy, which is a fine plugin, but I wanted to try a different one to see how I felt about it. As it turns out, I very very much like WP All Import. I started with the free version, and it mostly did what I wanted, but not quite. I got excellent support even though I was a free user, which was great. The paid version item I found most useful was ACF support. Since I was using ACF for the custom fields it seemed smart. I COULD perhaps have coded something, but that image field can get tricky, and simplicity was still king.
At this point I’d like to point out that well into the project, when I was really too far to turn back easily, the folks at WP All Import donated a license to The HeroPress Network with no promise of anything in return, other than public thanks. They’re good folk, and the product and support are excellent.
The Process
As mentioned above, each Podcast has two main data types, and that means I do two imports per Podcast. I MAY have been able to script it all into one, but again, simplicity!
Channel Data
I do one import that pulls in Channel data, which is the meta data for the feed. In this case it’s our Podcast information. Here’s what that process looks like:
Channel data doesn’t get re-imported on a schedule. It very rarely changes, and if it does I can simply click the Run Import button and it takes care of it.
Episode Data
Importing episode data is pretty easy also, even with a few quirks. One thing I had to do was find the meta key names for Page Links To and set them to the right values. One is simply a boolean, always set to yes. The other is the URL of the episode (not the mp3).
There are two difficult bits with episodes. One is that images can be a little odd. Not every feed has images for episodes. This isn’t too hard to take care of, I chose to grab the one for the entire feed and simply use it for every episode. Harder is the fact that some feeds use Apple tags and some use Google tags. WP All Import handles both, but when I’m setting up the import I need to see what the feed has, and make adjustments.
The other difficult bit deals with categories. I’m mapping episode categories to WordPress tags, and WP All Import handles this just fine. The trouble is that only 20% of all feeds have any categories at all for episodes. This makes it REALLY hard to sort Episodes by tag. Additionally, those 20% are split about in half between Apple and Google format. I’m still importing them, but honestly not enough have any at all to make tag searching anything like a comprehensive search. There’ll be a blog post about this.
Here’s a video of setting up an episode import.
Automation
Importing
A big part of this entire project is that it needs to run itself. There’s no way I’m logging in every day and clicking through 70 podcast buttons to run their importer. WP All Import has a bunch of options for this, and I actually anticipated using an external cron to run the importers but as it turns out that’s pretty complicated. I won’t get into it, but in the end I decided to pay for a service from WP All Import to manage the automation. It’s relatively inexpensive, and as I’ve mentioned before, simplicity rates really high in this project. So far it’s worked perfectly.
Tweeting
This one gave me the most trouble. There are a number of plugins that do ALMOST what I wanted, which is to make a tweet on publish, in a specific format. Most wouldn’t let me format them. Then I remembered my good friend Chris Klosowski makes a plugin called Post Promoter Pro. Among other things, it’s very good at “post a tweet on publish in the format I declare programmatically”.
The problem was, it wasn’t working. As it turns out, WP All Import creates and publishes a post before inserting the meta data. So the PMPro meta data went in AFTER the post was published, and so didn’t fire a tweet.
I pinged support for WP All Import and got a great solution. They sent me some code, which I could have put into a plugin, but WP All Import has a field in its setting to accept PHP code. Here’s what we put in there:
add_action( 'pmxi_saved_post', 'my_publish_post', 10, 3 );
function my_publish_post( $id, $xml, $is_update ) {
if ( $is_update ) return; // Don't tweet updated posts, only tweet new ones.
// Grab the post and make sure Post Promoter Pro's function is available.
$post_obj = get_post( $id );
if ( $post_obj && function_exists( 'ppp_tw_share_on_publish' ) ) {
// Populate the $_POST variable with data that Post Promoter Pro needs.
$_POST['_ppp_share_on_publish'] = get_post_meta( $id, '_ppp_share_on_publish', true );
$_POST['_ppp_share_on_publish_text'] = get_post_meta( $id, '_ppp_share_on_publish_text', true );
$_POST['_ppp_share_on_publish_include_image'] = get_post_meta( $id, '_ppp_share_on_publish_include_image', true );
// Tweet the post.
ppp_tw_share_on_publish( 'publish', 'new', $post_obj );
}
}
Summary
In summary, we leaned heavily on WP All Import to import podcasts as Categories, and Episodes as Posts. We used a plugin from Mark Jaquith to make Episodes link to the source, and we pay for a service to make the whole thing run automatically on certain days. Unless something breaks, this site will continue to grow itself until people stop podcasting.
I’d like to once again extend a thanks to the folks at WP All Import. Not only did they offer a free license for the entire HeroPress Network, they’ve been really great about supporting it, both helping me understand how it works as well as even writing custom code to help me get done what I needed to. Thanks folks!