Search by

aichadigital / lara-content

abkrim

Content management package for Laravel with pages, posts, blocks and menus. Blade + Livewire, multilingual support.

Package info

github.com/AichaDigital/lara-content

pkg:composer/aichadigital/lara-content

Fund package maintenance!

:vendor_name

Statistics

Installs: 21

Dependents: 0

Suggesters: 0

Stars: 0

v0.4.0 2026-09-29 18:39 UTC

This package is auto-updated.

Last update: 2026-09-30 06:07:20 UTC


README

ALPHA VERSION: This package is in early development. The API may change without notice. Not recommended for production use yet.

Latest Version Total Downloads Pipeline Coverage PHPStan level 6 PHP Version Laravel Version License

Development happens on gitlab.castris.com. The GitHub repository is a read-only distribution mirror: issues and pull requests opened there are not seen.

Content management package for Laravel with pages, posts, blocks and menus. Supports Blade templates with optional Livewire components for interactivity. Includes multilingual support via Spatie Translatable.

Features

  • Pages: Flexible page system with customizable layouts and block zones
  • Posts: Blog/news posts with author attribution and publishing workflow
  • Publishing workflow: 5-state lifecycle (draft, review, ready, published, archived) with published_at scheduling
  • Taxonomies: Categories and tags for posts, many-to-many, translatable names
  • SEO fields: Meta title/description, featured image alt, focus keyword, secondary keywords, internal notes (internal-only)
  • Markdown import: content:import-posts command with consumer-defined field mapping, idempotent by slug
  • Menus: Hierarchical menu system with nested items
  • Blocks: Modular content blocks (HTML, Recent Posts, Menu, Contact Form)
  • Layouts: Pre-built page layouts (Single, Sidebar Left/Right, Two/Three Column)
  • Multilingual: Full translation support via spatie/laravel-translatable
  • Extensible: Register custom layouts and blocks via registries
  • Secure: HTML sanitization with configurable allowed tags

Requirements

  • PHP 8.3+
  • Laravel 12+
  • Livewire 3+ (optional, for interactive blocks)
  • users.id UUID v7 char(36) — lara-content is UUID-first (see ADR-001). The consumer app's users table must use UUID v7 as primary key. Setup guide shared with the AichaDigital umbrella: aichadigital/larabill setup-uuid.md.

Installation

Install via composer:

composer require aichadigital/lara-content

Publish and run migrations:

php artisan vendor:publish --tag="lara-content-migrations"
php artisan migrate

Publish the config file:

php artisan vendor:publish --tag="lara-content-config"

Optionally publish views for customization:

php artisan vendor:publish --tag="lara-content-views"

Configuration

Key configuration options in config/content.php:

return [
    // Author model for posts (must have UUID v7 char(36) primary key)
    'author_model' => env('CONTENT_AUTHOR_MODEL', 'App\\Models\\User'),

    // Cache settings
    'cache' => [
        'enabled' => env('CONTENT_CACHE_ENABLED', true),
        'default_ttl' => env('CONTENT_CACHE_TTL', 3600),
    ],

    // Security: allowed HTML tags and attributes
    'security' => [
        'allowed_tags' => ['p', 'br', 'strong', 'em', 'a', 'img', ...],
        'allowed_attributes' => [...],
    ],
];

Usage

Pages

Pages support flexible layouts with multiple content zones:

use AichaDigital\LaraContent\Models\Page;

// Create a page
$page = Page::create([
    'title' => 'About Us',
    'slug' => 'about-us',
    'layout' => 'sidebar-right',
    'status' => 'published',
]);

// Add blocks to zones
$page->blocks()->create([
    'zone' => 'main',
    'block_type' => 'html',
    'content' => ['html' => '<p>Welcome to our company...</p>'],
    'order' => 1,
]);

Posts

Blog posts with author attribution and the publishing workflow:

use AichaDigital\LaraContent\Enums\PublishStatus;
use AichaDigital\LaraContent\Models\Post;

