WP Manifestindependent plugin directory
manifest / seo / trending-now-plugin

Trending Now WP Registry grade A. No security issues foundWP RegistryNo security issues foundOpen the reportA

★ 0stars
282release downloads
≈20active sites
v1.4.0latest · 1mo ago

Install

Download trending-now-1.4.0.zip From the v1.4.0 release on GitHub, Sep 2, 2026

Upload it under Plugins → Add New Plugin → Upload Plugin, then activate.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/advision-development/trending-now-plugin/releases/download/v1.4.0/trending-now-1.4.0.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

A WordPress plugin that renders a server-side Trending Now block of links aggregated from owned WordPress sites and third-party news, plus a paginated archive at /trending/.

The goal is crawl discovery: giving every newly published URL across a network a guaranteed window of exposure in an internally-linked, server-rendered block on higher-authority properties.


Contents


Why this exists

Search engines have to find a URL before they can index it. On a network of sites, new articles often sit unlinked from anywhere crawled frequently, so discovery lags publishing by days.

This plugin puts every new URL into a plain, server-rendered <a href> list on pages that get crawled often — the homepage and top-level section pages — and keeps it there for a guaranteed number of days rather than letting the next day's publishing burst push it out.

It improves discovery, not selection. If Googlebot is already fetching your new URLs and declining to index them, that is a content-quality problem and this plugin will not move it. Check your server logs before assuming otherwise.

Requirements

WordPress 6.4+
PHP 7.4+
MySQL / MariaDB 5.7+ / 10.3+
Composer for Action Scheduler (optional — falls back to WP-Cron)

PHP 7.4 is supported but end-of-life upstream — it stopped receiving security fixes in November 2022. It is here for legacy installs, not as a recommendation. The plugin is developed and tested against 8.x.

Loopback HTTP requests must succeed for Action Scheduler's queue runner. The Diagnostics tab tests this for you.

Install

git clone git@github.com:advision-development/trending-now-plugin.git trending-now
cd trending-now
composer install --no-dev

Place the directory at wp-content/plugins/trending-now and activate it. Activation creates the {prefix}advtn_items table, seeds defaults, generates the ingest secret and flushes rewrite rules.

Or grab the packaged zip from the latest release — it has vendor/ bundled and no development files — and install it through Plugins → Add New → Upload Plugin.

Quick start

  1. Trending Now → Sources → Add source. Point a wp_rest source at another site in your network (its site root, not a feed URL).
  2. Press Test fetch on that row. It performs a live fetch and shows the first three normalized items, timing and HTTP status — without writing anything.
  3. Save sources.
  4. Diagnostics → Run ingest now. This fetches everything in that one request and rebuilds the selection before the page reloads.
  5. Put [trending_now] on your homepage, or add the Trending Now block.

If nothing renders, Diagnostics will say why — see Troubleshooting.

Sources

Type Use for Notes
wp_rest Owned WordPress sites /wp-json/wp/v2/posts. Preferred: /feed/ is capped by the remote site's posts_per_rss with no per-request override.
rss Sites with the REST API disabled SimplePie via fetch_feed(), with its 12-hour cache disabled for the call.
serpapi Third-party news Google News via SerpAPI. Needs a key; one search credit per fetch.

Each row has a label, an item limit per cycle, a timeout, an enabled toggle, and drag-to-reorder (order sets the ingest stagger). SerpAPI rows additionally take a query, an optional allowed-domain list, country and language.

Timeout overrides the global http_timeout for that one row. Blank or 0 inherits the global; otherwise it is clamped to 1–120 seconds — double the global's ceiling of 60, because a per-row override is a considered choice about one provider, not a blunt default applied to everything. It sets how long that source's request may take; the TLS connect phase still follows the global http_timeout regardless.

The tab exports the whole list as JSON and imports it back — merging on source id or replacing outright — so a source set can be moved between installs. Imported rows are validated exactly as though typed into the form; bad rows are reported and skipped.

