Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Load the HTML, select the document’s <title> element, and read its text:

import * as cheerio from 'cheerio';

const $ = cheerio.load(html);
const title = $('title').text().trim();
console.log(title);

load parses an HTML string and returns Cheerio’s $ query function. $('title').text() returns the title text; trim() removes indentation and line breaks preserved from the source.

Get the title from an HTML string

Cheerio reads the markup you give it; it does not fetch a page or execute its JavaScript automatically. A complete Node.js example is:

import * as cheerio from 'cheerio';

const html = `<!doctype html>
<html>
  <head>
    <title>  Product documentation  </title>
  </head>
  <body><h1>Docs</h1></body>
</html>`;

const $ = cheerio.load(html);
const title = $('title').text().trim();

console.log(title); // Product documentation

Install Cheerio first with npm install cheerio. In an ES-module project, keep the import statement shown above. If your project uses CommonJS, use const cheerio = require('cheerio') and then call cheerio.load(html).

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The selector is deliberately just title: the element belongs to the document head, but Cheerio searches the whole parsed document. If the source contains more than one title element, .text() concatenates the matching text. For a strict first match, use const title = $('title').first().text().trim() and log a warning when $('title').length > 1.

Fetch the page before parsing it

When the HTML comes from a URL, use an HTTP client first, then pass the response text to Cheerio. Node’s built-in fetch works in current Node releases:

import * as cheerio from 'cheerio';

const response = await fetch('https://example.com/', {
  redirect: 'follow',
  headers: { 'user-agent': 'title-reader/1.0' }
});

if (!response.ok) {
  throw new Error(`HTTP ${response.status} ${response.statusText}`);
}

const html = await response.text();
const $ = cheerio.load(html);
const title = $('title').first().text().trim();

if (!title) {
  throw new Error('The received HTML has no non-empty <title> element');
}

console.log(title);

This separates network failures from parsing failures. Check the HTTP status before parsing, follow redirects when appropriate, and identify your client with a truthful user-agent. Set an application-level timeout with an AbortController so a stalled server does not hold a worker indefinitely.

const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), 15_000);
try {
  const response = await fetch(url, { signal: controller.signal });
  // parse response here
} finally {
  clearTimeout(timer);
}

Choose the loader that matches your input

Cheerio provides different entry points for different source forms. The extraction code remains the same after loading.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Input you have Loader Use it when Encoding behavior
Decoded HTML string cheerio.load(html) Your HTTP client already produced text. Uses the JavaScript string’s characters.
Raw bytes cheerio.loadBuffer(buffer) You downloaded bytes and are not certain which encoding the document uses. Cheerio can sniff the encoding while decoding.
Already-decoded text stream cheerio.stringStream(options, callback) Text arrives incrementally and its encoding is known. Expects decoded text.
Raw-byte stream cheerio.decodeStream(options, callback) You need streaming while the byte encoding is unknown. Decodes and parses the incoming bytes.
URL cheerio.fromURL(url) You want Cheerio to perform the asynchronous fetch for you. Handles the response as a URL load; configure options according to your Cheerio version.

For a normal API response, load is simplest. Prefer loadBuffer or decodeStream when preserving non-UTF-8 titles matters. A loader only changes how markup enters Cheerio; it does not add browser rendering.

Diagnose an empty title

Cheerio does not throw when a selector matches nothing. An empty selection’s .text() result is simply an empty string, so inspect the selection and the received document:

const $ = cheerio.load(html);
const matches = $('title');

console.log('title elements:', matches.length);
console.log('document bytes:', Buffer.byteLength(html, 'utf8'));
console.log('received markup:', $.html());

const title = matches.first().text().trim();
  • length === 0: the response has no matching element. You may have received an error page, a login page, a fragment without <head>, or markup that is different from what you expected.
  • Nonzero length but empty text: the element exists but contains no text, only whitespace, or content generated later by JavaScript.
  • Unexpected whitespace: source indentation and newlines are preserved. Call trim(); do not remove internal spaces unless your application requires additional normalization.
  • Unexpected title text: inspect the final response after redirects and check for multiple <title> elements. Use .first() or reject ambiguous documents according to your policy.

Do not use a CSS class or an Open Graph tag as a substitute without deciding what “title” means for your application. The document title is the <title> element. A social-card title such as meta[property="og:title"] is separate data and should be read explicitly when needed.

Handle JavaScript-rendered titles

Cheerio parses static markup and does not run scripts. A React, Vue, or other client-side application can insert or change <title> after the initial response; that title will not be present in the HTML fetched by Cheerio.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use a browser automation tool to load the page, wait for the application to finish, retrieve the rendered HTML, and then pass that HTML to Cheerio:

import { chromium } from 'playwright';
import * as cheerio from 'cheerio';

