Search by

shazzoo / content-studio-laravel

Shazzoo

Shows the blog articles from the Content Studio Strategy Engine on a Laravel site.

Package info

github.com/Shazzoo/content-studio-laravel-plugin

pkg:composer/shazzoo/content-studio-laravel

Statistics

Installs: 2

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.0.0 2026-09-18 10:45 UTC

This package is auto-updated.

Last update: 2026-09-18 11:36:14 UTC


README

Shows the blog articles from the Content Studio Strategy Engine on a plain Laravel site. The package syncs the articles into your database every 15 minutes and serves an overview page and an article page.

Installation

composer require shazzoo/content-studio-laravel
php artisan migrate
php artisan vendor:publish --tag=content-studio-assets

The images from the Engine are stored on the public disk, so the site needs the storage symlink. Most Laravel projects already have it; if yours doesn't:

php artisan storage:link

The publish command copies the tracking script to public/vendor/content-studio/tracking.js. Without it the article pages ask for a file that isn't there and nothing is tracked.

Keeping the script up to date

The script is published under the laravel-assets tag as well, which the default Laravel skeleton republishes on every composer update:

"post-update-cmd": [
    "@php artisan vendor:publish --tag=laravel-assets --ansi --force"
]

That covers composer update, but not composer install, and not a project whose composer.json lacks that script. To be sure, add this to your deploy:

php artisan vendor:publish --tag=content-studio-assets --force

A site that serves the file itself, through its own build or a CDN, can point the config at that URL instead and skip publishing:

'tracking' => [
    'script_url' => 'https://cdn.example.com/tracking.js',
],

Add the settings to .env:

CONTENT_STUDIO_API_KEY=
CONTENT_STUDIO_PROJECT_CODE=
CONTENT_STUDIO_ROUTE=blog
CONTENT_STUDIO_ARTICLES_PER_PAGE=12

The sync runs through the Laravel scheduler, so make sure php artisan schedule:run runs every minute. To sync right away:

php artisan content-studio:sync
php artisan content-studio:sync --status=all   # also fetch articles that are already live

An article whose images cannot be downloaded is not stored at all, so the site never shows a half-finished article; an article that is already live keeps the version it has. The command exits with an error, the article stays unconfirmed in the Engine's approved list, and the next sync downloads the images again. Images that did come through stay on disk, so a retry only fetches what is missing.

Routes

URL Name
/{route} content-studio.index
/{route}/{slug} content-studio.show
/{route}/sitemap.xml content-studio.sitemap

Set CONTENT_STUDIO_ROUTES=false to register the blog routes yourself; see Multiple languages.

Add the sitemap to your robots.txt or your own sitemap index:

Sitemap: https://example.com/blog/sitemap.xml

Latest articles on other pages

<x-content-studio::latest-articles :limit="3" title="From the blog" />

Without a title the heading and the "All articles" link are left out. The component renders nothing when there are no articles.

Using your own layout

By default the pages use a bare layout from the package. To render the blog inside your site, publish the config and point layout at your own layout:

php artisan vendor:publish --tag=content-studio-config
'layout' => 'layouts.app',

Your layout needs @yield('content'), and @stack('head') inside <head> for the title, meta description, Open Graph tags and JSON-LD.

Multiple languages

The blog is single language out of the box: it shows the articles in the main language of the Engine project, on the routes of the package. The sync always stores every language the Engine returns, so switching this on later needs no re-sync.

A site with multiple languages keeps its own way of working: its own routes, its own locale detection, its own URLs. The package asks the site for those three things instead of deciding them.

1. Register the routes yourself

CONTENT_STUDIO_ROUTES=false

The package then registers no blog routes, only the tracking script. Put the controller wherever it belongs, for example inside your own locale group:

use Shazzoo\ContentStudio\Http\Controllers\ArticleController;

Route::prefix('{locale}')->whereIn('locale', ['nl', 'en'])->group(function () {
    Route::get('blog', [ArticleController::class, 'index'])->name('blog.index');
    Route::get('blog/{slug}', [ArticleController::class, 'show'])->name('blog.show');
});

The article route must have a parameter named slug; every other parameter is yours. Add your own middleware, a translated prefix or a subdomain as you like.

2. Say which language to show

use Shazzoo\ContentStudio\ContentStudio;

// In AppServiceProvider::boot()
ContentStudio::resolveLocaleUsing(fn () => request()->route('locale') ?? app()->getLocale());

The overview, the article page, the sitemap and the latest-articles component all use this. The locale also becomes the app locale, so the package translations follow along. Default: the main language of the Engine project.

3. Say what the URLs look like

ContentStudio::resolveUrlUsing(fn (Article $article) => route('blog.show', [
    'locale' => $article->locale,
    'slug' => $article->slug,
]));

ContentStudio::resolveIndexUrlUsing(fn (string $locale) => route('blog.index', ['locale' => $locale]));

Every link in the views runs through these, so the cards, the previous/next links, the breadcrumbs, the canonical URL and the sitemap all follow your routes. Default: /{route}/{slug} and /{route}.

Translations of an article

Each language is its own record with its own slug, linked by the article id from the Engine. translations() finds the others, which is what you need for a language switcher or hreflang tags:

@foreach ($article->translations as $translation)
    <a href="{{ $translation->url() }}">{{ strtoupper($translation->locale) }}</a>
@endforeach

What stays yours

  • Tracking: the article view includes content-studio::partials.tracking. Write your own article view and you add it yourself:
    @include('content-studio::partials.tracking', ['article' => $article])
  • Sitemap: the package sitemap covers one language. For several languages, build your own from Article::query()->visible($locale)->get().
  • hreflang and the canonical per language: add these to your own layout with translations().
  • A missing translation gives a 404. Publish the views if you want to fall back to another language.

Styling

The views use Tailwind CSS (v4) with neutral default styling and dark: variants. Tailwind only generates classes from files it scans, so add the package views to your CSS entry file:

@source '../../vendor/shazzoo/content-studio-laravel/resources/views';

The bundled layout loads resources/css/app.css through Vite when a build or dev server is present. Every element also has a cs-* class to hook your own CSS onto. To change the markup itself, publish the views:

php artisan vendor:publish --tag=content-studio-views
php artisan vendor:publish --tag=content-studio-lang

Tests

composer test