lionelhenne / laravel-cockpit-cms
A lightweight client for the Cockpit CMS GraphQL API.
Package info
github.com/lionelhenne/laravel-cockpit-cms
pkg:composer/lionelhenne/laravel-cockpit-cms
Requires
- php: ^8.2
- illuminate/http: ^10.0 || ^11.0 || ^12.0 || ^13.0
- illuminate/support: ^10.0 || ^11.0 || ^12.0 || ^13.0
README
A simple and lightweight client to query the Cockpit CMS GraphQL API from a Laravel application.
Installation
You can install this package via Composer.
composer require lionelhenne/laravel-cockpit-cms
The Service Provider will be automatically registered thanks to Laravel's package discovery.
Configuration
-
Publish the configuration file
To set your API credentials, you first need to publish the package's configuration file. It will be copied to
config/cockpit.php.php artisan vendor:publish --tag="cockpit-config" -
Add your environment variables
Next, open your .env file and add the following keys with your information:
COCKPIT_URL="https://your-cockpit-site.com" COCKPIT_GRAPHQL_ENDPOINT="https://your-cockpit-site.com/api/gql" COCKPIT_API_TOKEN="API-xxxxxxxxxxxxxxxxxxxx"
Note regarding Public APIs: If your Cockpit API access is configured as Public (no token required), you can leave
COCKPIT_API_TOKENempty in your.envfile. The client will automatically skip theAuthorizationheader to avoid 401 errors.
-
Serve Cockpit images via a symlink
This package does not proxy or cache images itself. Cockpit images are expected to be served as static files through a symlink pointing to Cockpit's own upload directory:
ln -s /path/to/cockpit/storage/uploads public/cockpit-uploads
Cockpit::image()(see below) generates paths assuming this symlink exists atpublic/cockpit-uploads. Adjust the symlink name to match if you used a different one.
Usage
This package is designed to simplify fetching data by allowing you to batch multiple GraphQL query fragments into a single API call, with caching built-in.
High-Level Helpers (Recommended)
The recommended way to interact with Cockpit is via the high-level execute() and executeCached() methods. These methods automatically assemble your query fragments and return the contents of the GraphQL data key directly — no need to unwrap an envelope.
Cockpit::execute(array $queries): Assembles and executes a batch of query fragments without caching.Cockpit::executeCached(array $queries, string $cacheKey, $duration = null): Assembles, executes, and caches the result. The duration defaults to 1 month.
Both methods throw a CockpitRequestException on failure (network error, HTTP error, GraphQL error, or malformed response) — see Error Handling below.
Recommended Usage (Base Controller Pattern)
Every controller that needs Cockpit data has to decide between execute() (no cache, for local development) and executeCached() (production). Rather than repeating that ternary in every controller, push it once into your app's base Controller:
<?php namespace App\Http\Controllers; use App\Http\Controllers\Traits\CockpitGQLQueries; use lionelhenne\LaravelCockpitCms\Facades\Cockpit; abstract class Controller { use CockpitGQLQueries; protected function executeCockpitQueries(array $queries, string $cacheKey): array { return app()->environment('local') ? Cockpit::execute($queries) : Cockpit::executeCached($queries, $cacheKey); } }
This way, every controller extending Controller gets executeCockpitQueries() for free, and the cache-vs-no-cache decision lives in exactly one place. CockpitRequestException is left uncaught here on purpose — see Error Handling.
GraphQL fragments live in a trait, kept separate from the controller logic:
<?php namespace App\Http\Controllers\Traits; trait CockpitGQLQueries { protected function getBanner(): string { return ' banner: bannerModel { content _id } '; } protected function getLastNews(int $number = 2): string { return ' last_news: newsModel(sort: {date: -1}, limit: '.$number.') { _id date tag title content image } '; } protected function getOneNews(string $id): string { return ' one_news: newsModel(filter: {_state: 1, _id:"'.$id.'"}) { _id date tag title content image } '; } }
A controller can then batch several fragments — a singleton (bannerModel), a limited list, and a single item filtered by ID — into one API call:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; class CockpitTestController extends Controller { public function test(Request $request) { $data = $this->executeCockpitQueries([ $this->getBanner(), $this->getLastNews(2), $this->getOneNews('1e91181a37396565590000f6'), ], 'cache_banner'); return view('cockpit-test.index', [ 'banner' => $data['banner'], 'last_news' => $data['last_news'], 'one_news' => collect($data['one_news'] ?? [])->first(), ]); } }
A couple of things worth noting here:
bannerModelis a Cockpit singleton, so$data['banner']is already a single associative array — no unwrapping needed.newsModelis a collection, so it's always returned as a list, even when filtered down to a single_id. That's whygetOneNews()is unwrapped withcollect($data['one_news'] ?? [])->first()in the controller — this gives the view a plain array (ornullif nothing matched) instead of a single-item list, so$one_news['title']works directly in Blade rather than$one_news[0]['title'].
{{-- resources/views/cockpit-test/index.blade.php --}} @dump($banner) @dump($last_news) @dump($one_news)
Low-Level Usage
For simple calls or testing, you can still use the low-level query() method, which takes a single, complete GraphQL query string.
$query = '{ banner: bannerModel { content } }'; $result = Cockpit::query($query); $banner = $result['banner'] ?? [];
Images
This package does not proxy, download, or cache images — it relies on the symlink described in Configuration. Cockpit::image() simply builds the relative path:
// In your controller $data = $this->executeCockpitQueries([ $this->getBanner() ], 'cache_banner'); $banner = $data['banner'];
{{-- In your Blade view --}} <img src="{{ Cockpit::image($banner['image']['path']) }}" alt="">
Cockpit::image() returns a domain-relative path (e.g. /cockpit-uploads/2026/06/photo.jpg), suitable for <img src> on your own pages. If you need an absolute URL (Open Graph tags, RSS feeds, emails), combine it with Laravel's built-in asset() helper:
<meta property="og:image" content="{{ asset(ltrim(Cockpit::image($banner['image']['path']), '/')) }}">
Error Handling
Every API-calling method (query, execute, executeCached) throws a lionelhenne\LaravelCockpitCms\CockpitRequestException on failure — unreachable API, HTTP error response, GraphQL errors, or a response missing the expected data key. None of these methods silently return an error array.
use lionelhenne\LaravelCockpitCms\CockpitRequestException; try { $data = Cockpit::execute(['newsModel { title }']); } catch (CockpitRequestException $e) { report($e); // handle the failure (fallback view, retry, etc.) }
If you don't catch it, the exception propagates normally and Laravel renders its standard error page (full details in local/debug mode, a generic 500 in production — always logged to storage/logs/laravel.log).
API Reference
| Method | Description |
|---|---|
query(string $graphQLQuery, array $variables = []) |
Executes a raw GraphQL query, returns the contents of data. |
execute(array $queries) |
Assembles multiple fragments into one query and executes it. |
executeCached(array $queries, string $cacheKey, $duration = null) |
Same as execute(), with result caching. |
cachedQuery(string $key, $duration, callable $callback) |
Low-level helper, wraps Cache::remember(). |
assembleQuery(array $queries) |
Assembles fragments into a single { ... } GraphQL query string. |
image(?string $path) |
Builds the relative path to a Cockpit image, via the symlink. |
License
This project is open-source software licensed under the MIT license.
⚠️ This package was built for my personal needs. It may not be suitable for everyone, and I do not guarantee any support. Use it at your own risk.