Title: VaultFeed
Author: CCGnomes
Published: <strong>October 13, 2026</strong>
Last modified: October 13, 2026

---

Search plugins

![](https://ps.w.org/vaultfeed/assets/banner-772x250.png?rev=3744922)

![](https://ps.w.org/vaultfeed/assets/icon-256x256.png?rev=3744922)

# VaultFeed

 By [CCGnomes](https://profiles.wordpress.org/theteamccgnomes/)

[Download](https://downloads.wordpress.org/plugin/vaultfeed.0.9.0.zip)

 * [Details](https://wordpress.org/plugins/vaultfeed/#description)
 * [Reviews](https://wordpress.org/plugins/vaultfeed/#reviews)
 *  [Installation](https://wordpress.org/plugins/vaultfeed/#installation)
 * [Development](https://wordpress.org/plugins/vaultfeed/#developers)

 [Support](https://wordpress.org/support/plugin/vaultfeed/)

## Description

**VaultFeed** displays social media feeds on your WordPress site while keeping your
OAuth tokens encrypted on your own server — never uploaded to a third-party SaaS
platform.

Connect accounts from **20 feed sources**: **Facebook**, **Instagram**, **Twitter/
X**, **YouTube**, **TikTok**, **Threads**, **LinkedIn**, **Bluesky**, **Pinterest**,**
Mastodon**, **Reddit**, **Snapchat**, **Twitch**, **Vimeo**, **Google Business Profile**,**
Yelp**, and more. Every token is AES-256-GCM encrypted and stored only in your own
database.

#### Features

VaultFeed is fully functional with no artificial limits on feed count, post count,
or layout — everything below is included with the plugin as distributed on WordPress.
org:

 * Connect accounts from **20 feed sources**: Facebook Pages, Instagram Business/
   Creator, Twitter / X, YouTube, TikTok, Threads, LinkedIn, Bluesky, Pinterest,
   Mastodon (public accounts, no credentials needed), Reddit, Snapchat (Snap Kit
   Display API), Twitch (clips and VODs via Helix API), Vimeo (user/channel/group
   feeds), Google Business Profile, Yelp, and more
 * Display photo, video, reel, and **review** feeds in **Grid, List, Masonry, Carousel,
   Slider, Waterfall, Ticker, Timeline,** or **Wall** layout
 * Star-rating widget with WCAG-compliant aria-label for review feeds
 * Configurable columns (1–6), aspect ratio, hover effects, and post count
 * Lightbox for photos and videos with keyboard navigation
 * **Load More** button and infinite scroll
 * Feed header with optional follow button
 * Shortcode `[vaultfeed id="N"]` and Gutenberg block
 * Automatic content caching with configurable TTL, fully adjustable
 * Caption display, source icon, date, and like / comment counts
 * Optional “Powered by VaultFeed” attribution link — off by default, opt-in only
 * Fully translatable — `.pot` file included
 * WCAG 2.1 AA compliant — focus traps, live regions, 4.5:1 contrast
 * Zero telemetry — nothing phones home

#### Pro add-on

A separately-sold Pro add-on is available from [the VaultFeed website](https://ccgnomes.com/vaultfeed/)
and is **not** distributed via WordPress.org — none of its code is included in this
plugin. It adds functionality that depends on infrastructure this plugin does not
include or run:

 * ReelVault source — a video feed served from your own self-hosted Holochain node(
   requires running that node yourself; VaultFeed does not bundle or install it)
 * DHT token storage as an alternative to the local database storage described below
 * Priority support

None of the above is locked, disabled, or hidden in the WordPress.org version — 
it simply isn’t present, because it depends on external infrastructure (a Holochain
node) that this plugin doesn’t ship or manage.

#### How token storage works

When you connect an account, VaultFeed encrypts the OAuth token with AES-256-GCM
and stores it in your own WordPress database. The encryption key is derived from
your WordPress secret keys and a random per-installation salt that is generated 
at first activation and stored in your WordPress database (the `vaultfeed_token_salt`
option). VaultFeed never writes to `wp-config.php` or to any WordPress core, plugin,
or theme file. No token ever leaves your server.

#### External services

VaultFeed relies on third-party services to do its job: it fetches feed content 
from the social platforms you explicitly connect, and it can optionally send data
to a small number of integrations (email marketing, analytics, notifications, store
lookups, AI caption rewriting) that stay completely inactive until you enter your
own credentials or URL for them. Every service the plugin can contact is listed 
below with what is sent, when, and its terms and privacy policy. No data is ever
sent to VaultFeed or its authors; the plugin operates no background service and 
has no telemetry.

 1.  **Facebook Graph API** (`graph.facebook.com`) — when you initiate an OAuth connection
     from the Accounts page, and when fetching feed content. Governed by [Meta’s Terms of Service](https://www.facebook.com/terms.php)
     and [Privacy Policy](https://www.facebook.com/policy.php). Only called when you
     have a connected Facebook account.
 2.  **Instagram Graph API** (`graph.instagram.com`, `graph.facebook.com`) — when you
     initiate an Instagram OAuth connection from the Accounts page, and when fetching
     Instagram feed, tagged-post, or story content. Governed by [Meta’s Terms of Service](https://www.facebook.com/terms.php)
     and [Instagram’s Privacy Policy](https://privacycenter.instagram.com/policy). 
     Only called when you have a connected Instagram account.
 3.  **Twitter / X API v2** (`api.twitter.com`) — when you initiate a Twitter OAuth
     2.0 PKCE connection, and when fetching tweet content. Governed by [X’s Terms of Service](https://x.com/en/tos)
     and [Privacy Policy](https://x.com/en/privacy). Only called when you have a connected
     Twitter account.
 4.  **Google / YouTube Data API v3** (`accounts.google.com`, `oauth2.googleapis.com`,`
     www.googleapis.com`) — when you initiate a Google OAuth 2.0 connection (your browser
     is sent to Google’s consent screen), and when fetching YouTube channel or playlist
     content. Governed by [Google’s Terms of Service](https://policies.google.com/terms)
     and [Privacy Policy](https://policies.google.com/privacy). Only called when you
     have a connected YouTube account.
 5.  **TikTok for Developers API v2** (`www.tiktok.com`, `open.tiktokapis.com`) — when
     you initiate a TikTok OAuth 2.0 connection, and when fetching video content. Governed
     by [TikTok’s Terms of Service](https://www.tiktok.com/legal/page/row/terms-of-service/en)
     and [Privacy Policy](https://www.tiktok.com/legal/page/row/privacy-policy/en).
     Only called when you have a connected TikTok account.
 6.  **Threads API** (`graph.threads.net`) — when you initiate a Threads OAuth 2.0 
     connection, and when fetching posts. Governed by [Meta’s Terms of Service](https://www.facebook.com/terms.php)
     and [Privacy Policy](https://www.facebook.com/policy.php). Only called when you
     have a connected Threads account.
 7.  **Google Business Profile APIs** (`mybusiness.googleapis.com`, `mybusinessaccountmanagement.
     googleapis.com`, `mybusinessbusinessinformation.googleapis.com`) — when listing
     your business accounts/locations and when fetching Google business reviews. Uses
     the same Google OAuth credentials as YouTube (item 4 above); both APIs must be
     enabled in your Google Cloud project. Governed by [Google’s Terms of Service](https://policies.google.com/terms)
     and [Privacy Policy](https://policies.google.com/privacy). Only called when you
     have a connected Google Business Profile account.
 8.  **Yelp Fusion API** (`api.yelp.com`) — when you verify a Yelp API key connection,
     and when fetching business reviews. Governed by [Yelp’s Terms of Service](https://www.yelp.com/static?p=tos)
     and [Privacy Policy](https://www.yelp.com/static?p=privacy). Only called when 
     you have a connected Yelp account.
 9.  **LinkedIn REST API** (`api.linkedin.com`) — when you initiate a LinkedIn OAuth
     2.0 connection, and when fetching posts. Governed by [LinkedIn’s User Agreement](https://www.linkedin.com/legal/user-agreement)
     and [Privacy Policy](https://www.linkedin.com/legal/privacy-policy). Only called
     when you have a connected LinkedIn account.
 10. **Bluesky AT Protocol API** (`bsky.social`, `public.api.bsky.app`) — when you 
     connect a Bluesky account via App Password, and when fetching posts. Governed 
     by [Bluesky’s Terms of Service](https://bsky.social/about/support/tos) and [Privacy Policy](https://bsky.social/about/support/privacy-policy).
     Only called when you have a connected Bluesky account.
 11. **Pinterest API** (`api.pinterest.com`) — when you initiate a Pinterest OAuth 
     2.0 connection, and when fetching boards and pins. Governed by [Pinterest’s Terms of Service](https://policy.pinterest.com/en/terms-of-service)
     and [Privacy Policy](https://policy.pinterest.com/en/privacy-policy). Only called
     when you have a connected Pinterest account.
 12. **Mastodon API** (`{your-instance}/api/v1/`) — when you look up a Mastodon account
     by handle, and when fetching public statuses. The specific domain depends on the
     Mastodon instance you connect, and each instance publishes its own terms and privacy
     policy — for example, mastodon.social’s [Terms of Service](https://mastodon.social/terms-of-service)
     and [Privacy Policy](https://mastodon.social/privacy-policy). Only called when
     you have a connected Mastodon account.
 13. **Reddit OAuth2 / REST API** (`oauth.reddit.com`, `www.reddit.com`) — when you
     initiate a Reddit OAuth 2.0 client-credentials connection, and when fetching subreddit
     posts. Governed by [Reddit’s User Agreement](https://www.redditinc.com/policies/user-agreement)
     and [Privacy Policy](https://www.reddit.com/policies/privacy-policy). Only called
     when you have a connected Reddit account.
 14. **Snapchat Login Kit / Snap Kit Display API** (`accounts.snapchat.com`, `kit.snapchat.
     com`) — when you initiate a Snapchat OAuth 2.0 connection and exchange or refresh
     its token, and when fetching your profile and public stories. Governed by [Snapchat’s Terms of Service](https://www.snap.com/en-US/terms)
     and [Privacy Policy](https://www.snap.com/en-US/privacy/privacy-policy). Only 
     called when you have a connected Snapchat account.
 15. **Twitch Helix API** (`id.twitch.tv`, `api.twitch.tv`) — when you connect a Twitch
     app (an app access token is requested from `id.twitch.tv`), and when fetching 
     clips and VODs. Governed by [Twitch’s Terms of Service](https://www.twitch.tv/p/legal/terms-of-service/)
     and [Privacy Policy](https://www.twitch.tv/p/legal/privacy-policy/). Only called
     when you have a connected Twitch account.
 16. **Vimeo API** (`api.vimeo.com`) — when you connect a Vimeo account via Personal
     Access Token, and when fetching user, channel, or group video feeds. Governed 
     by [Vimeo’s Terms of Service](https://vimeo.com/terms) and [Privacy Policy](https://vimeo.com/privacy).
     Only called when you have a connected Vimeo account.
 17. **AI caption rewriting — via the WordPress AI Client** — optional, and never automatic:
     it runs only when you click “Enhance Caption” on the Moderation screen, and sends
     only that one caption’s text plus a short rewrite instruction (the chosen tone).
     No OAuth tokens, visitor data, or other site data is ever included. VaultFeed 
     hands the request to WordPress’s built-in AI Client (WordPress 7.0 and later),
     so the text goes to whichever AI provider **you** have connected under WordPress’s
     own AI settings; VaultFeed stores no credentials for it and contacts no AI provider
     on its own, and that provider’s terms and privacy policy apply. If you have not
     connected an AI provider in WordPress, the feature is simply unavailable and nothing
     is sent anywhere.
 18. **Mailchimp Marketing API** (`<dc>.api.mailchimp.com`) — used only by the optional
     email-capture feature. Only active if you enable email capture on a feed **and**
     enter your own Mailchimp API key and Audience (list) ID in VaultFeed  Settings.
     When a visitor submits the email-capture form, their email address is sent to 
     Mailchimp to be added as a subscriber to your audience. Nothing is sent if a Mailchimp
     key is not configured. Governed by [Mailchimp’s Terms of Use](https://mailchimp.com/legal/terms/)
     and [Privacy Policy](https://www.intuit.com/privacy/statement/).
 19. **ConvertKit / Kit API** (`api.convertkit.com`) — used only by the optional email-
     capture feature. Only active if you enable email capture on a feed **and** enter
     your own ConvertKit (Kit) API key and Form ID in VaultFeed  Settings. When a visitor
     submits the email-capture form, their email address is sent to ConvertKit to subscribe
     them to your form. Nothing is sent if a ConvertKit key is not configured. Governed
     by [Kit’s Terms of Service](https://kit.com/terms) and [Privacy Policy](https://kit.com/privacy).
 20. **Google Analytics 4 Measurement Protocol** (`www.google-analytics.com`) — used
     only for optional click analytics. Only active if you enter your own GA4 Measurement
     ID and API Secret in VaultFeed  Settings. When a visitor clicks a post, a `vaultfeed_click`
     event (feed ID, post ID, and post type — no personal data) is sent to your Google
     Analytics property. Nothing is sent if GA4 credentials are not configured. Governed
     by [Google’s Terms of Service](https://policies.google.com/terms) and [Privacy Policy](https://policies.google.com/privacy).
 21. **Slack incoming webhooks** (`hooks.slack.com`) — used only for optional new-post
     notifications. Only active if you paste your own Slack incoming-webhook URL in
     VaultFeed  Settings **and** enable new-post alerts on a feed. When new posts arrive
     in that feed, a short text message (the number of new posts and the feed’s name—
     no post content and no visitor data) is sent to your Slack webhook. Nothing is
     sent if no Slack URL is configured. Governed by [Slack’s Terms of Service](https://slack.com/terms-of-service/user)
     and [Privacy Policy](https://slack.com/trust/privacy/privacy-policy).
 22. **Zapier webhooks** (`hooks.zapier.com`) — used only for optional automation. 
     Only active if you paste your own Zapier “Catch Hook” URL in VaultFeed  Settings.
     When a feed refresh finds new posts, the feed ID and, for each new post, its platform
     ID, caption text, first media URL, permalink, and timestamp are sent to your Zapier
     webhook. This is public social content you chose to display — no visitor data 
     is included. Nothing is sent if no Zapier URL is configured. Governed by [Zapier’s Terms of Service](https://zapier.com/legal/terms-of-service)
     and [Privacy Policy](https://zapier.com/privacy).
 23. **Shopify Storefront API** (`{your-store}.myshopify.com`) — used only by the optional“
     Shop” badge. Only active if you enter your own Shopify store URL and Storefront
     API access token in VaultFeed  Settings and enable the shop badge on a feed. When
     a post’s hashtag has no product link you mapped manually, that hashtag is sent
     to your own store’s Storefront API as a product-handle lookup, together with your
     Storefront token, to find the product’s URL (results are cached for one hour).
     No visitor data is sent. Nothing is sent if Shopify is not configured. Governed
     by [Shopify’s Terms of Service](https://www.shopify.com/legal/terms) and [Privacy Policy](https://www.shopify.com/legal/privacy).
 24. **Your own outbound webhook URLs** — optional. Only active if you add one or more
     webhook endpoints in VaultFeed  Webhooks. When an event you selected fires (feed
     refreshed, feed error, post approved/rejected), a JSON payload is POSTed to the
     URL(s) **you** entered: the event name, the feed ID, and for refresh events each
     post’s platform ID, type, source, caption text (first 280 characters), permalink,
     and publish date. Requests are signed with HMAC-SHA256 using your webhook secret.
     The destination is whatever server you specify, so its terms and privacy policy
     are those of the service you choose; no data goes anywhere unless you add a URL.
 25. **Social platform media CDNs** (for example `scontent.cdninstagram.com`, `pbs.
     twimg.com`, `i.ytimg.com` — the exact hosts depend on the platforms you connect)—
     the images, thumbnails and videos inside your feeds are served from the platform’s
     own CDN, so a visitor’s browser requests them directly from that platform while
     viewing the feed, exactly as an ordinary embedded post would. The URLs come from
     the platform API response; VaultFeed does not rehost them. The platform’s terms
     and privacy policy are the ones listed for that platform above. If you would rather
     your visitors never contact those CDNs, switch on the built-in image proxy — `
     wp option update vaultfeed_image_proxy_enabled 1` — and your own server fetches
     and caches the images instead.
 26. **WordPress.org update API** — standard WordPress plugin update check behaviour.
     No VaultFeed-specific data is transmitted beyond the plugin slug and current version.

#### Third-party libraries

VaultFeed’s own scripts, styles and fonts are all bundled with the plugin — none
are loaded from an external server or CDN. (The post images and videos _inside_ 
your feeds are a separate matter: those are served from the social platforms’ CDNs,
as described in item 25 of “External services” above.)

 * **SortableJS 1.15.7** (MIT licence) — drag-to-reorder on the admin Moderation
   screen. Bundled at `assets/js/vendor/sortable.min.js` with the unminified source
   alongside at `assets/js/vendor/sortable.js` and the upstream licence at `assets/
   js/vendor/sortable-LICENSE.txt`. Source: [github.com/SortableJS/Sortable](https://github.com/SortableJS/Sortable).
 * All other minified files in `assets/js/*.min.js` and `assets/css/*.min.css` are
   built from the human-readable `.js` / `.css` files of the same name that ship
   next to them.

## Screenshots

[⌊The Feeds list page — all your feeds at a glance with shortcode copy.⌉⌊The Feeds
list page — all your feeds at a glance with shortcode copy.⌉[

The Feeds list page — all your feeds at a glance with shortcode copy.

[⌊Feed creation wizard — choose source, account, type, and display settings.⌉⌊Feed
creation wizard — choose source, account, type, and display settings.⌉[

Feed creation wizard — choose source, account, type, and display settings.

[⌊Live preview panel — see changes in real time as you configure.⌉⌊Live preview 
panel — see changes in real time as you configure.⌉[

Live preview panel — see changes in real time as you configure.

[⌊Grid layout with hover overlay effect on the front end.⌉⌊Grid layout with hover
overlay effect on the front end.⌉[

Grid layout with hover overlay effect on the front end.

[⌊List layout with caption, date, and meta displayed.⌉⌊List layout with caption,
date, and meta displayed.⌉[

List layout with caption, date, and meta displayed.

[⌊Accounts page — connected accounts with token status badges.⌉⌊Accounts page — 
connected accounts with token status badges.⌉[

Accounts page — connected accounts with token status badges.

## Blocks

This plugin provides 2 blocks.

 *   VaultFeed
 *   VaultFeed Rating

## Installation

 1. Upload the `vaultfeed` folder to `/wp-content/plugins/`
 2. Activate the plugin via **Plugins  Installed Plugins**
 3. Go to **VaultFeed  Settings** and enter your app credentials for the platforms 
    you want to use
 4. Go to **VaultFeed  Accounts** and connect your accounts
 5. Go to **VaultFeed  Feeds** and click **+ New Feed** to create your first feed
 6. Copy the shortcode (e.g. `[vaultfeed id="1"]`) and paste it into any post, page,
    or widget

#### Requirements

 * WordPress 6.4 or later
 * PHP 8.1 or later
 * Developer credentials for each platform you wish to connect:
    - **Facebook / Instagram** — a Meta Developer App with `pages_read_engagement`
      and `instagram_basic` permissions
    - **Twitter / X** — a Twitter Developer App with OAuth 2.0 enabled and scopes`
      tweet.read users.read offline.access`
    - **YouTube** — Google Cloud OAuth 2.0 credentials with the YouTube Data API
      v3 enabled
    - **TikTok** — a TikTok Developer App with `user.info.basic` and `video.list`
      scopes
    - **Threads** — a Meta Developer App with `threads_basic` permissions (separate
      from your Facebook / Instagram app)
    - **Google Business Profile reviews** — same Google Cloud project as YouTube,
      with the Business Profile APIs additionally enabled
    - **Yelp** — a Yelp Fusion API key from the [Yelp developer portal](https://docs.developer.yelp.com/)
    - **LinkedIn** — a LinkedIn Developer App with `openid profile email r_member_social
      w_member_social` scopes enabled
    - **Bluesky** — no developer account needed; create an App Password at bsky.
      app  Settings  App Passwords
    - **Pinterest** — a Pinterest Developer App with `boards:read`, `pins:read`,
      and `user_accounts:read` scopes
    - **Mastodon** — no credentials required; public accounts are accessible via
      the Mastodon public API

## FAQ

### Do I need developer accounts for the platforms I want to connect?

Most platforms require their own developer app credentials, entered in **VaultFeed
Settings**. The setup takes 10–15 minutes per platform. Two exceptions: **Bluesky**
uses an App Password (no developer account needed), and **Mastodon** uses public
accounts with no credentials at all. The credentials each platform needs are listed
under **Installation  Requirements** above, and each platform’s section in **VaultFeed
Settings** links to the matching developer portal. For the exact OAuth callback 
URL and required scopes for each platform, plus troubleshooting, see the [VaultFeed setup guide](https://ccgnomes.com/vaultfeed/support/).

### Where can I get help?

Setup, troubleshooting, and a support ticket form are on the [VaultFeed Setup & Support page](https://ccgnomes.com/vaultfeed/support/).
You can also use the plugin’s support forum here on WordPress.org.

### Where are my OAuth tokens stored?

Tokens are stored in the `vaultfeed_tokens` table in your WordPress database, encrypted
with AES-256-GCM. The encryption key is derived locally from your `AUTH_KEY` and
a random salt stored in your WordPress database (VaultFeed does not write to `wp-
config.php`). No token is ever transmitted to VaultFeed or any third party.

### Is there a limit on feeds, posts, or layouts in the free version?

No. There is no cap on the number of feeds you can create, no cap on posts per feed,
and all nine layouts (Grid, List, Masonry, Carousel, Slider, Waterfall, Ticker, 
Timeline, Wall) are available. The optional “Powered by VaultFeed” attribution link
is off by default and only appears if you turn it on yourself in a feed’s Display
Settings.

### Does this work with WordPress multisite?

Yes. Each sub-site manages its feeds and account connections independently.

### Is this GDPR-compliant?

VaultFeed sends nothing to its authors and makes no unsolicited third-party network
calls. By default it stores no visitor personal data: click and impression analytics
are aggregate counters, and the visitor IP address is used only transiently (hashed,
in short-lived rate-limit/de-duplication keys) and is never stored in readable form.
If you turn on the optional **email capture** form, the addresses visitors submit
are sent to the Mailchimp or ConvertKit account you configured — or, if neither 
is configured, kept in your own WordPress database — so you are the data controller
for them and should disclose that in your Privacy Policy. Your wider GDPR obligations
depend on the content of your social media feeds and your agreements with the respective
platforms. See the “External services” section for exactly what each optional integration
sends.

### Can I display the same feed on multiple pages?

Yes — use the same shortcode `[vaultfeed id="N"]` anywhere on your site. The cached
content is shared so it counts as one API call.

### Does the plugin support Instagram Stories?

Yes (free tier). Stories are a feed type you can choose in the feed creation wizard,
step 3.

### Is my data sent to VaultFeed servers?

No. VaultFeed operates no background service and receives no data from your site.
Out of the box, the plugin makes outbound HTTP calls only to the social platforms
you explicitly connect (to fetch feed content) and to the WordPress.org API (to 
check for plugin updates). The optional integrations listed under “External services”(
Mailchimp, ConvertKit, Google Analytics 4, Slack, Zapier, Shopify, and your own 
webhooks) contact those services only after you enter your own credentials or URL
for them — and even then, the data goes to **your** account with that service, never
to VaultFeed. Your OAuth tokens never leave your server.

### Does this work with caching plugins?

Yes. VaultFeed maintains its own server-side cache in the `vaultfeed_feed_cache`
database table, independent of any full-page cache. Feeds load correctly behind 
WP Super Cache, W3 Total Cache, WP Rocket, LiteSpeed Cache, and similar plugins.
If you use a page cache with a very long TTL, enable **auto-refresh** in your feed’s
Display Settings so content stays current without a full page reload.

### What PHP version is required?

PHP 8.1 or higher is required. PHP 8.2 and 8.3 are fully supported. WordPress 6.4
or higher is required.

### What happens if my token expires?

VaultFeed checks token expiry and marks the account as “expiring” in the Accounts
page. You can reconnect by clicking **Reconnect** on the account card. Feeds for
that account are paused until the token is refreshed.

## Reviews

There are no reviews for this plugin.

## Contributors & Developers

“VaultFeed” is open source software. The following people have contributed to this
plugin.

Contributors

 *   [ CCGnomes ](https://profiles.wordpress.org/theteamccgnomes/)

[Translate “VaultFeed” into your language.](https://translate.wordpress.org/projects/wp-plugins/vaultfeed)

### Interested in development?

[Browse the code](https://plugins.trac.wordpress.org/browser/vaultfeed/), check 
out the [SVN repository](https://plugins.svn.wordpress.org/vaultfeed/), or subscribe
to the [development log](https://plugins.trac.wordpress.org/log/vaultfeed/) by [RSS](https://plugins.trac.wordpress.org/log/vaultfeed/?limit=100&mode=stop_on_copy&format=rss).

## Changelog

#### 0.9.0

Fixes found by independent audits of 0.8.9 and 0.8.10. No database changes.

 * Security (WP.org review): the feed-import upload reader no longer carries any`
   phpcs:ignore` for a sanitisation rule. `$_FILES` fields are read on the line 
   that sanitises them: the client file name with `sanitize_file_name( wp_unslash())`,
   PHP’s own temporary path with `filter_var( …, FILTER_UNSAFE_RAW )`, which returns
   it byte for byte so `is_uploaded_file()` still accepts it.
 * Fix: the wizard’s live preview no longer stays on “Loading…” for a feed on this
   site’s own posts (its built-in account is never ticked) or when no account is
   ticked; it previews, or says what is missing.
 * Fix: editing an aggregated feed no longer drops members that are paused, scheduled
   or in error; they are listed with their status and kept unless you untick them.
 * Fix: editing a feed in the wizard keeps a “Show only” post-type list it has no
   button for (carousel, reel, text, link or three types) instead of turning it 
   into photos or photos and videos, and no longer clears a stored minimum of 0,
   a stored date that is not plain YYYY-MM-DD or a stored schedule saved without
   a time zone (read as UTC).
 * Fix: the legacy widget and the block editor keep a stored feed that is paused,
   scheduled or in error and label it with its name and status (before, the widget
   saved “no feed” and the block said “not available to you”).
 * Fix: an aggregated feed’s RSS ETag and Last-Modified now include the members 
   the reader may see, so hiding a post in a member, or pausing or restricting a
   member, changes the validators and a revalidating client gets the new feed instead
   of a 304. A shared cache may still serve its copy until it expires, so the aggregate’s`
   Cache-Control: max-age` is now the shortest limit over the feed and its visible
   members (60 s for a moderated member, 300 s with hidden posts, never past a scheduled
   end), not just the aggregate’s own settings.
 * Fix: a legacy widget that holds a paused, scheduled, errored or restricted feed
   can be cleared again by choosing “— Select feed —”; before, the save put the 
   old feed back.
 * Security: the block editor lists the names of paused, scheduled or errored feeds
   only to users who can edit others’ posts, and to anyone else only the feeds already
   used in the post they are editing (Contributors no longer see unpublished feed
   names).
 * Fix: the wizard’s live preview of an aggregated feed shows a translated explanation
   instead of a red untranslated “No adapter for source: aggregated”.
 * Fix: inactive members of an aggregated feed stay hidden in the wizard when they
   are filtered out (a CSS rule overrode the `hidden` attribute); the same rule 
   is applied to the account and feed-type lists.
 * Fix: the wizard shows a stored schedule that has no time zone as the UTC instant
   the server enforces, and an untouched save keeps stored dates (including those
   saved with a time) exactly as they were.
 * Fix: the feed-import upload reader no longer alters PHP’s temporary file path(
   double spaces and `%` sequences survived `sanitize_text_field()` badly); the 
   client file name still goes through `sanitize_file_name()`.
 * Performance: an aggregated feed’s RSS reads its members’ moderation versions 
   in one query instead of one per member.
 * Fix: What’s New shows the current version badge and lists this release.

#### 0.8.9

Supersedes the unreleased 0.8.6, 0.8.7 and 0.8.8 builds; everything below is new
since 0.8.5.

 * Fix: editing a feed in the wizard no longer resets anything the form did not 
   change. The edit form now loads every field it posts (auto-block terms, alert
   e-mail, approval mode, alert switch, call-to-action, UTM, ticker speed, link 
   overrides, GDPR gate and the rest) and posts an explicit 0 / empty for every 
   unchecked box or cleared field, and the server changes only the fields the form
   actually sent. Display settings are overlaid on the stored ones the same way,
   so values set through REST or an import that the wizard has no input for (dark-
   mode preference, e-mail capture) survive an edit. The last aggregate member and
   the last injected card can be removed, and a field can still be cleared on purpose.
 * Fix: the wizard can create and edit feeds for sources with no token row. Its 
   account step is now built from the same per-source rule as activation: Mastodon(
   account row only), this site’s own posts (a built-in “This site” account, no 
   connection needed) and the tagged / mentions sources (the Instagram / X accounts).
   Before, those sources offered no account, so such a feed could not be created
   or edited in the wizard.
 * Fix: the scheduled-feed cron (scheduled to active, expired to paused) now retires
   the cached feed lists; they stayed stale for up to 60 seconds on a persistent
   object cache. The list generation is a fresh token, not a counter, so an evicted
   counter can never revalidate an old list.
 * Fix: the wizard’s schedule fields now show and accept your browser’s time zone
   and store UTC (the schedule fields were labelled UTC but a stored UTC time was
   displayed unchanged and a local time was saved as UTC). An untouched schedule
   is saved byte for byte as stored.
 * Fix: `PUT /feeds/{id}` with a `filter_settings` object now merges the keys it
   names over the stored settings; before, an omitted `exclude_ids` un-hid every
   moderated post.
 * Fix: the legacy widget and the block editor keep a stored feed the current user
   may not view (it was dropped when that user saved the widget or block).
 * Fix: the RSS feed’s Last-Modified and ETag now change when a post is hidden, 
   restored, approved or rejected or the feed’s visibility, schedule or filters 
   are saved, so a conditional GET or shared cache cannot keep serving a moderated-
   away post; a feed in approval mode is shared for at most 60 seconds, one with
   hidden posts for 5 minutes.
 * Security: every `vaultfeed/v1` REST response that depends on who asks (including`
   GET /feeds/{id}/posts`, which answers 404 or posts per viewer) now sends `Cache-
   Control: no-cache, must-revalidate, max-age=0, no-store, private`. WordPress 
   sends those headers on REST responses only to logged-in requests. The embed script
   and the signed image proxy are the same for every viewer and stay cacheable.
 * Hardening: the verified Meta data-deletion payload is sanitised after the signature
   check; the feed-import upload fields are read through one helper that unslashes
   the client file name and keeps PHP’s own temporary path as reported.
 * Security: the RSS endpoint sent `Cache-Control: public, max-age=3600` on every
   response, replacing the no-cache headers WordPress sends to logged-in users, 
   so a shared cache or CDN could store an administrator’s copy of an aggregated
   or restricted feed and serve it to anonymous visitors for an hour. Responses 
   to logged-in users, and every feed that is not public to everyone, are now private
   and not stored; a public feed read anonymously may be shared for up to an hour,
   never past its schedule, and varies by Cookie. The JSON Feed and embed page always
   send no-cache headers, `admin-ajax.php` sends them for every AJAX action, and
   the image proxy and embed script are the same for every viewer.
 * Security: the block editor, the Elementor / Divi / Beaver Builder modules and
   the legacy widget listed every active feed in their feed pickers, so a Contributor
   opening the block editor learned the names and ids of editor-only and admin-only
   feeds. The pickers now offer only the feeds the user may view.
 * Fix: saving a feed in the wizard replaced its filter settings with the form’s
   fields; the form never carries the moderation list, the blocked accounts or the
   minimum-comments filter, so every edit un-hid every moderated post and cleared
   the blocklist. The wizard now starts from the stored settings and changes only
   the fields it shows.
 * Fix: hiding or restoring a post, toggling approval mode, pinning posts and saving
   the blocklists on an active feed whose source stores no token (Mastodon, this
   site’s own posts) or reads another source’s token (Instagram tagged, X mentions)
   failed with a 500, because every save re-ran the activation check against the
   token table. The connected-account check now runs only when a save takes a feed
   live or moves it to another account, follows each source’s own rule, and is answered
   as a 400 with the reason everywhere — including `wp vaultfeed feed create`, which
   used to stop with an uncaught exception.
 * Fix: “Editors and above” visibility now means the Editor role and above (`edit_others_posts`);
   earlier builds tested `edit_posts`, which Contributors and Authors also hold.
 * Fix: feed lists read with a limit (the block editor’s) were not invalidated on
   save and could be served up to 60 seconds stale under a persistent object cache.
 * Fix: RSS and JSON Feed autodiscovery links in `<head>` are emitted only for feeds
   that are public to everyone, so a page cache warmed by a logged-in reader never
   advertises a restricted feed.
 * Fix: a database insert failure is reported in plain language in the wizard, Duplicate,
   REST and WP-CLI instead of the raw `db_insert_failed` code; two repository messages
   are translatable.
 * Uninstall: also removes the network settings option (the multisite sitemeta row
   included) and the migration wizard’s per-user “dismissed” flags.
 * Network settings: the “share feeds across network” and “share tokens across network”
   switches were stored but read by nothing; both are removed from the Network Admin
   screen (the allowed-sources list and the subsite override remain).
 * Note: feed schedules are stored in UTC. A schedule saved by a build before 0.8.6
   in the site’s local time is left as stored (it is not rewritten); re-enter it
   in the wizard if it is off. The wizard converts between your browser’s time zone
   and UTC.
 * Security: on sites with a persistent object cache (Redis, Memcached — common 
   on managed hosts) a feed’s rendered HTML was cached for 60 seconds under the 
   feed id alone, so after an administrator viewed an aggregated feed, a copy that
   included its admin-only member feeds was served to anonymous visitors through
   the shortcode, block, widget, embed page and page builders. Rendered HTML now
   lives in a request-scoped (non-persistent) cache group and is keyed by viewer
   class and by the member feeds that viewer may see.
 * Security: the `[vaultfeed_hashtag]` shortcode read the cached posts of every 
   active feed, ignoring each feed’s visibility (logged-in / editor / admin), schedule,
   moderation list and approval mode. A post now appears only if the feed it came
   from would show it to the current visitor.
 * Security: an aggregated feed shows a member feed’s posts only to visitors who
   could view that member feed directly — a member restricted to logged-in users,
   editors or admins, paused, or outside its schedule contributes nothing to anonymous
   visitors. Only feeds whose source is “Aggregated” merge other feeds; a member
   list on a platform feed is dropped.
 * Security: `GET /wp-json/vaultfeed/v1/feeds/{id}/posts` returned a restricted 
   feed’s name, source and posts to anonymous callers. It now applies the same visibility
   gate as every other public surface and answers 404 (indistinguishable from a 
   nonexistent feed). An API key identifies a client for rate limiting; it does 
   not make a restricted feed public.
 * Activating a feed (bulk action, REST API, WP-CLI) or saving it active now checks
   that its account is really connected on this site, not just that an account id
   is present — an imported feed keeps the account id of the site it was exported
   from — and refuses with a clear message otherwise. Every imported feed is now
   created paused — 0.8.5’s admin-post import handler and format-1 files still created
   active feeds; connect the account under VaultFeed  Accounts, then activate.
 * Imports and stored settings: display numbers are clamped to the wizard’s ranges
   wherever a feed is read — import files, REST updates and feeds already stored—
   so a stored 100 posts per page renders as 50, and an auto-refresh interval the
   wizard does not offer runs at the nearest offered one (120 s becomes 60 s; nothing
   polls faster than 30 s) instead of switching auto-refresh off. Nested values 
   where text is expected are ignored instead of raising PHP notices. Imported schedules
   are stored in UTC in both the JSON copy and the indexed columns the scheduler
   reads (a “10:00+02:00” schedule used to be stored two hours late, and a non-ISO
   date used to restrict the renderer but never reach the scheduler). An aggregated
   feed’s member list is capped at 50 everywhere it is read and the members are 
   loaded in one query. Partial imports report which feeds were skipped and why,
   in plain language.
 * REST: creating or cloning a feed that fails validation (for example an empty 
   or unconnected account) returns 400 with the reason instead of a 500; the batch
   endpoint no longer echoes raw request values back in `failed`.
 * Fix: link-in-bio profiles never showed their embedded feed (the profile passed
   the feed as `feed_id`, the shortcode reads `id`).
 * Fix: the block’s “email capture after post” attribute is clamped to 1–100 like
   the stored setting; feed validation messages are translatable; double-escaped
   characters in import error messages.

#### 0.8.5

 * Fix wizard deprecation notices, aggregated feed save, export/import round trip
   and import error handling.

#### 0.8.4

 * Security (WP.org review): every request value is now sanitised at the point it
   is read with the WordPress function for its type, with no sanitisation finding
   suppressed. Feed imports (file upload, the Import Feed button and WP-CLI) validate
   the file with `is_uploaded_file()`, a 1 MB limit and a `.json` extension / MIME
   check, then size- and depth-limit the JSON and sanitise every decoded field before
   use. Injected cards are submitted as separate position / HTML / repeat fields,
   limited to 50 cards and a fixed HTML tag whitelist (no scripts, event handlers
   or `javascript:` links), and filtered again when rendered.
 * Behaviour change (intended): existing injected cards that used tags outside that
   whitelist — for example `table`, `video`, `iframe`, `form` or `section` — now
   have those tags stripped when the feed renders. Edit the card in the Feed Wizard
   if it relied on them. URL fields (link-in-bio links, webhook URLs, hashtag-to-
   URL maps) now keep percent-encoded characters intact.

#### 0.8.3

 * WP.org review: removed a third-party review-site integration (source, account
   connection, and all related code) that WordPress.org does not permit for plugins
   listed on WordPress.org. Feed source count is now 20. If you had connected an
   account for it, that connection and any feeds built on it are simply gone — nothing
   to clean up.

#### 0.8.2

 * Security: fixed a cross-site scripting hole in the optional social-proof popup,
   which inserted a post’s text, image URL and permalink into the page without escaping
   them. On hashtag, tagged-post, mention and review feeds that content is authored
   by third parties, so it could carry markup. All three values are now escaped,
   and every post URL the front-end script puts in an `href` or `src` must be a 
   plain http(s) link, so a `javascript:` or `data:` URL from a platform response
   can no longer be used.
 * Security: fixed a stored cross-site scripting hole in the review schema (JSON-
   LD) output. Review text and reviewer names come from the public on Google and
   Yelp, and were encoded in a way that let a review containing a closing `</script
   >` tag end the JSON-LD block and run as markup on your page. `<`, `>` and `&`
   are now hex-escaped inside the block; search engines read the data exactly as
   before.
 * Security: the Meta data-deletion callback (`/vaultfeed/v1/fb-delete`) wrote a
   new row to the options table for every request it received, including unsigned
   and forged ones, so an anonymous caller could grow the database without limit.
   The request’s Meta signature is now verified before anything is stored, unverified
   requests are refused outright, the route is rate limited, and old deletion logs
   are pruned after your data-retention period.
 * Security: the Embed SDK script — which runs on the third-party sites you embed
   feeds into — now only accepts plain http(s) URLs for post links and images, so
   a `javascript:` URL in a platform response cannot run on the host page.
 * Security: the public email-capture endpoint (`/vaultfeed/v1/subscribe`) accepted
   submissions even on sites that had never switched email capture on, writing addresses
   to the database or forwarding them to a configured Mailchimp/ConvertKit account.
   It now only accepts a sign-up for a feed the visitor is allowed to see and that
   has the email-capture form enabled, and is rate limited.
 * Security: fixed an XML injection in the RSS feed — a post whose text contained
   the literal sequence `]]>` closed the description’s CDATA section early, letting
   the rest of the post be parsed as raw markup by feed readers.
 * Security: every public surface that can expose a feed — the iframe embed page,
   the RSS feed, the JSON Feed endpoint, the oEmbed provider and the click/impression
   tracking endpoints — now goes through one shared access gate (`FeedLoader::is_viewable()`)
   that honours the feed’s status, its configured visibility (public / logged-in/
   editors / admins) and its scheduled window. Previously some of these surfaces
   checked only that the feed was active, so a feed restricted to logged-in users
   or admins could still have its name and embed URL disclosed. A `vaultfeed_feed_is_viewable`
   filter is available for custom rules.
 * Security: the oEmbed endpoint now resolves only embed URLs belonging to this 
   site (it previously accepted any URL carrying a `vaultfeed_embed` query variable,
   including URLs on other hosts), returns the same 404 for every rejection so responses
   cannot be used to tell which feed IDs exist, and is rate limited to 30 requests
   per IP per minute.
 * WP.org review: upgraded the bundled SortableJS from 1.15.2 to 1.15.7 (latest 
   stable) and shipped its MIT licence alongside it at `assets/js/vendor/sortable-
   LICENSE.txt`.
 * WP.org review: the plugin header was missing its `License URI`, and the packaged`
   composer.json` still declared `"license": "proprietary"` next to a GPL plugin
   header. The header is complete, the package now declares GPL-2.0-or-later throughout,
   and the full licence text ships as `LICENSE.txt`.
 * Change: AI caption rewriting now uses WordPress’s built-in AI Client (WordPress
   7.0+) exclusively — it sends the caption to whichever AI provider you have connected
   in WordPress. VaultFeed no longer contacts any AI provider directly and stores
   no AI key. The feature is available only when you have connected an AI provider
   in WordPress; the “Enhance Caption” button is hidden otherwise.
 * New: the Social Proof Popup can now actually be switched on — it has a section
   under VaultFeed  Settings (feed, corner, delay). The feature shipped before without
   any settings screen, and its script could not load a post; both are fixed, and
   it now works on pages that have no feed on them.
 * Fix: clicking “Save Settings” saved your changes but then left a half-rendered
   screen with a “headers already sent” warning, because the form was processed 
   after the page had started printing. It is now processed before any output.
 * Fix: the Welcome / onboarding screen lacked WordPress’s standard page wrapper,
   so admin notices from WordPress and other plugins were placed at an arbitrary
   point on it. They now appear above the welcome card.
 * Fix: on sites using plain permalinks the Moderation screen could not load any
   posts (its request URL contained two `?`).
 * Fix: on page-cached sites the nonce-refresh endpoint now also renews the click-
   tracking nonce, and its response is marked `no-store` so a cache or CDN can never
   serve a stale one. It is rate limited.
 * Fix: the email-capture form never actually submitted — the front-end script was
   not given the REST URL, and sent a nonce WordPress rejects. Sign-ups now work
   for visitors and logged-in users, including on page-cached sites.
 * Fix: the hourly clean-up job queried a column that does not exist (`logged_at`
   instead of `created_at`), so it logged a database error on every run and the 
   moderation log was never pruned.
 * Fix: the legacy JSON Feed address (`?vaultfeed_json_feed=1&feed_id=N`) always
   served feed 1 regardless of `feed_id`. Also stopped registering an unprefixed
   public `feed_id` query variable, which could collide with other plugins.
 * Fix: uninstalling on a multisite network overwrote WordPress’s `$blog_id` global
   while looping over subsites.
 * Fix: RSS items for posts with no author handle rendered an empty `(@)` after 
   the author name.
 * Housekeeping: `wp_json_encode()` and `rawurlencode()` in place of their plain
   PHP counterparts, and the last two ungated `error_log()` calls now only write
   when `WP_DEBUG` is on.
 * Docs: documented the social platform media CDNs under “External services” — feed
   images and videos are served from the platforms’ own CDNs to the visitor’s browser(
   or through your own server if you enable the image proxy) — and corrected the
   third-party libraries note, which previously read as though no images at all 
   were loaded from external servers.

#### 0.8.1

 * WP.org review: documented every previously-undisclosed optional third-party service
   in the “External services” section — Mailchimp and ConvertKit/Kit (email capture),
   the Google Analytics 4 Measurement Protocol (click analytics), Slack and Zapier
   webhooks (notifications/automation), the Shopify Storefront API (shop badge),
   and user-defined outbound webhooks. Each is opt-in and inactive unless you enter
   your own credentials or URL. Also added the missing Instagram privacy-policy 
   link and example Mastodon instance terms.
 * WP.org review: corrected a review-platform integration’s terms and privacy URLs,
   which now pointed to its current legal pages (the previous paths returned 404),
   and replaced three other dead links (Yelp developer portal, and two links to 
   a not-yet-live domain).
 * WP.org review: SortableJS is now bundled with the plugin (`assets/js/vendor/`)
   instead of being loaded from the jsDelivr CDN on the Moderation screen. The plugin
   no longer loads any asset from an external server.
 * Docs: corrected the readme’s description of token-salt storage — the salt lives
   in the `vaultfeed_token_salt` database option; the plugin does not write to `
   wp-config.php` (that behaviour was removed in 0.2.1).
 * Security: the image-proxy REST endpoint now has a real permission callback — 
   it responds only when the proxy is enabled and the requested URL carries a valid
   server-issued HMAC signature, so it can no longer be used as an open proxy.
 * Security: the AJAX “load more” endpoint now enforces the same feed visibility(
   public / logged-in / editor / admin) and schedule-window rules as the initial
   render.
 * Security: the public impression and click-tracking endpoints now reject feed 
   IDs that do not exist or are not active, so they cannot be used to create counter
   rows for arbitrary IDs.
 * Security: feed-import uploads are now validated with `is_uploaded_file()` and
   each `$_FILES` field is sanitised individually.
 * Fix: per-feed API-key restrictions were never enforced on `/feeds/{id}/posts`
   because the permission check read the wrong request parameter.
 * WP.org review: everything the plugin stores now uses the distinctive `vaultfeed_`
   prefix instead of the two-letter `vf_` — all 11 database tables, the impression-
   counter options, every transient, the notice-dismissal user meta, a nonce action,
   and the JavaScript globals (`vfConfig`  `vaultfeedConfig`, etc.). Existing sites
   are migrated automatically and losslessly (database migration 0007: tables are
   renamed in place, options and user meta are renamed, short-lived transients are
   discarded). Only VaultFeed’s own explicitly-listed names are touched.
 * Fix: uninstall now drops VaultFeed’s tables from an explicit list. It previously
   dropped every table matching `{prefix}vf_%`, which could have removed another
   plugin’s tables that happened to share the two-letter prefix.
 * Fix: pending database migrations now run on the first request of any kind after
   an update, not only on the next wp-admin visit, so the front end never queries
   a not-yet-migrated schema after a background auto-update.
 * Security: rate limiting and click/impression de-duplication now identify visitors
   by `REMOTE_ADDR` only. The `X-Forwarded-For` and `CF-Connecting-IP` headers —
   which any client can forge to obtain a fresh rate-limit bucket — are honoured
   only if you declare a trusted proxy under VaultFeed  Settings  Reverse Proxy /
   CDN. **If your site is behind Cloudflare or another reverse proxy, set this after
   updating**, otherwise all visitors share the proxy’s address for rate-limiting
   purposes. A `vaultfeed_client_ip` filter is available for custom set-ups.
 * Security: Twitch and Snapchat bearer tokens, and a newly-created API key awaiting
   its one-time display, are now encrypted (AES-256-GCM) before being cached in 
   a transient, matching the Reddit fix above. Any plaintext token cached by an 
   older build is discarded on upgrade.
 * Fix: three REST routes — the Embed SDK loader (`/embed.js`), the email-capture
   endpoint (`/subscribe`) and the image proxy (`/image`) — were never actually 
   registered, because each added its `rest_api_init` callback from inside that 
   same hook. The Embed SDK and the email-capture form now work.
 * Fix: the Settings screen raised “Undefined array key” PHP warnings under `WP_DEBUG`
   on sites that had not saved settings yet.
 * Fix: the admin-bar “VaultFeed” links and the feed-error notification email pointed
   at a non-existent admin page and produced a “not allowed” error.
 * i18n: regenerated `languages/vaultfeed.pot` (it had fallen out of date and was
   missing over half of the plugin’s strings).
 * Security: the front-end click-tracking AJAX endpoint now returns an explicit 
   error (never a misleading “success”) when the security nonce is missing or invalid.
 * Security: removed the redundant unauthenticated REST route `POST /feeds/{id}/
   posts/{platform_id}/click`. Click tracking is handled solely by the nonce-protected`
   admin-ajax` endpoint, closing an endpoint that allowed arbitrary click-counter
   increments.
 * Security: the Meta (Facebook/Instagram) data-deletion callback now purges cached
   content only when the `signed_request` signature is cryptographically verified
   against the stored app secret; unverifiable requests are acknowledged (per Meta’s
   spec) but take no privileged action.
 * Security: the image proxy now fetches through `wp_safe_remote_get()` (blocking
   private/reserved IPs on every redirect hop) and enforces a 5 MB response-size
   cap before caching, mitigating SSRF and unbounded-cache abuse.
 * Security: the Reddit app-only OAuth bearer token is now encrypted (AES-256-GCM)
   before being cached in a transient, rather than stored in plaintext.
 * Security: display, filter, and cache settings are now sanitised per-field when
   hydrated from imported/uploaded JSON (`from_json`) — text via `sanitize_text_field`,
   URLs via `esc_url_raw`, custom CSS via `wp_strip_all_tags`, CSS lengths and colours
   against strict patterns, emails via `sanitize_email`, injected-card HTML via `
   wp_kses_post`, and JSON maps decoded and re-encoded with typed values.
 * WP-CLI: `wp vaultfeed feed export` now always writes into the plugin’s folder
   inside `wp-content/uploads` (resolved at runtime via `wp_upload_dir()`); the `--
   file` option is treated as a filename only, never an arbitrary path.

#### 0.8.0

 * Housekeeping: versioning reset to 0.8.0 ahead of a 1.0 release (X.Y.Z, each of
   Y and Z rolling over to the next level after 9 — e.g. 0.8.9  0.9.0, 0.9.9  1.0.0).
   Prior internal builds were numbered 0.1.0–0.45.x.
 * Fix: two activation-fatal bugs — a stale Composer autoloader referencing dev-
   only packages, and Elementor/Divi/Beaver Builder integrations that errored on
   sites without those page builders installed.
 * Fix: WP-CLI command conflict between `wp vaultfeed` and its subcommands; renamed
   to `wp vaultfeed info`.
 * Fix: cron scheduling deferred to `init` in 5 classes to avoid a false “translation
   loaded too early” notice on WordPress 6.7+.
 * WP.org review: resolved remaining Plugin Check findings — output escaping, input
   sanitization, SQL preparation, discouraged functions, i18n, and nonce checks 
   across ~35 files.
 * Pro: removed the “no separate Meta developer account” option — Facebook/Instagram
   now always use your own Meta app, like every other platform. ReelVault, DHT storage,
   and priority support remain in Pro.

#### 0.45.13

 * WP.org review: fixed the free-build packaging scripts (`build/make-zip.py` and
   the `build-free` release job) so the WordPress.org zip no longer bundles directory
   screenshots or the Pro-only Holochain sidecar / DHT / ReelVault / license-validation
   code paths — that code now ships only in the separately-distributed Pro package,
   as already stated in this readme.
 * WP.org review: moved all remaining inline `<script>`/`<style>` output in the 
   admin UI to properly enqueued, conditionally-loaded assets (`wp_enqueue_script`/`
   wp_enqueue_style`) with dynamic values passed via `wp_add_inline_script`/`wp_localize_script`.
 * Security: added nonce verification to the front-end click-tracking AJAX endpoint(`
   vaultfeed_track_click`), including in embedded/iframe views.
 * Fix: Site Health’s token-encryption-salt check no longer reports a false “critical”
   error — it now recognizes the salt stored in `wp_options` (current behaviour)
   in addition to the legacy `wp-config.php` constant.
 * Docs: corrected readme claims about free-tier limits (feed count, post count,
   layout choice, forced attribution) that no longer reflected actual plugin behaviour—
   the WordPress.org edition has always been fully functional with no artificial
   caps.

#### 0.45.12

 * Fix: CacheSweep now calls EngagementMetrics::sweep_old_keys() on every hourly
   run so vf_imp_* impression options are pruned instead of accumulating indefinitely.
 * Fix: Plugin::uninstall() now deletes vf_imp_* impression options, which were 
   previously orphaned after plugin deletion because they use a different prefix
   than vaultfeed_*.

#### 0.45.11

 * Security: ajax_poll_new (nopriv AJAX) now enforces a 120 requests/minute rate
   limit per IP via RateLimiter.
 * Security: ajax_poll_new known_ids array is capped at 500 entries to prevent memory
   exhaustion from oversized payloads.

#### 0.45.10

 * Fix: ApiKeyManager SELECT queries used backtick-quoted %i placeholder, silently
   breaking all API key authentication; fixed for validate() and list_all().
 * Fix: vf_api_keys added to SiteHealthChecks EXPECTED_TABLES so a missing table
   is detected.
 * Fix: FeedRepository::delete() now calls ApiKeyManager::remove_feed_from_all_keys()
   to remove stale feed ID references from API key scope metadata.

#### 0.45.9

 * Fix: CacheSweep data-retention DELETE queries were silently failing due to backtick-
   quoted %i placeholder producing double-backtick SQL; fixed for vf_feed_cache 
   and vf_click_events.
 * Fix: Added vf_moderation_log retention purge to CacheSweep (previously no purge
   existed for this table).
 * Fix: FeedRepository::delete() now calls delete_option(‘vaultfeed_approvals_{id}’)
   to remove orphan approval maps on feed deletion.
 * Fix: vf_click_events added to SiteHealthChecks EXPECTED_TABLES so missing-table
   detection is complete.
 * Fix: Removed stale wiring TODO comments from EmailCaptureController, ImageProxy,
   NotificationsPage, and AnalyticsCommand (all already wired).
 * Fix: AnalyticsCommand SELECT queries used backtick-quoted %i placeholder; fixed
   to bare %i.
 * Consistency: to_json() in CacheSettings, DisplaySettings, and FilterSettings 
   now uses wp_json_encode() instead of bare json_encode().

#### Earlier versions

See the full version history in the plugin’s changelog on GitHub.

## Meta

 *  Version **0.9.0**
 *  Last updated **11 hours ago**
 *  Active installations **Fewer than 10**
 *  WordPress version ** 6.4 or higher **
 *  Tested up to **7.1.3**
 *  PHP version ** 8.1 or higher **
 * Tags
 * [facebook](https://wordpress.org/plugins/tags/facebook/)[Instagram](https://wordpress.org/plugins/tags/instagram/)
   [privacy](https://wordpress.org/plugins/tags/privacy/)[social feed](https://wordpress.org/plugins/tags/social-feed/)
   [social media](https://wordpress.org/plugins/tags/social-media/)
 *  [Advanced View](https://wordpress.org/plugins/vaultfeed/advanced/)

## Ratings

No reviews have been submitted yet.

[Your review](https://wordpress.org/support/plugin/vaultfeed/reviews/#new-post)

[See all reviews](https://wordpress.org/support/plugin/vaultfeed/reviews/)

## Contributors

 *   [ CCGnomes ](https://profiles.wordpress.org/theteamccgnomes/)

## Support

Got something to say? Need help?

 [View support forum](https://wordpress.org/support/plugin/vaultfeed/)