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();
});