Core Integration

Database and Models

Shipped Eloquent models, their key columns, methods, and how migrations are published.

Database and Models

Tagixo ships a complete set of Eloquent models backed by tables prefixed with tgx_. Every model lives under Ccast\Tagixo\Models\ and is registered through the package's service provider — you do not need to publish or reference them by path. The package also ships all migrations; running php artisan migrate after installing the package is all that is required.

You are not forced to use these models. Standalone integrations can store builder JSON in their own tables and ignore the package models entirely. They exist as a convenient default for projects that don't want to design their own schema.

Namespace: all models are in Ccast\Tagixo\Models\. Facades and helpers resolve them through the container — you will rarely need to import them directly in application code, but do so freely when querying from controllers, jobs, or artisan commands.

Migrations

The package bundles all migrations internally. By default they load automatically, controlled by tagixo.auto_load_migrations in config/tagixo.php. If you want a physical copy in your repo — to customise column types, add indexes, or track schema changes in git — publish them first:

php artisan vendor:publish --tag=tagixo-migrations

After publishing, disable auto-loading so the package does not try to run the same migrations twice:

// config/tagixo.php
'auto_load_migrations' => false,

Run migrations the normal way:

php artisan migrate
# or inside Sail
./vendor/bin/sail artisan migrate

The package includes create migrations and incremental alter migrations. If you publish only the creates and modify them, future package updates may ship additional alter migrations that reference columns you have renamed — keep that in mind before editing published migrations.

When upgrading the package, re-run vendor:publish --tag=tagixo-migrations --force to bring in any new migration files. Schema changes are additive whenever possible; soft-deleted rows are preserved across version bumps.

Table inventory

Table Model Notes
tgx_pages Page Primary content store
tgx_layouts Layout Header / body / footer wrappers
tgx_menus Menu Navigation menus
tgx_menu_items MenuItem Nestable links within a menu
tgx_forms FormSchema Form-builder definitions
tgx_sliders Slider Carousel / slideshow entities
tgx_global_variables GlobalVariable Design tokens (colors, fonts, numbers)
tgx_custom_fonts CustomFont Uploaded web font variants
tgx_media Media Uploaded files and crop variants
tgx_mail_templates MailTemplate Builder-authored email templates
tgx_pdf_templates PdfTemplate Builder-authored PDF templates
tgx_builder_templates BuilderTemplate Full-page reusable designs
tgx_builder_library_items BuilderLibraryItem Section / row / column presets
tgx_global_blocks GlobalBlock Shared builder subtrees (reference model)
tgx_site_scripts SiteScript Per-location raw script injection
tgx_site_settings SiteSettings Key-value site configuration
tgx_popups Popup Popup builder entities

PageStatus enum

The PageStatus backed string enum is shared by Page, MailTemplate, and PdfTemplate:

use Ccast\Tagixo\Enums\PageStatus;

// Cases
PageStatus::Draft      // 'draft'
PageStatus::Published  // 'published'
PageStatus::Scheduled  // 'scheduled'
PageStatus::Archived   // 'archived'
Case label() color()
Draft Draft gray
Published Published success
Scheduled Scheduled warning
Archived Archived danger

color() returns a Primix / Filament-compatible badge color string. Use both helpers in custom admin columns or display logic anywhere in your application.

Content models

Content models are the primary storage layer for builder-authored content.

Pagetgx_pages

The central model. Every URL the builder manages maps to a Page record.

Columns

Column Type Notes
title string Human-readable title shown in admin
slug string UNIQUE URL segment; unique across the table
excerpt text Optional short description
content json Full builder payload ({components, body})
rendered_html longText Cached render; null until first render
css longText Cached CSS; null until first render
meta_title string SEO title override
meta_description text SEO description
og_image string Open Graph image path
canonical_url string Canonical URL override
robots enum index,follow / noindex,follow / index,nofollow / noindex,nofollow
status PageStatus Cast to PageStatus enum
published_at datetime Publish-from window; null = immediate
published_until datetime Publish-until window; null = no expiry
parent_id FK→tgx_pages Self-referential hierarchy; nullOnDelete
order int Sibling sort order
metadata json Arbitrary extensible bag
template_type string static / archive / single / specific
model_class string FQCN of bound Eloquent model for dynamic pages
model_id bigint Specific record id (for specific template_type)
model_url_key string URL parameter attribute (default id; set to slug etc.)

