Content rendering

Learn how to fetch and render content for your slots.

This guide provides practical examples of how to use the Drupal SDK to fetch and render a Slot in your site.

Basic usage

Let’s say you have a slot called home-hero for the hero section of your homepage.

First, add it to your project with the CLI:

Command to add a slot to your project
npx croct@latest add slot home-hero

The CLI downloads the slot’s default content for use as automatic fallback and generates the type definitions for full autocomplete.

The idiomatic way to place personalized content in Drupal is a block plugin. Inject the Plug service, call fetchContent with the slot ID, and render the resulting content:

web/modules/custom/my_module/src/Plugin/Block/HomeHeroBlock.php
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152
<?php
declare(strict_types=1);
namespace Drupal\my_module\Plugin\Block;
use Croct\Plug\Plug;use Drupal\Core\Block\Attribute\Block;use Drupal\Core\Block\BlockBase;use Drupal\Core\Plugin\ContainerFactoryPluginInterface;use Drupal\Core\StringTranslation\TranslatableMarkup;use Symfony\Component\DependencyInjection\ContainerInterface;
#[Block(    id: 'home_hero',    admin_label: new TranslatableMarkup('Home hero'),)]final class HomeHeroBlock extends BlockBase implements ContainerFactoryPluginInterface{    private Plug $croct;
    public function __construct(array $configuration, string $pluginId, mixed $pluginDefinition, Plug $croct)    {        parent::__construct($configuration, $pluginId, $pluginDefinition);
        $this->croct = $croct;    }
    public static function create(ContainerInterface $container, array $configuration, $pluginId, $pluginDefinition): self    {        return new self($configuration, $pluginId, $pluginDefinition, $container->get(Plug::class));    }
    public function build(): array    {        $hero = $this->croct->fetchContent('home-hero')->getContent();
        return [            '#type' => 'inline_template',            '#template' => <<<'TWIG'                <section class="hero">                    <h1>{{ hero.title }}</h1>                    <p class="subtitle">{{ hero.subtitle }}</p>                    <a href="{{ hero.button.url }}">{{ hero.button.label }}</a>                </section>                TWIG,            '#context' => ['hero' => $hero],            // Personalized per visitor, so vary the render cache by session.            '#cache' => ['contexts' => ['session']],        ];    }}

Place the block in a region from the Block layout page at /admin/structure/block.

The actual structure of the content depends on the schema of the slot, but here is an example:

Content response
123456789101112
{"title": "The best personalization platform for developers","subtitle": "Best-in-class developer experience and enterprise-grade reliability.","image": {  "url": "https://cdn.croct.io/devs.png",  "alt": "A screenshot of an editor showing a code snippet."},"button": {  "label": "See docs",  "url": "https://docs.croct.com"}}

Typing

The PHPStan and Psalm plugins shipped with the SDK infer the exact shape of every slot, so fetchContent is typed with no manual annotations:

$content = $croct->fetchContent('home-banner')->getContent();$title = $content['title']; // string

The CLI writes these definitions to a slots.stub file whenever you add or update slots, and the plugins load it automatically.

Fault tolerance

You should always provide a fallback content to make your site resilient to unexpected errors, downtime, and network failures.

Pass a fallback, and the SDK returns it whenever the content cannot be fetched:

use Croct\Plug\FetchOptions;
$options = FetchOptions::defaults()->withFallback([    'title' => 'Welcome to Croct!',    'subtitle' => 'The easiest way to personalize your site.',]);$hero = $croct->fetchContent('home-hero', $options)->getContent();

Without a fallback, a failed request throws an exception, which you can catch to handle the error yourself.

Version control

You can lock a specific slot version to keep the content structure aligned with your site’s expectations. This gives your team the freedom to evolve the structure over time without the risk of breaking things.

Specify the version by passing a versioned ID in the form <id>@<version>. For example, passing home-hero@2 fetches the content for the home-hero slot in version 2. Not specifying a version is the same as passing home-hero@latest, which loads the latest content:

$content = $croct->fetchContent('home-hero@2')->getContent();

For more information, see Slot versioning.

Localization

The module detects the visitor locale from Drupal’s language negotiation automatically, so content is returned in the matching language out of the box.

To request a specific locale instead, use the preferred locale option:

use Croct\Plug\FetchOptions;
$options = FetchOptions::defaults()->withPreferredLocale('en-ca');$hero = $croct->fetchContent('home-hero', $options)->getContent();

By default, if the content is not available in the preferred locale, it is returned in the default locale of your workspace.

Context variables

Sometimes you need to provide additional information to personalize or segment your users.

For example, if you are working on a SaaS application, you may want to personalize the content based on the subscription plan, quota usage, features, or any other application-specific information. You can achieve this by passing any relevant information as a custom attribute:

use Croct\Plug\FetchOptions;
$options = FetchOptions::defaults()->withAttribute('plan', 'premium');$content = $croct->fetchContent('upgrade-banner', $options)->getContent();

These values are then accessible as custom attributes in the context variable:

context's plan is "premium"

Keep in mind that the context has some constraints on the number of attributes and levels of nesting. For more information, see the attribute documentation.

Caching

The module marks personalized responses as private automatically and varies the render cache per visitor, so personalized content is never shared between visitors. If you run a CDN or reverse proxy in front of your site, see Caching for guidance.