$post = Post::create([
    'title' => 'Getting Started',
    'slug' => 'getting-started',
    'content' => '<p>Introduction...</p>',
    'author_id' => auth()->id(),
    'publish_status' => PublishStatus::PUBLISHED,
    'published_at' => now(),
]);

// Only PUBLISHED posts whose published_at is null or past are public.
$visible = Post::published()->get();

Publishing workflow, taxonomies and SEO

Posts and pages carry a 5-state editorial lifecycle via the publish_status column: draft, review, ready, published, archived (PublishStatus enum). The published_at timestamp schedules publication: a published post with a future date stays out of Post::published().

Taxonomies are many-to-many with translatable names:

use AichaDigital\LaraContent\Models\Category;
use AichaDigital\LaraContent\Models\Tag;

$category = Category::create(['slug' => 'tips', 'name' => ['es' => 'Consejos', 'en' => 'Tips']]);
$tag = Tag::create(['slug' => 'grammar', 'name' => ['en' => 'Grammar']]);

$post->categories()->sync([$category->id]);
$post->tags()->sync([$tag->id]);

SEO fields on posts: meta_title, meta_description, featured_image_alt (translatable), plus internal-only editorial fields (focus_keyword, secondary_keywords, internal_notes). Internal fields are declared in Post::INTERNAL_ATTRIBUTES and excluded from Post::publicAttributes(); they must never be rendered or exposed by public API surfaces.

Importing markdown posts

php artisan content:import-posts ./content/markdown --author=<uuid> [--dry-run]

The command reads *.md files with YAML frontmatter and upserts posts by slug (idempotent; files without a slug key are skipped). Markdown bodies are converted to sanitized HTML at import time (content_type = html).

Frontmatter keys are consumer data, mapped through config/content.php:

  • import.field_map: frontmatter key → post attribute (or categories/tags)
  • import.status_map: consumer editorial state → PublishStatus value
  • import.locale: locale key for single-language frontmatter values
  • import.max_meta_title (60) / import.max_meta_description (155)

The command exits non-zero when any file fails validation; --dry-run validates and reports without writing.

Menus

Hierarchical menus with nested items:

use AichaDigital\LaraContent\Models\Menu;

$menu = Menu::create([
    'name' => 'Main Navigation',
    'slug' => 'main-nav',
]);

$menu->items()->create([
    'title' => 'Home',
    'url' => '/',
    'order' => 1,
]);

Blocks

Render blocks in your views:

@foreach($page->blocks as $block)
    {!! app(BlockRenderer::class)->render($block) !!}
@endforeach

Available Layouts

Slug Name Zones
single Single Column main
sidebar-left Sidebar Left main, sidebar
sidebar-right Sidebar Right main, sidebar
two-column Two Column left, right
three-column Three Column left, center, right

Available Blocks

Slug Name Interactive Description
html HTML Block No Raw HTML content
recent-posts Recent Posts No List of recent posts
menu Menu Block No Render a menu
contact-form Contact Form Yes Livewire contact form

Extending

Custom Layouts

Register custom layouts in your service provider:

use AichaDigital\LaraContent\Registries\LayoutRegistry;
use App\Content\Layouts\CustomLayout;

public function boot(): void
{
    app(LayoutRegistry::class)->register(new CustomLayout());
}

Custom Blocks

Register custom blocks:

use AichaDigital\LaraContent\Registries\BlockRegistry;
use App\Content\Blocks\CustomBlock;

public function boot(): void
{
    app(BlockRegistry::class)->register(new CustomBlock());
}

Testing

composer test

Changelog

Please see CHANGELOG for recent changes.

License

AGPL-3.0-or-later. See License File for details.

Lara Content (Español)

VERSION ALPHA: Este paquete está en desarrollo inicial. La API puede cambiar sin previo aviso. No recomendado para producción todavía.

Paquete de gestión de contenido para Laravel con páginas, posts, bloques y menús. Soporta plantillas Blade con componentes Livewire opcionales para interactividad. Incluye soporte multilingüe via Spatie Translatable.

