How far I’ll go to make an RSS feed of your website

So you’ve decided to publish timely content on the Internet but your website does not have an RSS feed.

Or even worse, you have played some role in designing or building a tool or service that other people use to publish timely content on the Internet, and you unforgivably allowed it to ship without support for RSS feeds.

And you probably just assume that a few remaining RSS feed nerds out there are just quietly backing away in defeat. “They’ll just have to manually visit the site every day if they want updates,” you rationalize. “They can download our app and get push notifications instead,” you somehow say with a straight face.

No.

I don’t know who you are. I don’t know what you want. If you are looking for clicks, I can tell you I don’t have any clicks left to give.

Screen capture from the movie Taken where Liam Neeson's character gives a version of the "special skills" speech.

But what I do have are a very particular set of skills, skills I have acquired over a very long career. Skills that make me a nightmare for people like you.

If you publish your content in an RSS feed now, that’ll be the end of it. I will not look for you, I will not pursue you, I will simply subscribe to your feed.

But if you don’t, I will look for you, I will find you, and I will make an RSS feed of your website.

And here’s how I’ll do it.

(Psst, this last bit is a playful reference to the movie Taken, I am a nice person IRL and don’t actually tend to threaten or intimidate people around their technology choices. But my impersonation of a militant RSS advocate continues herein…)

Jump straight to a scenario and solution:

You’re just hiding your RSS feed

Okay, heh, simple misunderstanding, you actually DO have an RSS feed but you do not mention or link to it anywhere obvious on your home page or content index. You know what, you’re using a <link rel="alternate" type="application/rss+xml" href="..." /> tag, so good for you.

I found it, I’m subscribing, we’re moving on, but for crying out loud please also throw a link in the footer or something to help the next person out.

Further reading and resources

You’re hiding your RSS feed URL structure in documentation or forum responses

Oh so you don’t have an advertised RSS feed or <link> tag but you do still support RSS feeds after all, it’s just that you tucked that fact away in some kind of obscure knowledgebase article or a customer support rep’s response in a community forum?

Fine. I would like you to feel a bit bad about this and I briefly considered writing a strongly worded email to that end, but instead I’m using the documented syntax to grab the link, subscribe and move on. Just…do better.

Further reading and resources

You forgot that feeds are special URLs

You decided to put your RSS feed behind a Cloudflare access check. Or you aggressively cached it. Or your web application firewall is blocking repeated accesses to the URL from strange network locations and user agents, which is exactly the profile of feed readers.

I understand, we can’t always remember to give every URL special attention.

But I will email your site administrator or tech support team about it, and you will open a low-priority ticket or send my request to your junk folder, and I will keep following up until you treat your RSS feed URLs with the respect they deserve.

Further reading and resources

Your feed is malformed

You somehow ended up with some terrible, custom, non-standard implementation of RSS feed publishing and you forgot to handle character encoding or media formats or timestamps or weird HTML tags or some other thing that every other established XML parsing and publishing library has long since dealt with and now you have an RSS feed that does not validate.

Guess what, RSS feeds that don’t validate might as well be links to a Rick Astley video on YouTube given how picky most RSS feed readers are. It’s just not going to work.

But I will work with that. I will write a cron job that downloads your feed every hour and republishes it with fixes needed to make it valid.

#!/bin/sh

# Define variables for URLs, file paths, and the fix to apply
RSS_FEED_URL="https://example.com/rss.xml"
TEMP_FILE="/tmp/rss_feed.broken"
OUTPUT_FILE="/path/to/feeds/rss_feed.xml"
SEARCH_STRING="Parks & Recreation"
REPLACEMENT_STRING="Parks &amp; Recreation"

# Download the RSS feed
curl -s -L -o "$TEMP_FILE" "$RSS_FEED_URL"

# Fix invalid characters or strings in the RSS feed
perl -pi -e "s/$SEARCH_STRING/$REPLACEMENT_STRING/gi;" "$TEMP_FILE"

# Move the fixed file to the desired output location
cp "$TEMP_FILE" "$OUTPUT_FILE"

# Clean up the temporary file
rm "$TEMP_FILE"

If I want to embarrass you in front of your friends I will announce the new, valid feed URL publicly and let others use it too. But really, you should just fix your feed and use a standard library.

Further reading and resources

You have no feed but your content is structured

