> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blinkpdf.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Fonts & Typography: Font Stacks, Google Fonts & Roles

> Pick a hosted font cascade with fontStack, pull any Google Fonts family by URL, and bind body, heading, and custom roles with the typography object — all in one JSON render request.

Blink PDF embeds every font it renders with, so output looks identical everywhere and stays PDF/UA-conformant. You have three layers of control, all in the same `POST /v1/render` body:

* **`fontStack`** — choose from the hosted families that ship on every plan.
* **`googleFontsUrl` / `googleFontsUrls`** — pull any Google Fonts family by URL.
* **`typography`** — declare the font pool, bind body/heading and custom roles, and tune per-family weights.

Per-block visual styling — sizes, colors, alignment, borders, and which font *variant* a block uses — lives in the [`rules`](/configuration/styling-rules) array. This page is about which *typefaces* are available and how they map to roles.

<Note>
  The fields below are validated against the live API schema. The **API Reference → Render** endpoint is generated directly from the server's OpenAPI contract and is always the authoritative source for every field, enum value, and constraint. You can also call [`GET /v1/fonts`](/api-reference/overview) at runtime to list the hosted catalog and Google Fonts limits for your key.
</Note>

## Selecting hosted fonts with `fontStack`

`fontStack` is an ordered list of hosted font-family keys. Earlier families take precedence; later ones provide fallback coverage (for example, emoji glyphs the text family doesn't contain).

<ParamField body="fontStack" type="string[]" default="[&#x22;inter&#x22;, &#x22;noto-emoji-mono&#x22;, &#x22;twemoji-mozilla&#x22;, &#x22;jetbrains-mono&#x22;]">
  An ordered cascade of 1–4 hosted font-family keys. Omit the field to use the default stack above.
</ParamField>

### Available hosted families

| Key               | Kind                  | Role                                                          |
| ----------------- | --------------------- | ------------------------------------------------------------- |
| `inter`           | Weighted (text)       | Body and headings — provides regular/italic/bold/bold-italic. |
| `lora`            | Weighted (serif text) | Serif body/heading option.                                    |
| `source-serif-4`  | Weighted (serif text) | Serif body/heading option (used by the academic themes).      |
| `jetbrains-mono`  | Code                  | Monospace for fenced code blocks and inline code.             |
| `noto-emoji-mono` | Emoji                 | Monochrome emoji coverage.                                    |
| `twemoji-mozilla` | Emoji (color)         | Color (COLR) emoji coverage.                                  |

All six bundled families are available on **every plan**, including the free tier. There are no font uploads — you select from this catalog, apply a [theme](/configuration/themes) (which references bundled families), or pull a Google Fonts family by URL (below). The default `fontStack` remains `inter` + the code and emoji companions.

<Info>
  Emoji families are appended automatically when you omit them, so a `fontStack` of just `["inter"]` still gets full emoji coverage. The weighted text family (`inter`) is the one that supplies body and heading text.
</Info>

```json theme={null}
{
  "markdown": "# Report 📊\n\nBody text with `inline code`.",
  "fontStack": ["inter", "jetbrains-mono", "twemoji-mozilla"]
}
```

## Using Google Fonts by URL

Pass a Google Fonts URL to render body and headings in any family from [Google Fonts](https://fonts.google.com) — available on every plan. The Google family takes the body/heading slot; the hosted stack then only supplies the emoji and code roles (any weighted family in `fontStack` is ignored).

<ParamField body="googleFontsUrl" type="string">
  A single Google Fonts URL (alias of `googleFontsUrls[0]`). Do not pass it together with `googleFontsUrls`. Accepted forms:

  * CSS API — `https://fonts.googleapis.com/css2?family=Playfair+Display`
  * Specimen — `https://fonts.google.com/specimen/Playfair+Display`
  * Share — `https://fonts.google.com/share?selection.family=...`

  Weight axes in the URL set the min/max endpoint weights fetched for the four-variant model (regular, italic, bold, bold-italic). Up to 4 families per URL.

  Only the `wght` and `ital` axes drive the fetch. Any `opsz`, `wdth`, or custom axis is accepted but ignored — `validate_render_pdf` surfaces this as a `google_font_axis_ignored` typography note. A `..` weight range keeps only its valid 100-step endpoints (100–900); an off-grid weight is dropped (the family renders at its remaining requested weights) and reported as `google_font_weight_snapped`.
</ParamField>

<ParamField body="googleFontsUrls" type="string[]">
  One or two Google Fonts URLs merged into a single pool of at most **4 distinct families**. Default mapping: the first family of the first URL becomes **body**; the first family of the second URL (or the second family of the first URL) becomes **heading**. Override with a [`rules`](/configuration/styling-rules) `set.family` entry or by reordering the URLs. Two URLs do not raise the 4-family cap.
</ParamField>

```json theme={null}
{
  "markdown": "# Annual Review\n\nElegant body copy in a serif face.",
  "googleFontsUrl": "https://fonts.google.com/specimen/Playfair+Display"
}
```

## Binding roles with `typography`

The `typography` object is the unified place to declare the font pool, assign body/heading families, define custom named roles, and tune per-family weights. Positional URL defaults apply when you set nothing.

<ParamField body="typography" type="object">
  Unified font selection layered over `fontStack` and `googleFontsUrl(s)`.

  <Expandable title="typography fields">
    <ParamField body="typography.body" type="string">
      The body text family: the hosted key `inter`, a declared Google Fonts display name (e.g. `Playfair Display`), or a custom role from `typography.named`.
    </ParamField>

    <ParamField body="typography.heading" type="string">
      The heading text family, in the same form as `typography.body`. Per-depth heading families are assigned with [`rules`](/configuration/styling-rules) (`select` by heading `depth` + `set.family`).
    </ParamField>

    <ParamField body="typography.pool" type="object[]">
      Optional override of the families parsed from `googleFontsUrl(s)` (max 4). Each entry has a `name`, optional explicit `weights` (1–4 values from 100–900 in steps of 100), and an optional `italic` flag.
    </ParamField>

    <ParamField body="typography.named" type="object[]">
      Up to two custom roles, each binding a `role` name to a `family`. The lowercase kebab-case `role` (e.g. `accent`, `display`) is then referenced by a rule's `set.family` or `set.font` (`<role>.<variant>`).
    </ParamField>

    <ParamField body="typography.headingVariant" type="string">
      Hosted heading variant policy — `regular` or `bold`.
    </ParamField>

    <ParamField body="typography.variantMap" type="object[]">
      Per-family static weight maps for the four-variant PDF model. Each entry targets a `family` and optionally narrows by `role` (body/heading/named) and, for headings, by `depth`. Resolution is by specificity: depth > role > family.
    </ParamField>
  </Expandable>
</ParamField>

<Info>
  The four-variant model means each role resolves to exactly four embedded faces — regular, italic, bold, and bold-italic. `variantMap` lets you decide which actual upright weights back the regular and bold variants for a given family or role.
</Info>

## Last-resort coverage with `glyphFallback`

When a selected font (hosted or Google) doesn't cover a character in your document, the render falls back per glyph so nothing renders as a missing-glyph box. By default the fallback family is `inter`; set `glyphFallback` to choose your own.

<ParamField body="glyphFallback" type="string">
  A document-level, last-resort family applied per glyph — only codepoints the primary fonts miss route to it. Accepts the same values as `typography.body` (a hosted key, a declared Google Fonts name, or a `typography.named` role). The family actually used is reported in the `X-Render-Font-Fallback` response header.
</ParamField>

## Fonts and themes

A [theme](/configuration/themes) may reference bundled font families (the academic themes use `source-serif-4`, for example). Theme fonts are **all-or-nothing**: if your request declares any font source — a weighted family in `fontStack`, `googleFontsUrl(s)`, or `typography.body`/`typography.heading` — the theme's typography is dropped wholesale so your selection is never mixed with the theme's. Everything else in the theme still applies.

## Reading back what was embedded

Successful renders report the resolved typography in response headers, so you can confirm what was actually used without opening the PDF:

| Header                      | Meaning                                                                        |
| --------------------------- | ------------------------------------------------------------------------------ |
| `X-Render-Fonts`            | The per-role families actually embedded, including per-depth heading overrides |
| `X-Render-Font-Fallback`    | The family used for glyphs the primary fonts missed (e.g. `inter`)             |
| `X-Render-Typography-Notes` | Informational notes about typography resolution                                |

## Example: serif headings over a sans body

This request pulls two Google Fonts families and maps one to headings and one to body.

<CodeGroup>
  ```bash cURL theme={null}
  curl https://api.blinkpdf.io/v1/render \
    --request POST \
    --header 'Accept: application/pdf' \
    --header 'Content-Type: application/json' \
    --header 'x-api-key: bp_xxxxxxxxxxxxxxxxxxxx' \
    --data '{
      "markdown": "# The Monthly Digest\n\nProduct updates, engineering notes, and upcoming events.",
      "metadata": { "title": "The Monthly Digest" },
      "googleFontsUrls": [
        "https://fonts.google.com/specimen/Inter",
        "https://fonts.google.com/specimen/Playfair+Display"
      ],
      "typography": {
        "body": "Inter",
        "heading": "Playfair Display"
      }
    }'
  ```

  ```javascript Node.js theme={null}
  const response = await fetch("https://api.blinkpdf.io/v1/render", {
    method: "POST",
    headers: {
      "x-api-key": "bp_xxxxxxxxxxxxxxxxxxxx",
      "Accept": "application/pdf",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      markdown:
        "# The Monthly Digest\n\nProduct updates, engineering notes, and upcoming events.",
      metadata: { title: "The Monthly Digest" },
      googleFontsUrls: [
        "https://fonts.google.com/specimen/Inter",
        "https://fonts.google.com/specimen/Playfair+Display",
      ],
      typography: {
        body: "Inter",
        heading: "Playfair Display",
      },
    }),
  });

  const pdf = await response.arrayBuffer();
  ```

  ```python Python theme={null}
  import httpx

  response = httpx.post(
      "https://api.blinkpdf.io/v1/render",
      headers={
          "x-api-key": "bp_xxxxxxxxxxxxxxxxxxxx",
          "Accept": "application/pdf",
          "Content-Type": "application/json",
      },
      json={
          "markdown": "# The Monthly Digest\n\nProduct updates, engineering notes, and upcoming events.",
          "metadata": {"title": "The Monthly Digest"},
          "googleFontsUrls": [
              "https://fonts.google.com/specimen/Inter",
              "https://fonts.google.com/specimen/Playfair+Display",
          ],
          "typography": {
              "body": "Inter",
              "heading": "Playfair Display",
          },
      },
  )

  with open("digest.pdf", "wb") as f:
      f.write(response.content)
  ```
</CodeGroup>

<Tip>
  Validate font configurations before spending a render. [`POST /v1/render/validate`](/api-reference/overview#validate-before-render) reports the declared pool, the active body/heading families, and any unused declarations — without metering usage.
</Tip>

<Info>
  Fonts are embedded (and subset) in every PDF, so documents render identically regardless of which fonts the recipient has installed — a requirement for PDF/UA-1 conformance.
</Info>