content defaults to {"components":[],"body":[]} — the canonical empty builder payload. Records can be created without passing content explicitly.

Casts

'content'       => 'array',
'metadata'      => 'array',
'status'        => PageStatus::class,
'published_at'  => 'datetime',
'published_until' => 'datetime',

Relationships

$page->parent();    // BelongsTo(Page)  — immediate parent
$page->children();  // HasMany(Page)    — children ordered by 'order'

Scopes

Page::published()     // status=published AND inside the publish window
Page::root()          // whereNull('parent_id')
Page::userManaged()   // whereNull('source') — excludes plugin-seeded pages

The published() scope checks all three conditions atomically:

WHERE status = 'published'
  AND (published_at IS NULL OR published_at <= NOW())
  AND (published_until IS NULL OR published_until >= NOW())

Accessors

$page->is_published   // bool — status=Published AND inside the window
$page->url            // string — hierarchical URL built by walking parent chain

Methods

// Resolve the effective layout via LayoutResolver service
$page->getEffectiveLayout(): ?Layout

// Toggle publish state
$page->publish();
$page->unpublish();

// Dynamic page — check and resolve bound model
$page->isDynamic(): bool                               // template_type in [single, specific]
$page->resolveRecord(string|int|null $key): mixed      // looks up model_class by key or model_id

// Render to a full standalone HTML string
$page->renderFull(?string $layoutView = null): string

// Wrap renderFull() in an HTTP response
$page->toResponse(int $status = 200, array $headers = []): Response

// Bypass the published() gate for signed builder preview URLs
Page::isAuthorizedPreviewRequest(Request $request): bool

Cache invalidation: when the builder saves a page, rendered_html and css are cleared automatically. The next call to renderFull() or PageRenderer::renderWithLayout() rebuilds them. If you update content programmatically, clear the cache yourself:

$page->update(['rendered_html' => null, 'css' => null]);

Dynamic pages (template_type other than static): these are template pages that bind to an Eloquent model. The archive type renders a listing context; single resolves a record from the URL path (/{slug}/{key}); specific is always bound to the single record at model_id. The model is not resolved automatically — call resolveRecord() in your controller:

// PublicPageController
$page = Page::published()->where('slug', $slug)->firstOrFail();

$record = $page->isDynamic()
    ? $page->resolveRecord(request()->route('key'))
    : null;

if ($page->isDynamic() && $record === null) {
    abort(404);
}

return $page->toResponse();

Soft deletes: Page uses SoftDeletes. Trashed pages are excluded from all default queries. The published() scope does not add whereNotNull('deleted_at') explicitly because the global soft-delete scope already handles it.

MailTemplatetgx_mail_templates

Builder-authored transactional email templates. Uses the same PageStatus enum as Page.

Columns

Column Type Notes
name string Admin label
slug string UNIQUE Machine identifier
subject string Default email subject
preheader string Preview text shown in client list view
content json Builder payload (defaults to empty payload)
rendered_html longText Cached render
css longText Cached CSS
status PageStatus Cast to enum
published_at datetime Publish-from date
metadata json Extensible bag

Key method

// Returns email-client-safe HTML with inlined CSS
$template->renderHtml(): string

// Typical usage from a Mailable:
Mail::html(
    MailTemplate::where('slug', 'welcome')->firstOrFail()->renderHtml(),
    fn ($m) => $m->to($user)->subject('Welcome aboard')
);

The MailRenderer is optimised for the mail context — it excludes layout wrappers and outputs a 600 px-constrained table structure rather than the full page layout. See mail-and-pdf-templates for the full rendering pipeline.

Soft deletes: MailTemplate uses SoftDeletes.

PdfTemplatetgx_pdf_templates

Same shape as MailTemplate with three additional columns that drive the @page CSS rule:

Column Type Default
paper_size string A4
orientation string portrait
margin string 2cm
// Returns print-ready HTML; feed to any PDF engine
$html = PdfTemplate::where('slug', 'invoice')->firstOrFail()->renderHtml();

// Browsershot (Puppeteer wrapper):
Browsershot::html($html)->save(storage_path('app/invoice.pdf'));

// DomPDF:
return Pdf::loadHTML($html)->download('invoice.pdf');

Soft deletes: PdfTemplate uses SoftDeletes.

Layout and design models

Layout models control the structural wrappers applied to pages and the reusable design library available in the builder sidebar.

Layouttgx_layouts

Header / body / footer wrappers applied to pages. The layout wraps the page content slot — the page's own modules render between the layout's header and footer.

Columns

Column Type Notes
name string Admin label
is_global boolean Default false; only one global layout allowed at a time
conditions json Condition rules for automatic layout assignment
header_content json Builder payload for the header zone
body_content json Builder payload for the body wrapper zone
footer_content json Builder payload for the footer zone
header_css text Cached CSS for header
body_css text Cached CSS for body wrapper
footer_css text Cached CSS for footer
header_rendered_html longText Cached header HTML
body_rendered_html longText Cached body wrapper HTML
footer_rendered_html longText Cached footer HTML

Static helper

Layout::global(): ?Layout   // returns the single global layout, or null

Boot behaviour

  • Saving a layout with is_global = true automatically unsets the flag on all other layouts — only ONE global layout exists at any time.
  • Attempting to delete the global layout throws \RuntimeException. Reassign the global flag to another layout before deleting.

Layout resolution: pages no longer carry a layout_id foreign key (removed in a later alter migration). The LayoutResolver service evaluates each layout's conditions and returns the first matching one, falling back to Layout::global(). Call $page->getEffectiveLayout() to retrieve the resolved layout from any context.

BuilderTemplatetgx_builder_templates

Full reusable page designs used by the "Save as template" / "Load template" affordances in the builder.

Columns: name, description, content (json), context (page|form|mail|pdf), thumbnail.

Scopes: forContext(string), pages(), forms(), mails(), pdfs(), ordered().

BuilderLibraryItemtgx_builder_library_items

Section / row / column level presets. Intended for reusing layout pieces, not whole pages.

Columns: name, description, type (section|row|column), category, content (json), context, is_global (boolean).

When is_global = true the item is treated as a shared reference — editing it in one place propagates to all pages that embed it.

GlobalBlocktgx_global_blocks

Builder subtrees (sections, rows, columns, or individual modules) stored once and referenced by id. Pages hold a placeholder node carrying global_block_id rather than a copy of the content — editing the block updates every page that references it.

Columns: title, slug, type, category, content (json), rendered_html, css, status.

Scopes: published(), draft().

Method: isEmpty(): bool — returns true when content.components is empty.

Soft deletes: GlobalBlock uses SoftDeletes. Trashing a global block leaves orphan placeholder nodes on pages — the renderer falls back gracefully to rendering nothing for missing global blocks, but clean up references before trashing.

Navigation models

Menutgx_menus

Named navigation menus. A menu is a container; the actual links live in MenuItem records.

Columns: name, slug, description, css_class, metadata (json).

Relationships

$menu->items()        // HasMany(MenuItem) — root items only, ordered by 'order'
$menu->allItems()     // HasMany(MenuItem) — all items regardless of nesting level
$menu->visibleItems() // HasMany(MenuItem) — root visible items only, ordered

Static helper

Menu::findBySlug('main-navigation'): ?Menu

Soft deletes: Menu uses SoftDeletes. Deleting a menu cascades to all its MenuItem records via the database FK constraint (cascadeOnDelete).

MenuItemtgx_menu_items

Individual links within a menu, nestable to arbitrary depth via parent_id.

Columns

