Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Canvas Laravel Package

cnvs/canvas

Canvas is a lightweight Laravel publishing platform for creating and managing a beautiful blog with ease. It offers a clean admin interface, Markdown writing, tags, customizable templates, and SEO-friendly pages, all seamlessly integrated into your Laravel application.

View on GitHub
Deep Wiki
Context7

Getting Started

Canvas is a self-hosted publishing package: it ships an admin SPA at a configurable path plus a PHP API, but it does not render your public site. You install it, write/publish posts in the admin UI or via Eloquent, then surface that content from your own routes and Blade views.

Minimal install (4 commands + 1 visit):

composer require austintoddj/canvas
php artisan canvas:install   # publishes config, migrations, assets
php artisan migrate
php artisan canvas:user      # create your first admin

Then hit /canvas (default path). The admin SPA is served from public/vendor/canvas.

Where to look first:

Need Look at
Host-facing how-tos docs/ in the package (install, config, auth, UI, content, webhooks) — readme.md is just a flyer
Every knob you can change config/canvas.php after publishing (config/canvas.php in vendor/cnvs/canvas)
Breaking changes between versions .github/UPGRADE.md
What posts actually are Canvas\Models\Post, Canvas\Enums\Role, database/migrations/

First real use case: publish a post in the admin, then read it back on your own front end:

use Canvas\Models\Post;

Route::get('/blog/{slug}', function (string $slug) {
    $post = Post::query()
        ->where('slug', $slug)
        ->whereNotNull('published_at')
        ->where('published_at', '<=', now())
        ->firstOrFail();

    return view('blog.show', compact('post'));
});

If that query returns nothing, the post is a draft or scheduled — check published_at before blaming the route.

Implementation Patterns

Render public pages yourself

Canvas gives you data, not templates. The typical workflow:

  • Listing page — paginate published posts, eager-load the relations you render (user, tags/topics) to avoid N+1.
  • Detail page — resolve by slug with the published constraint shown above; 404 on drafts.
  • SEO/meta — build <title>, canonical, OG tags from your own controller/view. Canvas stores post meta, but the <head> is yours.
  • Sitemap/RSS — generate from the same published-post scope; don't hand-maintain it.

Keep the "published" filter in one place (a local scope or query object) so list, detail, sitemap, and feed can't drift.

Seed or import content programmatically

Canvas models are ordinary Eloquent models, so fixtures, migrations from another CMS, and bulk imports are straightforward:

Post::create([
    'title' => 'Hello',
    'slug' => 'hello',
    'summary' => '…',
    'body' => $html,
    'published_at' => now(),
    'user_id' => $admin->id,
]);

For anything non-trivial, write a one-off artisan command in your app rather than clicking through the SPA. Wrap imports in a transaction and slug-deduplicate up front.

Wire host auth into Canvas

Canvas expects your App\Models\User (or whatever you point the guard at) and roles: Contributor / Editor / Admin (Canvas\Enums\Role). Two integration shapes:

  1. Single-user / small team — leave the package defaults, create users with canvas:user, done.
  2. Host-integrated — add the optional Canvas\Concerns\HasCanvasAccess trait to your User model and map your own role column or gate into Canvas's role checks. Decide this before you ship, because retrofitting role sources means auditing every policy and middleware.

Also confirm the auth guard in config/canvas.php matches the guard your host app actually logs people in with. Mismatched guards produce "logged in but kicked out of /canvas" symptoms.

Customize the admin without forking

php artisan canvas:ui publishes the SPA stubs/views so you can adjust layout, branding, or add small extensions. Prefer this over editing vendor/. Remember the shipped resources/dist/ build is committed — if you touch JS, you own the rebuild step in your deploy pipeline.

Webhooks and events

Canvas supports webhooks (plus integrations like Unsplash, AI writing, and a weekly digest). Treat webhook delivery as best-effort: accept the delivery, persist it, process in a queued job, and make the handler idempotent (deliveries retry). Store the signing secret in .env and never in config/canvas.php committed to git.

Localization

UI strings live in resources/lang/{locale}/app.php — 17 locales, en authoritative. Publishing those lang files lets you reword labels for your editors without touching the React build. If you add a language, mirror the full key set; partial catalogs produce mixed-language UI.

Gotchas and Tips

  • canvas:install is not idempotent in spirit. It publishes config, migrations, and assets. Re-running after an upgrade is normal, but use --force deliberately — it will overwrite any edits you made to published stubs.
  • Assets go stale after upgrades. public/vendor/canvas is a copy, not a symlink. After composer update, re-publish assets or the SPA will talk to an API that changed underneath it. Bake this into your deploy script.
  • config:cache bites. Any change to path, guard, or disks requires php artisan config:clear (or a fresh deploy). A stale config cache is the #1 cause of "route 404 / wrong middleware" confusion.
  • Route collisions. The default /canvas path can shadow a host route or get shadowed by a catch-all. Change path in config rather than fighting route order; also check domain if you're multi-tenant.
  • You own the public output. Canvas will not give you SEO, sitemaps, pagination markup, or a public layout. Budget for that — it's the most common underestimated piece of the integration.
  • Sanitize post bodies. Bodies are HTML (with embeds). If untrusted users can be Contributors, pass the rendered body through an HTML sanitizer before echoing it with {!! !!}.
  • Uploads need storage:link. Featured images land on the configured disk; check config/canvas.php disk settings and that the public URL resolves on your deploy target (S3 vs. local differs).
  • Upgrade path is explicit. Read .github/UPGRADE.md before every minor bump; new migrations may add columns that your custom queries or imports must populate.
  • Don't fork to fix UI copy. canvas:ui stubs + lang files cover nearly every branding/localization need; a fork costs you every future upgrade.
  • Testing your integration. Keep your own feature tests around the published-post scope and any import command — those are your contracts, and the package's internals will move.
  • Debugging checklist when the SPA misbehaves: (1) config cache cleared, (2) assets re-published, (3) guard/middleware match, (4) migrations run, (5) browser console for API 401/419 (session vs. CSRF) before assuming a package bug.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
pharaonic/laravel-notification-dependencies
beautinow/translug
dev-brainiac/bot-cloaked
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
adachsoft/api-version-bundle
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core