Noma
Laravel · REST API · 2026

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.

Mental model

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.

HTTP client

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/api
Service class

Encapsulate 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.

Controllers & Blade

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.

Queues & webhooks

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);
    }
}
Preview & drafts

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.

Testing

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.

AI & editors

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.

Links

Documentation cross-references

Now available

Start building with Noma

Create a free account, spin up a project, and ship structured content with our API, SDK, and AI tools.