Canvas Headless Publishing for Laravel

repository·main·Indexed 25 days ago

https://github.com/austintoddj/canvas

A headless publishing package for Laravel (PHP >= 8.3, Laravel >= 12) that provides an admin SPA for content authors. It allows developers to build custom public-facing readers using Eloquent models, webhooks, and SEO helpers, or use the optional Blade-based starter frontend. Key features include a TipTap-powered editor, scheduled publishing, post analytics, and a role-based access system (Contributor, Editor, Admin).

Tokens
5.8K
Snippets
13
Records
44
Agent score
85%

What's inside Canvas

  1. Install the optional Canvas reader frontend

    main

    If you want a ready-to-use public blog, run the canvas:ui command. This publishes Blade views, a controller stub, and routes to your application, providing a starter reader frontend at the /canvas-ui path.

    php artisan canvas:ui
  2. Integrate with Webhooks for headless workflows

    main

    Configure webhooks in Admin → Integrations → Webhooks. Canvas sends signed JSON POST requests to your HTTPS URL upon lifecycle events: publish, schedule, update, unpublish, and delete.

    Payload Content: Includes metadata (slug, title, summary, featured image, SEO meta, topic/tags, author) but does not include the full HTML body.

    Recommended Workflow:

    1. Receive the event (e.g., post.published).
    2. Fetch the full post in your application using Eloquent (e.g., Post::published()->where('slug', ...)).
    3. Revalidate your cache, rebuild static pages, or purge your CDN.
  3. Install Canvas in a Laravel application

    main

    To install Canvas, require the package via Composer and run the installation Artisan command. Canvas requires PHP >= 8.3 and Laravel >= 12. It uses your application's existing authentication (defaulting to the web guard).

    composer require austintoddj/canvas
    php artisan canvas:install
  4. Upgrade Canvas

    main

    For minor and patch updates, update the package via Composer and then run the canvas:publish command to ensure assets are updated.

    Note: Major versions may contain breaking changes; refer to the official upgrade guide for those instances.

    composer update austintoddj/canvas
    php artisan canvas:publish
  5. Install the optional Canvas UI starter

    main

    To quickly set up a Blade-based reader frontend, run the canvas:ui artisan command. This publishes views, a controller stub, and routes, and automatically appends the necessary route requirement to your routes/web.php file.

    Use the --force flag to overwrite existing files if you are re-installing or resetting the UI.

    php artisan canvas:ui
  6. Render Post body HTML

    main

    The post.body field contains HTML generated by the TipTap editor, not Markdown. When rendering on a custom frontend, you should treat the body as unescaped HTML (e.g., using {!! $post->body !!} in Blade) because it is written by authenticated users.

    Key Styling Requirements:

    • Wrap the body HTML in a root element with the class canvas-post-body.
    • Use CSS to handle specific block types like img.canvas-post-body-image, pre.canvas-post-body-code, and table.canvas-post-body-table.
    • For embeds (YouTube, Vimeo, X/Twitter), ensure you apply responsive aspect ratios (e.g., 16:9 for videos).
    • Important: X/Twitter cards require a JavaScript resize listener to handle height changes reported via postMessage. You can reuse the pattern found in resources/js/lib/posts/iframe-resize.ts or the logic in the Canvas UI partial ui/partials/embeds.blade.php.
  7. Configure Canvas settings in config/canvas.php

    main

    The following keys in config/canvas.php affect how the admin and frontend interact:

    KeyDefaultDescription
    pathcanvasAdmin SPA mount point
    domainnullOptional admin subdomain
    user_modelApp\Models\UserThe Eloquent model for authors
    guardwebAuth guard used for the admin
    storage_diskpublicDisk used for media
    storage_pathcanvasPrefix for media in storage
    middleware['web']Extra middleware for admin routes
  8. Automate Canvas asset publishing on Composer update

    main

    To ensure Canvas assets are automatically published whenever you run composer update, add the canvas:publish command to the post-update-cmd section of your application's composer.json file.

    {
        "scripts": {
            "post-update-cmd": ["@php artisan canvas:publish --ansi"]
        }
    }
  9. Configure media storage and paths

    main

    Media uploads use the disk and path defined in config('canvas.storage_disk') and storage_path. By default, these are the public disk and the canvas/ prefix.

    Post bodies and featured images often store root-relative paths (e.g., /storage/canvas/...). Ensure your storage is linked via php artisan storage:link (which canvas:install does automatically) or that your custom CDN/disk is configured to serve these paths correctly. For absolute URLs (e.g., for Open Graph), use helpers like Canvas\Support\MediaUrl or PostSeo.

  10. Implement a Laravel API for a separate SPA

    main

    If building a separate frontend (Next.js, Nuxt, mobile), do not use the Admin API. Instead, create your own routes using Eloquent to maintain control over auth, caching, and response shapes.

    Example implementation in routes/api.php:

    // routes/api.php — example, you own this
    Route::get('/blog/posts', function () {
        return Post::published()
            ->select(['id', 'slug', 'title', 'summary', 'featured_image', 'published_at', 'user_id', 'topic_id'])
            ->with(['topic:id,name,slug', 'user:id,name'])
            ->latest()
            ->paginate(10);
    });
    
    Route::get('/blog/posts/{slug}', function (string $slug) {
        $post = Post::published()
            ->with(['tags:name,slug', 'topic:id,name,slug', 'user:id,name'])
            ->firstWhere('slug', $slug) ?? abort(404);
    
        return [
            'post' => $post,
            'seo' => PostSeo::resolve($post, url("/blog/{$slug}")),
        ];
    });
  11. Use PostSeo for SEO metadata

    main

    To generate SEO-friendly metadata (title, description, canonical URL, image URL, etc.) for a post, use the PostSeo::resolve method. This is recommended for custom frontends to ensure consistency with the Canvas UI.

    use Canvas\Support\PostSeo;
    
    $seo = PostSeo::resolve($post, url()->current());
    // Returns object with: title, description, canonical_url, image_url, image_alt
  12. Query tags, topics, and authors

    main

    Use the following patterns to retrieve posts associated with specific tags, topics, or authors via Eloquent.

    use Canvas\Models\Tag;
    use Canvas\Models\Topic;
    use Canvas\Models\CanvasUser;
    use Canvas\Models\Post;
    
    // Get posts by Tag
    $tag = Tag::firstWhere('slug', $slug);
    $posts = $tag->posts()->published()->with(['user', 'topic'])->latest()->paginate();
    
    // Get posts by Topic
    $topic = Topic::firstWhere('slug', $slug);
    $posts = $topic->posts()->published()->with(['user', 'tags'])->latest()->paginate();
    
    // Get posts by Author (CanvasUser)
    $canvasUser = CanvasUser::query()->where('username', $username)->firstOrFail();
    $posts = Post::query()
        ->where('user_id', $canvasUser->user_id)
        ->published()
        ->latest()
        ->paginate();