You programmatically publish your timely content in a structured format from a structured source but offer no RSS feed? What a terrible decision you’ve made for the distribution of your information, for your readers, for your stakeholders, and for the whole world.

But ’tis no matter, I’ll have an RSS feed of your site up and running shortly using a DOM crawler (e.g. Symfony’s DomCralwer component in PHP) that can navigate HTML and XML documents using XPath, HTML tags or CSS selectors to get me what I want.

Your stuff’s generated using an HTML/CSS template with a loop? Great? Or it’s even just in a plain HTML table? Let’s do this.

$nodes = $crawler->filter('.post_content table tr');

foreach ($nodes as $node) {
	$tr = new Crawler($node);
	$date         = $tr->filter('td')->eq(0)->text();
	$title_column = $tr->filter('td')->eq(1);
	$title        = $title_column->text();
	$links        = $title_column->filter('a');
	if ($links->count()) {
		$url = $this->resolveUrl($source, $links->first()->attr('href'));
	} else {
		continue;
	}
	$items[] = array(
		'pubDate' => Carbon::createFromFormat('!m-d-Y', $date),
		'title' => ucwords(strtolower($title)),
		'url' => $url,
	);
}

My software will programmatically visit your site every hour to see what it finds and publish an RSS feed of the latest.

Further reading and resources

You use asynchronous requests to fetch JSON and render your content

You were right there at the finish line, buddy, and you choked.

You had everything you need — structured data storage, knowledge of how to convey data from one place to another in a structured format, probably some API endpoints that already serve up exactly what you need — all so you can dynamically render your webpage with a fancy pagination system or a super fast search/filter feature.

And yet, still, you somehow left out publishing your content in one of the most commonly used structured data formats on the web. (Psst, that’s RSS that I’m talking about. Pay attention, this whole thing is an RSS thing!)

So I will come for your asynchronous requests. I will use my browser’s dev tools to inspect the network requests made when you render your search results, and I will pick apart the GET or POST query issued and the headers and data that are passed with it, and I will strip out the unnecessary stuff so that we have the simplest query possible.

If it’s GraphQL, I will still wade through that nonsense. If it uses a referring URL check as some kind of weak authentication, I will emulate that. If you set an obscurely named cookie on first page visit that is required to get the content on page visit two, be assured that I will find and eat and regurgitate that cookie.

And then I will set up a regularly running script to ingest the JSON response and create a corresponding RSS feed that you could have been providing all along. I can do this all day long.

public function generateRssItems(Source $source): RssItemCollection
{
	$response = HTTP::withUserAgent($userAgent)
		->get($source->source_url);
	$this->searchResultsToArray($response->json(), $source);

	return RssItemCollection::make($this->feedItems);
}

private function searchResultsToArray(array $results, Source $source): void
{
	if (! empty($results['combined_feeds'])) {
		foreach ($results['combined_feeds'] as $result) {
			$this->feedItems[] = array(
				'pubDate' => $this->getPubDate($result),
				'title' => $this->getTitle($result),
				'url' => $this->getArticleUrl($result, $source),
				'description' => $this->getArticleContent($result),
			);
		}
		return;
	}
}
Further reading and resources

You block requests from hosted servers

Even though I build my content fetching tools to be good citizens on the web — at most checking once per hour, exponential backoff if there are problems, etc. — you may still decide to block random web requests for random reasons. Maybe it’s the number of queries to a certain URL, maybe it’s the user agent, or maybe it’s because they’re coming from the IP address of a VPS in a data center that you assume is attacking you.

An image from Taylor Swift's "Look What You Made Me Do" music video

Fine. Whatever. I used to spend time trying to work around individual fingerprinting and blocking mechanisms, but I got tired of that. So now I pay about $25/year for access to rotating residential IP addresses to serve as proxies for my requests.

I don’t like supporting these providers but when I come to make an RSS feed of your content, it will look like it’s from random Jane Doe’s home computer in some Virginia suburb instead of a VPS in a data center.

Look what you made me do.

Further reading and resources

You’re super in to email newsletters

You don’t put timely new content on your website, but you sure do love your fancy email newsletters with the timely updates your audience is looking for. You like them so much that you favor them over every other possible content distribution channel, including a website with an RSS feed.

That’s okay. Fortunately, many email newsletter platforms have figured out that offering a web-based archive of past newsletter campaigns is a valuable service they can provide to, um, people like you, and many of those have figured out that including an RSS feed of said campaigns is worthwhile too.

