Menu
Sign up

Emoji and fonts in Puppeteer screenshots: Docker, Linux and web fonts

Why emoji render as boxes and fonts look wrong in Puppeteer on Linux and Docker: what the official image lacks, the packages that fix it, web fonts.

Published November 6, 2026 ยท 4 min read

Emoji that render as empty boxes, Chinese text as squares, a brand font replaced by Times โ€” in Puppeteer these are almost always fonts missing on the machine, not bugs in your page. Chrome draws text with the fonts the operating system has, and Linux servers and Docker images ship few. This page shows what is missing in a typical setup, which packages fix it, and how to make sure web fonts have loaded before the capture. The tests were run on 2026-11-06 with Puppeteer 25.7.0 in the official ghcr.io/puppeteer/puppeteer image.

What the official Puppeteer image renders

We rendered one line each of emoji, Japanese, Chinese and Korean, Polish and Arabic in the official image, unchanged:

  • CJK, Arabic and Polish rendered correctly. The image installs fonts for them (it lists 125 fonts; six cover Japanese).
  • Emoji did not. fc-list found no emoji font. ๐Ÿ˜€ came out as a black-and-white outline from a text font, ๐ŸŽ‰, ๐Ÿ‘๐Ÿฝ and ๐Ÿ‡ต๐Ÿ‡ฑ as empty boxes, and a pixel count found no coloured pixels in the emoji line.

After adding two Debian packages to the image, the same page rendered all four emoji in colour (about 2,250 strongly coloured pixels in that line) and the image listed 156 fonts:

FROM ghcr.io/puppeteer/puppeteer:25.7.0
USER root
RUN apt-get update \
 && apt-get install -y --no-install-recommends fonts-noto-color-emoji fonts-noto-cjk \
 && rm -rf /var/lib/apt/lists/*
USER pptruser

If you build your own image from node:*-slim or another minimal base, assume it has even fewer fonts and install at least:

Need Debian / Ubuntu package
Colour emoji fonts-noto-color-emoji
Chinese, Japanese, Korean fonts-noto-cjk
Latin, Greek, Cyrillic with common metrics fonts-liberation (metric-compatible with Arial, Times New Roman, Courier New)
Many other scripts fonts-noto-core (large)

The emoji package installs Google's Noto Color Emoji font (Debian package). Alpine uses different names (font-noto-emoji, font-noto-cjk). Install fonts at image build time; if you add them to a running container, restart Chrome so it picks them up.

Check what a container has

Before changing anything, ask the container itself. Chrome on Linux finds fonts through fontconfig, so its command-line tools show what Chrome can use:

fc-list | wc -l                    # how many fonts are installed
fc-list | grep -i emoji            # any emoji font? (none in the official Puppeteer image)
fc-list :lang=ja family            # fonts that cover Japanese
fc-match "Noto Color Emoji"        # what a request for this family resolves to

If fc-match answers with a different family than the one you asked for, the font is not installed and Chrome will fall back as well. Run these in the same image that runs Puppeteer โ€” a font on your build machine or your laptop does not count.

How Chrome picks a font for a character

For each character Chrome tries the fonts named in your CSS (font-family), then falls back to system fonts that contain the character, found through fontconfig on Linux. If no installed font has a glyph, you get an empty box, often called "tofu". That is why the fix is a package, not CSS: font-family: "Noto Color Emoji" does nothing on a machine without that font.

Two consequences:

  • The same page looks different on your laptop and in production. macOS and Windows ship emoji and CJK fonts; your container may not. Render a test page with emoji and CJK on the production image once.
  • Text width changes with the fallback font, so a layout that fits on your machine can wrap differently in the container. Install the fonts your design assumes, or load them as web fonts.

Web fonts: wait for them

Fonts loaded with @font-face arrive over the network after the HTML. If the screenshot runs first, it shows the fallback font โ€” or no text, during the font's block period. We served a font with a 1.5-second delay and measured the text's width:

waitUntil Font status at capture Width
domcontentloaded loading 361 px (fallback font)
load loaded 260 px
networkidle0 loaded 260 px

After await page.evaluate(() => document.fonts.ready), every variant had the right font. font-display: swap made no difference to the result. load waited for the font here because the text using it was in the initial HTML; text added later by JavaScript, or fonts requested by a script, can arrive after load. So whatever waitUntil you use, add:

await page.goto(url, { waitUntil: 'networkidle2' });
await page.evaluate(() => document.fonts.ready);
await page.screenshot({ path: 'shot.png' });

document.fonts.ready resolves when the fonts the page has requested so far are loaded (MDN).

Fonts for OG images and generated cards

If you render your own HTML (cards, certificates, OG images) rather than other people's pages, you control the fonts completely:

  • Embed them as data URLs in the HTML or serve them locally, so the render never waits on a font CDN.
  • Subset them to the characters you use, to keep the page small.
  • Name a fallback with the same metrics (for example Liberation Sans for Arial) so a missing font does not reflow the layout.
  • Add an emoji font to the stack explicitly, after your text font: font-family: "Inter", "Noto Color Emoji", sans-serif.

Or use a renderer that has them

Shareglint renders templates and screenshots in a Chrome that already has colour emoji and CJK fonts installed; the list of families for templates is at GET /v1/fonts (you cannot upload your own font file there โ€” templates use the installed families). For screenshots of other sites, the site's own web fonts load as in any browser. Details on the screenshot API and HTML to image pages.

FAQ

Why are emoji boxes in my Puppeteer screenshot?

Chrome draws emoji with an emoji font installed on the machine, and Linux containers often have none. In the official Puppeteer Docker image we found no emoji font: most emoji came out as empty boxes. Installing fonts-noto-color-emoji fixed it.

Which packages do I install for emoji and Asian text in Docker?

On Debian or Ubuntu: fonts-noto-color-emoji for colour emoji and fonts-noto-cjk for Chinese, Japanese and Korean. The official Puppeteer image already renders CJK, Arabic and Polish, but not colour emoji.

Why does my web font not show up in the screenshot?

The screenshot was taken before the font arrived. With waitUntil domcontentloaded the text used the fallback font in our test; with load the font was ready. Wait for document.fonts.ready before capturing, whatever waitUntil you use.

Can I use emoji in a Puppeteer PDF too?

Yes, with the same font installed. In our test Chrome embedded the colour emoji in the PDF as Type3 font glyphs, and they printed in colour; without the font they are boxes there too.