Características

  • Páginas: Sistema flexible de páginas con layouts personalizables y zonas de bloques
  • Posts: Posts de blog/noticias con atribución de autor y flujo de publicación
  • Flujo de publicación: Ciclo de 5 estados (borrador, revisión, listo, publicado, archivado) con programación via published_at
  • Taxonomías: Categorías y etiquetas para posts, many-to-many, nombres traducibles
  • Campos SEO: Meta título/descripción, alt de imagen destacada, palabra clave principal, secundarias y notas internas (solo internas)
  • Import de markdown: Comando content:import-posts con mapping definido por el consumidor, idempotente por slug
  • Menús: Sistema jerárquico de menús con elementos anidados
  • Bloques: Bloques de contenido modulares (HTML, Posts Recientes, Menú, Formulario de Contacto)
  • Layouts: Layouts predefinidos (Una Columna, Sidebar Izquierda/Derecha, Dos/Tres Columnas)
  • Multilingüe: Soporte completo de traducciones via spatie/laravel-translatable
  • Extensible: Registra layouts y bloques personalizados via registries
  • Seguro: Sanitización HTML con tags permitidos configurables

Requisitos

  • PHP 8.3+
  • Laravel 12+
  • Livewire 3+ (opcional, para bloques interactivos)
  • users.id UUID v7 char(36) — lara-content es UUID-first (ver ADR-001). La tabla users de la app consumidora debe usar UUID v7 como clave primaria. Guía de setup compartida con el paraguas AichaDigital: aichadigital/larabill setup-uuid.md.

Instalación

Instalar via composer:

composer require aichadigital/lara-content

Publicar y ejecutar migraciones:

php artisan vendor:publish --tag="lara-content-migrations"
php artisan migrate

Publicar archivo de configuración:

php artisan vendor:publish --tag="lara-content-config"

Opcionalmente publicar vistas para personalización:

php artisan vendor:publish --tag="lara-content-views"

Uso

Páginas

use AichaDigital\LaraContent\Models\Page;

// Crear una página
$page = Page::create([
    'title' => 'Sobre Nosotros',
    'slug' => 'sobre-nosotros',
    'layout' => 'sidebar-right',
    'status' => 'published',
]);

// Añadir bloques a zonas
$page->blocks()->create([
    'zone' => 'main',
    'block_type' => 'html',
    'content' => ['html' => '<p>Bienvenido a nuestra empresa...</p>'],
    'order' => 1,
]);

Posts

use AichaDigital\LaraContent\Models\Post;

$post = Post::create([
    'title' => 'Primeros Pasos',
    'slug' => 'primeros-pasos',
    'content' => '# Introducción...',
    'author_id' => auth()->id(),
    'status' => 'published',
    'published_at' => now(),
]);

Menús

use AichaDigital\LaraContent\Models\Menu;

$menu = Menu::create([
    'name' => 'Navegación Principal',
    'slug' => 'nav-principal',
]);

$menu->items()->create([
    'title' => 'Inicio',
    'url' => '/',
    'order' => 1,
]);

Layouts Disponibles

Slug Nombre Zonas
single Una Columna main
sidebar-left Sidebar Izquierda main, sidebar
sidebar-right Sidebar Derecha main, sidebar
two-column Dos Columnas left, right
three-column Tres Columnas left, center, right

Bloques Disponibles

Slug Nombre Interactivo Descripción
html Bloque HTML No Contenido HTML
recent-posts Posts Recientes No Lista de posts recientes
menu Bloque Menú No Renderiza un menú
contact-form Formulario Contacto Sí Formulario Livewire

Extensión

Layouts Personalizados

use AichaDigital\LaraContent\Registries\LayoutRegistry;
use App\Content\Layouts\MiLayout;

public function boot(): void
{
    app(LayoutRegistry::class)->register(new MiLayout());
}

Bloques Personalizados

use AichaDigital\LaraContent\Registries\BlockRegistry;
use App\Content\Blocks\MiBloque;

public function boot(): void
{
    app(BlockRegistry::class)->register(new MiBloque());
}

Tests

composer test

Licencia

AGPL-3.0-or-later. Ver archivo de licencia para detalles.