Mailchimp puts a link to right there in the header of their web-based view of an individual campaign send unless (boo) a list owner has turned off public archives, which you should seriously NOT do unless your list really needs to be private/subscribers only.

A screenshot of a Safari browser window visiting a Mailchimp email campaign archive, which includes a header bar with buttons "Subscribe", "Past Issues", "Translate" and "RSS".
Further reading and resources

Your email newsletter has no RSS feed

You’re using an email newsletter platform that doesn’t have RSS feeds built in?

Please, allow me to make one for you. I will subscribe to your email newsletter with a special address that routes messages to a WordPress site I set up just for the purpose of creating public posts with your newsletter content. Once that’s done, the RSS part is easy given that WordPress has that support built right in.

I’ve detailed the technical pieces of that particular puzzle in a separate post:

Your mobile app is where you put all the good stuff

No RSS feed. No email newsletter. The timely content updates are locked up in your mobile app where the developers involved have absolutely no incentive to provide alternative channels you didn’t ask for.

No hope? This is probably where a reasonable person would say “nah, I’m out.” Me? I’m still here.

The nice thing is that if a mobile app is reasonably well designed and supports any kind of information feed, push notifications, etc. then it almost has to have some kind of API driving that. Sometimes the APIs are gloriously rich in the information they make available, but of course usually also completely undocumented and private.

I’ve found Proxyman to be an indispensable tool for what I need to do next:

  1. Intercept all web traffic from my mobile device and route it through my laptop
  2. Install a new certificate authority SSL certificate on my mobile device that tells it to trust my laptop as a source of truth when verifying the validity of SSL certificates for certain domains.
  3. Start making requests from your mobile app that loads the information I want to be in an RSS feed.
  4. Review the HTTP traffic being proxied through my laptop and look for a request that has a good chance of being what I want.
  5. Tell my laptop to intercept not only the traffic meta data but the secure SSL-encrypted traffic to the URL I care about.
  6. Inspect the request and response payload of the HTTPS request and learn what I need to know about how the API powering that mobile app feed operates.

It’s an amazing and beautiful process, and if it’s wrong I don’t want to be right.

If too many mobile app developers get wind of this kind of thing happening, they’ll start using SSL certificate pinning more, which builds in to the app the list of certificates/certificate authorities that can be trusted, removing the ability to intercept by installing a new one. So, you know, shhh. (Joking aside, secure all the things, use certificate pinning…but then also publish an RSS feed!)

Once I have your mobile app’s API information, it’s back to the mode of parsing a JSON response noted above, and generating my own RSS feed from it.

Further reading and resources

Escalations

There are other escalations possible, of course. Taking unstructured data and feeding it to an LLM to generate structured data. Recording and replaying all the keystrokes, mouse moves and clicks of a web browsing session. Convincing an unsuspecting IT department worker to build a special export process without really mentioning it to anyone. Scraping data from social media platforms.

I’ve done it all, none of it is fun, and none of it should be necessary.

Publish your info in a structured, standard way.

What do I do with all of these feeds?

Thanks for asking. I own a newspaper. We publish in print and online. Our job is to be aware of what’s happening in the community so we can distill what’s most important and useful in to reporting that benefits our readers. We have a very small staff tasked with keeping up with a lot of information. Aggregating at least some of it through RSS feeds has made a huge difference in our work.

Oh, you meant at the technical level? I have one Laravel app that is just a bunch of custom classes doing the stuff above and outputting standard RSS feeds. I have another Laravel app that aggregates all of the sources of community information I can find, including my custom feeds, in to a useful, free website and a corresponding useful, free daily email summary. I also read a lot of it in my own feed reader as a part of my personal news and information consumption habits.

Related writings

I’ve written a lot of stuff elsewhere about how to unlock information from one online source or another, and the general concepts of working on a more open, interoperable, standards-based web. If you enjoyed the above, you might like these:

Updated March 3, 2025 at 10 a.m. Eastern to note: Hacker News discussion

WooCommerce Subscriptions API start_date bug workaround

The WooCommerce Subscriptions plugin has a bug where if you use its REST API endpoints to make updates to a subscription, it will almost always reset the start date of the subscription you are updating to the current date and time.