Column Type Notes
menu_id FK→tgx_menus Parent menu; cascadeOnDelete
parent_id FK→tgx_menu_items Nullable self-ref for nesting; cascadeOnDelete
label string Display text
target_type MenuItemTargetType url / page / route / anchor
target_value string The link value (URL string, page slug/id, route name, or anchor id)
target_meta json Extra data; route params when target_type=route
new_tab boolean Open in new tab (target="_blank")
icon string Icon identifier (Blade Icons pack)
css_class string Extra CSS classes on the <a> element
dropdown_type string null = standard dropdown; custom type identifiers supported
order int Sibling sort order
visible boolean Whether the item is rendered in the public menu

MenuItemTargetType enum

Case Value Resolved as
Url url Raw string, may be external
Page page Page slug or id → $page->url
Route route Laravel named route + target_meta params
Anchor anchor # prepended to target_value

Relationships

$item->menu()            // BelongsTo(Menu)
$item->parent()          // BelongsTo(MenuItem)
$item->children()        // HasMany(MenuItem) ordered by 'order'
$item->visibleChildren() // HasMany(MenuItem) visible=true, ordered

Methods

// Resolve the final URL (handles all target_type cases; returns null on failure)
$item->resolveUrl(): ?string

// True when the resolved URL points to a different host than the current request
$item->isExternal(): bool

// True when this item has nested children
$item->hasChildren(): bool

// Active-link detection
// "/" matches only the home root; other paths match prefix
// (e.g. "/blog" stays active on "/blog/post-1")
// External links, pure anchors, and URL-less items always return false
$item->isActiveForPath(string $currentPath): bool
// Typical rendering loop:
$menu = Menu::findBySlug('primary');
$currentPath = request()->getPathInfo();

foreach ($menu->visibleItems as $item) {
    $url    = $item->resolveUrl();
    $active = $item->isActiveForPath($currentPath);
    $target = $item->new_tab ? '_blank' : '_self';
    // render $url, $active, $target ...
}

Form model

FormSchematgx_forms

Form definitions authored in the form builder. Submissions are not persisted to the database by default — on submit, the configured form action runs (send email, webhook, etc.).

Columns

Column Type Notes
title string Admin label
slug string UNIQUE Used as form_key in public routes
description text Optional description
content json Form-level body props (settings, styles)
fields json Ordered array of field component definitions
metadata json Extensible bag
status string draft / published / archived
form_target string Denormalised from content.body.form_target.value; default universal

The form_target column is denormalised: the model's saving boot hook extracts it from the content payload automatically. Do not set it manually — it will be overwritten on the next save.

Scopes

FormSchema::published()  // status = 'published'
FormSchema::draft()      // status = 'draft'

Method

$form->isEmpty(): bool   // true when fields array is empty

Global and media models

These models back the design system and media library used across all builder contexts.

GlobalVariabletgx_global_variables

User-defined design tokens emitted as CSS custom properties on :root. The renderer outputs --tgx-{key}: {value} for every variable, making them available to any module's CSS without further configuration.

Columns

Column Type Notes
key string UNIQUE CSS variable name fragment — becomes --tgx-{key}
name string Human-readable label
type string color / font / number / link / image
group string Admin grouping for the variable picker
value json Type-specific value bag
order int Display order within the group
is_protected boolean Protected variables cannot be deleted from the admin

Scopes: colors(), fonts(), numbers(), links(), images(), ordered().

The package ships a GlobalVariablesSeeder that creates default tokens from the package config. Run it once on a fresh install:

php artisan db:seed --class="Ccast\\Tagixo\\Database\\Seeders\\GlobalVariablesSeeder"

Or call it from your own DatabaseSeeder:

$this->call(\Ccast\Tagixo\Database\Seeders\GlobalVariablesSeeder::class);

CustomFonttgx_custom_fonts

Uploaded web font variants available in the builder's font picker.

Columns: family, weight (100–900), style (normal|italic), format (woff2|woff|ttf|otf), file_path, original_name, file_size.

Accessor: $font->url — resolves the public Storage URL for the font file.

Static helper: CustomFont::getFamilies() returns variants grouped by family name. Use it to build @font-face CSS blocks in your layout:

foreach (CustomFont::getFamilies() as $family => $variants) {
    foreach ($variants as $variant) {
        // emit @font-face { font-family: ...; src: url(...); }
    }
}

