CMS for Laravel
Laravel applications can consume Noma through the same Content API as our JavaScript SDK: send a bearer token and project-id header on every request, map JSON entries into Eloquent-free view models or DTOs, and wire webhooks into queues for cache and search invalidation.
Headless CMS from PHP
Noma is API-first. Your Laravel app is one of many possible clients—alongside Next.js, Astro, mobile apps, or internal tools. Editors work in the Noma dashboard; Laravel reads published snapshots for public pages and can use state=draft for preview routes protected by your own authentication.
Base URL and headers are documented in Authentication. Rate limits apply (on the order of tens of requests per minute per default group); cache responses in Redis or Laravel's cache when you fan out lists across many workers.
Laravel Http facade and bearer tokens
Laravel wraps Guzzle with the HTTP client. Use withToken() for Authorization: Bearer and add the project-id header on every call. Example configuration:
// config/services.php
'noma' => [
'base_url' => env('NOMA_BASE_URL', 'https://app.nomacms.com/api'),
'project_id' => env('NOMA_PROJECT_ID'),
'token' => env('NOMA_API_KEY'),
],.env
NOMA_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
NOMA_API_KEY=noma_...
# Optional override (defaults to Noma SaaS):
# NOMA_BASE_URL=https://app.nomacms.com/apiEncapsulate Noma calls
Keep controllers thin: inject a NomaClient service that knows how to map errors (401/403/404/429) into exceptions your handlers understand.
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Config;
class NomaClient
{
public function listEntries(string $collection, array $query = []): array
{
$response = Http::baseUrl(Config::get('services.noma.base_url'))
->withToken(Config::get('services.noma.token'))
->withHeaders([
'project-id' => Config::get('services.noma.project_id'),
'Accept' => 'application/json',
])
->get("/{$collection}", $query);
$response->throw();
return $response->json();
}
public function getEntry(string $collection, string $uuid, array $query = []): array
{
$response = Http::baseUrl(Config::get('services.noma.base_url'))
->withToken(Config::get('services.noma.token'))
->withHeaders([
'project-id' => Config::get('services.noma.project_id'),
'Accept' => 'application/json',
])
->get("/{$collection}/{$uuid}", $query);
$response->throw();
return $response->json();
}
}Pagination: when you pass paginate and page, the API returns { data: [...], meta, links }. For limit-only lists, the body may be a bare array—mirror the defensive data ?? payload pattern from our JS guides.
Singleton collections return a single object from list endpoints—see Content for parity with the SDK.
Rendering HTML
<?php
namespace App\Http\Controllers;
use App\Services\NomaClient;
use Illuminate\View\View;
class PostController extends Controller
{
public function index(NomaClient $noma): View
{
$payload = $noma->listEntries('posts', [
'state' => 'published',
'paginate' => 20,
'page' => (int) request('page', 1),
'sort' => 'created_at:desc',
]);
$posts = $payload['data'] ?? $payload;
return view('posts.index', ['posts' => $posts]);
}
public function show(NomaClient $noma, string $uuid): View
{
$post = $noma->getEntry('posts', $uuid, ['state' => 'published']);
return view('posts.show', ['post' => $post]);
}
}In Blade, read display values from $post['fields'] (not a nested data key). Rich text fields follow the field's configured output format (markdown vs HTML) as in the API reference.
Invalidate caches when content changes
Configure Noma webhooks to POST to a Laravel route. Verify signatures if enabled, then dispatch a job that clears tagged cache keys or warms CDN URLs. Keep the webhook controller fast—defer heavy work to the queue.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class NomaWebhookController extends Controller
{
public function __invoke(Request $request): Response
{
// Validate signature from Noma webhook settings, then dispatch a job
// to warm cache, sync search indexes, or notify downstream systems.
return response('OK', 200);
}
}Authenticated preview routes
Build a middleware-protected route that calls the API with state=draft and the same entry UUID. Never expose draft content on anonymous routes. For cross-locale previews, pass the appropriate locale query parameters documented under Content API entries.
Http::fake in PHPUnit / Pest
Use Http::fake() to stub Noma responses in feature tests so CI does not call production. Assert your service translates 404 into the correct Laravel NotFoundHttpException for public pages.
MCP for PHP teams
MCP is editor-centric (Node stdio server). PHP developers still benefit from @nomacms/mcp-server in Cursor or Claude Code while Laravel handles runtime traffic. Skills today target JS frameworks; pair them with this page for backend work—see agent skills.