I reported the bug to the WooCommerce folks in November 2022 and I believe it has a GitHub issue filed. When I checked in about it recently I was told it’s a low priority to fix. I consider it somewhat serious for my purposes — unexpectedly overwriting/losing key data about a record that’s used in calculating user-facing financial transactions —  so I’m documenting it here for others who might encounter it, along with a possible workaround.

The bug is in the plugin file includes/api/class-wc-rest-subscriptions-controller.php that processes the incoming API request. In particular, in the function prepare_object_for_database() there’s this code:

// If the start date is not set in the request, set its default to now.
if ( ! isset( $request['start_date'] ) ) {
	$request['start_date'] = gmdate( 'Y-m-d H:i:s' );
}

All of the valid dates contained in the $request array are subsequently copied into an array called $dates. Later in the same function, there’s this code:

if ( ! empty( $dates ) ) {
	...
	try {
		$subscription->update_dates( $dates );
	} catch ( Exception $e ) {
		...
	}
}

The implication is that for completely unrelated API requests to change something like, say, a meta field value or the subscription’s status, the date validation and update logic will be run. And because the start date value is overridden to be the current date, it means that any API request to update a completely unrelated field is going to unintentionally reset the start date of the subscription.

Nothing about the documentation at https://woocommerce.github.io/subscriptions-rest-api-docs/?php#update-a-subscription indicates that a start_date value is required in the API requests. In fact, the example given in those docs where the status of a subscription is updated would trigger this bug.

I’ve even noticed this bug surfacing even in regular wp-admin operations involving WooCommerce Subscriptions, as I think some of the logic used to do something like put a subscription on hold or cancel it from within the admin interface is calling the same internal functions.

My workaround for this bug, at least on the API client side, introduces an extra API request, and so is less than ideal for any kind of production or long-term use.

For every API request I make to the Subscriptions API update endpoint, if I’m not explicitly setting/changing the start_date field in my request, I first fetch the existing subscription record and then set the start_date field in my request to the current value.

if ('subscription' === $recordType && empty($updateValues['start_date'])) {
      $current_values = $wooApiFacade::find($recordId);
      $updateValues['start_date'] = Carbon::parse($current_values['start_date_gmt'])->format('Y-m-d\ H:i:s');
}

Hopefully they’ll fix this issue sooner rather than later so that users of the plugin don’t unexpectedly see subscription start dates overwritten.

Tools and tech we’re using to publish a print, online newspaper

Wow, it’s been over a month since I took ownership of a print and online newspaper here in my community. There’s a lot to say about that experience and what I’ve learned so far. In this post, I’ll be focused on the tools and technology we’re using  to operate this business. Some of these were in place before I came in, some are new in the last month.

I’m sharing this because (a) I generally enjoy the topic of if/how tools can make life and business easier, and (b) I hope it could be useful to someone else publishing a newspaper or building a media organization.

Print Layout and Design

It’s Adobe Creative Cloud all the way, for better or worse. InDesign for newspaper layout, Photoshop for image editing. Given the way our staff is set up and our weekly newspaper production process works, almost everyone touches the newspaper pages at some point or another, so the monthly license costs to cover all of that is somewhat ouch. If there were a viable alternative to InDesign, we’d probably switch to it.

Issue and Story Budget Planning

We’re using an Airtable base that helps us record story ideas and plan for our upcoming issues by tracking what articles are going to go where, what state their in, and all the associated data that goes with them such as photos, source info, internal notes, etc. It’s pretty great and the real-time collaboration that it makes possible is hard to beat. I think down the road we may move toward a custom Laravel-powered solution that allows for tighter integration of all of our business operations, but that’s a ways off.

Phone System

We’re using a self-hosted FreePBX (Asterisk) installation with the Sysadmin Pro and EndPoint Manager paid add-on modules. Digital Ocean had a 1-click installer on their marketplace that made it super fast to get going. We’re using VOIP.ms for our trunk lines and they made DID porting in very easy.

Having used Asterisk in a previous business I was already familiar with its architecture and features, but FreePBX meant I could configure everything via web interface instead of editing dialplan files – amazing. We have extensions, queues, interactive voice menus, voicemail speech to text transcription (using this tool) and more, and it sets up a nice foundation for future integration with other tools like our CRM data.

We’re using Yealink T31P and T33G VOIP phones and so far Counterpath’s Bria Mobile has been the most compatible/feature complete softphone for iOS that I’ve found.