Recent attempts. A row that has been fetched at least once shows a disclosure with its last 20 attempts: timestamp, ok/fail, duration, HTTP code and error, newest first, plus a summary line of count, median (p50) and max duration next to the timeout currently in force. This is what turns "it failed once, three weeks ago" into "it has been drifting toward its timeout for the last six hours."

Test fetch versus Ingest now. Every saved, enabled row has both, side by side. Test fetch runs the fetch and shows the first three normalized items, timing and HTTP status — it writes nothing. Ingest now writes: it fetches that one source for real, upserts whatever it finds, rebuilds the selection and purges caches, exactly like a normal cycle but for a single source. It also ignores that source's failure backoff, which is the point of pressing it on a source that has been failing — see Troubleshooting.

The two read different things. Test fetch uses whatever is on screen, saved or not; Ingest now uses the saved row, because it goes through the stored configuration. Change a field and you must save before the retry will use it — the button leaves the page, so the admin confirms first rather than silently discarding the edit. Ingest now is also not a completed cycle: it does not update last_ingest, so it neither defers the next scheduled run of your other sources nor clears the stale-ingest banner.

A SerpAPI source runs in one of two modes. Top stories is Google News' mainstream front page for the country and language you pick — no query needed, and the usual source of a general news mix. Search query takes a query string, where Google operators such as site: work. Either mode takes an optional domain allowlist, applied after the response, since an aggregator returns whoever it likes unless constrained.

Top stories returns roughly ten articles per fetch. If your news share needs more than that — say 50% of a 30-link widget — add a second news source rather than expecting one call to fill it.

Cost. One credit per fetch, not per item, so a source on a daily cycle costs about 30 a month regardless of limit. Identical repeat queries are served from SerpAPI's own cache and are not billed, so re-testing a row while configuring it is free.

A GDELT provider shipped in 1.0.0 and was removed: free, but 10–20s per request and a rate limit of roughly one request per five seconds whose penalty outlasts its own window. Any gdelt source rows are dropped automatically on upgrade; items already ingested from it are kept, still counted as news, and age out normally.

Trending Now → Manual links is a hand-curated list that mixes into the same widget as ingested links. Each entry takes the same fields an ingested item has — URL, title, excerpt, image, source name, publish date — plus two of its own:

  • Position — the slot it should occupy, 1-based. 0 means "include it, but let it fall where it may". Out-of-range positions clamp to the end rather than dropping the link.
  • Expires — a UTC timestamp, with +1 day / +3 days / +1 week / +30 days shortcuts, or empty for indefinite. The row shows the time remaining at a glance.

Curated links are stored in the items table like everything else, so they deduplicate against anything a source also finds, appear in the archive, and carry the same display counters. They reserve their slots before the tiers run, so a widget limit of 30 with three curated links leaves 27 for automatic selection.

Two deliberate differences from ingested sources:

  • Links back to this site are allowed. The self-link rule exists to catch a source echoing your own content; choosing one by hand is a different thing.
  • Expiry marks the row stale rather than deleting it — off the widget, kept in the archive. When a timer runs out the list is rebuilt immediately via a scheduled action, rather than waiting for the next ingest cycle, which on the default interval could be 20 hours away.

Subscribing to a feed

Instead of typing the same curated links into every site in a network, a site can subscribe to a list maintained somewhere else. While subscribed, Trending Now → Manual links becomes a read-only mirror: the rows render disabled and every fetch replaces them.

Four settings, on that same tab:

Feed URL the whole address, query string included
Auth token optional — a public feed needs none
Fetch every hours, 1–168. Default 6
Subscribed both this and a URL are required

Unticking Subscribed leaves the links in place and editable again. Nothing disappears from the front end at the moment somebody unticks a box; the site simply stops following the feed and starts diverging, which is the point of unsubscribing.

What it fetches, and when

advtn_manual_feed_fetch recurs on its own clock, separate from the ingest cycle. Like that cycle it uses a due-check, so a missed window runs late rather than being skipped — and like that cycle it depends on WP-Cron, which runs on pageviews rather than on a clock. On a PBN that is less of a problem than it sounds: the crawler this plugin exists to attract is traffic, and its visit fires the schedule.

