Skip to content

Quick start ​

This walks you from an empty project to a rendered, editable content area. It assumes a working Symfony app with Doctrine and a MySQL database. For the full install (Flex recipe, asset wiring, without-Flex path) see Installation.

For AI agents

A condensed, deterministic version of this path lives in AGENTS.md at the repo root.

1. Install the packages ​

bash
composer require klehm/content-blocks klehm/content-blocks-kit

Register the bundles (Flex does this automatically — otherwise add them to config/bundles.php) and mount the routes. See Installation for the manual snippets, and Stimulus controllers & admin CSS for the required assets/controllers.json entries.

2. Attach a ContentArea to your entity ​

Give your own entity (here Page) a OneToOne relation. The cascade: ['persist', 'remove'] is required — ContentAreaType returns a transient ContentArea on submit and relies on cascade to commit it.

php
use ContentBlocks\Entity\ContentArea;

#[ORM\Entity]
class Page
{
    #[ORM\OneToOne(targetEntity: ContentArea::class, cascade: ['persist', 'remove'])]
    #[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
    private ?ContentArea $contentArea = null;
}

Generate and run the migration (this also creates the cb_* tables):

bash
php bin/console doctrine:migrations:diff
php bin/console doctrine:migrations:migrate

3. Wire the two required host services ​

ContentBlocks doesn't know your auth model or how a ContentArea maps back to a URL. Provide both — the defaults deny access and throw, respectively.

yaml
# config/services.yaml
services:
    ContentBlocks\Security\AccessCheckerInterface:
        class: App\Security\PageAccessChecker

    ContentBlocks\Preview\ContentAreaUrlResolverInterface:
        class: App\Preview\PageContentAreaUrlResolver

See Host services for the (small) implementations.

Where uploads go ​

Images and files uploaded in the builder need a storage; without one the upload fails. The Flex recipe sets this for you, otherwise add:

yaml
# config/packages/content_blocks.yaml
content_blocks:
    upload:
        directory: '%kernel.project_dir%/public/uploads/content-blocks'
        public_prefix: '/uploads/content-blocks'

For S3 or any other storage, alias FileStorageInterface instead (File storage).

4. Add the builder to a form ​

php
use ContentBlocks\Form\Type\ContentAreaType;

$builder->add('contentArea', ContentAreaType::class);

The widget renders the "Edit content" launcher. On a brand-new entity that hasn't been saved yet, it shows a "save first" placeholder — by design, it never writes to the DB on a GET.

5. Render the area on the public page ​

This step is required. The builder edits your real public page in an iframe, and the editing markers are produced by the render call — without it, nothing is editable.

twig
{# templates/page/show.html.twig — your public template #}
<article>
    <h1>{{ page.title }}</h1>
    {{ cb_render_content_area(page.contentArea) }}
</article>

cb_render_content_area() accepts null (renders an empty string), so you don't need an {% if %} guard.

6. Include the kit stylesheet ​

The kit's blocks render neutral markup styled by one shipped stylesheet. Include it once in your front layout:

twig
<link rel="stylesheet" href="{{ cb_kit_stylesheet_url() }}">

The URL carries a hash of the file, so browsers and CDNs keep it for a year and fetch the new one after an upgrade. A plain path('content_blocks_kit_asset_css') works too, revalidated every five minutes.

Done ​

Open your page as an authenticated editor, click Edit content, and start adding sections and blocks. The preview is your live page.

Next:

Released under the MIT License.