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

Cms Framework Laravel Package

artisanpack-ui/cms-framework

A modular CMS framework for Laravel that provides the building blocks for content-driven applications, including admin UI components, content management scaffolding, and customizable backend tooling to speed up development.

View on GitHub
Deep Wiki
Context7

Getting Started

This is a Laravel package for building a block-based CMS on top of a visual editor: it supplies the content model (records/custom post types), the block-rendering pipeline, theme.json-driven global styles, query loops, and block-theme template parts.

Start here, in this order:

  1. Install and publish. Pull the package in, publish its config, and confirm the service provider is registered. The config file is the map of the rest of the system — read it end to end before writing feature code.
  2. Set up a theme. You need a theme directory with theme.json and a parts/ folder. theme.json is load-bearing: it drives global styles and declares template part areas (see Gotchas).
  3. Register one custom post type. Give the model a status column (or a published() scope) up front — this is the single decision that most affects whether query loops behave.
  4. Render a page with a query loop. Build a template with a query loop over that post type, add a queryId, and paginate it. This exercises the three subsystems you'll touch daily: records, QueryRuntime, and the renderer.

First meaningful use case: a "related posts" loop placed inside a record's own block content. It's the shortest path to understanding how the record context propagates through rendering, and it's a pattern the package explicitly supports.

Implementation Patterns

Query loops and pagination. Blocks that loop over content are resolved by QueryRuntime. Give each loop a queryId — pagination state is stored per loop on a query-{queryId}-page variable, so two loops on one page (e.g. a featured strip and a main archive) paginate independently. If you omit the ID, you get shared state. Supported attributes:

  • postIn (or its alias include) — an explicit list of records, returned in the order you requested. Good for hand-curated rails and editor-picked content.
  • random (or rand) — randomised ordering.
  • Standard ordering, paging, and per-page attributes alongside those.

Note that loop attributes are not a trusted source for visibility. status on a block attribute is ignored by resolve().

Visibility and authorization tiers. QueryRuntime::resolve() returns published content only, for every post type — posts, pages, and custom-type records alike. When a request has already been authorized to see drafts or private records (an authenticated preview, an editor surface, an API endpoint behind a policy), call QueryRuntime::resolveTrusted(), which honours status. Treat resolveTrusted() as a privileged entry point: never let a public-facing template reach it because a visitor supplied a status value.

Preset class slugs. Preset classes come from the editor, which uses WordPress-style kebab casing — letters split from digits. A fontSize: "h2" block is tagged has-h-2-font-size, not has-h2-font-size. When you write your own CSS or target presets in custom blocks, match the split form. If you're emitting preset classes yourself, emit both forms when they differ — the package does. CSS custom property names are not split, so --wp--preset--font-size--h2 stays as-is. This bites hardest on h1–h6 size presets and colour/gradient/font-family slugs like gray900.

Targeting blocks in styles.blocks. Rules keyed under styles.blocks are emitted as scoped selectors. For core/* and other namespaces the mapping is the obvious one. For artisanpack/* — which covers forks of core blocks that still render the core class — the emitter targets both .wp-block-x and .wp-block-artisanpack-x:

:root :where(.wp-block-quote, .wp-block-artisanpack-quote)
:root :where(.wp-block-quote h2, .wp-block-artisanpack-quote h2)

So write your styles.blocks entry against the underlying core block name when you're styling a fork, and it will reach both. First-party blocks that aren't forks still match via .wp-block-artisanpack-x. Block names are validated.

Records rendering their own block content. HasRenderedBlockContent::renderContent() runs the visual editor's entity passes before rendering, in the same order as the editor's own block component: synced patterns are inlined, query loops are inlined with the record as host, then the tree is stamped against the record. In practice this means post-title, post-excerpt, post-terms and other post-* blocks inside a record's content resolve to that record — no manual binding context needed. Related-posts loops authored into the record body work. Two deliberate non-behaviours: a post-content block inside a record's own content renders empty (it would recurse), and a loop that lists the record currently being rendered skips it. Each pass is skipped if visual-editor doesn't supply it, and a pass that throws leaves the tree untouched rather than half-rendered.

Template parts from theme files. Parts can live purely as parts/{slug}.html or parts/{slug}.blade.php. Previously their area was guessed from the slug. Now a theme-file part takes the area from the active theme's theme.json templateParts entry whose name matches the slug, falling back to the slug guess (header/footer/sidebar/uncategorized) when there's no entry or the entry omits area. A part saved in the Site Editor keeps its own stored area, which wins over theme.json.

{
  "templateParts": [
    { "name": "mobile-menu", "area": "navigation-overlay" }
  ]
}

This matters immediately for the Navigation block, which only renders an overlay part whose area is navigation-overlay. Declare it in theme.json and it works on a fresh install instead of requiring a save round-trip.

Gotchas and Tips

  • Two cache keys moved. The emitter cache key is now v13 (having been bumped to v12 earlier in the same series). If you're upgrading, clear your compiled CSS / object cache, or you'll keep serving selectors from the old key and conclude the fixes didn't land. Add a deploy step for this.
  • status in block attributes is silently ignored. If a loop isn't showing drafts, the fix is resolveTrusted() behind an authorization check — not a status attribute on the block. Attribute values are user-editable and treated as untrusted.
  • Custom types need a status source to be filtered. resolve() uses the model's published() scope if present, otherwise an == 'published' filter when the table has a status column. A model with neither is returned unfiltered — drafts, everything. If you have such a type, add the scope or column; don't rely on the default.
  • Pages now default to published. If you were relying on the old behaviour where pages came back in every status from an admin context, that's a behaviour change. Audit any admin listing that assumed the looseness.
  • Recursion is guarded, not configurable. A post-content block inside a record's own body renders empty, and self-referential loops skip the current record. This is by design; don't try to work around it by nesting a second record fetch — restructure the content instead.
  • Theme-file parts need theme.json for non-guessable areas. If your overlay/mega-menu part renders nothing, check templateParts in the active theme's theme.json first, then check whether the part has ever been saved in the Site Editor (a saved part's area wins and can mask the file).
  • Preset slug forms are a common source of "it works in the editor but not the canvas". The emitter lists both .has-h2-font-size and .has-h-2-font-size, but any CSS you write by hand is on you. Grep for has-[a-z]+[0-9] patterns when debugging style discrepancies.
  • Fork styling is two-sided. When writing styles.blocks rules for an artisanpack/* fork, be aware the selector expands to both classes — specificity is :root :where(...), i.e. zero specificity, so user CSS and block-level styles will beat it. That's intended for a base layer, but it surprises people expecting global styles to win.
  • Extension points worth knowing: the renderer's entity passes are individually skippable and non-fatal when absent — so visual-editor can be swapped or partially configured without breaking record rendering. If you're writing a custom resolver, keep it on resolve() unless you own the authorization check, as the bundled visual-editor resolver does.
  • Debugging tip: when block tree output looks wrong, determine which stage failed — pattern inlining, loop inlining, or stamping — by rendering the same content outside a record context. If it's correct there, the issue is in the record pass, not your block markup.
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.
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log
gregsphotography/site-builder-components
io-digital/clickatell
snowsoft/laravel-admin
oxygen/auth
relicsoftware/barakah-payment-gateway
tobya/work-with-composer
equipc/laravel-email-validator
equipc/laravel-email-database-log
adedsec/nova-translated-labels
wazza/laravel-db-encryption
hokny/laravel-generator
vonsogt/backup
jengo/queues
jeremykenedy/laravel-observability
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums