GitLab
The GitLab provider covers gitlab.com and self-managed GitLab. It reads and writes issues, merge requests and commit comments. It also covers to-dos, pipelines and jobs, commit statuses, releases, vulnerabilities and project and group webhooks. Merge requests have kind: 'pull_request'.
| Detail | Value |
|---|---|
| Import | forges/gitlab |
| Factories | gitlab(), gitlabLite() |
| Auth | token, anonymous |
Authentication
The provider accepts a personal, project, group or OAuth access token:
import { gitlab } from 'forges/gitlab'
const forge = gitlab({
auth: { type: 'token', token: process.env.GITLAB_TOKEN! },
}).create()
Project and group access tokens act as bot users. Use one when you need a credential that isn't tied to a person.
Without auth, the provider makes anonymous public reads.
Self-managed instances
Set baseUrl to the root of the instance. The provider appends /api/v4:
gitlab({ baseUrl: 'https://gitlab.example.com', auth })
Webhook delivery logs and redelivery require GitLab 17.0 or later. Pass instanceVersion if you know the version. Otherwise, refreshCapabilities() reads it from /version.
Nested groups
RepoRef.owner is the full namespace path. A project in a subgroup has owner: 'acme/platform' and name: 'widgets'.
Notifications are to-dos
notifications.list() reads the user's to-dos. A to-do is either pending or done, with no separate read state:
markRead()rejects withUnsupportedOperationError.markDone()marks the to-do done.unsubscribe()requires{ thread }, because GitLab can't look up a to-do by ID.
Event kinds from system notes
GitLab reports label, state and assignment changes as English text in system notes. The provider parses this text, so capabilities.eventKinds is 'heuristic'. Text that the provider doesn't recognise becomes an event with kind: 'other'. The event's kindRaw and payload keep the original note.
Approvals as reviews
threads.reviews() returns one review for each approver, with the ID approval:<username>. createReview({ event: 'approve' }) approves the merge request. A body, inline comments or any other event reject with UnsupportedOperationError.
GitLab responds with 401 when the user isn't allowed to approve. The provider raises this response as InsufficientScopeError.
Release assets
GitLab release assets are links to arbitrary URLs, so releases.downloadAsset() is unavailable. To download an asset, fetch asset.url.
Search
search.commits() uses Advanced Search. GitLab doesn't report whether an instance has Advanced Search, so the capability is 'experimental'. On an instance without it, the search returns an empty page.
Commit search accepts free text only. If you pass author, committer, since, until or sort, the result has a filter_unsupported warning. search.threads() has no involves filter and adds the same warning when you pass one.
Webhooks
GitLab doesn't sign the body of a delivery. It sends the hook's secret token unchanged in X-Gitlab-Token, and the provider compares that header with webhookSecret:
const forge = gitlab({
webhookSecret: process.env.GITLAB_WEBHOOK_SECRET,
}).create()
const events = await forge.webhooks.ingest({
headers: request.headers,
body: await request.text(),
})
The event name is in X-Gitlab-Event, and the delivery ID is in X-Gitlab-Event-UUID.
An issue or merge request update that changes labels, assignees or reviewers becomes one event per label or person added or removed. When there's more than one, each event ID is the delivery ID followed by : and the event's index.
When you create a hook with webhooks.create(), nativeEvents takes GitLab's event flag names, such as issues_events.
Recording fixtures
GITLAB_TOKEN=glpat-... FIXTURE_GITLAB_PROJECT=group/project \
FIXTURE_GITLAB_MR=1 pnpm record-fixtures gitlab
To add an issue, set FIXTURE_GITLAB_ISSUE. To record a self-managed instance, set GITLAB_BASE_URL.
Capabilities
Generated by pnpm capability-matrix; do not edit by hand.
| Capability | Support |
|---|---|
experimental | ❌ |
sources.poll | ✅ |
sources.webhook | ✅ |
sources.subscribe | ❌ |
repos.get | ✅ |
users.get | ✅ |
repos.list | ✅ |
repos.labels | ✅ |
repos.createLabel | experimental |
repos.milestones | ✅ |
repos.collaborators | ✅ |
repos.permissionFor | ✅ |
repos.addCollaborator | experimental |
repos.assignableUsers | ✅ |
repos.reviewerCandidates | emulated |
threads.get | issue, PR, commit (experimental) |
threads.list | issue, PR |
threads.getMany | ✅ |
comments.list | issue, PR, commit (experimental) |
comments.edit | issue (experimental), PR (experimental) |
comments.delete | issue (experimental), PR (experimental) |
reactions.list | issue, PR |
notifications.list | ✅ |
notifications.markRead | ❌ |
notifications.markDone | ✅ |
notifications.unsubscribe | ✅ |
notifications.markAllRead | ❌ |
notifications.markAllDone | experimental |
notifications.unreadCount | ✅ |
writes.comment | issue (experimental), PR, commit (experimental) |
writes.upsertComment | issue (emulated), PR (emulated) |
writes.close | issue (experimental), PR |
writes.reopen | issue (experimental), PR |
writes.create | issue (experimental), PR (experimental) |
writes.update | issue (experimental), PR (experimental) |
writes.setLabels | issue (experimental), PR (experimental) |
writes.addLabels | issue (experimental), PR (experimental) |
writes.removeLabels | issue (experimental), PR (experimental) |
writes.setMilestone | issue (experimental), PR (experimental) |
writes.react | issue (experimental), PR (experimental) |
writes.setAssignees | issue (experimental), PR (experimental) |
writes.requestReview | PR (experimental) |
writes.merge | ✅ |
writes.approveAndMerge | ✅ |
writes.transfer | experimental |
writes.markDuplicate | emulated |
subscriptions.get | issue, PR |
subscriptions.set | issue, PR |
installations | ❌ |
checks.thread | PR |
checks.list | ✅ |
checks.report | experimental |
checks.rerun | experimental |
ci.runs | ✅ |
ci.run | ✅ |
ci.jobs | ✅ |
ci.log | experimental |
contents.file | ✅ |
contents.tree | ✅ |
contents.branches | ✅ |
contents.tags | ✅ |
contents.resolveRef | ✅ |
contents.commits | ✅ |
contents.commit | ✅ |
contents.compare | ✅ |
contents.threadFiles | ✅ |
contents.threadCommits | ✅ |
reviews.list | emulated |
reviews.create | emulated |
reviews.submit | ❌ |
reviews.approve | ✅ |
reviews.resolveThread | experimental |
releases.list | ✅ |
releases.get | ✅ |
releases.latest | ✅ |
releases.getByTag | ✅ |
releases.downloadAsset | ❌ |
webhooks.list | experimental |
webhooks.create | experimental |
webhooks.update | experimental |
webhooks.delete | experimental |
webhooks.rotateSecret | experimental |
webhooks.deliveries | ❌ |
webhooks.redeliver | ❌ |
search.threads | ✅ |
search.repos | ✅ |
search.commits | experimental |
securityAlerts | dependency (experimental), code scanning (experimental), secret (experimental) |
eventKinds | heuristic |
auth | token, anonymous |
limits | body 1048576, comment 1000000, label 255 |