Pages, posts, taxonomy, menus, widgets, advertisements and comments — what each does, how it is stored, and how it is rendered.
In the user guide: the Content chapter (pages, blog posts, menus, widgets, advertisements, support tickets).
A Page (app/Models/Page.php) is a translatable, sluggable, media-aware record. There is no separate "homepage" or "contact page" model: the homepage, blog index, contact page, privacy and terms pages are simply Page rows that an admin selects in Settings → Page setup (see Page types and public URLs).
| Aspect | Detail |
|---|---|
| Translated attributes | title, content, styles, scripts, excerpt, meta_title, meta_description, og_title, og_description (table page_translations) |
| Plain columns | author_id, published (bool), settings (JSON object), soft deletes |
settings | Page-level overrides. PageSlugRenderer calls override_theme_options($page->settings), so a page can pin a primary menu, header preset, floating/sticky header, breadcrumb mode, etc. for itself only |
| Media | Collection featured-image (JPEG, PNG, GIF, WebP) |
| Slug | HasSlug — legacy slug column plus the global slugs table; renderer PageSlugRenderer |
| Scopes | published(), home(), blog(), contact(), status(), byAuthor(), slug() |
| Other traits | SearchableTrait, InteractsWithViews (view counts), Sitemapable |
Editing:Admin\PageController has two editing surfaces. edit/update manage metadata and per-locale titles; editContent/updateContent open the GrapesJS page builder for one locale (?locale=en) and save content + styles through clean_page_builder().
Rendering:PageSlugRenderer registers the assets of every shortcode that appears in the content (ShortcodeRegistry::registerContentAssets()), compiles the saved content with Blade::render() (so <x-shortcode-*> tags execute), applies page-level theme overrides, sets SEO meta and breadcrumbs, then renders pages.show.
Post: translatable title, contents, excerpt, meta/og fields; status string, featured flag (0/1/2), comments_status; media collection featured-image with responsive images; reading_time accessor (220 words/minute); relations author(), categories() and tags() (polymorphic catable / taggable pivots).Category and Tag are translatable and use generic morph pivots designed for reuse by other models; only Post attaches them today. Category.type exists for future taxonomies (only post is used).record_page_visit()) when the post_views setting is on, with a cooldown per visitor.The public blog uses App\Services\Blog\Publication to prepare the lead story, supporting features, numbered archive rows and related stories; templates live in resources/themes/echo/blog and posts. There is intentionally no layout registry or settings selector — see docs/blog-publication.md. Search is ?q= on the blog route; the blog index shows the authored Page::blog() content unless a search is active or the page is empty.
The blog and blog-cards shortcodes use the same publication layer as the blog index. The posts widget and the post binding source query published posts directly.
Navigation trees are Menu + MenuItem records managed in Appearance → Menus (Admin\MenuController). An item can be a custom URL, a named route with parameters, or a resource (morphTo) pointing at any linkable model (a page, a post, a booking service…).
mega options: columns, heading, image, width mode; children become columns with column_span, badge, highlighted).Menu::fitBucket() pre-computes a "weight" so CSS decides whether the menu fits inline or collapses to the drawer (see the header rules in Presets, SCSS and assets).MenuBuilder::register() — see Menus and navigation.Widget areas are named regions in the theme (sidebar, categories-sidebar, tags-sidebar, pages-sidebar, post-sidebar, and footer-1…footer-6; the footer count follows the theme option footer_widget_columns). Each area holds ordered widget instances (Widget model: name, title, settings, order, web/mobile visibility, async flag).
Built-in widgets: tru-menu, tru-text, tru-html, tru-advertisement, tru-posts, tru-posts-categories, tru-posts-tags. Modules add more (booking-services, trucommerce-cart-mini, …). Widgets are Arrilot widget classes; instances are loaded from the database for the current device class (web or mobile) and cached for one hour per device. See Widgets and widget areas.
Advertisement records have a type (1 = text, 2 = image, 3 = code), an options JSON blob, a status flag and a click counter. They render through the tru-advertisement widget or the adslot shortcode, and an AdvertisementObserver clears the related caches.
Threaded comments on posts are provided by a comments package (App\Models\Comment extends its entity). They are permission-aware, show a sign-in prompt to guests and are styled in resources/themes/echo/vendor/comments. CommentsPresenter loads the conversation and commenter media once and builds the reply tree in memory. A comment change flushes the response cache.
A Faqs model (translatable) and a FAQ block exist in the page builder, but the core has no admin CRUD route for FAQ records. If you need managed FAQs, build them in a plugin and expose them as a shortcode.
Meta::setMeta($model) (macro from MetaTagsServiceProvider) sets title, description, canonical URL, Open Graph and Twitter Card from a model that has title/meta_title, meta_description and a og-image media collection; for posts it also adds author, published/modified times and section.Admin\SitemapController, System tools) crawls the site and adds pages, posts, categories and tags that implement Sitemapable, plus the contact page. Models from plugins are not added automatically.SearchableTrait); there is no search server.HasSlug + ResolvableSlug — Page types and public URLs.