Backward compatibility
ContentBlocks follows semantic versioning. From 1.0.0, everything this page lists is stable: it will not break in a 1.x release.
This page is the whole promise. Anything it does not list is internal, and may change in any minor release without a deprecation cycle. In PHP almost everything is reachable: a controller can be instantiated, a trait used, a service aliased. Reachable is not the same as supported, and a package that promised everything it happens to expose could never fix anything.
@internal in the code is a reminder of that rule, put where someone is most likely to reach for the wrong thing: a public setter on an entity, a class next to an interface you implement. It is not the rule itself. A class carrying no marker is internal all the same if it is not listed here.
What is covered
PHP — interfaces
These are the extension surface: implement them, alias them, decorate them. Their method signatures are frozen, and so is the meaning of what they return.
Core (klehm/content-blocks), 41 interfaces:
| Area | Interfaces |
|---|---|
| Access, preview, pickers | AccessCheckerInterface, ContentAreaUrlResolverInterface, ContentAreaProviderInterface, SectionTemplateManagerInterface, AssetReportViewerInterface |
| Blocks | BlockTypeInterface, BlockPreviewHintInterface, BlockDataDefaultsProviderInterface, BlockDecoratorInterface, BlockFormExtensionInterface, TranslatableFieldsInterface |
| Sections and columns | SectionDecoratorInterface, SectionSettingsDefaultsProviderInterface, SectionStyleProviderInterface, SectionClonerInterface, BlockCloneObserverInterface, ColumnCloneObserverInterface |
| Rendering | BlockRendererInterface, BlockDataResolverInterface, ColumnSettingsResolverInterface, ImageUrlResolverInterface |
| Content and publishing | ContentManipulatorInterface, ContentAreaPublisherInterface, UnpublishedChangesProviderInterface |
| Builder UI | BuilderActionProviderInterface, BuilderShellExtensionInterface, UiIconProviderInterface, ColorPaletteProviderInterface |
| Storage and assets | FileStorageInterface, AssetInventoryInterface, AssetResolverInterface, AssetReferenceProviderInterface |
| Clipboard, templates, transfer | BlockSnapshotSerializerInterface, SectionTemplateSerializerInterface, SectionTemplateInstantiatorInterface, ContentAreaExporterInterface, ContentAreaImporterInterface, ContentAreaTransferExtensionInterface, SnapshotExtensionInterface |
| Versioning | ContentVersionUpgraderInterface, EnvelopeUpgraderInterface |
Kit: RichTextEditorInterface, IconProviderInterface.
i18n: TranslationProviderInterface, RenderLocaleResolverInterface, WorkbenchBackUrlResolverInterface, LocalizedPageUrlResolverInterface, TargetLocalesProviderInterface, LocaleFallbacksProviderInterface.
PHP — classes you extend, construct or reference
- Base classes and their documented extension points.
AbstractBlockType; the kit'sAbstractKitBlock(defaultOptions(),option(),choiceFields(),choices(),choiceConstraint(),defaults(),describe(), andgetDefaultData()stayingfinal);AbstractRichTextEditor. - The 19 kit block classes, as subclassable. Their
protectedmethods are covered. See extending a kit block. - Attributes:
#[AsContentBlock],#[AsBlockFormExtension]. - Form types:
ContentAreaTypeand its options (enable_replace,enable_import_export,enable_import,enable_export,enable_public_link,topbar_actions).PaletteColorType,ImageUploadType,VideoUploadType, as fields for your own blocks.BlockFormType,SectionSettingsTypeandStylingTypeas targets of a form type extension, with theirTAB_*andPANEL_*constants. The fields they hold are not frozen: a field may move, but a key you add keeps working.
- The sidebar form options.
cb_group,cb_panel,cb_help_tooltipandcb_panels_exclusiveon every form type;cb_icons,cb_icon_layout,cb_icon_columnsandcb_icon_labelsonChoiceType;cb_open_entriesonLiveCollectionType. Also the names of the shipped UI icons they refer to (Laying out sidebar fields): a name may gain a better drawing, but it is not renamed or removed. - Value objects your implementation builds or reads. Their public properties and named constructors are frozen:
- rendering:
RenderContext,RenderMode,ResolvedImage,BlockPreviewHint,BlockDecoration,SectionDecoration - publishing:
PublishContext - builder UI and styling:
BuilderAction,BuilderShellFragment,PaletteColor,SectionStyle - storage:
StoredAsset - translation: the kit's
RichTextEditorView; i18n'sTranslationRequest,TranslationOutcome,TranslationJobandFieldStatus
- rendering:
- Values you read but do not build.
ImportResult,InstantiationResult,SectionTemplateSnapshot, and the transfer helpersAssetTokenizerandAssetRewriterhanded to aContentAreaTransferExtensionInterface. Their public reads are frozen; their constructors are@internal, so the package can add fields to them. - Services you inject.
BlockTypeRegistry(get(),has(),all(),getChoices()), and the shipped implementations named as defaults in the guides:LocalFileStorage,PassthroughImageUrlResolver,DenyOnMismatchUpgrader,AllowAllAccessCheckerandDenyAllAccessChecker. They are covered as services to alias or decorate; their constructors are not. - Symfony events you listen to, with their public properties and the moment each is dispatched (Server-side events):
BeforeContentAreaPublishEvent,AfterContentAreaPublishEvent,BeforeContentAreaDiscardEvent,AfterContentAreaDiscardEvent,BeforeBlockSaveEvent,AfterBlockSaveEvent,BeforeBlockDeleteEvent,AfterBlockDeleteEvent; their baseRefusableEvent(refuse(),isRefused(),getReasons()) andActionRefusedException. A later version may add properties or events, but will not remove or rename one. - Exceptions you throw or catch.
ContentBlocksAccessDeniedException(a 403),IncompatibleContentVersionException,ImportRefusedException,UnsupportedTemplateFormatException,IncompatibleTemplateException,ContentManipulationExceptionand itsreasoncodes. ContentBlocks\Testing\CrossRequestStateScanner, for pointing the worker-mode check at your own code.- The entities and their public accessors:
ContentArea,Section,Column,Block,SectionTemplate, and i18n'sBlockTranslationandColumnTranslation. The exception is the setters of published state, which carry@internal.publish()is the only writer of a published field, so code building content writes the draft and callspublish().
Configuration
Every key of the three semantic config trees (content_blocks, content_blocks_kit, content_blocks_i18n) and their default values. A default is as frozen as a signature: changing one silently changes behaviour for every host that never set it.
HTTP
Route names, for every route the packages ship. Hosts generate URLs with them and write firewalls around them. Mount paths belong to the host: the core ships
config/routes/editor.phpandconfig/routes/public.php, and the i18n packageconfig/routes/bare.php, so that a host can mount the routes wherever its firewall covers (Mounting the routes).Methods, payloads and CSRF requirement, for the routes a host calls itself:
Route What is frozen content_blocks_uploadPOSTmultipart:fileandarea, theX-CSRF-Tokenheader; the JSON answer'surl(anderroron a refusal)content_blocks_exportGETon an area, answering the export archive;?assets=0content_blocks_importPOSTof an export in one request, the single-request path kept for scriptscontent_blocks_area_publish,content_blocks_area_discardPOSTwith the CSRF header; a409witherror: "refused",messageandreasonswhen a listener refusedcontent_blocks_asset_layout,content_blocks_asset_styling,content_blocks_asset_slider,content_blocks_kit_asset_cssGET, the stylesheet or script a public page links;?v=with the content's version is cached for a yearcontent_blocks_asset_reportGET, the read-only asset report pagecontent_blocks_i18n_workbenchGET, the translation workbench pagecontent_blocks_i18n_area_publish,content_blocks_i18n_area_discardPOSTwith the CSRF header, one language of an area; a409witherror: "source_unpublished"while the area has a draft of its own, or"refused"as aboveThe export format,
content-blocks/v1: any 1.x release imports an export written by an earlier 1.x release.
Every other route is how the builder talks to its own server. Its name is stable, but its method, payload and response shape are internal.
Console
The five commands, their names and their options: content-blocks:assets:gc, content-blocks:backfill-collection-ids, content-blocks-kit:blocks, content-blocks:i18n:status, content-blocks:i18n:translate.
For content-blocks:assets:gc, the shape of the safety design is part of the promise too: reporting is the default and --force is the opt-in. Inverting that would silently turn an existing habit into a deletion, so it will not be inverted.
Twig
- Functions:
- core:
cb_render_content_area,cb_preview_url,cb_public_url,cb_api_base,cb_color_palette,cb_color_tone,cb_color_is_dark,cb_css_color,cb_image,cb_ui_icon,cb_shell_fragments,cb_has_unpublished_changes - kit:
cb_embed_url,cb_kit_icon,cb_kit_token,cb_kit_stylesheet_url - i18n:
cb_i18n_workbench_url,cb_i18n_locales,cb_i18n_progress,cb_i18n_hreflang,cb_i18n_alternates
- core:
- Filters (kit):
cb_kit_safe_url,cb_kit_rich_html. A template override of a kit view keeps the same guards by using them. - Template paths. Every shipped template path, since overriding one under
templates/bundles/is a supported integration. Their contents are not frozen, and a template may be restructured, but the path will resolve. - Block names. The block names a host overrides keep working, including the empty blocks shipped for host additions:
- builder shell:
cb_shell_topbar_left_end,cb_shell_topbar_right_start,cb_shell_topbar_right_end,cb_shell_end - workbench:
cb_wb_head,cb_wb_topbar_left_end,cb_wb_topbar_right_start,cb_wb_topbar_right_end,cb_wb_end
- builder shell:
- A block view's variables. A view receives
dataandblock_id; later versions may pass more, never less.
Front-end
The 16 Stimulus controller names, which hosts write into
assets/controllers.json:- core (13):
cb-builder-launcher,cb-builder,cb-autosave,cb-section-settings-form,cb-block-styling-form,cb-spacing-link,cb-viewport-tabs,cb-range,cb-tabs,cb-collection-sort,cb-condition,cb-file-upload,cb-tree - kit (3):
cb-tinymce,cb-ckeditor,cb-gallery
Their targets, values and actions are not frozen, except
data-cb-condition(see Host services).- core (13):
Every documented
--cb-*CSS custom property (CI fails on an undocumented one): the chrome tokens and form alias layer in Styling, the kit's seven content tokens in the Block Kit, and the workbench's fifteen in Translation.Seven
cb:*DOM events, with thedetailfields listed in Builder events. All of them bubble.Event Direction Dispatched on detailcb:readyout the builder element, once the preview is interactive areaIdcb:block:savedout the builder, after a block's form saved blockIdcb:section:savedout the builder, after a section's settings saved sectionIdcb:builder:actionout the builder, when a contributed action is clicked key,areaId,buttoncb:block:renderedout inside the preview iframe, on a block's fresh node after an in-place refresh blockIdcb:area:changedin dispatched by you at the builder after changing the area server-side hasUnpublishedChanges(optional)cb:notifyin dispatched by you at the builder, to speak in its snackbar message,link(optional)A later version may add fields to a
detail, but will not remove or rename one.
The other cb:* events are internal choreography between the preview overlay, the iframe and the builder shell: the …-requested, …:apply, …:patch and …:desync families. They are how the builder talks to itself, and they change as it changes. The postMessage traffic between the iframe and the builder is internal too.
Storage
- The eight table names and their columns:
cb_content_area,cb_section,cb_column,cb_block,cb_section_template,cb_action_log, and i18n'scb_block_translationandcb_column_translation. Hosts write migrations against these. - Conventions in stored data, which are contracts even though they are not code:
- the
cb_translatablefield tag - the
_idkey on collection entries - the reserved
_prefix inBlock.data - the names of the kit's icon set, since a stored
iconblock holds the name
- the
Behaviour
Some defaults are load-bearing enough to be API:
- Secure by default.
AccessCheckerInterfacedefaults toDenyAllAccessChecker,ContentAreaUrlResolverInterfaceto a resolver that throws,SectionTemplateManagerInterfaceto one that denies, andAssetReportViewerInterfaceto a viewer that denies (the report route 404s rather than 403s). They stay that way. - No uploaded file is ever deleted as a side effect of a builder action: not on block delete, not on publish, not on discard. Reclaiming storage is an explicit, separate act; see Asset lifecycle.
- Nothing the builder does changes the published page until Publish.
- The publish and discard events are dispatched for whatever publisher the interface points to: the before event ahead of every decorator, the after event once all of them have run. A refused action writes nothing.
- The kit's
html_rawblock is registered only withenabled: true, whatever else its config entry holds. ContentAreaType::buildView()writes nothing to the database on a GET.
What is not covered, by name
Everything missing from the lists above is internal. These are named because they are the likeliest to be mistaken for API:
- HTTP controllers and their helpers. The controllers and
CsrfProtectedTrait: the routes are the contract, not the classes behind them. - Wiring.
ContentBlocks\DependencyInjection\, the compiler passes, and the bundle classes' methods. - Builder internals.
BlockComponent(a Live Component driven by the builder's own templates) and everything underHistory\. - Collaborator services.
BlockRenderer,ContentAreaPublisher,SectionCloner,ContentAreaExporter,ContentAreaImporter,SectionTemplateSerializer, the*Collectionand*Registryaggregators (exceptBlockTypeRegistry), the decorators and resolvers the core ships, andBlockTranslationRepository. Depend on their interface; an implementation's constructor can change in a minor release. - Builder-only Twig functions:
cb_history_state,cb_section_layouts,cb_section_layout_rects,cb_asset_path. - HTTP payloads of the routes not in the table above, and every
cb:*event not in the events table. - Anything carrying
@internal.
How changes are made
Additive changes land in minor releases: a new interface, a new config key, a new optional constructor argument on a shipped implementation, a new field in an event's detail or a value object.
CI holds the PHP part of this page to it: every change is compared with the last release by roave/backward-compatibility-check, on the classes listed above.
A breaking change to anything on this page waits for the next major. Where a change is unavoidable within 1.x, the old path is kept working and marked @deprecated with the version that will remove it, and the CHANGELOG says so.
Two consequences worth spelling out, because they are the ones that catch hosts:
- Adding a parameter to a published interface method is a breaking change, even an optional one: an existing implementor stops satisfying the interface. This is why
BlockRendererInterfacetakes aRenderContextandContentAreaPublisherInterfaceaPublishContextrather than growing parameter lists: a context object gains fields without touching the signature. New seams follow the same shape. - Changing a default is a breaking change. It reaches every host that never set the value, which is usually most of them.
See also
- Upgrade guide (beta → 1.0)
- Content versioning: the other compatibility promise, about the shape of stored block data rather than the code