Mediatgx_media

Uploaded files and their crop / size variants. Crop variants are stored as child records with parent_id pointing to the original.

Columns

Column Type Notes
uuid uuid UNIQUE Stable external reference; use this in serialised data
filename string Stored filename on disk
original_filename string Original upload filename
path string Storage path
disk string Laravel filesystem disk (default public)
mime_type string Full MIME type
extension string(10) File extension
size bigint Bytes
width uint Pixel width (images only)
height uint Pixel height (images only)
alt_text string Accessibility alt text
title string Optional display title
description text Optional description
folder string Virtual folder path
metadata json Extensible bag (EXIF, focal point, etc.)
parent_id FK→tgx_media Parent record for crop / size variants; cascadeOnDelete
uploaded_by FK→users Uploader user id; nullOnDelete

The media gallery, picker fields, and MediaPropType all reference media by uuid. See media-gallery for the full upload and crop lifecycle.

Miscellaneous models

Slidertgx_sliders

Carousel / slideshow entities. Each slider is a self-contained builder context with its own slides as components within the content payload.

Columns: title, slug, content (json), rendered_html, css, status.

Scopes: published(), draft(). Method: isEmpty(): bool.

Soft deletes: Slider uses SoftDeletes.

Popuptgx_popups

Standalone popup definitions authored in their own builder context. Popups are referenced by id from the Section or page-wrapper Advanced settings tab. The Popup model follows the same shape as Slider.

SiteScripttgx_site_scripts

Raw script / markup blobs injected verbatim into every public page at a specific DOM location. Useful for analytics tags, Google Tag Manager, verification meta, or chat widgets.

Locations: head, body_open, body_close.

// In your Blade layout — echoes raw HTML:
{!! \Ccast\Tagixo\Models\SiteScript::html('head') !!}
{!! \Ccast\Tagixo\Models\SiteScript::html('body_open') !!}
{!! \Ccast\Tagixo\Models\SiteScript::html('body_close') !!}

html() returns an HtmlString safe for {!! !!}. It returns an empty string when the location is unknown, the script is disabled, or the table does not exist yet (mid-migration boot).

SiteSettingstgx_site_settings

Key-value store for global site configuration (site name, logo, favicon, contact email, etc.). Access is via static helpers:

SiteSettings::get('site_name', 'My Site');
SiteSettings::set('site_name', 'Tagixo Website');

App-level models that reference Tagixo tables

Consumer applications often need to reference builder content from their own Eloquent models — for example, a Product that links to its detail Page, or a BlogPost backed by a Page for rendering.

The idiomatic approach is to add a nullable foreign key and a BelongsTo relation:

// Migration in your app
Schema::table('products', function (Blueprint $table) {
    $table->foreignId('page_id')
        ->nullable()
        ->constrained('tgx_pages')
        ->nullOnDelete();
});
// Product model
use Ccast\Tagixo\Models\Page;

class Product extends Model
{
    public function page(): BelongsTo
    {
        return $this->belongsTo(Page::class, 'page_id');
    }
}

For dynamic pages (using template_type = 'single'), the relationship is inverted — the Page record holds model_class and model_url_key, and the router resolves your model from the URL key at request time. Your PublicPageController calls $page->resolveRecord($keyFromUrl) and makes the result available to the view or renderer data context.

Do not add domain models for content that belongs in the builder. Form submissions, page content, and layout data all live in Tagixo's own tables. Your app models should reference builder records, not duplicate their columns.

When writing custom queries against Tagixo tables from your controllers or jobs, always import the model class explicitly — do not construct raw DB::table('tgx_*') queries unless you have a specific performance reason, as raw queries bypass casts, scopes, and soft-delete behaviour:

use Ccast\Tagixo\Models\Page;
use Ccast\Tagixo\Enums\PageStatus;

// Correct — respects all scopes and casts
$pages = Page::published()->root()->orderBy('order')->get();

// Avoid — bypasses SoftDeletes, casts, and scopes
$pages = DB::table('tgx_pages')->where('status', 'published')->get();

See also