# The gitlab gem: a Ruby wrapper and CLI over the GitLab REST API

> The gitlab gem is a BSD licensed Ruby library and command line tool that wraps the GitLab REST API, with global configuration, sudo mode, auto pagination and a CLI where any client method becomes a subcommand. It suits Ruby teams automating GitLab and platform engineers driving it from a shell, and the API surface is generated from the GitLab documentation.

**NARKOZ/gitlab** — Ruby wrapper and CLI for the GitLab REST API

- Repository: https://github.com/NARKOZ/gitlab
- Website: https://narkoz.github.io/gitlab
- Stars: 1,086 · Forks: 399
- Language: Ruby
- License: BSD-2-Clause
- Published: 2026-10-19 · Updated: 2026-10-19 · Language: en
- Canonical page: https://hysenlabs.com/projects/narkoz-gitlab

## Global configuration or a per-request client

Gitlab is a Ruby wrapper and CLI for the GitLab API, and the first thing to understand is that it has two ways to be used, one global and one local.

The global form configures a module-level client:

```ruby
Gitlab.configure do |config|
  config.endpoint       = 'https://example.net/api/v4'
  config.private_token  = 'qEsq1pt6HJPaNciie3MG'
end
```

The comment on the endpoint line is the useful part. The default is `ENV['GITLAB_API_ENDPOINT']` and it falls back to `ENV['CI_API_V4_URL']`, so in a CI environment the second one is what usually applies. The private token defaults to `ENV['GITLAB_API_PRIVATE_TOKEN']`, and the token can be a user's private token or an OAuth2 access token, which matters for anything that acts on behalf of a user rather than as the job itself.

The optional settings are the ones you only discover you need later. There is `user_agent`, defaulting to a string identifying the gem and its version. There is `sudo`, the username for sudo mode, defaulting to nil. And there is `body_as_json`, defaulting to false, which uses `application/json` for all requests with a body. That last one exists because some GitLab installations and proxies behave differently depending on the content type, and turning it on is a debugging move as much as a feature.

The alternative is a per-request client, and the difference is more than stylistic. The `Gitlab.client` form takes the endpoint, the token and an `httparty` options hash, which is how you set custom headers:

```ruby
g = Gitlab.client(
  endpoint: 'https://example.com/api/v4',
  private_token: 'qEsq1pt6HJPaNciie3MG',
  httparty: {
    headers: { 'Cookie' => 'gitlab_canary=true' }
  }
)
```

A global client is convenient for a script that only talks to one GitLab. A per-request client is what you want in a multi-tenant tool, a test suite, or anywhere two different GitLab instances have to be addressed in the same process.

There is also a proxy setting at the module level, `Gitlab.http_proxy('proxyhost', 8888)`, with a variant that takes a username and a password for basic auth, and a timeout setting through `ENV['GITLAB_API_HTTPARTY_OPTIONS'] = '{read_timeout: 60}'`.

The gem is BSD-2-Clause licensed, the last push was on 2026-10-01, and the recent releases are v6.1.0 on 2025-12-05, v6.0.0 on 2025-06-28 and v5.1.0 on 2024-10-29.

## Responses as objectified hashes, and pagination you can drive

The response handling is the part that will shape your code, so it is worth looking at what a call actually returns.

A project list comes back as an array of `Gitlab::ObjectifiedHash` objects, and each one carries the parsed JSON under a `data` key with the keys accessible as methods, so `user.email` returns the string rather than a hash lookup. The hash also nests, so a project's `owner` is itself an objectified hash. That is a deliberate choice to make the JSON feel like Ruby rather than making you remember the key names.

The trade is that the object is a view over the parsed body rather than a typed model. There is no compile-time guarantee that a field exists, and if GitLab changes a key the failure appears as a missing method at runtime rather than as an error at load.

Pagination is where the gem does more than a thin wrapper, and it is the feature most worth knowing. A paginated response is itself an object you can interrogate:

```ruby
projects = Gitlab.projects(per_page: 5)

projects.has_next_page?
projects.next_page
```

So you can walk the pages explicitly, checking for a next page and asking for it. And for the common case there is `auto_paginate`, which takes a block and iterates every project for you, or returns the whole collection as an array if you call it without a block.

That is a meaningful convenience, since the naive version of this is a loop with a page counter and a stop condition, and getting it wrong is how you silently process half a dataset. The block form also means the pages are fetched lazily, so iterating a large collection does not require holding it all in memory.

One more piece of global state is worth calling out because it is a security feature rather than a convenience. `Gitlab.sudo = 'other_user'` puts the client into sudo mode, performing API calls as another user, and `Gitlab.sudo = nil` disables it. Sudo mode is how an admin token can act on behalf of a specific user, and it is the sort of switch that should be set and unset deliberately rather than left in a global.

## The CLI is the client methods turned into subcommands

The command line interface is not a separate tool with its own vocabulary. It is the client methods exposed as commands, and the documentation says so directly: when you want to know which CLI commands are supported, look at the client methods implemented in the gem, and any of those methods can be called as a command by passing the parameters of the commands as parameters of the CLI.

That is a clever design, because there is exactly one implementation of every operation and therefore no possibility of the CLI and the library drifting apart. The cost is that the CLI surface is as large as the API surface, which is not something you learn from a help screen.

The CLI needs two environment variables, and a third that is optional:

```sh
export GITLAB_API_ENDPOINT=https://gitlab.example.com/api/v4
export GITLAB_API_PRIVATE_TOKEN=<your private token from /profile/personal_access_tokens>

export GITLAB_API_HTTPARTY_OPTIONS="{verify: false}"
```