const browser = await chromium.launch();
try {
  const page = await browser.newPage();
  await page.goto('https://example.com/app', {
    waitUntil: 'networkidle',
    timeout: 30_000
  });
  await page.waitForFunction(() => document.title.trim().length > 0);

  const renderedHtml = await page.content();
  const $ = cheerio.load(renderedHtml);
  console.log($('title').first().text().trim());
} finally {
  await browser.close();
}

Use a more specific readiness condition than networkidle when the site keeps analytics or socket connections open. Waiting for a known selector, an application state, or a bounded delay can be more reliable. Browser rendering is slower and consumes more memory than parsing an existing response, so reserve it for pages whose title genuinely depends on execution.

Use cURL or Python to obtain the source

These commands retrieve HTML; Cheerio still performs the extraction in JavaScript.

cURL

curl --fail --location --silent --show-error 
  --user-agent 'title-reader/1.0' 
  https://example.com/ 
  --output page.html
node read-title.mjs page.html

Node.js file parser

// read-title.mjs
import { readFile } from 'node:fs/promises';
import * as cheerio from 'cheerio';

const html = await readFile(process.argv[2], 'utf8');
const $ = cheerio.load(html);
console.log($('title').first().text().trim());

Python downloader

import requests

response = requests.get(
    'https://example.com/',
    headers={'User-Agent': 'title-reader/1.0'},
    timeout=15,
)
response.raise_for_status()
with open('page.html', 'wb') as output:
    output.write(response.content)

Saving bytes rather than immediately decoding them lets a later loadBuffer call handle uncertain encodings. If you decode in Python first, use the response’s declared encoding and understand that a wrong decoding can corrupt non-ASCII title text.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Production concerns: correctness, speed, and safety

  • Verify the response: check status, content type, redirect destination, and a sensible maximum response size before parsing untrusted URLs.
  • Limit concurrency: Cheerio parsing is lightweight, but downloading many pages can exhaust sockets or memory. Use a queue and bounded parallelism.
  • Cache when appropriate: cache the fetched HTML or extracted title with a documented time-to-live when repeated reads do not need real-time data.
  • Keep browser jobs isolated: reuse a controlled browser process for batches, close pages in finally blocks, and enforce navigation and overall job timeouts.
  • Protect your network: URL-fetching services should block private IP ranges, local hostnames, and dangerous protocols to reduce server-side request forgery risk.
  • Preserve the original: log a hash, status, final URL, and a bounded sample of the response when debugging. Avoid logging secrets embedded in pages or request headers.

Common errors and fixes

Symptom Likely cause Fix
Cannot find package 'cheerio' Dependency is not installed in the current project. Run npm install cheerio in that project and verify you are running the same Node environment.
title is always empty The fetched response has no title, is an error/login page, or the title is client-rendered. Check response.url, status, matches.length, and $.html(); use browser rendering for client-generated titles.
Title contains line breaks Formatting whitespace exists inside the element. Call .trim(); normalize internal whitespace only if that is part of your data contract.
Characters such as “é” are corrupted Bytes were decoded with the wrong encoding before parsing. Keep raw bytes and use loadBuffer or decodeStream.
Request times out The origin is slow, blocked, or waiting on resources that never complete. Set bounded HTTP/browser timeouts, retry only safe transient failures, and record the final error.
Different title from a browser tab The tab shows a post-load JavaScript update or a redirect destination. Compare the initial response with rendered browser content and capture the title after the update.

Or skip the browser setup

If your immediate need is a clean visual capture of a rendered page rather than extracting its DOM title, ScreenshotNeo provides a one-call screenshot API. It is not a replacement for Cheerio’s title selector and does not return rendered HTML, but it can remove the browser-installation and capture plumbing when you need an image for review or a downstream visual workflow.

For a rendered capture:

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

See the ScreenshotNeo API documentation for response options. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 screenshots; every feature is available on every plan. If that fits your capture workflow, sign up for the free plan.

What to remember

  1. Obtain the HTML, then call cheerio.load(html).
  2. Select $('title'), read with .text(), and use .trim() when source whitespace is unwanted.
  3. Check length and the received markup when the result is empty.
  4. Render the page in a browser first when JavaScript creates or changes the title.

Frequently Asked Questions

Does Cheerio’s .text() return an error when no title exists?

No. An empty selection returns an empty string. Check $('title').length when you need to distinguish a missing element from an empty one.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Can I get the title from a page protected by a consent banner?

Cheerio can parse the HTML it receives, but it cannot click or dismiss a banner. Use browser automation when the title appears only after interaction, and apply the site’s access and terms requirements.

Should I read og:title instead of <title>?

Only when your application specifically needs the social-sharing title. og:title is metadata and can differ from the document title.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.