For a site quiet enough that this matters, POST /wp-json/advtn/v1/feed-fetch is the escape hatch — signed with the same secret as /ingest, and documented under REST API. There is also wp trending-now feed-fetch [--force].

Repeated fetches are cheap. The plugin sends the version it already holds as an If-None-Match, so a feed that has not changed answers 304 and nothing is written.

--force skips both the interval and that If-None-Match. The second half matters more than it looks: force is what somebody reaches for when a site's list looks wrong, and the ETag is the site claiming its list is fine. Asking conditionally would let the feed answer "you already have it" to a site holding nothing, and the repair would be refused in the one case it was requested — reported as success. So a forced fetch always asks outright and always re-commits.

What can go wrong, and what it costs

Nothing, is the answer to the second half. A failed fetch leaves the stored list exactly as it was; only a response that validated replaces anything.

The failure worth understanding is the one that does not look like one:

GET /trending/feeeed?feed=whatever   →   200 OK
<!doctype html><html>…

A feed served from a single-page app's host answers 200 with HTML for any path it does not recognise. One letter wrong in the URL therefore looks exactly like success, on every subscribed site, for as long as nobody checks. So the plugin does not trust the status code: a payload counts only if its body carries both a feed object and an items list.

The other refusal to know about is 401, which does not mean the token is wrong. A feed may answer 401 because the token is missing or wrong — or because the feed named in the URL does not exist, which it answers identically on purpose so that nobody can discover which feeds exist by guessing. Check the slug as well as the token.

The last fetch, its HTTP code, the number of links stored, the next due time and the last error are all on the subscription card.

What the site tells the feed about itself

Every fetch carries two extra query parameters — site, which is this install's home_url(), and v, the plugin version. They let whoever runs the feed see which sites subscribe and which version each is on; without them a subscriber is only an IP address, and shared hosting puts several sites behind one.

site is taken from home_url() and is deliberately not a setting. The value identifies this install to the feed, so a field somebody could type wrong is a field that points the feed at the wrong site.

Nothing else changes. A feed that does not recognise the parameters serves exactly what it served before, so upgrading is safe whatever the feed is running.

Displaying the widget

Three entry points, one renderer.

Shortcode — works in Elementor's Text Editor widget, Bricks' Shortcode element, Gutenberg, classic widgets and theme templates:

[trending_now limit="30" layout="news" heading="Trending Now"
              show_images="1" show_source="1" show_date="1"
              show_icons="0" show_excerpt="0" show_see_all="1"
              match_path="/,/trending"]

Block — Trending Now, with the same options in the inspector.

Every display attribute is optional. Omit one and it inherits the setting; supply it and it wins for that instance. Off by default for excerpts, since not every source provides one — GDELT never did, and Google News does not either.

match_path (shortcode) / matchPath (block) limits a placement to a comma-separated list of paths; an empty or absent value renders everywhere, which is the same as before this attribute existed. The example above is the homepage plus the archive at its default slug — write whatever archive_slug is actually set to, since /trending is only the default. Matching is exact and trailing-slash-insensitive: /trending covers /trending and /trending/, but not /trending/page/2/ and not /trending-2024. Query strings are ignored, so /?utm_source=x still matches /. On a subdirectory install, / means the site's homepage, not the server root. A pasted full URL is reduced to its path, so https://example.com/trending behaves as /trending. On the shortcode, both match_path and matchPath work, because WordPress lowercases shortcode attribute names before it reads them.

Three layouts, and news is the default — what you get with a bare [trending_now]. It is the Google News / MSN style: source name and a relative timestamp, the headline, and a thumbnail on the right. Thumbnails carry fixed dimensions so they reserve their space instead of shifting the layout, and everything below the first card is lazy-loaded — the first is fetched eagerly because it is usually the one above the fold. list is compact text links, cards a grid with excerpts. An item with no thumbnail simply lets its headline span the full width, as Google News does.