The third one sets any HTTParty option you need, and the documented example is disabling SSL verification, which the note describes as useful if using a self-signed certificate.

The command examples show what is possible:

```sh
gitlab users
gitlab user
gitlab user 2
gitlab user --only=id,username
gitlab user --except=email,bio
gitlab user 2 --json
gitlab create_merge_request 4 "New merge request" "{source_branch: 'new_branch', target_branch: 'master', assignee_id: 42}"
```

The `--only` and `--except` flags are the ones that make the CLI usable at scale, because the default output for a user object includes every field, and filtering to the identifiers you need makes the output pipeable into `jq` without post-processing.

The `--json` flag renders the result as JSON, which is what makes the CLI composable with other tools rather than a human-only interface.

And the last example shows the interesting one. Options that need structure are passed as a YAML hash, so `assignee_id: 42` and two branch names go through as a single string argument. That is a slightly unusual interface, and it is a direct consequence of the design, where a command is a method call and a method call takes keyword arguments that the CLI has to serialise.

There is also an interactive shell. `gitlab shell` starts a session, `help` lists the available commands, and you can run them directly, for example `groups` to list groups or `protect_branch 1 master` to protect a branch. The presence of a shell is a nod to the workflow of an administrator who wants to explore a GitLab instance before deciding what to script.

## The repository, the docs and the local GitLab

The repository carries more than the gem, and the extra parts tell you about the project's maturity.

There is a `docs/` directory with a VitePress site, and the root `package.json` is named `gitlab-docs` with scripts for `docs:dev`, `docs:build` and `docs:preview`. The documentation site is published at narkoz.github.io/gitlab, and there is a separate `gitlab-live` repository linked from the README.

That `gitlab-live` link is a pattern worth recognising in API client projects. The documentation examples show real response objects, and the way to keep them honest is to point them at a running instance, so the examples are generated from reality rather than written by hand and left to rot.

The Ruby side is conventional and well equipped. There is a `gitlab.gemspec`, a `Gemfile`, a `Rakefile`, a `spec/` directory for the tests, a `bin/` and an `exe/` directory, and a `.rubocop.yml` with a `.rubocop_todo.yml` beside it. The todo file is the interesting part, since a RuboCop todo file is a list of offences that are knowingly grandfathered in, and having one means the project accepts new code to a standard while not rewriting the old.

The Docker setup is for running a GitLab to test against, not for running the gem. The compose file brings up two services, an `app` service built from the repository's own Dockerfile with the source mounted and a `docker.env` file, and a `gitlab` service using the `gitlab/gitlab-ce:latest` image with a hostname of `gitlab.example.com` and a port mapping of 3000. The `GITLAB_OMNIBUS_CONFIG` environment variable holds the Omnibus configuration, with a comment explaining that any other gitlab.rb configuration goes there, one per line.

The Dockerfile for the app is minimal, built on `ruby:3.2` with a bundle install and a `CMD` of `bin/console`.

There is a `CHANGELOG.md`, a `CONTRIBUTING.md`, a `LICENSE.txt` and a `docs/` directory, and the gem's API documentation is published on rubydoc.info.

One piece of naming to be aware of: the gem is called `gitlab` on rubygems, the Homebrew formula is `gitlab-gem`, and the documentation warns that the Homebrew version may not be the latest.

## Conclusion

The gitlab gem fits a Ruby service that needs to talk to GitLab without hand writing HTTP calls, and a platform engineer who wants the same operations available from a terminal, because the CLI is generated from the client methods rather than written separately. Skip it if your GitLab work is in a language other than Ruby and you want an official SDK, and note that the API surface tracks the GitLab REST API, so a GitLab upgrade can add or change methods. Before you depend on it, decide how you will authenticate, since the private token and the OAuth2 access token are both accepted, and set the endpoint explicitly rather than relying on the environment defaults. The last push was on 2026-10-01 and the current release is v6.1.0 from 2025-12-05.

## FAQ

### What is the gitlab gem?

It is a Ruby wrapper and CLI for the GitLab API, with a client whose methods cover the REST API, a global configuration block, and a command line interface where any client method can be called as a command.

### How do I configure the gitlab gem's endpoint and token?

Through `Gitlab.configure`, setting `config.endpoint` and `config.private_token`, or at the module level with `Gitlab.endpoint` and `Gitlab.private_token`. The endpoint defaults to ENV['GITLAB_API_ENDPOINT'] falling back to ENV['CI_API_V4_URL'], and the token to ENV['GITLAB_API_PRIVATE_TOKEN'].

### How does pagination work in the gitlab gem?

A paginated response exposes `has_next_page?` and `next_page`, and `auto_paginate` iterates every page for you, either with a block or returning the full collection as an array.

### How do I use the gitlab gem as a command line tool?

Set GITLAB_API_ENDPOINT and GITLAB_API_PRIVATE_TOKEN, and optionally GITLAB_API_HTTPARTY_OPTIONS for HTTParty settings, then call the client methods as commands, for example `gitlab user 2 --json` or `gitlab user --only=id,username`.

### What licence does the gitlab gem use?

BSD-2-Clause, with the licence in LICENSE.txt in the repository. The last push was on 2026-10-01 and the current release is v6.1.0 from 2025-12-05.

## Sources

- [License: BSD-2-Clause](https://github.com/NARKOZ/gitlab/blob/master/LICENSE)
- [NARKOZ/gitlab on GitHub](https://github.com/NARKOZ/gitlab)
- [Project website](https://narkoz.github.io/gitlab)
- [README](https://github.com/NARKOZ/gitlab/blob/master/README.md)
- [Releases](https://github.com/NARKOZ/gitlab/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/narkoz-gitlab
