Facebook Social Feed
Displays recent posts from a Facebook Page on your WordPress website via the Meta Graph API, using secure server-side requests and the [facebook_social_feed] shortcode.
by Paradorn · github.com/paradonk/facebook-social-feed · website
Install
No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:
wp plugin install https://github.com/paradonk/facebook-social-feed/archive/refs/heads/main.zipReadme
Facebook Social Feed
Displays recent posts from a single Facebook Page on your WordPress website, retrieved server-side through the Meta Graph API, via the [facebook_social_feed] shortcode. Built with a security-first design: the Page access token never leaves the server.
Contents
- Features
- Requirements
- Installation
- Facebook/Meta prerequisites
- Configuration through WordPress
- Recommended wp-config.php configuration
- Shortcode usage
- Styling / CSS customization
- Caching behavior
- WP-Cron behavior
- Error handling
- Token security
- Troubleshooting
- Development notes
- Hooks for developers
- Release procedure
Features
- Server-side Graph API integration (
wp_safe_remote_get()); the browser never talks to Facebook directly. - Access token and App Secret are never exposed in HTML, JavaScript, REST responses, logs, or public error messages.
- Credentials can be defined in
wp-config.php(recommended) or entered in the admin settings page. - Normalizes Facebook's response into a stable internal post format, filtering out Facebook's auto-generated boilerplate titles (e.g. "Photos from X's post").
- Caches the feed with the WordPress Transients API; falls back to the last successfully retrieved feed if Facebook is temporarily unavailable.
- Responsive grid (1-4 columns) and list layouts, framework-free CSS, with CSS custom properties for easy restyling.
- Admin "Test Connection", "Refresh Feed Now", and "Clear Cache" actions (AJAX, nonce + capability protected).
- Automatic refresh via WP-Cron.
- Sanitized, mapped error handling for expired tokens, missing permissions, rate limiting, and transport failures.
- Hooks (filters/actions) for developers; no required JavaScript to display the feed.
Requirements
- WordPress 6.4 or later
- PHP 8.0 or later
- A Facebook Page and a Page access token with
pages_read_engagement(or equivalent) permission
Installation
- Copy the
facebook-social-feedfolder intowp-content/plugins/. - Activate Facebook Social Feed from the Plugins screen.
- Go to Settings → Facebook Social Feed and configure your Page ID and access token (or define them in
wp-config.php, see below). - Add the
[facebook_social_feed]shortcode to any post or page.
Facebook/Meta prerequisites
You need:
- A Facebook Page you administer.
- A Meta App (developers.facebook.com) associated with that Page.
- A Page access token with permission to read the Page's posts. Generate a long-lived Page access token via the Graph API Explorer or your app's token flow; this plugin does not perform any OAuth flow itself.
- The Page's numeric Page ID (found in the Page's "About" section or via the Graph API Explorer).
This plugin does not create, refresh, or extend tokens for you. When your token expires, generate a new one and update it in wp-config.php or the settings page.
Configuration through WordPress
On Settings → Facebook Social Feed:
- Connection settings: Page ID, Page access token, Graph API version (e.g.
v23.0). - Feed settings: default number of posts, cache duration, default layout/columns, and which fields to show.
- Maintenance: Test Connection, Refresh Feed Now, Clear Cache, plus status information (current cache status, last refresh, last successful refresh, last sanitized API error) and a Debug mode toggle.
The access token field is a password input that is never pre-filled with the saved value. It shows only Token saved: Yes/No. Leaving it blank on save preserves the existing token; check Remove the stored access token to clear it.
Recommended wp-config.php configuration
For better security, define credentials as constants instead of storing them in the database:
define( 'FSF_FACEBOOK_PAGE_TOKEN', 'replace-with-page-token' );
define( 'FSF_FACEBOOK_PAGE_ID', 'replace-with-page-id' );
When these constants are defined, they always take precedence over the database settings, the corresponding settings-page fields become read-only status displays, and the token is never rendered back into any admin field.
Shortcode usage
[facebook_social_feed]
[facebook_social_feed limit="6" columns="3" layout="grid"]
[facebook_social_feed limit="5" layout="list" show_image="no"]
Attributes
| Attribute | Type | Range / values | Falls back to settings if invalid |
|---|---|---|---|
limit |
integer | 1-20 | yes |
columns |
integer | 1-4 | yes |
layout |
string | grid, list |
yes |
show_message |
boolean-ish | yes/no/true/false/1/0 |
yes |
show_date |
boolean-ish | yes/no/true/false/1/0 |
yes |
show_image |
boolean-ish | yes/no/true/false/1/0 |
yes |
open_new_tab |
boolean-ish | yes/no/true/false/1/0 |
yes |
class |
CSS class list | sanitized as CSS classes | n/a (empty if invalid) |
The shortcode is safe to use more than once on the same page: assets are only enqueued once regardless of how many instances are rendered.
Styling / CSS customization
The frontend stylesheet (public/css/social-feed.css) exposes CSS custom properties on .fsf-feed, so you can restyle the feed from your theme without editing plugin files:
| Property | Default | Controls |
|---|---|---|
--fsf-gap |
1.25rem |
Space between cards |
--fsf-border-color |
#e2e2e2 |
Card and placeholder borders |
--fsf-border-radius |
8px |
Card corner radius |
--fsf-background |
#ffffff |
Card background |
--fsf-text-color |
#1d1d1d |
Primary text color |
--fsf-muted-color |
#6b6b6b |
Date and status-message color |
--fsf-link-color |
#1877f2 |
"View post on Facebook" link color |
--fsf-columns |
3 |
Grid column count (set via shortcode/settings, not usually overridden directly) |
--fsf-card-height |
440px |
Total height of each grid card |
--fsf-card-media-height |
260px |
Height of the image/video thumbnail area in grid cards |
--fsf-card-message-lines |
3 |
Number of message lines shown before truncating with an ellipsis (grid cards) |
Grid cards are a fixed, uniform height with the title clamped to 2 lines and the message/description clamped to --fsf-card-message-lines; overflowing text is clipped rather than growing the card. List layout is left flexible (image beside text, height follows content) since forcing uniform height there tends to crop images awkwardly.
Example override, e.g. in your theme's Additional CSS:
.fsf-feed--grid {
--fsf-card-height: 500px;
--fsf-card-media-height: 300px;
--fsf-card-message-lines: 4;
}
Caching behavior
- If a fresh, unexpired cache exists (WordPress Transients API), it is returned immediately -- no Graph API request is made.
- If the cache has expired, the plugin requests fresh data (guarded by a 60-second refresh lock so concurrent visitors/cron do not trigger duplicate requests).
- On success, the fresh data is cached for the configured duration and also stored as the "last successful feed".
- On failure, the last successful feed is served instead (with the error recorded, sanitized, for administrators only). If no previous feed exists yet, a generic "temporarily unavailable" message is shown.
Cache duration options: 15 minutes, 30 minutes, 60 minutes (default), 2 hours, 4 hours, 12 hours, 24 hours.
WP-Cron behavior
A single WP-Cron event (fsf_refresh_facebook_feed) is scheduled hourly on activation and unscheduled on deactivation. Because the cache duration is configurable up to 24 hours while the cron event itself runs hourly, the cron handler only contacts the Graph API when the live cache has actually expired -- otherwise it is a no-op. This avoids the complexity of per-duration custom cron recurrences while still respecting the configured cache duration.
Error handling
Facebook/Graph API errors are mapped to internal WP_Error codes (fsf_missing_page_id, fsf_missing_token, fsf_invalid_token, fsf_missing_permission, fsf_page_not_found, fsf_rate_limited, fsf_http_error, fsf_transport_error, fsf_invalid_json, fsf_empty_response, fsf_api_error) with human-readable, sanitized messages. Public visitors never see raw API responses or technical details -- only "No Facebook posts are available at this time." or "Facebook updates are temporarily unavailable." (and only when no cached feed exists at all). Administrators can see the sanitized error message plus the Meta error code/subcode on the settings page.
Token security
- The access token is never printed into frontend HTML, JavaScript, REST responses, or public error output.
- The token field on the settings page is never pre-filled with the real value.
- Logging (only active when Debug mode is enabled) redacts tokens, App Secrets, and
access_token=.../Bearer ...patterns from any logged string. - The Graph API host is hard-coded to
graph.facebook.com; only the API version is configurable, never an arbitrary hostname. - The token is excluded from the plugin's own diagnostic display and from log output.
Troubleshooting
| Symptom | Likely cause |
|---|---|
| "The Facebook access token is invalid or expired." | The token expired or was revoked; generate a new Page access token. |
| "The application does not have permission to read this Facebook Page." | The token/app lacks the required Page permission. |
| "The configured Facebook Page could not be found or is not accessible with this token." | Wrong Page ID, or the token does not belong to that Page. |
| "The Facebook API rate limit has been reached." | Too many requests in a short window; the plugin will retry on the next cache expiry. |
| Feed shows old content | Expected -- the cache is still fresh, or a previous request failed and the last-known-good feed is being shown. Use "Refresh Feed Now" to force an update. |
Enable Debug mode on the settings page to log sanitized, high-level events (refresh started/completed, post count, HTTP status, sanitized Graph error code) to the PHP/WordPress error log.
Development notes
- Namespace:
Paradorn\FacebookSocialFeed. Classes are autoloaded fromincludes/class-{kebab-case-name}.php. - No Composer dependency, no jQuery, no frontend CSS framework.
tests/fixtures/contains sanitized Graph API JSON fixtures for manual/local development without live credentials; seetests/README.mdfor the full manual test plan.- This plugin was developed and reviewed without a local PHP CLI available for
php -l; run a syntax/lint pass (php -l, PHPCS with the WordPress ruleset) in your own environment before deploying to production, and complete a live Graph API test as described intests/README.md.
Hooks for developers
Filters:
| Hook | Purpose |
|---|---|
fsf_api_fields |
Change the Graph API fields parameter. |
fsf_api_post_limit |
Adjust the number of posts requested from the API (still capped at 25). |
fsf_normalized_post |
Modify a single normalized post array. |
fsf_normalized_posts |
Modify the full normalized posts array. |
fsf_cache_duration |
Override the effective cache duration (seconds). |
fsf_shortcode_attributes |
Modify the default shortcode attributes (from settings) before validation. |
fsf_feed_template / fsf_feed_item_template |
Override the template file path for the feed wrapper / a single item. |
fsf_formatted_message |
Modify the formatted (already-escaped) post message HTML. |
fsf_formatted_date |
Modify the formatted post date string. |
Actions:
| Hook | Fires |
|---|---|
fsf_before_feed_refresh |
Before a Graph API refresh attempt starts. |
fsf_after_feed_refresh |
After a successful refresh, with the normalized posts. |
fsf_feed_refresh_failed |
After a failed refresh, with the WP_Error. |
fsf_before_feed_render |
Before the shortcode output is built. |
fsf_after_feed_render |
After the shortcode output is built. |
The access token is never passed to any filter or action.
Release procedure
- Update
CHANGELOG.mdand theStable tag/ version headers infacebook-social-feed.phpandreadme.txt. - Run
php -lacross all PHP files and, if available, PHPCS with the WordPress Coding Standards ruleset. - Work through
tests/README.mdmanually, including at least one live Graph API test against a real Page/token. - Zip the plugin directory (excluding
tests/and VCS metadata, if desired) and tag the release.
License
GPLv2 or later. See LICENSE.