---
title: "Work with several forges"
description: "Register several providers and route calls by ref or by URL."
canonical_url: "https://forgeslink-3mwmle35v-poetry.vercel.app/guides/multiple-forges"
---
# Work with several forges

> Register several providers and route calls by ref or by URL.

Tools that work across forges, such as an inbox or a bot, shouldn't need to know which forge a thread lives on. `createForges()` makes that possible. It takes a list of providers and returns one object that finds the right one for each ref or URL:

```ts
import { createForges, github, gitlab } from 'forges'

const forges = createForges([
  github({ auth: { type: 'token', token: process.env.GITHUB_TOKEN! } }),
  gitlab({ auth: { type: 'token', token: process.env.GITLAB_TOKEN! } }),
])
```

## Route by ref

Every ref carries a `forge` and an `instance`, and that is all `forges` needs to find the provider it belongs to. `forges.for(ref)` returns that provider, or `undefined` when you haven't registered one for the ref's forge and instance. Call the operation on the provider, so that you can check what it supports first:

```ts
const provider = forges.for(thread.ref)
if (provider?.can('threads.close', thread.ref.kind)) {
  await provider.threads.close(thread.ref)
}
```

To pick a provider by forge rather than by ref, use `forges.get('github')` or `forges.all('gitlab')`.

## Read a pasted URL

Users often paste a link rather than a ref. `forges.parseUrl()` turns a web URL on any registered instance into refs, and it returns `undefined` when none of your providers serves that URL:

```ts
const parsed = forges.parseUrl('https://github.com/nuxt/nuxt/issues/1')

if (parsed?.thread) {
  const thread = await parsed.provider.threads.get(parsed.thread)
}
```

It also understands clone URLs, such as the output of `git remote get-url origin`, as long as the host is the same as the instance's web host:

```ts
const { repo } = forges.parseUrl('git@github.com:nuxt/nuxt.git') ?? {}
```

The same goes for the value of a `repository` field in `package.json`, such as `git+https://github.com/nuxt/nuxt.git` or `git://github.com/nuxt/nuxt.git`. The `github:`, `gitlab:` and `bitbucket:` shorthands refer to `github.com`, `gitlab.com` and `bitbucket.org`, so a self-hosted instance doesn't match them:

```ts
const { repo } = forges.parseUrl('github:nuxt/nuxt') ?? {}
```

A file URL sets `file`, with the repository-relative `path`, the `at` revision and the first selected `line`:

```ts
const parsed = forges.parseUrl('https://github.com/nuxt/nuxt/blob/main/README.md#L3')

if (parsed?.file) {
  const readme = await parsed.provider.contents.getFile(parsed.repo, parsed.file.path, { ref: parsed.file.at })
}
```

The revision is the first path segment after the file route, so in `blob/feat/a/README.md` it is `feat` and the path is `a/README.md`. `at` is unset when the URL leaves the revision to the default branch, as an Azure DevOps file URL without a `version` does. Some forges, such as Forgejo, Bitbucket and Azure DevOps, use the same URL for a folder, so `file` can name a folder there.

A comparison URL, such as `compare/main...feat` or `compare/v1.0.0..v1.1.0`, sets `compare` with its `base` and `head` revisions. A GitLab tag page sets `tag`, and a release page sets `release`. A commit within a pull request, such as GitHub's `pull/42/commits/{sha}`, sets both `thread` and `commit`:

```ts
const parsed = forges.parseUrl('https://github.com/nuxt/nuxt/compare/v4.0.0...v4.1.0')

if (parsed?.compare) {
  const comparison = await parsed.provider.contents.compare(parsed.repo, parsed.compare.base, parsed.compare.head)
}
```

A forge that spreads its services over several hosts, such as Sourcehut, links issues on `todo.sr.ht` and patches on `lists.sr.ht` rather than on the git host. `parseUrl()` reads those hosts too, so `https://todo.sr.ht/~oys/astro-resume/4195` becomes an issue ref.

To go the other way, `provider.urlFor()` builds the web URL for a ref without sending a request.

## Read references in text

Issue and comment bodies mention other threads and commits by reference rather than by URL, such as `#42`, `nuxt/nuxt#42`, GitLab's `!42` or a commit sha. `forges.parseReferences()` finds them in a piece of text and returns each one with its `start` and `end` offsets, so that you can turn it into a link. Pass the repository the text is written in as `from`, because `#42` refers to a thread in that repository:

```ts
const from = { forge: 'github', instance: 'github.com', owner: 'nuxt', name: 'nuxt' } as const
const text = 'Fixes #42, see nuxt/cli@a1b2c3d'

const links = forges.parseReferences(text, { from }).map(({ start, end, ref }) => ({
  text: text.slice(start, end),
  url: forges.for(ref)?.urlFor('sha' in ref ? { commit: ref } : { thread: ref }),
}))
```

It reads the references that `provider.referenceFor()` writes, and the ones people type on that forge, such as `GH-42` on GitHub. `#42` reads as an issue on forges where issues and pull requests share numbers, and as a pull request on Bitbucket, which has no issue tracker, and `threads.get()` returns the thread with its kind. A bare sha needs 7 to 40 hex characters, with at least one letter and one digit, so that numbers and words don't read as one. References inside a URL are skipped, but code is not, so leave code spans and blocks out of the text that you pass. On Tangled, `#42` is a thread's display number; `referenceFor()` writes it back as `#42`, or `owner/repo#42` across repositories.

## Fan out reads

Some calls make sense across all your forges at once, so `createForges()` runs them on every provider that supports them:

- `forges.notifications.list()` lists the notifications of one provider after another.
- `forges.search.threads()` and `forges.search.repos()` search every provider that supports search and merge the results, with the most recently updated first. To merge by creation time instead, pass `sort: 'created'`, and to start with the oldest, pass `direction: 'asc'`.
- `forges.threads.getMany()` takes refs from any of your forges, groups them by provider, batches each group with that provider's `threads.getMany()` and returns the threads in the order you passed the refs. A ref for a forge that you haven't registered becomes a warning in its place.

Searches only read the next page when you iterate that far, so you can `break` out of the loop as soon as you have enough results. Two details are worth knowing. A provider that can't sort the way you asked only keeps its results in order within each page. And if a provider fails, you get a warning and no further items from it, while the other providers carry on.

## Run two instances of one forge

A provider takes its instance from the host of its `baseUrl`. This means you can register github.com and a GitHub Enterprise Server side by side:

```ts
const forges = createForges([
  github({ auth: personal }),
  github({ baseUrl: 'https://ghe.example.com/api/v3', auth: work }),
])
```

Since `forges.for()` finds a provider by forge and instance, two providers can't share both, and `createForges()` throws if they do. If you need two providers for the same host, for example with different credentials, give one of them its own name by passing a different `instance`.


## Sitemap

See the full [sitemap](https://forgeslink-3mwmle35v-poetry.vercel.app/sitemap.md) for all pages.