Continue reading Tools and tech we’re using to publish a print, online newspaper

CrowdTangle API SDK in PHP

After not finding anyone else who has done so, I created a minimal PHP implementation of the CrowdTangle API, which I needed anyway for a project I’m working on:

Example usage syntax:

$client = new ChrisHardie\CrowdtangleApi\Client($accessToken);

// get lists
$client->getLists();

// get accounts in a list
$client->getAccountsForList($listId);

// get posts
$client->getPosts([
    'accounts' => '12345678',
    'startDate' => '2022-03-01',
]);

// get a single post
$client->getPost($postId);

I’m sure there’s plenty to improve but I hope it’s helpful to anyone working with CrowdTangle in PHP.

Ending support for some little-used WordPress plugins

Today I am making these three little-used WordPress plugins that I created inactive, in the sense that I will no longer update them, test them against newer versions of WordPress, or provide support for them. If after a time no one else wants to take over maintaining them, they will be closed.

Each plugin has around 10 or fewer active installations and minimal or no user reviews. Given that it takes time to test each plugin I maintain for upcoming WordPress releases and PHP changes, and to support any inquiries or feature requests they may receive, I have to balance the requirements of continuing that work against the value each plugin offers. In these cases, I’d like to free up that maintenance time for other things.

I will leave the GitHub repos public indefinitely in case anyone wants to fork them or take them over.

This change does not affect the other WordPress plugins I maintain.

If you’re interested in taking over responsibility for one or more of these plugins, please contact me.

Thanks to everyone who tried them out and gave feedback along the way.

Creating a personalized, private RSS feed for users in Laravel

In building WP Lookout I had the need to create user-specific, private RSS feeds as a way for users to get updates and information specific to their account. Here’s how I did it.

I started with the laravel-feed package from Spatie, which allows you to easily generate RSS feeds, either from a model’s records or from some other method that you define. I found that someone has proposed a feature and submitted a PR to add support for signed routes, which is Laravel’s way of adding a hash key to a given URL so that it can’t be accessed if it’s been modified, perfect for something like a user-specific RSS feed. But the Spatie folks decided not to include the feature, so I had to figure out how to do it on my own.

First, I added the package to my project and published the related config file:

$ composer require spatie/laravel-feed
$ php artisan vendor:publish \
    --provider="Spatie\Feed\FeedServiceProvider" \
    --tag="config"

Then, I added a user-specific feed identifier to the User model:

$ php artisan make:migration add_feed_id_to_users_table --table=users

The migration looked like this in part:

public function up()
{
    Schema::table('users', function (Blueprint $table) {
        //
        $table->string('feed_id');
    });

    DB::statement('UPDATE users SET feed_id = ...');
}

The UPDATE statement there is used to set the initial value of the feed identifier for existing users. An alternative would have been to make the field nullable and then set it elsewhere. For new users, I set the feed identifier as users are created, via the boot() method in the User model:

Continue reading Creating a personalized, private RSS feed for users in Laravel

Remembering DB_Browser

Book cover for the O'Reilly book Oracle & Open Source

Today on Twitter I was remembering that one time when an @OReillyMedia software book said that some code I’d written was “definitely worth a look.”

It started as a project for an internship at a local ISP, where they had a need to quickly browse, search or update a certain database using a web browser. I threw it together in a few days using Perl and the data dictionary info offered by PostgreSQL (and later MySQL and Oracle)

I called it DB_Browser, and it was basically what PhpMyAdmin became, but abstracted out enough to use with any of those three database systems. (Solid database abstraction layers used to be a real thing!)

It was almost certainly full of security vulnerabilities, relying entirely on the use of .htaccess rules to prevent total chaos. The UI was as ugly as sin, the code not much better. But it worked, and I believe it was used in some form until the ISP was acquired years later.

I took the time to package it up and publish it. I think I put it on “Freshmeat,” a site that was just a feed of newly released software, back when you could even try to track such things. It started getting downloads and people started using it.

Continue reading Remembering DB_Browser

My first CiviCRM extension: Slack notifications

I’ve started using CiviCRM for an organization I’m working with. If you’re not familiar with it, it’s an open source constituent relationship management tool for not-for-profits, political organizations and other entities that might have a need to manage relationships with supporters, volunteers, donors, members and so on.

