GitLab PHP Client: a typed wrapper over the GitLab v4 API
GitLab API v4 client for PHP
At a glance
- What is it?
- GitLab PHP Client is an MIT licensed PHP client for the GitLab API v4 that stays out of the way of your HTTP stack by speaking PSR-7, PSR-17, PSR-18 and HTTPlug, while shipping Laravel and Symfony integrations as separate packages and its own pager and client builder.
- Who is it for?
- Adopt GitLab PHP Client if you need to drive a GitLab instance from PHP and want a typed API surface without committing to a specific HTTP library, since the client is decoupled through PSR-7, PSR-17, PSR-18 and HTTPlug and covers PHP 8.1 through 8.5, and the repository is not archived with a last push on 2026-05-06.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 169 days ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
A client for the v4 API that does not own your HTTP stack
The client wraps the GitLab API v4 for PHP, and its most interesting decision is architectural rather than functional.
The README states that the package is decoupled from any HTTP messaging client by using PSR-7, PSR-17, PSR-18 and HTTPlug. In practice that means you bring the HTTP client and the PSR-17 factory implementations, and the library does the GitLab part. The installation instructions say explicitly that you also need packages that provide psr/http-client-implementation and psr/http-factory-implementation, so the decoupling is not free, it is a dependency swap you perform once.
The payoff is that a Laravel application using Guzzle and a Symfony application using another client can both use the same GitLab layer, and a test suite can substitute its own HTTP client implementation. The README points at the HTTPlug documentation for library users, which is where the mechanics live.
Provenance matters here too. The README says the client is strongly based on php-github-api by KnpLabs, and it lists the sibling clients that followed the same pattern: bitbucket/client by Graham Campbell and knplabs/github-api by KnpLabs. So this is one member of a family of clients with a shared design, which is a good sign for API consistency and a caution about the project's independence.
The package on Packagist is m4tthumphrey/php-gitlab-api, and the repository's default branch is 12.1, matching the current release line.
Authenticating and making the first call
The entry point is a plain constructor and one authenticate call, with the choice of credential left to you.
$client = new Gitlab\Client();
// Choose one authentication method.
// Personal access token authentication
$client->authenticate('your_access_token', Gitlab\Client::AUTH_HTTP_TOKEN);
// OAuth2 authentication
// $client->authenticate('your_oauth_token', Gitlab\Client::AUTH_OAUTH_TOKEN);Two authentication modes are offered, a personal access token over HTTP token auth and an OAuth2 token, and the README shows the second one commented out. The choice of constant is explicit rather than inferred, which is a small but good sign for a client that will be used with credentials of different lifetimes.
The call itself reads like the endpoint it hits:
$project = $client->projects()->create('My Project', [
'description' => 'This is a project',
'issues_access_level' => 'disabled',
]);Each API area is reached through a method on the client, projects() here, and the method returns the decoded response. The API methods live under src/Api, and the README links that directory for the full list, which is the reference to bookmark rather than any tutorial.
What is missing from the example is equally informative. There is no retry configuration, no timeout setting and no rate limit handling in the snippet, which means those behaviours are either inherited from the HTTP client you supply or not present at all.
Pointing the client at a self-hosted GitLab
Not every GitLab lives at gitlab.com, and the client handles that with a single call before authentication:
$client = new Gitlab\Client();
$client->setUrl('https://git.yourdomain.com');
$client->authenticate('your_access_token', Gitlab\Client::AUTH_HTTP_TOKEN);The order matters. The URL is set first, then the token is attached, which is the natural sequence for a client that builds request URIs from a base address.
The same mechanism is what makes the client usable against a self-managed instance with a different base path, a reverse proxy, or a test double. Nothing else in the documented usage changes.
One limitation is worth naming here rather than discovering later: the README does not document certificate configuration or a way to point the client at a custom CA bundle. For an internal deployment with a private certificate authority, that means the problem has to be solved at the HTTP client layer, since that is where the transport is configured, and the README does not describe how the client's own request options are exposed.
ResultPager for endpoints that return pages
GitLab list endpoints are paginated, and hand rolling that loop is where most quick clients fall down. The client ships a pager for it:
$pager = new Gitlab\ResultPager($client);
$issues = $pager->fetchAll($client->issues(), 'all', [null, ['state' => 'closed']]);The signature is worth reading closely. fetchAll takes the API method, a scope argument, and then an array of arguments, where the first element is the project id and the second is the parameter set. The result is the full collection across pages rather than the first page only.
That distinction is the whole point. A naive call to the issues endpoint returns one page and silently truncates whatever is beyond it, which in a report or a sync job looks like the data simply ends. The pager makes the truncation explicit in the API design, so the correct usage is the easy one.
The pager takes the client in its constructor, so it reuses whatever HTTP client and authentication you already configured rather than opening its own connection.
Replacing the HTTP client through the builder
When you need to change how requests are made, the README shows a builder passed into the client constructor.
$plugin = new Http\Client\Common\Plugin\HeaderSetPlugin([
'User-Agent' => 'Foobar',
]);
$builder = new Gitlab\HttpClient\Builder();
$builder->addPlugin($plugin);
$client = new Gitlab\Client($builder);The example sets a custom User-Agent through an HTTPlug plugin, which is the most common real reason to reach for this: a corporate gateway or a rate-limited API that identifies clients by user agent. The README links the HTTPlug plugin documentation for the general mechanism.
This is where the PSR decoupling pays off twice. A plugin is an HTTPlug concept, so the same plugin works regardless of which PSR-18 client is underneath, and the builder is the client's own type rather than a generic container.
The same builder route is the answer to timeouts, retries and logging, none of which appear in the documented usage. The README does not enumerate the builder's full configuration surface, so the source under src is the place to check what else can be set.
Framework integration lives in separate packages
If you are on Laravel or Symfony, the README does not ask you to wire the client up by hand. It points at two third party packages that do it.
For Laravel:
$ composer require "graham-campbell/gitlab:^8.1"For Symfony:
$ composer require "zeichen32/gitlabapibundle:^7.0"The credits are given in the README, with the Laravel integration by Graham Campbell and the Symfony bundle by Jens Averkamp, and they are described as separate packages rather than as extras of this one. That distinction has practical consequences. Their version lines are their own, 8.1 and 7.0 in the commands above, and they will move at their own pace against the client's 12.x line, so pinning the client does not pin the integration.
The standard installation path is the direct Composer require:
$ composer require "m4tthumphrey/php-gitlab-api:^12.1" "guzzlehttp/guzzle:^7.9.2"That command pins the client to the 12.1 line and pulls Guzzle alongside it, which satisfies the PSR-18 and PSR-17 requirements in one step. The version constraint on Guzzle is specific, ^7.9.2, so an application locked to an older Guzzle release has to reconcile that before this dependency can be added.
Docker driven checks and a slow release line
The contribution setup is worth a look because it says something about how the project is policed.
The README asks contributors to run make install and make test, and the Makefile shows what those do. The install, phpunit and analysis tasks are docker runs against the registry.gitlab.com/grahamcampbell/php:8.1-base or php:8.1-cli images, with the working directory bind mounted and, for install, the Composer cache at ~/.composer. The phpunit, phpstan-analyze and psalm-analyze targets exist as separate commands, and test chains phpunit, phpstan-analyze and psalm-analyze together.
There is more than usual here. Both static analysers have baseline files, phpstan-baseline.neon and psalm-baseline.xml, and there are targets to regenerate them, plus a psalm-show-info target. In other words, the project accepts known findings in a tracked file and analyses the rest, which is how a large legacy codebase gets a static analysis story without pretending to be clean. The composer.json, phpunit.xml.dist, psalm.xml and phpstan.neon.dist files, and the vendor-bin/ directory for tool-specific Composer dependencies, complete the picture.
The release cadence tells a different story from the tooling. Version 12.1.0 is dated 2026-05-06, 12.0.0 dates from 2025-02-23, and 11.14.0 from 2024-03-17. That is a major release roughly a year apart and a minor release after a gap of more than a year, with the default branch named after the current line, 12.1. The repository is not archived and the last push was on 2026-05-06.
So the code quality tooling is thorough while the release rhythm is slow, and a team depending on new GitLab API surface should plan around that rather than assume a monthly tag. The licence is MIT, and the README asks that security issues go to Graham Campbell by email, with a published security policy alongside the code of conduct and contribution guidelines.
Editorial conclusion
Adopt GitLab PHP Client if you need to drive a GitLab instance from PHP and want a typed API surface without committing to a specific HTTP library, since the client is decoupled through PSR-7, PSR-17, PSR-18 and HTTPlug and covers PHP 8.1 through 8.5, and the repository is not archived with a last push on 2026-05-06. Do not adopt it if you need a maintained framework integration today, because the Laravel and Symfony packages are separate projects with their own version lines, or if you need features beyond what src/Api exposes. Before you commit, verify two things: that the API method you need exists in the src/Api tree of the branch you plan to pin, and that the psr/http-client-implementation and psr/http-factory-implementation packages are resolvable in your project, since the client requires both and only the Guzzle pairing is shown in the README.
Frequently asked questions
How do I install the GitLab PHP API client?
Require it with Composer, using composer require "m4tthumphrey/php-gitlab-api:^12.1" "guzzlehttp/guzzle:^7.9.2". This version supports PHP 8.1 to 8.5, and you also need packages providing psr/http-client-implementation and psr/http-factory-implementation.
How do I authenticate the GitLab PHP Client?
Call authenticate on the client with a personal access token and Gitlab\Client::AUTH_HTTP_TOKEN, or with an OAuth2 token and Gitlab\Client::AUTH_OAUTH_TOKEN. The README shows both forms, with the OAuth2 line commented out.
How do I use the GitLab PHP Client with a self-hosted GitLab?
Call $client->setUrl('https://git.yourdomain.com') on the client before authenticating, as the README's self-hosted example does. The README does not document certificate or custom CA configuration, which has to be handled at the HTTP client layer.
How do I fetch every page of results with the GitLab PHP Client?
Use the pager, which is constructed with the client and calls fetchAll with the API method and its arguments, for example $pager->fetchAll($client->issues(), 'all', [null, ['state' => 'closed']]). It returns the collection across all pages.
Are there official Laravel and Symfony integrations for the GitLab PHP Client?
The README points to two separate community packages rather than extras of this one: graham-campbell/gitlab for Laravel and zeichen32/gitlabapibundle for Symfony. Each has its own version line, so pinning the client does not pin the integration.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/gitlabphp-client)Was this useful?