Enabling site icons puts a publisher favicon beside each source name, as in the Google News and MSN feeds. The URL is derived from the stored host — a news API's own icon field turns out to be exactly that anyway — so it costs no ingest work and covers your own sites as well as third-party ones. The image is fetched by the visitor's browser, not the server, so the no-HTTP-during-render rule still holds; it does mean Google's favicon service sees your visitors, which is why it is off by default. Filter advtn_source_icon_url to self-host or swap providers.

Timestamps come in two styles, set by date_style. relative shows 45m or 6h inside the last day and a date before that, which is what makes a feed read as current at a glance. date always shows the date.

Either way the label is recalculated on every request from the datetime attribute already in the markup, not baked into the cached HTML. Without that, an article rendered at 42m would still claim 42m a full ingest interval later — up to 20 hours of a page insisting day-old news is minutes old.

Relative stamps advertise your publishing rhythm. On a once-a-day cycle every item drifts towards 20h or 23h together, which reads as a batch update rather than a live feed. If that matters more to you than the freshness cue, use date.

Template tags:

advtn_render( array( 'limit' => 20, 'layout' => 'cards' ) );  // echoes
$html = advtn_get_html();                                     // returns

Templates live in templates/ and are overridable from a theme at {theme}/trending-now/{template}.php — widget-list.php, widget-cards.php, archive.php.

Markup and CSS

<section class="advtn advtn--list" aria-labelledby="advtn-heading-a1b2">
  <h2 class="advtn__heading" id="advtn-heading-a1b2">Trending Now</h2>
  <ul class="advtn__items">
    <li class="advtn__item advtn__item--network">
      <a class="advtn__link" href="https://example.com/article/">Article title</a>
      <span class="advtn__source">example.com</span>
      <time class="advtn__date" datetime="2026-08-05T14:22:00+00:00">Aug 5</time>
    </li>
  </ul>
  <p class="advtn__more"><a class="advtn__more-link" href="/trending/">See all…</a></p>
</section>

advtn is the configurable class_prefix. The stylesheet is generated from it and inlined once per page inside the cached HTML, so there is no extra request and no hard-coded prefix. assets/css/trending-now.css is an unenqueued reference copy.

Because the widget renders after wp_head, that `

This README is longer than the copy stored here. Read the rest on GitHub →

Releases

12 releases. Each count is every asset in that release; expand a row for the breakdown.

Tag
Published
Assets
Downloads
v1.4.0 latest
Sep 2, 2026 1mo ago
trending-now-1.4.0.zip
98
Sep 1, 2026 1mo ago
trending-now-1.3.0.zip
58
Aug 25, 2026 1mo ago
trending-now-1.2.0.zip
80
Aug 10, 2026 2mo ago
trending-now-1.1.7.zip
22
Aug 8, 2026 2mo ago
trending-now-1.1.6.zip
3
Aug 8, 2026 2mo ago
trending-now-1.1.5.zip
2
Aug 7, 2026 2mo ago
trending-now-1.1.4.zip
3
Aug 7, 2026 2mo ago
trending-now-1.1.3.zip
1
Aug 7, 2026 2mo ago
trending-now-1.1.2.zip
4
Aug 7, 2026 2mo ago
trending-now-1.1.1.zip
5
Aug 7, 2026 2mo ago
trending-now-1.1.0.zip
3
Aug 6, 2026 2mo ago
trending-now-1.0.0.zip
3

Active-site estimate ≈20 comes from the median of recent superseded releases. Method.

A grade

Security

Trending Now 1.4.0 · audited by WP Registry

No security issues found.

Audited release
1.4.0
Findings
none
Worst severity
—
Content hash
2b710ae067c8418ad2c9d6f3…

WP Registry hashes the installable build and reports on that exact bytes-for-bytes copy. Embargoed findings are withheld until they are disclosed, so a clean verdict means nothing public is outstanding. WP Manifest does not audit code itself.