{"id":342510,"date":"2026-08-20T12:15:19","date_gmt":"2026-08-20T12:15:19","guid":{"rendered":"https:\/\/wordpress.org\/plugins\/the-wp-feeds\/"},"modified":"2026-09-17T10:57:35","modified_gmt":"2026-09-17T10:57:35","slug":"freshet-feeds","status":"publish","type":"plugin","link":"https:\/\/co.wordpress.org\/plugins\/freshet-feeds\/","author":15137713,"comment_status":"closed","ping_status":"closed","template":"","meta":{"version":"1.0.2","stable_tag":"1.0.2","tested":"7.1.1","requires":"6.5","requires_php":"8.2","requires_plugins":null,"header_name":"Freshet Feeds","header_author":"Freshet Studio","header_description":"Developer-first external feeds (LinkedIn first) with a shared item model and theme-overridable templates.","assets_banners_color":"1425ff","last_updated":"2026-09-17 10:57:35","external_support_url":"","external_repository_url":"","donate_link":"","header_plugin_uri":"https:\/\/freshet.studio\/docs","header_author_uri":"https:\/\/freshet.studio","rating":0,"author_block_rating":0,"active_installs":0,"downloads":149,"num_ratings":0,"support_threads":0,"support_threads_resolved":0,"author_block_count":0,"sections":["description","installation","faq","changelog"],"tags":{"1.0.1":{"tag":"1.0.1","author":"kristoffbertram","date":"2026-08-20 12:14:57","revision":3656796},"1.0.2":{"tag":"1.0.2","author":"kristoffbertram","date":"2026-09-17 10:57:35","revision":3700127}},"upgrade_notice":[],"ratings":[],"assets_icons":{"icon-128x128.png":{"filename":"icon-128x128.png","revision":3700127,"resolution":"128x128","location":"assets","locale":"","width":128,"height":128},"icon-256x256.png":{"filename":"icon-256x256.png","revision":3700127,"resolution":"256x256","location":"assets","locale":"","width":256,"height":256},"icon.svg":{"filename":"icon.svg","revision":3700127,"resolution":false,"location":"assets","locale":false}},"assets_banners":{"banner-1544x500.png":{"filename":"banner-1544x500.png","revision":3700127,"resolution":"1544x500","location":"assets","locale":"","width":1544,"height":500},"banner-772x250.png":{"filename":"banner-772x250.png","revision":3700127,"resolution":"772x250","location":"assets","locale":"","width":772,"height":250}},"assets_blueprints":{"blueprint.json":{"filename":"blueprint.json","revision":3700127,"resolution":false,"location":"assets","locale":"","contents":"{\"$schema\":\"https:\\\/\\\/playground.wordpress.net\\\/blueprint-schema.json\",\"description\":\"Freshet Feeds on a small site: one feed on the bundled Mock provider, cached and rendered on the front page through the Feed block in its grid and list layouts, landing on that page.\",\"landingPage\":\"\\\/\",\"preferredVersions\":{\"php\":\"8.3\",\"wp\":\"latest\"},\"features\":{\"networking\":true},\"steps\":[{\"step\":\"login\",\"username\":\"admin\"},{\"step\":\"defineWpConfigConsts\",\"consts\":{\"WP_ENVIRONMENT_TYPE\":\"local\"}},{\"step\":\"installPlugin\",\"pluginData\":{\"resource\":\"wordpress.org\\\/plugins\",\"slug\":\"freshet-feeds\"},\"options\":{\"activate\":true},\"progress\":{\"weight\":1,\"caption\":\"Installing Freshet Feeds\"}},{\"step\":\"setSiteOptions\",\"options\":{\"blogname\":\"Northwind Roastery\",\"blogdescription\":\"Small-batch coffee, and a feed of what we are up to\"}},{\"step\":\"runPHP\",\"code\":\"<?php\\n\\\/**\\n * Freshet Feeds - Playground preview fixture.\\n *\\n * A blank WordPress has no feeds and no page to put one on, so the front end\\n * would show none of what this plugin does. This seeds one feed on the bundled\\n * Mock provider (fixture posts, no credentials, no remote API), warms its cache,\\n * and puts the Feed block on a page in both layouts, so the preview lands on a\\n * rendered feed rather than on wp-admin.\\n *\\\/\\nrequire_once 'wordpress\\\/wp-load.php';\\n\\nuse FreshetFeeds\\\\Feed\\\\Feed;\\nuse FreshetFeeds\\\\Plugin;\\n\\nglobal $wp_rewrite;\\n\\n\\\/\\\/ The page is reached by name, and item permalinks read better with pretty URLs.\\n$wp_rewrite->set_permalink_structure('\\\/%postname%\\\/');\\n$wp_rewrite->flush_rules(false);\\n\\n$plugin = Plugin::instance();\\n$feeds  = $plugin->feeds();\\n\\n\\\/\\\/ One feed on the Mock provider. WP_ENVIRONMENT_TYPE is set to \\\"local\\\" by the\\n\\\/\\\/ step before this one: the provider refuses to fetch on a production site, and\\n\\\/\\\/ a Playground that does not say otherwise reports production.\\n$feed = $feeds->findBySlug('roastery-news') ?? $feeds->save(new Feed(\\n    id: 0,\\n    name: 'Roastery news',\\n    slug: 'roastery-news',\\n    providerId: 'mock',\\n    settings: [],\\n    count: 6,\\n    ttl: HOUR_IN_SECONDS,\\n    defaultLayout: 'grid',\\n));\\n\\n\\\/\\\/ Warm the cache now, so the landing page serves items instead of fetching\\n\\\/\\\/ them on its first view. Image localisation runs here too; where a download\\n\\\/\\\/ does not complete the item keeps its remote image URL and still renders.\\n$plugin->items($feed->slug);\\n\\n$content = implode(\\\"\\\\n\\\", [\\n    '<!-- wp:heading --><h2 class=\\\"wp-block-heading\\\">Latest from the roastery<\\\/h2><!-- \\\/wp:heading -->',\\n    '<!-- wp:paragraph --><p>The six most recent posts from the roastery&#8217;s feed, rendered on the server through the plugin&#8217;s own templates: no iframe, no script, real markup in the page. Every card below is the Feed block in its grid layout.<\\\/p><!-- \\\/wp:paragraph -->',\\n    sprintf('<!-- wp:freshet-feeds\\\/feed {\\\"feedId\\\":%d,\\\"layout\\\":\\\"grid\\\",\\\"count\\\":6} \\\/-->', $feed->id),\\n    '<!-- wp:heading --><h2 class=\\\"wp-block-heading\\\">The same feed as a list<\\\/h2><!-- \\\/wp:heading -->',\\n    '<!-- wp:paragraph --><p>One feed, another layout: the block picks the layout, the plugin&#8217;s <code>layout-list.php<\\\/code> renders it, and a theme can override either file. The feed itself is set up once under Feeds in wp-admin.<\\\/p><!-- \\\/wp:paragraph -->',\\n    sprintf('<!-- wp:freshet-feeds\\\/feed {\\\"feedId\\\":%d,\\\"layout\\\":\\\"list\\\",\\\"count\\\":3} \\\/-->', $feed->id),\\n]);\\n\\n$page_id = wp_insert_post([\\n    'post_type'    => 'page',\\n    'post_title'   => 'News',\\n    'post_name'    => 'news',\\n    'post_status'  => 'publish',\\n    'post_content' => $content,\\n], true);\\n\\n\\\/\\\/ Land on the feed, not on the blog index: the page is the front page.\\nif (!is_wp_error($page_id)) {\\n    update_option('show_on_front', 'page');\\n    update_option('page_on_front', (int) $page_id);\\n}\\n\",\"progress\":{\"weight\":2,\"caption\":\"Seeding a feed and a page to show it on\"}}]}"}},"all_blocks":{"freshet-feeds\/feed":{"$schema":"https:\/\/schemas.wp.org\/trunk\/block.json","apiVersion":3,"name":"freshet-feeds\/feed","title":"Feed","category":"widgets","icon":"rss","description":"Display an external feed (RSS, YouTube, Bluesky) using Freshet Feeds templates.","keywords":["feed","rss","social"],"textdomain":"freshet-feeds","attributes":{"feedId":{"type":"integer","default":0},"layout":{"type":"string","default":""},"count":{"type":"integer","default":0}},"supports":{"align":["wide","full"],"spacing":{"margin":true},"html":false},"editorScript":"file:.\/index.js","style":"file:.\/style-index.css","render":"file:.\/render.php"}},"tagged_versions":["1.0.1","1.0.2"],"block_files":[],"assets_screenshots":{"screenshot-1.png":{"filename":"screenshot-1.png","revision":3700127,"resolution":"1","location":"assets","locale":"","width":1280,"height":768},"screenshot-2.png":{"filename":"screenshot-2.png","revision":3700127,"resolution":"2","location":"assets","locale":"","width":1280,"height":768},"screenshot-3.png":{"filename":"screenshot-3.png","revision":3700127,"resolution":"3","location":"assets","locale":"","width":1280,"height":768},"screenshot-4.png":{"filename":"screenshot-4.png","revision":3700127,"resolution":"4","location":"assets","locale":"","width":1280,"height":768}},"screenshots":{"1":"An RSS feed rendered on the front end in the theme's own markup: thumbnails, dates, linked titles and excerpts in a grid, with no iframes and no vendor styling.","2":"The Feed block selected in the block editor, with its settings panel choosing the feed, the layout and the number of items; themes can add custom layouts via <code>freshet-feeds\/layout-{name}.php<\/code>.","3":"The Feeds list in wp-admin, showing each feed's name, slug, provider, last fetch time and status, with the hint for rendering it in theme code or with the Feed block.","4":"The edit screen for a feed: name, slug, provider, item count, and the field each provider needs, a feed URL for RSS \/ Atom, a channel ID for YouTube or a handle for Bluesky."}},"plugin_section":[],"plugin_tags":[223781,252,850,2877,1610],"plugin_category":[56],"plugin_contributors":[276640],"plugin_business_model":[],"class_list":["post-342510","plugin","type-plugin","status-publish","hentry","plugin_tags-bluesky","plugin_tags-feed","plugin_tags-rss","plugin_tags-social-feed","plugin_tags-youtube","plugin_category-social-and-sharing","plugin_contributors-kristoffbertram","plugin_committers-kristoffbertram"],"banners":{"banner":"https:\/\/ps.w.org\/freshet-feeds\/assets\/banner-772x250.png?rev=3700127","banner_2x":"https:\/\/ps.w.org\/freshet-feeds\/assets\/banner-1544x500.png?rev=3700127","banner_rtl":false,"banner_2x_rtl":false},"icons":{"svg":"https:\/\/ps.w.org\/freshet-feeds\/assets\/icon.svg?rev=3700127","icon":"https:\/\/ps.w.org\/freshet-feeds\/assets\/icon.svg?rev=3700127","icon_2x":false,"generated":false},"screenshots":[{"src":"https:\/\/ps.w.org\/freshet-feeds\/assets\/screenshot-1.png?rev=3700127","caption":"An RSS feed rendered on the front end in the theme's own markup: thumbnails, dates, linked titles and excerpts in a grid, with no iframes and no vendor styling."},{"src":"https:\/\/ps.w.org\/freshet-feeds\/assets\/screenshot-2.png?rev=3700127","caption":"The Feed block selected in the block editor, with its settings panel choosing the feed, the layout and the number of items; themes can add custom layouts via <code>freshet-feeds\/layout-{name}.php<\/code>."},{"src":"https:\/\/ps.w.org\/freshet-feeds\/assets\/screenshot-3.png?rev=3700127","caption":"The Feeds list in wp-admin, showing each feed's name, slug, provider, last fetch time and status, with the hint for rendering it in theme code or with the Feed block."},{"src":"https:\/\/ps.w.org\/freshet-feeds\/assets\/screenshot-4.png?rev=3700127","caption":"The edit screen for a feed: name, slug, provider, item count, and the field each provider needs, a feed URL for RSS \/ Atom, a channel ID for YouTube or a handle for Bluesky."}],"raw_content":"<!--section=description-->\n<p>Freshet Feeds displays external feeds inside WordPress the way developers wish every feed plugin worked: <strong>your theme owns the markup<\/strong>. No vendor styling panels, no iframes, no third-party JavaScript on your pages.<\/p>\n\n<p>Every provider \u2014 RSS\/Atom, YouTube channels, Bluesky profiles \u2014 normalizes into one item model and renders through one template chain, overridable WooCommerce-style from your theme.<\/p>\n\n<p>Use it where you would otherwise reach for a feed embed: an RSS feed block on a page, a YouTube channel feed, a social media feed from a Bluesky or Mastodon profile in a block widget area, a podcast feed's episode list. Items are rendered as your own markup, not an embed, and nothing is imported as posts.<\/p>\n\n<p><strong>For developers<\/strong><\/p>\n\n<ul>\n<li>A loop API: <code>freshet_feeds( 'my-feed' )<\/code> returns normalized item objects; <code>freshet_feeds_render( 'my-feed' )<\/code> runs the full template chain.<\/li>\n<li>Template overrides: copy <code>item.php<\/code> into <code>{your-theme}\/freshet-feeds\/<\/code> and edit. An item hierarchy (<code>item-{feed-slug}.php<\/code> \u2192 <code>item-{provider}.php<\/code> \u2192 <code>item.php<\/code>) gives per-feed and per-type markup with clean fallbacks.<\/li>\n<li>Custom layouts by convention: drop <code>layout-carousel.php<\/code> in your theme, pass <code>carousel<\/code> \u2014 no registration.<\/li>\n<li>Hooks for everything: providers, template resolution, cache refresh events.<\/li>\n<\/ul>\n\n<p><strong>Performance &amp; privacy by design<\/strong><\/p>\n\n<ul>\n<li>Rendering never blocks on a remote API (the only exception is a brand-new feed's very first fetch): items are cached server-side, stale content is served instantly while a background refresh runs.<\/li>\n<li>Feed images are stored locally in your uploads dir (some providers serve expiring signed image URLs \u2014 hotlinking them breaks).<\/li>\n<li>Visitors' browsers never contact the source platforms. Content lives in your DOM: real SEO, no consent baggage.<\/li>\n<\/ul>\n\n<p><strong>Providers<\/strong><\/p>\n\n<ul>\n<li><strong>RSS \/ Atom<\/strong> \u2014 any feed URL; also covers Mastodon, subreddits, podcasts.<\/li>\n<li><strong>YouTube (channel)<\/strong> \u2014 keyless public channel feed, no API key required.<\/li>\n<li><strong>Bluesky (profile)<\/strong> \u2014 public API, no authentication.<\/li>\n<li><strong>Mock (fixture data)<\/strong> \u2014 bundled sample posts, no credentials and no network calls, for building and styling templates before a live connection exists. Hidden and blocked in <code>production<\/code>.<\/li>\n<\/ul>\n\n<p>The plugin is fully functional with unlimited feeds. Full developer documentation: <a href=\"https:\/\/freshet.studio\/docs\">freshet.studio\/docs<\/a>.<\/p>\n\n<h3>External services<\/h3>\n\n<p>This plugin talks to external services only to fetch the feed content you configure:<\/p>\n\n<ul>\n<li><strong>YouTube feed<\/strong> (www.youtube.com\/feeds) \u2014 only for configured YouTube feeds. <a href=\"https:\/\/www.youtube.com\/t\/terms\">Terms<\/a>, <a href=\"https:\/\/policies.google.com\/privacy\">Privacy<\/a>.<\/li>\n<li><strong>Bluesky public API<\/strong> (public.api.bsky.app) \u2014 only for configured Bluesky feeds. <a href=\"https:\/\/bsky.social\/about\/support\/tos\">Terms<\/a>, <a href=\"https:\/\/bsky.social\/about\/support\/privacy-policy\">Privacy<\/a>.<\/li>\n<li><strong>Any RSS\/Atom URL you configure<\/strong> is fetched from your server on your cache schedule.<\/li>\n<\/ul>\n\n<p>All fetching happens server-side on your cache schedule; site visitors never contact these services.<\/p>\n\n<h3>Source code<\/h3>\n\n<p>The complete, unminified source \u2014 including the block editor JavaScript in <code>blocks\/<\/code> and the build setup \u2014 ships with the plugin and is maintained publicly at <a href=\"https:\/\/github.com\/freshetstudio\/freshet-feeds\">github.com\/freshetstudio\/freshet-feeds<\/a>. The compiled bundle in <code>build\/<\/code> is generated from <code>blocks\/<\/code> by running <code>npm install<\/code> and <code>npm run build<\/code> (uses @wordpress\/scripts).<\/p>\n\n<!--section=installation-->\n<ol>\n<li>Install and activate the plugin.<\/li>\n<li>Go to <strong>Feeds \u2192 Add feed<\/strong>, pick a provider, and configure it (a feed URL, channel ID, or handle).<\/li>\n<li>Add the <strong>Feed<\/strong> block to a page, or call <code>freshet_feeds_render( 'your-feed-slug' )<\/code> in your theme.<\/li>\n<\/ol>\n\n<p>No credentials yet? Pick the <strong>Mock (fixture data)<\/strong> provider in step 2 and start building templates immediately \u2014 see the FAQ below.<\/p>\n\n<!--section=faq-->\n<dl>\n<dt id=\"can%20i%20build%20templates%20before%20i%20have%20a%20live%20feed%20set%20up%3F\"><h3>Can I build templates before I have a live feed set up?<\/h3><\/dt>\n<dd><p>Yes \u2014 that is what the <strong>Mock (fixture data)<\/strong> provider is for. Create a feed with it and you get a bundled set of realistic sample posts (single image, multi-image, shared article, text-only, an image-only post with no text, and a long multi-paragraph one) without any credentials or network call. It is also the fastest way to evaluate the plugin.<\/p>\n\n<p>Mock posts run through exactly the same normalizer, item model and template chain as live posts, so everything you build against them works unchanged on real data \u2014 you switch the feed's provider and nothing else.<\/p>\n\n<p>The provider is hidden in the provider dropdown on sites reporting a <code>production<\/code> environment type, and refuses to fetch there even for a feed created earlier, so fixture posts cannot end up on a live site.<\/p><\/dd>\n<dt id=\"why%20is%20there%20no%20x%20%28twitter%29%20provider%3F\"><h3>Why is there no X (Twitter) provider?<\/h3><\/dt>\n<dd><p>X has no free read API and no RSS. We don't build on scraping. If that changes, we'll add it.<\/p><\/dd>\n<dt id=\"how%20do%20i%20change%20the%20markup%3F\"><h3>How do I change the markup?<\/h3><\/dt>\n<dd><p>Copy any template from the plugin's <code>templates\/<\/code> folder into <code>{your-theme}\/freshet-feeds\/<\/code> and edit it. See the <a href=\"https:\/\/freshet.studio\/docs\/templates\">template docs<\/a>.<\/p><\/dd>\n<dt id=\"does%20it%20slow%20my%20site%20down%3F\"><h3>Does it slow my site down?<\/h3><\/dt>\n<dd><p>No. Feeds are fetched in the background and served from a local cache; pages never wait on a remote API (except the one-time first fetch of a newly created feed). Images are served from your own uploads directory.<\/p><\/dd>\n<dt id=\"how%20do%20i%20display%20an%20rss%20feed%20on%20a%20page%3F\"><h3>How do I display an RSS feed on a page?<\/h3><\/dt>\n<dd><p>Go to <strong>Feeds \u2192 Add feed<\/strong>, pick the RSS \/ Atom provider and paste the feed URL. Then add the <strong>Feed<\/strong> block to the page \u2014 or to a block widget area such as a sidebar or footer \u2014 and pick the feed, a layout and an item count. In a theme template, <code>freshet_feeds_render( 'your-feed-slug' )<\/code> does the same. The items are rendered as HTML in your page from the plugin's templates or your theme's overrides; there is no iframe and no script from the source site.<\/p><\/dd>\n<dt id=\"can%20it%20embed%20a%20youtube%20channel%20feed%20without%20an%20api%20key%3F\"><h3>Can it embed a YouTube channel feed without an API key?<\/h3><\/dt>\n<dd><p>Yes. The YouTube provider reads the public channel feed, so there is no API key and no quota; you enter the channel ID. The default item template shows each video as a thumbnail card that links to YouTube rather than embedding an iframe player, which keeps the page fast and keeps YouTube's scripts off it until the visitor clicks. If you want an inline player, copy <code>item-youtube.php<\/code> into your theme and change the markup.<\/p><\/dd>\n<dt id=\"does%20it%20show%20a%20bluesky%20or%20mastodon%20feed%3F\"><h3>Does it show a Bluesky or Mastodon feed?<\/h3><\/dt>\n<dd><p>Bluesky profiles are a provider of their own: enter the handle and the plugin reads the public API \u2014 no authentication. Reposts, replies and pins are left out, and embedded images keep their alt text. Mastodon has no dedicated provider because it does not need one: every Mastodon account exposes an RSS feed at <code>https:\/\/instance\/@user.rss<\/code>, so use the RSS \/ Atom provider with that URL. The same goes for subreddits (append <code>\/.rss<\/code>) and podcast feeds. Either way the posts render through your templates as a social media feed in your own markup, without the platform's embed script.<\/p><\/dd>\n\n<\/dl>\n\n<!--section=changelog-->\n<h4>1.0.2<\/h4>\n\n<ul>\n<li>New <strong>Settings<\/strong> tab on the Feeds screen with an opt-in to remove all plugin data (feeds, cached items and stored images) when the plugin is uninstalled.<\/li>\n<li>The wordpress.org package is trimmed to the runtime files it needs.<\/li>\n<li>Dropped the <code>Domain Path<\/code> header and the manual text-domain load; wordpress.org language packs load on their own.<\/li>\n<li>Listing: real screenshots, a live preview in WordPress Playground, and a readme written in the words people search for.<\/li>\n<\/ul>\n\n<h4>1.0.1<\/h4>\n\n<ul>\n<li>First release published on WordPress.org.<\/li>\n<li>Fixed cached feed items being corrupted, and in some cases discarded entirely, when an item contained a quote or a non-ASCII character.<\/li>\n<\/ul>\n\n<h4>1.0.0<\/h4>\n\n<ul>\n<li>Initial release: RSS\/Atom, YouTube, and Bluesky providers.<\/li>\n<li>Template override chain with item hierarchy, custom layouts, loop API.<\/li>\n<li>Server-rendered Feed block, stale-while-revalidate caching, local image storage.<\/li>\n<li>WP-CLI commands (<code>wp freshet-feeds fetch<\/code>, <code>wp freshet-feeds status<\/code>).<\/li>\n<\/ul>","raw_excerpt":"Display an RSS feed, a YouTube feed or a Bluesky feed in WordPress through templates your theme owns \u2014 no iframes, no third-party JavaScript.","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin\/342510","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin"}],"about":[{"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/types\/plugin"}],"replies":[{"embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/comments?post=342510"}],"author":[{"embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wporg\/v1\/users\/kristoffbertram"}],"wp:attachment":[{"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/media?parent=342510"}],"wp:term":[{"taxonomy":"plugin_section","embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_section?post=342510"},{"taxonomy":"plugin_tags","embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_tags?post=342510"},{"taxonomy":"plugin_category","embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_category?post=342510"},{"taxonomy":"plugin_contributors","embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_contributors?post=342510"},{"taxonomy":"plugin_business_model","embeddable":true,"href":"https:\/\/co.wordpress.org\/plugins\/wp-json\/wp\/v2\/plugin_business_model?post=342510"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}