ScreenshotNeo

BlogHow-to

How to Capture a Logged-In Page Using Puppeteer with a Chrome Extension

Load a Chrome extension, sign in through the site’s normal flow, and capture an authenticated page with Puppeteer.

By the ScreenshotNeo team4 October 20269 min read

To capture a logged-in page with Puppeteer and a Chrome extension, launch Chrome with the unpacked extension enabled, use a dedicated writable userDataDir, sign in through the website’s permitted login flow, wait for a page-specific signal that confirms authentication, and save the screenshot with page.screenshot(). The extension can modify or interact with the page, but it does not authenticate your website account for you.

This guide uses Puppeteer’s documented extension APIs. Check the Puppeteer version installed in your project before using the examples: extension API availability and behavior depend on the version and browser environment. See the official Chrome Extensions guide, LaunchOptions reference, and Screenshots guide.

1. Prepare Puppeteer, Chrome, and the extension

Install Puppeteer in a Node.js project and ensure the extension is unpacked into a directory readable by the process. A Chrome extension directory normally contains a manifest.json at its root. Keep the extension files available wherever the capture script runs.

npm install puppeteer

For repeatable runs, use an isolated, writable profile directory. A persistent profile can retain the authorized session between runs, while a fresh profile starts without that session. Treat the profile directory as sensitive: it may contain cookies and other session data. Do not point automation at a personal browser profile without checking the site’s rules and the operational risks.

2. Load the extension and authenticate through the site

The following CommonJS script launches Puppeteer with a known unpacked extension path, opens the site’s login page, and pauses for you to complete the permitted sign-in flow in the browser. It then checks for an example authenticated-page selector, navigates to the target page, waits for a target-specific readiness condition, and writes a PNG.

Replace the extension path, login URL, target URL, and selectors with values for your extension and application. This interactive login approach is useful for initial setup and debugging. For unattended automation, use a session setup explicitly authorized by the site and your organization.

const puppeteer = require('puppeteer');
const path = require('node:path');

(async () => {
  const extensionPath = path.resolve('./my-unpacked-extension');
  const profilePath = path.resolve('./puppeteer-profile');
  const loginUrl = 'https://example.com/login';
  const targetUrl = 'https://example.com/account';

  const browser = await puppeteer.launch({
    headless: false,
    userDataDir: profilePath,
    enableExtensions: [extensionPath],
  });

  try {
    const page = await browser.newPage();
    await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });

    // Complete the site's permitted login flow in the opened browser.
    // Close the browser window after signing in to continue this script.
    await new Promise((resolve) => {
      browser.once('disconnected', resolve);
    });

    // Relaunch with the same isolated profile to use the saved session.
    const authenticatedBrowser = await puppeteer.launch({
      headless: false,
      userDataDir: profilePath,
      enableExtensions: [extensionPath],
    });

    try {
      const authenticatedPage = await authenticatedBrowser.newPage();
      await authenticatedPage.goto(targetUrl, { waitUntil: 'domcontentloaded' });

      // Replace this with an element that only appears when signed in.
      await authenticatedPage.waitForSelector('[data-testid="account-home"]', {
        timeout: 15000,
      });

      await authenticatedPage.screenshot({ path: 'capture.png', fullPage: true });
    } finally {
      await authenticatedBrowser.close();
    }
  } finally {
    // The first browser is already disconnected if the login step completed.
    if (browser.connected) await browser.close();
  }
})().catch((error) => {
  console.error(error);
  process.exitCode = 1;
});

For a simpler one-process workflow, launch headful, wait for a manual signal in your own application (for example, a local prompt or a page selector), and continue in the same browser. The relaunch pattern above illustrates how the same persistent profile can be opened after an interactive login; adapt lifecycle handling to your environment so that the login browser is not closed before the session is saved.

Important: the example’s selector is only a placeholder. Choose a selector, URL, or visible page condition that demonstrates the user is actually authenticated. A successful navigation or an idle network connection alone does not prove login worked.

3. Trigger extension behavior when needed

Loading an extension makes it available to Chrome. If the capture depends on the extension’s default action, trigger it explicitly after opening the relevant page. Puppeteer documents both page.triggerExtensionAction(extension) and extension.triggerAction(page). The exact extension object and API usage should follow the installed Puppeteer version’s extension guide.

// Illustrative API usage; obtain the Extension object using the API
// documented for your installed Puppeteer version.
await page.triggerExtensionAction(extension);

Extension actions are separate from account authentication. The extension guide also describes inspecting its service worker or background page and evaluating code in an extension content-script context. Use those tools to debug extension behavior; do not treat them as a way to bypass a site’s login controls.

4. Choose an extension-loading approach

Approach Use it when Trade-off
enableExtensions: [path] at launch The extension path is known when Chrome starts. Simple and explicit for a fixed automation setup.
enableExtensions: true, then browser.installExtension(path) The script needs to install extensions at runtime. Offers runtime installation when supported by the installed version.

Consult the current Puppeteer extension guide for the precise calls available in your version. Keep extension versions controlled in deployment so that an update does not unexpectedly change captured pages or extension behavior.

5. Understand authentication and session choices

Website login versus HTTP authentication

page.authenticate() supplies credentials for HTTP authentication challenges. It is not a general-purpose login method for an application with a login form, identity provider, or session cookie. For normal web-app accounts, use the site’s permitted login flow or a session setup that the site explicitly authorizes. See Puppeteer’s Page.authenticate() reference.

