---
title: "Write an API link adapter"
description: "Add API auto-linking for another language or library, with an adapter that finds names in code and resolves them to reference pages."
url: "https://ewels.github.io/starlight-codeblocks/extend/write-an-api-link-adapter/"
markdown: "https://ewels.github.io/starlight-codeblocks/extend/write-an-api-link-adapter.md"
section: "Extend"
site: "starlight-codeblocks documentation"
context: "This page is from the documentation of starlight-codeblocks. A Starlight plugin that adds focus, line states, annotations, API auto-linking and 22 more features to the code blocks of a site."
index: "https://ewels.github.io/starlight-codeblocks/llms.txt"
---

# Write an API link adapter

> Add API auto-linking for another language or library, with an adapter that finds names in code and resolves them to reference pages.

[API auto-linking](https://ewels.github.io/starlight-codeblocks/features/api-auto-linking/) links names in code blocks through adapters. The plugin has adapters for Python and Nextflow. A site can add its own adapter for another language, or for its own library, in the `apiLinks.adapters` option.

## The interface

An adapter is an object with a name, a list of languages, two functions and an optional third:

```ts
interface ApiLinkAdapter {
  name: string;
  languages: string[];
  setup(context: AdapterContext): Promise<void>;
  findSymbols(code: string, language: string, attributes: Record<string, string>): SymbolRef[];
  describe?(symbol: SymbolRef): Promise<string | undefined>;
}

interface SymbolRef {
  start: number;
  end: number;
  href: string;
  name: string;
  kind?: string;
  signature?: string;
  summary?: string;
  source: string;
  icon?: string | false;
}
```

| Property | Description |
|---|---|
| `name` | The name of the adapter. Build warnings start with it. |
| `languages` | The languages of the code blocks that the adapter reads, such as `py`. A name also covers the other names of its language: `py` covers `python`. |
| `setup` | Loads the indexes. The plugin calls it once, before the first code block in one of the languages. |
| `findSymbols` | Returns the names in the code that are links, with the link and the text of the card. `attributes` has the attributes on the fence line of the code block that have a text value, such as `title`. |
| `describe` | Optional. Returns a summary for a name that `findSymbols` returned with no `summary`. Use it for text that needs a fetch, such as a documentation page. |

A `SymbolRef` is one name in `code`, its link and the text of its card:

| Property | Description |
|---|---|
| `start` | The index of the first character of the name in `code`. |
| `end` | The index after the last character of the name. |
| `href` | The URL of the reference page. A URL that starts with `/` gets Astro's `base` in front of it. |
| `name` | The qualified name, such as `pathlib.Path`. |
| `kind` | The kind of object, such as `function`, `class` or `process`. |
| `signature` | The first line of the card. Without a signature, the card shows the kind and the name. |
| `summary` | One sentence under the signature. |
| `source` | Where the link comes from, at the bottom of the card. |
| `icon` | The [Simple Icons](https://simpleicons.org/) slug of the icon before the source, such as `flask`, or `false` for no icon. By default, the plugin uses the project at the start of `source`: `NumPy 2.1 documentation` gets the NumPy icon. |

## The adapter context

`setup()` gets a context with these properties:

| Property | Description |
|---|---|
| `root` | The project root, as an absolute path. |
| `cacheDir` | Astro's cache folder, where other integrations keep their data. |
| `fetch(url, check?, options?)` | Gets a URL once, and keeps the body on disk. Returns the body as a `Uint8Array`, or `null`. With `{ quiet: true }`, a failed request shows no warning. |
| `warn(message)` | Logs a build warning with the name of the adapter. |

Use `fetch` from the context to load an index from another site. The body stays in `node_modules/.cache/starlight-codeblocks/`, so later builds read it from disk and work without the network. If the request fails and there is no copy on disk, `fetch` logs a warning and returns `null`. Continue without that index:

```js
async setup(context) {
  const data = await context.fetch('https://example.com/docs/index.json');
  if (data) index = JSON.parse(new TextDecoder().decode(data));
},
```

A server can send an HTML error page with a success status. To stop `fetch` from keeping that page, give it a `check` function that throws for a body you cannot use. `fetch` then keeps nothing, logs a warning that ends with the error message, and returns `null`. If a copy on disk fails the check, `fetch` gets the URL again:

```js
async setup(context) {
  await context.fetch('https://example.com/docs/index.json', (data) => {
    index = JSON.parse(new TextDecoder().decode(data));
  });
},
```

## An example adapter

This site links the names that a JavaScript block imports from starlight-codeblocks to their reference pages. The adapter reads the `import` lines, then finds each imported name outside strings and comments:

```js title="src/adapters/codeblocks-api.mjs"
/** Reference pages of the names that this package exports. */
const pages = {
  codeblocks: {
    href: '/reference/options/',
    signature: 'codeblocks(options?: CodeblocksOptions): StarlightPlugin',
    summary: 'Adds the features to the Starlight site.',
  },
  pluginCodeblocks: {
    href: '/reference/expressive-code-plugins/',
    signature: 'pluginCodeblocks(options?: CodeblocksOptions): ExpressiveCodePlugin[]',
    summary: 'Returns every feature as Expressive Code plugins.',
  },
  python: {
    href: '/features/api-auto-linking/#python-adapter',
    signature: 'python(options?: PythonAdapterOptions): ApiLinkAdapter',
    summary: 'Links Python names through the imports in each block.',
  },
  nextflow: {
    href: '/features/api-auto-linking/#nextflow-adapter',
    signature: 'nextflow(options?: NextflowAdapterOptions): ApiLinkAdapter',
    summary: 'Links Nextflow channel factories, operators and modules.',
  },
};

// Comments and strings come first, so that names inside them never match as names.
const TOKENS = /\/\/.*|\/\*[\s\S]*?\*\/|'(?:\\.|[^'\\\n])*'|"(?:\\.|[^"\\\n])*"|`(?:\\.|[^`\\])*`|[A-Za-z_$][\w$]*/g;
const IMPORT = /^import\s+(?:(\w+)\s*,?\s*)?(?:\{([^}]*)\})?\s+from\s+'starlight-codeblocks(?:\/[\w/-]+)?';?$/gm;

/** Links the names that a JavaScript block imports from starlight-codeblocks to their reference pages. */
export function codeblocksApi() {
  return {
    name: 'codeblocks-api',
    languages: ['js', 'mjs', 'ts'],
    async setup() {},
    findSymbols(code) {
      const imported = new Set();
      for (const [, name, list] of code.matchAll(IMPORT)) {
        for (const item of [name, ...(list?.split(',') ?? [])]) if (item) imported.add(item.trim());
      }
      const symbols = [];
      for (const { 0: name, index } of code.matchAll(TOKENS)) {
        const page = imported.has(name) && pages[name];
        const isKeyOrProperty = code[index - 1] === '.' || /^\s*:(?!:)/.test(code.slice(index + name.length));
        if (page && !isKeyOrProperty) {
          symbols.push({
            start: index,
            end: index + name.length,
            name,
            ...page,
            source: 'starlight-codeblocks reference',
          });
        }
      }
      return symbols;
    },
  };
}
```

To use an adapter, add it to `apiLinks.adapters`. Keep the built-in adapters in the list, because the list replaces the default:

```js title="astro.config.mjs"
import codeblocks from 'starlight-codeblocks';
import { nextflow } from 'starlight-codeblocks/adapters/nextflow';
import { python } from 'starlight-codeblocks/adapters/python';
import { codeblocksApi } from './src/adapters/codeblocks-api.mjs';

codeblocks({
  apiLinks: { adapters: [python(), nextflow(), codeblocksApi()] },
});
```

The code block above shows the result: `codeblocks`, `nextflow` and `python` are links.

## Rules

- Leave out a name when you are not certain about its link. A wrong link is worse than no link.
- Never return names inside strings or comments from `findSymbols`.
- Give positions in the `code` that `findSymbols` gets. The plugin ignores a name that spans two lines.
- Keep `findSymbols` synchronous. Do all slow work, such as a fetch, in `setup`.
- Return the same result for the same code. The functions run at build time, so they cannot read anything from the browser.
- Do not throw in `setup` for a missing index. Log a warning with `context.warn` and continue. If `setup` throws, the build logs a warning, and the adapter links nothing.
- Use `http` or `https` URLs, or URLs inside the site. The plugin drops a link with any other scheme, such as `javascript:`.

If two adapters return the same name, or names that overlap, the first adapter in the list wins.

## Cards on links outside code blocks

Another plugin can give its own links the same card, for example the type names in the signatures of starlight-pydocs. The plugin writes the text of the card on the link. starlight-codeblocks owns the script and the styles.

```md
<p data-scb-api-links="">A mapping is a <a href="https://docs.python.org/3/library/stdtypes.html#dict" target="_blank" rel="noopener" data-scb-api-head="class dict" data-scb-api-summary="Create a new dictionary." data-scb-api-source="Python 3 documentation" data-scb-api-action="Opens docs.python.org in a new tab." aria-description="class dict. Create a new dictionary. Python 3 documentation. Opens docs.python.org in a new tab.">dict</a> of names to values.</p>
```

| Attribute | Where | Description |
|---|---|---|
| `data-scb-api-links` | An element that holds the links | Loads the card script on the page. |
| `data-scb-api-head` | Each `a` element | The first line of the card, such as a signature. The attribute marks the link for the card. |
| `data-scb-api-summary` | Each `a` element, optional | One sentence under the first line. |
| `data-scb-api-source` | Each `a` element, optional | Where the link comes from. |
| `data-scb-api-icon` | Each `a` element, optional | The key of the icon before the source, in `data-scb-api-icons`. |
| `data-scb-api-icons` | The element that holds the links, optional | JSON that maps each icon key to the path of a 24 by 24 SVG icon. |
| `data-scb-api-action` | Each `a` element, optional | What the link does when a reader clicks it, such as "Opens docs.python.org in a new tab." |

- The card works on every page, also on a page with no code blocks. It gets the colours of the code block themes.
- The card is only for sighted readers. Give each link an `aria-description` with the same text for screen readers.
- The attributes are a public interface. A change to them is a breaking change.
- The card needs `codeblocks()` in the Starlight plugins. It does not work with the Expressive Code preset alone.
