# Writing a provider

> Wrap a foundry API, an internal font server, or a folder on disk.

A provider is a function that returns up to three methods. `defineFontProvider()` gives it a name and types its options:

```ts
import { defineFontProvider } from 'unifont'

export interface FoundryOptions {
  token: string
}

export const foundry = defineFontProvider('foundry', async (options: FoundryOptions, ctx) => {
  // Runs once, when the instance is created.
  return {
    listFonts() { /* … */ },
    getFontProperties(family) { /* … */ },
    resolveFont(family, options) { /* … */ },
  }
})
```

Use it like any built-in:

```ts
const unifont = await createUnifont([
  foundry({ token: process.env.FOUNDRY_TOKEN }),
  providers.google(),
])
```

## The context

`unifont` passes two globally-configured primitives into each provider via a `ctx`.

`ctx.fetch` retries failures and rewrites the built-in providers' URLs to [`apiBase`](/docs/browser). Your own endpoints aren't rewritten, so a custom provider works in a browser only if its API sends CORS headers or you proxy it yourself.

`ctx.storage.getItem(key, init)` is a cache that fills itself. Pass a function and it runs once, then reads from whatever storage the user configured:

```ts
export const foundry = defineFontProvider('foundry', async (options: FoundryOptions, ctx) => {
  const families = await ctx.storage.getItem('foundry:index.json', () =>
    ctx.fetch('https://api.foundry.example/families').then(res => res.json()),
  )
  // …
})
```

## `listFonts()`

Optional. Return the family names you can resolve, or `undefined` if listing doesn't make sense for your source.

```ts
listFonts() {
  return families.map(family => family.name)
}
```

## `getFontProperties()`

Optional. Return `undefined` for a family you don't know, and `unifont` will move on to the next provider.

```ts
getFontProperties(family) {
  const match = families.find(item => item.name === family)
  if (!match) {
    return undefined
  }
  return {
    weights: match.weights,
    styles: match.styles,
    subsets: match.subsets,
    formats: ['woff2'],
  }
}
```

## `resolveFont()`

The only method that's required for every provider. Return `undefined` for families you don't know. Otherwise return `fonts`, and `fallbacks` if you have them. Key your cache on the options, because the same family may resolve differently from one request to the next:

```ts
import { hash } from 'ohash'

async resolveFont(family, options) {
  const match = families.find(item => item.name === family)
  if (!match) {
    return
  }

  return {
    fonts: await ctx.storage.getItem(`foundry:${family}-${hash(options)}.json`, async () => {
      const css = await ctx.fetch(match.cssUrl).then(res => res.text())
      return parseIntoFontFaceData(css)
    }),
    fallbacks: ['sans-serif'],
  }
}
```

Most providers are really just a CSS parser. If your source hands you a stylesheet, read the built-in ones in [the repository](https://github.com/unjs/unifont/tree/main/src/providers): `google.ts` for subset-split woff2, `fontshare.ts` for whole-file families, `npm.ts` for local resolution.

## Family options

Declare a family options type and it's inferred at the call site, under your provider's name:

```ts
export interface FoundryFamilyOptions {
  optical?: 'display' | 'text'
}

import type { ResolveFontOptions } from 'unifont'

export const foundry = defineFontProvider('foundry', async (options: FoundryOptions, ctx) => ({
  async resolveFont(family, options: ResolveFontOptions<FoundryFamilyOptions>) {
    options.options?.optical // typed
    // …
  },
}))
```

```ts
await unifont.resolveFont('Foundry Sans', {
  options: {
    foundry: { optical: 'display' },
  },
})
```

## Failing

If a provider throws while starting up, it's dropped with a console error and the rest carry on, unless the user set `throwOnError`.

Don't try to avoid that by returning empty data when something fails. An empty result means "I know this family and it has nothing", which stops anything else being asked. Throw, or return `undefined`.
