# Providers

> The seven built-in providers, and what each one can tell you.

A provider is a small adapter over one font source. `createUnifont()` takes them in order and asks each in turn until one answers.

```ts
import { createUnifont, providers } from 'unifont'

const unifont = await createUnifont([
  providers.google(),
  providers.bunny(),
  providers.fontshare(),
  providers.fontsource(),
  providers.googleicons(),
  providers.npm(),
])
```

## What each one can do

Not every provider implements every method. `listFonts()` is optional, for example.

| Provider | `resolveFont` | `getFontProperties` | `listFonts` | Needs options |
| --- | --- | --- | --- | --- |
| `google` | yes | yes | yes | no |
| `bunny` | yes | yes | yes | no |
| `fontshare` | yes | yes | yes | no |
| `fontsource` | yes | yes | yes | no |
| `googleicons` | yes | yes | yes | no |
| `npm` | yes | yes | no | no |
| `adobe` | yes | yes | no | `id` |

`npm` can't list, because the answer would be the whole registry. `adobe` can't, because Typekit ties everything to a project id.

## Google

```ts
providers.google()
```

The largest open library. It serves woff2 with a `unicode-range` per subset, which is why one weight of a Google family often comes back as a dozen faces, one per script.

Two experimental options are worth knowing about. `experimental.glyphs` asks for a subset containing only the characters you name. That's how you get a 2 kB file for a logo:

```ts
providers.google({
  experimental: {
    glyphs: { Poppins: ['Hello', 'World'] },
  },
})
```

`experimental.variableAxis` gives you axes other than weight (slant, casual, cursive, mono) for families that publish them:

```ts
providers.google({
  experimental: {
    variableAxis: {
      Recursive: { CASL: [['0', '1']], MONO: [['0', '1']] },
    },
  },
})
```

You can pass both per family too, through `resolveFont`'s `options.google`, which wins over whatever the provider was created with.

| Option | Type |
| --- | --- |
| `experimental.glyphs` | `{ [family: string]: string[] }` |
| `experimental.variableAxis` | `{ [family: string]: Partial<Record<VariableAxis, ([string, string] \| string)[]>> }` |
| `options.google.experimental.glyphs` | `string[]` |
| `options.google.experimental.variableAxis` | `Partial<Record<VariableAxis, ([string, string] \| string)[]>>` |

## Bunny

```ts
providers.bunny()
```

The same library as Google, mirrored on `fonts.bunny.net` without the analytics. The family names match, so you can swap `bunny` in wherever a request to Google would be a privacy problem. The [compare view](/compare) shows where the two disagree on subsets.

## Fontshare

```ts
providers.fontshare()
```

Indian Type Foundry's library. Around a hundred families, free for commercial use, and better drawn than most free fonts.

Fontshare doesn't split by subset, so a family comes back as one file per weight and style, with no `unicode-range` at all.

## Fontsource

```ts
providers.fontsource()
```

Open fonts packaged for npm and served from jsDelivr. Useful if you want the Google library without touching Google's servers, or the exact files a lockfile would pin.

## Google Icons

```ts
providers.googleicons()
```

Material Symbols, as variable icon fonts. `experimental.glyphs` matters more here than anywhere else. The full Material Symbols file is huge, and naming the icons you use cuts it to almost nothing.

```ts
providers.googleicons({
  experimental: {
    glyphs: {
      'Material Symbols Outlined': ['arrow_right', 'favorite', 'arrow_drop_down'],
    },
  },
})
```

As with Google, `options.googleicons.experimental.glyphs` narrows it to one family and wins. Either way it only works on the newer `Material Symbols` families.

| Option | Type |
| --- | --- |
| `experimental.glyphs` | `{ [family: string]: string[] }` |
| `options.googleicons.experimental.glyphs` | `string[]` |

## npm

```ts
providers.npm()
```

Resolves fonts from npm packages, either from `node_modules` or from a CDN. It reads your `package.json` dependencies and recognises `@fontsource/*`, `@fontsource-variable/*`, and other font packages it knows about.

In a build tool you usually want local resolution with the CDN fallback turned off, so nothing leaves the machine:

```ts
import { access, readFile } from 'node:fs/promises'

providers.npm({
  readFile: path => readFile(path, 'utf-8').catch(() => null),
  exists: path => access(path).then(() => true).catch(() => false),
  remote: false,
})
```

With `remote: false` the sources come back as `file://` URLs into `node_modules`, ready to copy or hash into your own build. Pass `resolve` for any layout where the package isn't linked under `<root>/node_modules`: pnpm's isolated store, hoisting to a monorepo root, Yarn PnP, or a bundler alias.

```ts
import { fileURLToPath } from 'node:url'

providers.npm({
  readFile: path => readFile(path, 'utf-8').catch(() => null),
  resolve: id => fileURLToPath(import.meta.resolve(id)),
})
```

| Option | Type | Default |
| --- | --- | --- |
| `cdn` | `string` | `'https://cdn.jsdelivr.net/npm'` |
| `remote` | `boolean` | `true` |
| `readFile` | `(path: string) => Promise<string \| null>` | none |
| `exists` | `(path: string) => Promise<boolean>` | falls back to `readFile` |
| `resolve` | `(id: string) => string \| null \| Promise<string \| null>` | `import.meta.resolve`, then `<root>/node_modules/<id>` |
| `root` | `string` | `'.'` |

Return `null` from `resolve` (or throw) when a package isn't installed, and the provider falls back to the CDN unless `remote` is `false`. Without `exists`, checking for a font file means reading and decoding all of it through `readFile`.

Any CDN that serves packages by path works for `cdn`, so `providers.npm({ cdn: 'https://esm.sh' })` is fine.

Three more options apply to one family at a time:

| Family option | Type | Default |
| --- | --- | --- |
| `package` | `string` | detected from `package.json`, or inferred from the family name |
| `version` | `string` | `'latest'`, and only used for CDN resolution |
| `file` | `string` | the per-weight and per-style entry points (`<weight>.css`, `<weight>-italic.css`), then `index.css` |

```ts
const { fonts } = await unifont.resolveFont('Roboto', {
  options: {
    npm: { package: '@fontsource/roboto', file: 'latin.css' },
  },
})
```

## Adobe

```ts
if (!process.env.TYPEKIT_ID) {
  throw new Error('TYPEKIT_ID is required to resolve Adobe fonts.')
}

providers.adobe({ id: process.env.TYPEKIT_ID })
```

Adobe Fonts, through one Typekit project id or several. The id is yours, so this site can't ask Adobe on your behalf. Families that only exist on Adobe are missing from the catalogue here, even though `unifont` can resolve them on your own machine.

`id` is required and takes a `string` or a `string[]`. Read it from the environment rather than writing it into your source.

## Limiting a single call

Providers belong to the instance, but you can limit any one call to a few of them:

```ts
const { fonts } = await unifont.resolveFont('Poppins', {}, ['bunny'])
const properties = await unifont.getFontProperties('Poppins', ['bunny'])
```

That's how the [compare view](/compare) asks every provider the same question and labels each answer.
