Skip to main content

Documentation

No results found.
Features Members

Social Feed Embed

WebProCMS ships with a built-in Social Feed Embed for showing your latest Instagram, Facebook, and YouTube posts on any page. Unlike the typical third-party embed widget, posts are synced into the CMS on a schedule and served entirely from...

WebProCMS ships with a built-in Social Feed Embed for showing your latest Instagram, Facebook, and YouTube posts on any page. Unlike the typical third-party embed widget, posts are synced into the CMS on a schedule and served entirely from your own site — no tracking scripts, no third-party iframes, no consent-banner implications, and no layout-shifting external JavaScript.


What it does

  • Three platforms — Instagram, Facebook Pages, and YouTube channels. Connect any number of accounts across platforms.
  • Two ways to connect Meta accounts — a one-click Connect with Facebook OAuth flow (sign in, pick your Page and/or Instagram account from a list), or the original paste-a-token path for people who already have tokens. YouTube stays API-key based.
  • Local mirror — each sync stores the latest N posts (per-account, default 12, up to 48) in the social_feed_posts table and downloads each post's image to storage/app/public/social-feed/{account}/. Provider CDN URLs (Instagram especially) expire after a few days — the local copy is the only URL the public site ever renders, so feeds never rot.
  • Album carousels — Instagram carousel posts and Facebook multi-photo posts cache every slide image (up to 10). The grid row flips through them in place with arrows and dots — visitors browse the whole album without leaving your site.
  • Inline video — Instagram and Facebook video posts download the actual video file (up to 60 MB) alongside the thumbnail. The grid row renders a real click-to-play <video> player (poster = the cached thumbnail, preload="none" so it costs nothing until clicked). Videos that can't be cached (YouTube, oversized files) keep the thumbnail + ▶ badge and link out to the platform.
  • Moderation — hover any cached post on the dashboard to pin it (shows first in every feed row, and survives aging out of the provider's feed window) or hide it (never renders publicly, but stays in the mirror so you can unhide it later). Both toggles clear the response cache immediately.
  • Zero third-party requests for visitors — the public site serves cached copies from your own storage. This also means the feature is invisible to the Cookie Consent system: nothing to gate, no banners needed.
  • Design-library rows — a new Social category in the design library (gated by the feature flag) ships two rows built on the collection pattern:
    • Social Feed – Grid: filterable card grid (platform filter pills) with captions, platform labels, dates, inline album carousels and video players, and links to each post.
    • Social Feed – Follow Strip: a compact single-row strip of the six latest post images with a heading and a follow button — a good fit just above the footer.
  • Data source preset — social_posts (group "Social") is available to any collection row/editor data binding, with platform + account filters and tokens for caption, excerpt, image, permalink, platform, handle, profile URL, date, video file URL, album images (JSON), and pin/video/album flags.
  • Automatic syncing — a social-feed:sync LazyCron task runs every 6 hours (no queue worker or system cron needed). Each sync that changes content clears the response cache so cached pages pick up new posts.
  • Token self-renewal (Instagram) — pasted long-lived Instagram tokens expire after 60 days; the syncer opportunistically refreshes them on every sync, so they never lapse as long as the site gets traffic at least once every 60 days. OAuth-connected accounts run on never-expiring Page tokens, and Facebook long-lived Page tokens and YouTube API keys don't expire either.
  • Post pruning — the local mirror matches the provider's latest N posts exactly. Posts deleted on the platform (or aged out of the window) are pruned along with their cached images — except pinned posts, which are kept deliberately.

How to turn it on

  1. Dashboard → Settings → Features → enable Social Feed Embed.
  2. Dashboard → Social Feed (appears in the sidebar once enabled) → Add account.
  3. Open the page editor and add a row from the design library's Social category.

Connecting accounts

Connect with Facebook (OAuth)

The recommended path for Instagram and Facebook. One-time setup per install (each site brings its own Meta app, same model as the Reviews addon's Google sign-in):

  1. Click the gear icon on Dashboard → Social Feed (or Set up Facebook login in the Add-account modal).
  2. Create an app in the Meta developer portal, add the Facebook Login product, and copy the redirect URI shown in the CMS into the app's Valid OAuth Redirect URIs.
  3. Save the App ID and App secret in the CMS modal.
  4. Click Connect with Facebook, sign in, and grant access to your Page(s).

The callback lists every Facebook Page you manage plus each Page's linked Instagram business account — connect any or all of them from the picker. Connections made this way use the Page's access token, which never expires (no token babysitting at all). Reconnecting the same Page/IG account updates the stored token in place rather than duplicating the account.

The known cost: the three permissions involved (pages_show_list, pages_read_engagement, instagram_basic) work immediately for users with a role on the Meta app (admin/developer/tester). For anyone else to use the login, the app must pass Meta App Review. For a single-site install where the site owner is also the app admin, review is usually unnecessary — add yourself to the app and connect.

Paste a token (fallback)

The original connection model — no OAuth, no app review flow:

Platform What you paste Where to get it Expiry
Instagram Long-lived access token Create an "Instagram API with Instagram Login" app in the Meta developer portal, generate a long-lived token for your professional (business/creator) account 60 days, auto-renewed on every sync
Facebook Page Long-lived Page access token e.g. via the Graph API Explorer with your Page selected Never (long-lived Page tokens)
YouTube API key + channel reference Create an API key with YouTube Data API v3 enabled in Google Cloud Console; the channel can be a URL, @handle, or UC… channel ID Never

Connecting validates the credential against the provider API, fills in the account identity (label, handle, profile URL), and runs the first sync immediately. Tokens are stored encrypted (Laravel encrypted cast) in the social_feed_accounts table; the Meta app secret lives in Settings like the Reviews addon's OAuth client.

The dashboard page

Dashboard → Social Feed shows every connected account with status (Connected / Error / Token expired), connection type (accounts connected through the OAuth flow carry a "Facebook login" note), cached post count, and last-synced time; per-account Sync now and Remove actions; a Sync all button; and a thumbnail grid of the latest cached posts (what the public rows render). Hovering a post reveals the pin and hide moderation controls; pinned/hidden posts carry corner badges, and album/video posts show count/▶ badges. Removing an account deletes its cached posts and images from your site — the actual social account is untouched.

Sync failures never break the site: the error is recorded on the account row (shown as a red badge with the provider's message) and the previously cached posts keep rendering publicly until a successful sync replaces them.

Architecture

  • Module: app/Features/SocialFeed/ — standard feature-module layout (ServiceProvider, Models, Support/Providers, DataSources, Http/Controllers, Database, routes, dashboard view). Feature key: social_feed, default off.
  • Providers: Support/Providers/{InstagramProvider,FacebookProvider,YouTubeProvider} implement the FeedProvider interface (connect, fetchPosts, refreshTokenIfDue) and normalize each platform's response to one post shape (including optional album_urls + video_url). Adding a platform = one new class + one match arm in SocialFeedSyncer::providerFor().
  • Two Instagram APIs, one provider: pasted-token accounts talk to graph.instagram.com (Instagram API with Instagram Login); OAuth-connected accounts carry meta.api = 'graph' + meta.ig_user_id and talk to graph.facebook.com (Instagram Graph API) authenticated by the linked Page's token. InstagramProvider branches internally; everything downstream is identical.
  • OAuth: Support/MetaOAuth (consent URL, code → long-lived user token exchange, Page/IG candidate listing, candidate → account connect) + Http/Controllers/MetaOAuthController (state-checked round-trip; single candidate connects directly, multiple candidates go to a session-fed picker modal). Mirrors the Reviews addon's GoogleBusinessOAuth pattern.
  • Syncer: Support/SocialFeedSyncer mirrors each account's latest N posts (upsert by external_id; image, album-slide, and video downloads to the public disk; prune-what-dropped-out with a pinned-posts exception) and clears the response cache when anything changed. Video downloads are size-capped (MAX_VIDEO_BYTES, 60 MB) and mime-checked so an HTML error page can never be stored as an .mp4.
  • Moderation columns: is_hidden + pinned_at on social_feed_posts. The preset's buildQuery() excludes hidden posts and orders pinned-first ahead of whatever ordering the row picked; re-syncs never touch either flag.
  • Sync cadence: LazyCron::register('social-feed:sync', interval: 21600) — piggybacks on normal request traffic like every other background task. The artisan command no-ops when the feature is disabled.
  • Rows: resources/design-library/rows/social/ — both rows carry @requiresFeature social_feed and use the collection pattern against the social_posts preset, so filter pills, editor Data controls, and drill-in editing all work like any other collection row. The carousel/video rendering lives in x-dl.image's structural video-src / album token attrs — public-render only, so the editor preview keeps the plain drillable image, and any collection row (not just the social ones) can opt in.
  • Cache behavior: the cached page HTML never varies per visitor. New posts land via sync → ResponseCache::clear(), so pages re-render with fresh content on the next request. Pin/hide toggles clear it too.

Limitations

  • Instagram requires a professional (business or creator) account — both Instagram APIs only serve business/creator accounts, and the OAuth path additionally requires the IG account to be linked to a Facebook Page.
  • The OAuth flow requires Meta App Review before other people can sign in through your app; users with a role on the app (admin/developer/tester) can connect without review.
  • X/Twitter and TikTok are not included (X's API is paid; TikTok's display API requires app review). The provider interface makes them straightforward to add later.
  • Facebook text-only posts are stored with media_type: text and no image; the shipped rows are image-first, so they render captions for those cards via the excerpt token.
  • YouTube videos are never downloaded (their thumbnail links out to YouTube); Instagram/Facebook videos above the 60 MB cap fall back to the same thumbnail + link behavior.
  • The Follow Strip row deliberately stays images-only — carousels and players don't fit its small teaser tiles.