Contensio logo

Querying Content

How to query pages, posts, and custom content types from your Laravel application using the Contensio models.

All content in Contensio — pages, posts, custom types — lives in the contents table and is accessed through the Contensio\Models\Content model. Use standard Eloquent queries; there is no separate API client needed.

Basic queries

use Contensio\Models\Content;
use Contensio\Models\ContentType;
use Contensio\Models\Language;

$lang = Language::where('is_default', true)->first();

// All published posts
$posts = Content::whereHas('contentType', fn ($q) => $q->where('name', 'post'))
    ->where('status', 'published')
    ->with(['translations', 'featuredImage.variants', 'terms.translations'])
    ->latest('published_at')
    ->paginate(10);

// A single post by slug
$trans = \Contensio\Models\ContentTranslation::where('language_id', $lang->id)
    ->where('slug', 'my-post-slug')
    ->with('content.featuredImage.variants')
    ->firstOrFail();

$post = $trans->content;

Custom content types

$type     = ContentType::where('name', 'product')->firstOrFail();
$products = Content::where('content_type_id', $type->id)
    ->where('status', 'published')
    ->with(['translations', 'featuredImage'])
    ->latest()
    ->get();

Accessing translations

Each Content model has a translations HasMany relationship to ContentTranslation. Each translation record holds title, slug, excerpt, meta_title, meta_description for one language.

$post->load('translations');

$defaultTrans = $post->translations
    ->firstWhere('language_id', $lang->id)
    ?? $post->translations->first();

echo $defaultTrans->title;
echo $defaultTrans->slug;

Accessing custom fields

Use the field() method to retrieve a custom field value by its key:

// Non-translatable field — language ID doesn't matter
$price = $product->field('price');

// Translatable field — pass the language ID
$summary = $product->field('summary', $lang->id);

field() returns the raw stored value (string, JSON string for multi-select, etc.). For media fields it returns the media ID as a string.

Loading featured images

$post->load('featuredImage.variants');

$image   = $post->featuredImage;     // Media model
$url     = $image->url();            // Original file URL
$medUrl  = $image->variantUrl('medium');   // 900×600 WebP
$thumbUrl = $image->variantUrl('thumbnail'); // 150×150 WebP

variantUrl() falls back to the original URL if the variant doesn't exist.

Filtering by taxonomy terms

use Contensio\Models\Term;
use Contensio\Models\TermTranslation;

// Find the term
$termTrans = TermTranslation::where('slug', 'laravel')
    ->where('language_id', $lang->id)
    ->firstOrFail();

// Posts tagged with that term
$posts = Content::whereHas('terms', fn ($q) => $q->where('terms.id', $termTrans->term_id))
    ->where('status', 'published')
    ->with(['translations', 'featuredImage'])
    ->latest('published_at')
    ->paginate(10);

Frontend routes

Contensio's built-in frontend handles these URLs:

URL Controller
/ Homepage
/blog Post archive
/blog/{slug} Single post
/{slug} Page by slug
/search Search results
/{taxonomy}/{term} Taxonomy term archive

Custom content types have no built-in public route. Add your own:

use Contensio\Models\Content;
use Contensio\Models\ContentType;

Route::get('/products/{slug}', function (string $slug) {
    $lang = \Contensio\Models\Language::where('is_default', true)->firstOrFail();

    $trans = \Contensio\Models\ContentTranslation::where('slug', $slug)
        ->where('language_id', $lang->id)
        ->whereHas('content', fn ($q) => $q
            ->where('status', 'published')
            ->whereHas('contentType', fn ($q) => $q->where('name', 'product'))
        )
        ->with('content.featuredImage.variants')
        ->firstOrFail();

    return view('products.show', [
        'product' => $trans->content,
        'trans'   => $trans,
    ]);
});

Querying from a plugin or service class

Inside a plugin you have full access to the Contensio models after the service provider has booted. No extra setup required — just use Contensio\Models\Content; and query normally.

Caching

Contensio does not add its own caching layer on top of Eloquent. Wrap expensive queries in Laravel's cache:

$featured = Cache::remember('featured_posts', now()->addMinutes(15), function () {
    return Content::whereHas('contentType', fn ($q) => $q->where('name', 'post'))
        ->where('status', 'published')
        ->with(['translations', 'featuredImage.variants'])
        ->latest('published_at')
        ->take(6)
        ->get();
});