npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

facebook-comments-client

v0.1.5

Published

Fetch comments from Facebook posts (including Group posts) using an authorized Playwright session. Returns clean structured JSON. No database, no queues, no scheduling.

Readme

facebook-comments-client

Fetch comments from Facebook posts — including posts inside Facebook Groups where your account already has legitimate access — using an authorized Playwright session. Returns clean, normalized JSON that is independent of Facebook's internal response structure.

This is a focused library. It contains no database, no Supabase, no queues, no cron jobs, and no backend server. Its sole responsibility:

Facebook Post URL → Authenticate/Reuse Authorized Session → Fetch Available Comments → Return Clean Structured JSON

Installation

npm install facebook-comments-client
# The library drives a real browser via Playwright:
npx playwright install chromium

Requirements: Node.js 20+.

Quick start

import { FacebookCommentsClient } from "facebook-comments-client";

const client = new FacebookCommentsClient({
  session: {
    type: "file",
    path: "./facebook-session.json"
  }
});

const result = await client.getComments({
  url: "https://www.facebook.com/groups/GROUP_ID/posts/POST_ID/",
  maxComments: 100,
  includeReplies: true
});

console.log(result.comments);
await client.close();

Interactive login

The library never enters credentials for you. It opens a visible browser, you log in manually, and the authorized session is exported:

import { FacebookCommentsClient } from "facebook-comments-client";

await FacebookCommentsClient.login({
  outputPath: "./facebook-session.json"
});

Or from the CLI:

npx fb-comments login --output ./facebook-session.json

The helper waits for the Facebook c_user cookie to appear (up to 5 minutes by default), saves the Playwright storage state, and closes the browser.

Session file usage

const client = new FacebookCommentsClient({
  session: { type: "file", path: "./facebook-session.json" }
});

type: "storage" is accepted as an alias. The file is a standard Playwright storage state (as produced by context.storageState() and by the login helper above).

Environment variable usage

Handy for deployments. Encode a storage state once:

const encoded = Buffer.from(JSON.stringify(storageState)).toString("base64");

Then configure the client:

const client = new FacebookCommentsClient({
  session: { type: "env", envKey: "FACEBOOK_STORAGE_STATE" }
});

The variable is base64(-url) decoded, JSON parsed, and validated with Zod before a browser context is created. You can also pass a plain cookie array via { type: "cookies", cookies } or an inline object via { type: "state", state }.

Fetching comments

const result = await client.getComments({
  url: "https://www.facebook.com/groups/123/posts/456/",
  maxComments: 100,      // default 50
  includeReplies: true,  // default false
  maxReplies: 20,        // default 20, per comment
  timeout: 30000,        // navigation timeout ms
  headless: true         // override client default
});

Returns a normalized shape:

{
  post: { id: "456", url: "https://www.facebook.com/groups/123/posts/456/" },
  comments: [
    {
      id: "comment_123" | null,
      author: { id: "user_123" | null, name: "John Doe" | null, profileUrl: "..." | null },
      message: "Hello!",
      createdAt: "2026-08-28T10:30:00.000Z" | null,
      reactions: { total: 5 | null },
      parentId: null,
      replies: [ /* same shape */ ]
    }
  ],
  pagination: { hasMore: false, nextCursor: null }
}

Every field degrades to null instead of crashing when Facebook's markup differs — missing profile URLs, unparseable timestamps, unavailable reaction counts, etc.

Fetching replies

Set includeReplies: true (and optionally maxReplies). The fetcher expands "View more replies" controls (bounded) and nests replies under their parent comment with parentId set.

Pagination

const page1 = await client.getComments({ url, maxComments: 25 });

if (page1.pagination.hasMore) {
  const page2 = await client.getComments({
    url,
    maxComments: 25,
    cursor: page1.pagination.nextCursor
  });
}

The cursor is an opaque base64url token — an offset into the comment stream. No Facebook internals leak into your code.

Error handling

import {
  FacebookClientError,
  FacebookAuthenticationError,
  FacebookSessionExpiredError,
  FacebookPostNotFoundError,
  FacebookAccessDeniedError,
  FacebookExtractionError
} from "facebook-comments-client";

try {
  const { comments } = await client.getComments({ url: postUrl });
} catch (error) {
  if (error instanceof FacebookSessionExpiredError) {
    console.log("Facebook session needs to be refreshed.");
  }
}

Every error carries a stable code (FB_AUTH, FB_SESSION_EXPIRED, FB_POST_NOT_FOUND, FB_ACCESS_DENIED, FB_EXTRACTION, FB_CLIENT), a safe human-readable message, and a retryable boolean. Error messages never contain cookies or storage-state content. See docs/errors.md.

Security notes

  • Treat session files as passwords. A storage state grants full access to the logged-in account.
  • The library never logs cookie values, tokens, or storage states; its logger redacts sensitive keys, and error messages are scrubbed.
  • facebook-session.json, *.storage-state.json, and .env are in .gitignore — never commit them.
  • Environment variables are convenient for deployment, but session expiration and secret handling are your responsibility. Prefer your platform's secret manager over plaintext .env files.
  • Use sessions belonging to accounts that legitimately have access to the requested content. This library does not bypass logins, CAPTCHAs, or access controls, and contains no stealth, fingerprint-spoofing, proxy-rotation, or restriction-evasion features.

Session refresh instructions

Facebook sessions expire (typically days to weeks):

  1. client.validateSession() returns { valid: true, authenticated: false }, or getComments throws FacebookSessionExpiredError.
  2. Re-run the login helper: await FacebookCommentsClient.login({ outputPath }) (or fb-comments login).
  3. If you use the env-var provider, re-encode the fresh state and update the variable.

Known limitations

  • Facebook markup is unstable by nature. All selectors and text patterns live in src/config/selectors.ts; when extraction quality degrades, that one file is the place to look. Extraction issues throw FacebookExtractionError rather than returning garbage silently.
  • The structured-data extractor is opportunistic; on most modern pages the accessibility-based DOM extractor does the real work.
  • Timestamps shown as relative times ("2h") are approximations computed from fetch time.
  • Comment ids are frequently absent in the DOM and are returned as null.
  • Localization: text patterns currently target English UI text. Non-English Facebook UIs may need additional patterns in selectors.ts.
  • Very long threads (hundreds of comments) require pagination; the load-more loop is intentionally bounded.
  • Integration tests against live Facebook are opt-in (tests/integration/live.test.ts) and need FB_TEST_URL + FACEBOOK_STORAGE_STATE.

CLI

# Login (manual, headed browser)
fb-comments login --output ./facebook-session.json

# Fetch comments
fb-comments comments "https://www.facebook.com/groups/123/posts/456/" --replies --limit 100

# Machine-readable output
fb-comments comments POST_URL --json

# Session from env var
FACEBOOK_STORAGE_STATE="..." fb-comments comments POST_URL

Development

npm install
npx playwright install chromium
npm test        # unit + fixture tests (offline, no Facebook login needed)
npm run build   # dist/ ESM + CJS + types + CLI
npm run lint

See docs/api.md for the full API surface, docs/authentication.md and docs/session-management.md for session details.

License

MIT