Persistent profile versus fresh profile

  • Persistent isolated profile: useful when an authorized session must be retained between runs. Protect its directory and restrict access.
  • Fresh profile: useful when each run should begin without prior browser state. The workflow must then authenticate through an authorized process each time.

Puppeteer exposes userDataDir as a launch option. Chrome needs that directory to be writable. Do not assume an existing personal Chrome profile can be reused safely or without conflicts; the cited documentation does not promise that.

Cookies

Puppeteer’s cookie API describes cookie properties such as name, value, domain, path, expiry, and security attributes. Copying cookies is not automatically appropriate, sufficient, or permitted for a particular site. Prefer the site’s normal authorized login or documented session integration. See the CookieData reference.

6. Make the screenshot reliable

Capture only after the page is in the state you need. Navigate, verify the authenticated state, wait for the content to render, and then call page.screenshot(). Puppeteer documents screenshot options in its Screenshots guide.

await page.goto('https://example.com/account/report', {
  waitUntil: 'domcontentloaded',
});
await page.waitForSelector('[data-testid="report-ready"]', { timeout: 15000 });
await page.screenshot({ path: 'report.png', fullPage: true });

Use a readiness condition tied to the page’s actual content: an authenticated navigation URL, a user-specific element, or the report’s loaded state. Network-idle waits can help on suitable sites, but analytics, polling, and long-lived connections can prevent idle from occurring, and idle by itself does not prove authentication.

7. Run the capture in a deployment environment

  • Pin and verify the Puppeteer and extension versions used by the job.
  • Ensure the extension directory and profile directory exist and are accessible to the browser process.
  • Keep the Chrome sandbox enabled in normal deployments. Puppeteer troubleshooting strongly discourages using --no-sandbox; configure the host sandbox instead.
  • Use a profile location with sufficient disk space and appropriate access controls. Avoid sharing one mutable profile among concurrent jobs.
  • Set navigation and selector timeouts that fit the target application, and record whether failures occur at launch, login, navigation, readiness, or screenshot time.

The current official Puppeteer documentation identifies version 25.12.0 and says Puppeteer v20 and later uses Chrome for Testing, with headless and headful modes sharing the same browser code path. Verify actual extension behavior in the version and environment you deploy. See Supported browsers and Troubleshooting.

8. Troubleshoot common failures

Symptom Likely cause Fix
Chrome fails to launch The profile directory is missing or not writable, or the host cannot start the browser with its configured sandbox. Check directory ownership and permissions, verify the browser installation, and configure the host sandbox. Keep the sandbox enabled rather than routinely adding --no-sandbox.
The extension is not available The extension path is wrong, not unpacked, or the installed Puppeteer version does not support the API used. Check that the path contains the extension manifest, confirm the API against the versioned guide, and keep the files present for the process lifetime.
The screenshot shows a login page The session was not saved, expired, or was not accepted; the script may also be checking the wrong page state. Complete the site’s permitted login flow, reuse the intended isolated profile, inspect the final URL, and wait for an authenticated-only element.
waitForSelector times out The selector is incorrect, the app is slower than the timeout, or the target is unauthenticated or in an error state. Inspect the rendered page and URL, choose a stable site-specific selector, and adjust the timeout based on observed application behavior.
Extension action has no visible effect The action was triggered before the correct page was ready, or the extension is not enabled in this browser instance. Verify extension loading, wait for the target tab and page state, then trigger the action using the API documented for your Puppeteer version.
Capture differs between local and container runs Browser version, viewport, fonts, profile state, extension version, or timing differs. Pin the browser and extension inputs, use a controlled profile and viewport, and wait for a page-specific readiness signal.

For additional launch diagnostics, follow Puppeteer’s official troubleshooting guide.

9. Performance, reliability, and cost considerations

Browser startup and application rendering are often the main contributors to a single capture’s elapsed time. A persistent browser or profile may avoid repeating some setup, but it also carries mutable session state and needs careful isolation. Reusing a single profile for concurrent tasks can create state collisions; separate profiles or serialize work when session integrity matters.

Reliability depends on more than the screenshot call: extension compatibility, the site’s authentication flow, session expiry, page readiness, and browser launch permissions all matter. Make each stage observable and fail clearly when authentication checks do not pass. Do not silently save a login page as though it were the requested account page.

Self-hosted Puppeteer has no per-screenshot API charge from Puppeteer itself, but it consumes compute, memory, storage, and engineering time. The amount depends on the host, target page, and workload; no universal benchmark applies. Protect profile data as credentials and delete it according to your session-retention needs.

Or skip the browser setup

If you need a screenshot API instead of managing Chrome, ScreenshotNeo takes a screenshot or PDF from one GET request. See the ScreenshotNeo API documentation. For a publicly accessible page, the request looks like this:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp

ScreenshotNeo is for capturing website pages; it does not log you into a website account or replace the authorized Puppeteer session workflow for private pages. Before capture, it accepts cookie or consent banners like a visitor and removes 60+ known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and responses include X-Page-Verdict and X-Billed headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan.

Sign up free for 1,000 screenshots a month, with no card required.

Frequently asked questions

Can Puppeteer load a Chrome extension in headless mode?

Puppeteer’s current documentation supports Chrome for Testing in both headless and headful modes, but verify that your particular extension works in the exact browser and Puppeteer version you deploy.

Does triggering an extension action sign me into the site?

No. It triggers extension behavior. Website authentication must come from the site’s permitted login or authorized session process.

Can I use this method for a private page with ScreenshotNeo?

The one-call ScreenshotNeo example is for a URL the service can access; it does not perform a website account login. Use an authorized browser session for pages that require your account.