Auto post from RSS feeds to WordPress: a plugin you can build today
· 4 min read
A lot of WordPress sites pull from RSS feeds: a company blog that republishes its own posts from another platform, a local news page that aggregates press releases, a deals site that imports items from a partner feed. The need is the same in each case. New items appear in a feed, new posts should appear in WordPress.
FeedWordPress served this need for over a decade but has not seen significant updates in some time. Compatibility with current PHP and WordPress versions varies. WP RSS Aggregator covers the same job in a paid tier that most single-feed sites will not want to buy. The gap between a largely unmaintained free plugin and a subscription aggregator is where this build sits.
A plugin that handles one feed is not complicated. It needs a settings page with a URL and a few options, a WP-Cron job that runs on a schedule, and an import loop that skips anything already in the database. That is around sixty lines of functional code and a small settings form.
What one build gives you
- An admin settings page to configure the feed URL, schedule, post category, and post status
- A WP-Cron job that fetches the feed on the chosen schedule and creates WordPress posts from new items
- Duplicate detection using each feed item's GUID stored as post meta so the same item is never imported twice
- The source URL stored as post meta on every imported post
What it does not do
- Multiple feed sources from one admin screen: one feed URL per plugin instance
- Full-article content retrieval: the plugin imports only what the feed itself provides, not the linked page
- Featured image import from feed enclosures or linked article thumbnails
- A manual import trigger from the settings screen: imports run only on the cron schedule
- Author attribution from feed item author fields
FeedWordPress does these. This covers the part most sites use.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called RSS Auto Poster. Plugin Name: RSS Auto Poster. Description: Creates WordPress posts from an RSS or Atom feed on a schedule using WP-Cron. Single PHP file. No dependencies beyond WordPress core. Register a settings page under Settings > RSS Auto Poster using add_options_page. Register settings with register_setting in a group named rss_apt_settings. Fields: rss_apt_feed_url (text), rss_apt_post_status (select: draft or publish, default draft), rss_apt_category (integer, default 0 for uncategorised), rss_apt_frequency (select: hourly, twicedaily, daily; default daily), rss_apt_max_items (integer, default 10). Sanitize: feed URL with esc_url_raw, post status with sanitize_key restricted to draft or publish, category with absint, frequency with sanitize_key restricted to hourly/twicedaily/daily, max_items with absint clamped to 1-50. Settings page: a form using settings_fields and do_settings_sections. Add one settings section named rss_apt_main to the page slug rss-auto-poster. Add fields for: Feed URL (text input), Post Status (select: Draft / Published), Category (wp_dropdown_categories with show_option_all All/Uncategorised and echo false, selected on the saved value), Frequency (select: Hourly / Twice Daily / Daily), Max Items Per Run (number input min 1 max 50). Use add_settings_field for each. Wrap the form in a div with class wrap, an h1 heading, and a submit button via submit_button. Cron management: on register_activation_hook, unschedule any existing rss_apt_run event then call wp_schedule_event(time(), get_option('rss_apt_frequency','daily'), 'rss_apt_run'). On register_deactivation_hook, call wp_clear_scheduled_hook('rss_apt_run'). Hook a function to update_option_rss_apt_frequency that calls wp_clear_scheduled_hook then wp_schedule_event with the new frequency value. Import function hooked to rss_apt_run: get all options. If rss_apt_feed_url is empty, return. Call fetch_feed($url). If is_wp_error, return. $max = absint(get_option('rss_apt_max_items',10)); $items = $feed->get_items(0,$max). For each $item: $guid = $item->get_id(). Run get_posts with post_type post, post_status any, meta_key _rss_apt_guid, meta_value $guid, posts_per_page 1, fields ids. If non-empty, continue. Otherwise: $title = sanitize_text_field($item->get_title()), $content = wp_kses_post($item->get_content()), $date = $item->get_date('Y-m-d H:i:s') with fallback to current_time('mysql'), $link = esc_url_raw($item->get_permalink()). Call wp_insert_post with post_title, post_content, post_status from option, post_date, post_category as array containing absint of rss_apt_category option. If result is a positive integer, call update_post_meta twice: _rss_apt_guid to $guid and _rss_apt_source_url to $link. No inline JS. No AJAX. No external assets. No database tables. No block registration.
What the plugin does
An admin settings page under Settings > RSS Auto Poster holds five things: the feed URL, the import schedule, the category to assign imported posts, the post status, and how many items to pull per run. Post status defaults to draft so every item sits in a queue for review before going live.
On the chosen schedule, WP-Cron fetches the feed using WordPress's built-in SimplePie library, which handles both RSS 2.0 and Atom formats. It loops through the returned items and checks each one against a stored GUID. If a post already exists with that GUID in its meta, the item is skipped. New items become WordPress posts with the title and content from the feed.
- Feed URL: any RSS 2.0 or Atom feed
- Import schedule: hourly, twice daily, or daily
- Post status: draft (default) or published
- Category assignment: any existing category, or uncategorised
- Max items per run: 1 to 50, default 10
How imported posts appear
Each post inherits the title, content, and publication date from the feed item. The content is whatever the feed provides: some feeds include full article HTML, others only a summary. The plugin imports what the feed gives it and nothing more.
The post date is set from the feed item's own timestamp, so posts from yesterday sit in your archive as yesterday, not today. Each imported post also gets the original source URL stored as post meta, so you always know where it came from.
Draft status is the default for a reason. On first run you can review a small batch, confirm the content is arriving cleanly, and switch to published status once you are satisfied. For a feed you control entirely, such as your own RSS from a newsletter platform, you can set it to published from the start.
Duplicate detection and schedule changes
The duplicate check uses the feed item's GUID, which is the permanent identifier RSS and Atom feeds attach to each entry. The plugin stores this GUID as post meta after each import. An item is only ever imported once, even if it appears in ten consecutive feed fetches.
When you update the Frequency setting, the plugin automatically unschedules the existing cron event and registers a new one at the new interval. You do not need to deactivate and reactivate the plugin after changing the schedule.
When a dedicated aggregation plugin makes more sense
This plugin handles one feed. If you need to pull from multiple sources, map different feeds to different categories, and manage everything from one list screen, WP RSS Aggregator is built for exactly that: it is well-maintained and the price reflects a different scope.
If the feed you want to import only provides a summary, and you want the full article text, you need a plugin that fetches the linked page and extracts the body content. This plugin does not do that.
For a site running one editorial feed, a partner import, or a republishing workflow you control, the single-feed plugin is the simpler tool. You own the code, there is no subscription, and extending it is one follow-up message away.
Questions
- How do I avoid importing a large backlog of old items on first run?
Set Max Items Per Run to a small number like 3 before activating the plugin and let the first cron run import a manageable batch. Review those drafts, then raise the limit. The GUID check ensures nothing is imported twice, so increasing the limit afterward is safe.
- Does this work with Atom feeds, not just RSS?
Yes. WordPress's built-in SimplePie library handles both RSS 2.0 and Atom formats, so the feed URL can point to either.
- What happens if the feed URL is temporarily unavailable?
The import function returns early without creating any posts. On the next scheduled cron run it will try again. No error is thrown and no data is lost.
- Can I change the import frequency without losing previously imported posts?
Yes. Updating the Frequency setting unschedules the old cron event and registers a new one at the new interval. Previously imported posts are not affected, and the GUID check continues to prevent duplicates.
The prompt
Loads into the composer so you can edit it first. Nothing is built, and nothing is charged, until you send it.
Build a WordPress plugin called RSS Auto Poster. Plugin Name: RSS Auto Poster. Description: Creates WordPress posts from an RSS or Atom feed on a schedule using WP-Cron. Single PHP file. No dependencies beyond WordPress core. Register a settings page under Settings > RSS Auto Poster using add_options_page. Register settings with register_setting in a group named rss_apt_settings. Fields: rss_apt_feed_url (text), rss_apt_post_status (select: draft or publish, default draft), rss_apt_category (integer, default 0 for uncategorised), rss_apt_frequency (select: hourly, twicedaily, daily; default daily), rss_apt_max_items (integer, default 10). Sanitize: feed URL with esc_url_raw, post status with sanitize_key restricted to draft or publish, category with absint, frequency with sanitize_key restricted to hourly/twicedaily/daily, max_items with absint clamped to 1-50. Settings page: a form using settings_fields and do_settings_sections. Add one settings section named rss_apt_main to the page slug rss-auto-poster. Add fields for: Feed URL (text input), Post Status (select: Draft / Published), Category (wp_dropdown_categories with show_option_all All/Uncategorised and echo false, selected on the saved value), Frequency (select: Hourly / Twice Daily / Daily), Max Items Per Run (number input min 1 max 50). Use add_settings_field for each. Wrap the form in a div with class wrap, an h1 heading, and a submit button via submit_button. Cron management: on register_activation_hook, unschedule any existing rss_apt_run event then call wp_schedule_event(time(), get_option('rss_apt_frequency','daily'), 'rss_apt_run'). On register_deactivation_hook, call wp_clear_scheduled_hook('rss_apt_run'). Hook a function to update_option_rss_apt_frequency that calls wp_clear_scheduled_hook then wp_schedule_event with the new frequency value. Import function hooked to rss_apt_run: get all options. If rss_apt_feed_url is empty, return. Call fetch_feed($url). If is_wp_error, return. $max = absint(get_option('rss_apt_max_items',10)); $items = $feed->get_items(0,$max). For each $item: $guid = $item->get_id(). Run get_posts with post_type post, post_status any, meta_key _rss_apt_guid, meta_value $guid, posts_per_page 1, fields ids. If non-empty, continue. Otherwise: $title = sanitize_text_field($item->get_title()), $content = wp_kses_post($item->get_content()), $date = $item->get_date('Y-m-d H:i:s') with fallback to current_time('mysql'), $link = esc_url_raw($item->get_permalink()). Call wp_insert_post with post_title, post_content, post_status from option, post_date, post_category as array containing absint of rss_apt_category option. If result is a positive integer, call update_post_meta twice: _rss_apt_guid to $guid and _rss_apt_source_url to $link. No inline JS. No AJAX. No external assets. No database tables. No block registration.
Steem