I’ll try to reflect more soon on the process of getting started with CiviCRM; it was a little rocky. But to get myself familiar with CiviCRM’s inner workings, I decided to write a simple extension that would allow the organization to get Slack notifications when certain objects (contacts, contributions, pledges) were created or updated. I couldn’t find any similar existing extensions in their directory or in my searching.

Here’s the settings screen:

(with apologies for the weird extra bullets, can’t seem to make them go away yet) and here’s what a resulting Slack message might look like:

The extension is available on GitHub. I’m sure there are many ways to improve it, so issues and pull requests are welcome.

Update on April 20, 2021: the extension now provides a more flexible CiviRules action for Slack notifications instead of a standalone notification method, so some of the above information is outdated.

Life so far with a 2020 13″ MacBook Pro

I recently switched from using a Mid-2015 15″ MacBook Pro to a 2020 13″ MacBook Pro with Apple’s Silicon M1 chip. It was a Big Deal in the sense that my computer is a primary daily tool in my personal and professional life. So much of my work, my creativity and the management of my life is handled through this one device, so it’s always a little scary to make a change. (I actually could have been happy continuing with my previous laptop if its battery hadn’t been expanding, causing the entire computer to bulge in weird and alarming ways.)

Here are a couple of things I observed in making this transition and in using the MacBook every day since:

Apples to Apples

My previous MacBook Pro was pretty high end (2.8 GHz Intel Core i7 Processor, 16 GB 1600 MHz DDR3 RAM, Intel Iris Pro 1536 MB Graphics, 1TB HD) and very fast for the things I used it for. These included software development, hosting multiple software development environments, audio and video editing and rendering, graphic design and photo editing, and LOTS of browse tabs. It took everything I could throw at it and I never felt slowed down by the computer itself.

So the idea of “downgrading” to a smaller screen (13″ instead of 15″), fewer ports, and the same amount of RAM but 5 years later was a bit nerve-wracking.  Conventional wisdom for a long time was that 13″ MacBook Pros were fine for some kinds of advanced computing but that the 15″ model was always the best option for the kinds of things I use it for. Maybe this was just me naively buying into Apple’s marketing, but it seemed to be supported by testimonials from colleagues over the years, and was a strong narrative in my head nonetheless.

But I’d heard and read that the Apple Silicon M1 chip was a game-changer, and that any comparison between Intel and the newer processors was not really valid. And after seeing enough stories from real users where they said the new chip plus 16GB of RAM was even faster running some of the same software I do, even with Rosetta 2 translation turned on, I was sold.

Continue reading Life so far with a 2020 13″ MacBook Pro

Testing my WordPress plugins in preparation for WordPress core releases

A couple times per year, WordPress plugin authors and owners get an email like this one:

WordPress 5.6 is imminent! Are your plugins ready?

You’re receiving this email because you have commit access or ownership of existing, open plugins hosted on WordPress.org. The next release of WordPress, 5.6, is scheduled for 08 December 2020.

We would like you to take this time to review your existing plugins and ensure their ongoing compatibility with WordPress. Once you’ve done so, you can update the readme “Tested up to:” value to 5.6. This information provides peace of mind to users and helps encourage them to update WordPress.

Here are the current “Tested up to:” values for each of your plugins:

The message goes on from there to list the plugins I’m responsible for and some notes and details about what’s new in the upcoming WordPress release.

In case it’s not clear, this is an important moment because the authors of tens of thousands of WordPress plugins are being asked to help ensure that when the many millions of WordPress sites out there upgrade to the upcoming release, that those sites continue to look and function as expected by their users. It’s an impressive example of how the WordPress developer community works together in the background to help sustain and grow the larger WordPress ecosystem.

For authors of widely used plugins, by the time this email goes out their plugin may already be fully ready, especially if they’ve been following or maybe even contributing to the development of the new WordPress core release. Some plugin authors rightly have an extensive automated test suite in place to confirm that every part of their plugin’s functionality works against the latest beta or release candidate version of the new version before it comes out.

Authors and maintainers of smaller plugins (like me) may not have the same infrastructure set up, and instead need to perform some manual testing of our plugins to ensure they’re ready.

So, here are the steps I follow every major release cycle to make sure my plugins have been tested and are ready for the new version.

Continue reading Testing my WordPress plugins in preparation for WordPress core releases