Skip to main content

Facebook

This guide explains our Facebook Page integration in depth: goals, security posture, scopes, token flows, data we persist, and how publishing works.

Quick links: FrontendBackend

Goals and constraints

  • Multi-account support: Users can connect multiple Facebook Pages
  • We never post to a personal profile; publishing is Page-only.
  • We prefer Page-scoped access tokens for posting to /PAGE_ID/feed.
  • We store the minimum necessary data to render UI and perform posting.

Why we do it this way

  • Page access token is required to post to a Page feed. User tokens are insufficient for Page posting.
  • Users explicitly choose which Page to grant to the app using Facebook’s Page selector. We capture that selection as the first granted Page from the me/accounts response constrained by granted scopes.
  • Long‑lived tokens reduce login churn. We exchange the short‑lived token for a long‑lived user token, and we also persist the Page access token returned by me/accounts.

References: Facebook Page Graph API, Facebook User Graph API

Permissions and scopes

Default scopes requested:

  • public_profile
  • email
  • pages_show_list
  • pages_read_engagement
  • pages_manage_posts
  • pages_manage_metadata
  • business_management

Notes:

  • pages_show_list and/or pages_manage_metadata are used to enumerate and access granted Pages via me/accounts and to retrieve Page tokens.
  • pages_manage_posts is required to publish to /PAGE_ID/feed.
  • business_management helps in cases where the Page is owned by a Business Manager and tokens for pages would otherwise be missing.
  • We set auth_type=rerequest to force the Page selector dialog so users can re‑grant if they change their choice.

Backend

See also: Frontend

Token flow

  1. Start: We build the Facebook OAuth URL with the scopes above and state for CSRF protection. We store { uid, provider: 'facebook', returnTo } in Firestore oauthStates.
  2. Callback: Exchange code for a short‑lived user token.
  3. Page discovery:
    • First try me/accounts using the short‑lived token to get granted Pages and their Page access tokens.
    • If empty, exchange for a long‑lived token, then:
      • Use debug_token to extract granular scopes and target Page IDs, then fetch each Page directly to obtain the Page access token; or
      • Retry me/accounts with the long‑lived token.
  4. Selection: Users can connect multiple Pages. Each Page is persisted separately.
  5. Persist: We write to users/{uid}/integrations/facebook/{pageId}:
    • accessToken (user long‑lived)
    • pageId, pageAccessToken, accountName
    • profile (for UI: platformId, displayName, avatarUrl, accountType: 'page')
    • expiresIn, expiresAt, updatedAt

We log the entire flow using [facebook] tagged console messages to aid troubleshooting.

Multi-Account Support

Users can connect multiple Facebook Pages. Each Page is stored separately with a unique accountId.

Storage path: users/{uid}/integrations/facebook/{accountId}

Where accountId is the Facebook Page ID.

Account selection:

  • If user has one Page: Automatically selected, no accountId required in API requests
  • If user has multiple Pages: accountId is required in API requests (e.g., /v1/posts)
  • Missing required accountId returns 400 error with code missing_account

Data model

Firestore path: users/{uid}/integrations/facebook/{accountId}

Fields:

  • accountId: Unique identifier for this Page (same as pageId)
  • accessToken: Long‑lived user token
  • pageId: Selected Page ID
  • pageAccessToken: Selected Page's Page token (confers Page context)
  • accountName: Page name
  • profile: UI profile card
    • platformId, displayName, username, avatarUrl, accountType
  • expiresIn, expiresAt, updatedAt
  • enabled_for_repost: (optional) boolean flag for automatic reposting

Publishing

Endpoint: Cloud Function publishFacebook (via orchestrator)

Flow:

  1. Resolve account: Determine accountId from request or auto-select if only one Page exists
  2. Load users/{uid}/integrations/facebook/{accountId}.
  3. Use stored pageId and pageAccessToken.
  4. POST to /${pageId}/feed with { message, access_token: pageAccessToken }.
  5. Return { ok: true, pageId, postId }.

Common errors:

  • Missing pageAccessToken: grant did not include the Page. Reconnect; the dialog is rerequest.
  • Permissions error: ensure pages_manage_posts is approved for the app and granted by the user.

Observability

  • All critical steps log with prefix [facebook].
  • Examine Cloud Function logs for: starting auth, token exchange, pages response counts/IDs, selected page, Firestore write summary, publish attempts, and failures.

Security posture

  • Tokens are stored under the authenticated user document and not exposed to other users.
  • We store only the minimum required Page and profile data.
  • We honor Facebook rate limits and handle OAuth errors; sensitive error bodies are logged only in server logs.

Frontend

See also: Backend

Hooks and services:

  • apps/web/src/features/integrations/hooks/useConnectIntegration.ts
    • Starts OAuth by calling startAuth('facebook', idToken, returnTo, isLocal).
  • apps/web/src/features/integrations/hooks/usePublishFacebook.ts
    • Triggers a publish by calling publishFacebook(functionsBase, idToken).
  • apps/web/src/features/integrations/services/publish.ts
    • Implements POST {functionsBase}/fbPublish.

UI touchpoints:

  • apps/web/src/features/integrations/components/PlatformPicker.tsx renders the connected Page using integrations.facebook.profile.
  • apps/web/src/app/settings/page.tsx initiates connect via the hook.

Request/response shapes:

  • Connect: handled server‑side; client receives { authUrl } and redirects.
  • Publish: no body; server posts to /PAGE_ID/feed and returns a textual result or JSON { ok, pageId, postId } depending on implementation details.

Error handling:

  • The publish hook throws text from the HTTP response; common cases are missing Page token or insufficient permissions.