Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Add Docs
Sulu
https://github.com/sulu/sulu
Admin
Sulu is an extensible open-source PHP content management system built on the Symfony framework,
...
Tokens:
127,107
Snippets:
592
Trust Score:
8.3
Update:
6 months ago
Context
Skills
Chat
Benchmark
65.7
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Sulu CMS - Context Documentation ## Introduction Sulu is a highly extensible open-source PHP content management system built on the Symfony framework. It provides a comprehensive platform for building robust multi-lingual and multi-portal websites with an intuitive administration interface. The system requires PHP 8.2 or higher and is compatible with Symfony 6.4 and 7.x. Sulu 3.0 represents a major architectural evolution, transitioning from PHPCR-based storage to a modern Doctrine ORM-based content management system with dimension-based content handling for enhanced flexibility and performance. The architecture is composed of multiple specialized bundles that work together to deliver enterprise-grade CMS functionality. Core features include a dimension-based content management system for hierarchical content storage with locale and workflow support, a comprehensive REST API for all operations, a modern React-based admin interface, support for multiple webspaces (multi-site management), advanced media handling with format generation, sophisticated security and role management, flexible content templating with reusable components, activity tracking for audit logging, and trash management for content recovery. The new architecture uses Symfony Messenger for asynchronous operations and provides enhanced content versioning capabilities. ## Core APIs and Functions ### Content Manager API The ContentManager provides the primary interface for content persistence and retrieval in Sulu 3.0, replacing the legacy DocumentManager. ```php <?php use Sulu\Content\Application\ContentManager\ContentManagerInterface; use Sulu\Content\Domain\Model\ContentRichEntityInterface; use Sulu\Content\Domain\Model\DimensionContentInterface; use Sulu\Page\Domain\Model\PageInterface; use Sulu\Page\Domain\Repository\PageRepositoryInterface; // Inject the content manager and page repository public function __construct( private ContentManagerInterface $contentManager, private PageRepositoryInterface $pageRepository ) {} // Find a page by ID $page = $this->pageRepository->findById('550e8400-e29b-41d4-a716-446655440000'); // Resolve content with dimension attributes (locale and workflow stage) $dimensionAttributes = [ 'locale' => 'en', 'stage' => 'draft' // or 'live' ]; $dimensionContent = $this->contentManager->resolve($page, $dimensionAttributes); // Access content properties echo $dimensionContent->getTemplateKey(); // 'default' echo $dimensionContent->getTemplateData()['title']; // Page title echo $dimensionContent->getWorkflowStage(); // 'draft' or 'published' // Create or update page content $data = [ 'template' => 'default', 'title' => 'My New Page', 'url' => '/my-new-page', 'article' => '<p>Page content here</p>', 'navigationContexts' => ['main'], 'seo' => [ 'title' => 'SEO Title', 'description' => 'SEO Description' ] ]; $updatedContent = $this->contentManager->persist($page, $data, $dimensionAttributes); // Apply workflow transition (publish/unpublish) $publishedContent = $this->contentManager->applyTransition( $page, ['locale' => 'en', 'stage' => 'draft'], 'publish' ); // Copy content from one locale to another $copiedContent = $this->contentManager->copy( $page, // source entity ['locale' => 'en', 'stage' => 'live'], // source dimensions $page, // target entity (same page for locale copy) ['locale' => 'de', 'stage' => 'draft'], // target dimensions [] // options ); // Normalize content to array $normalizedData = $this->contentManager->normalize($dimensionContent); ``` ### REST API - Pages Endpoint CRUD operations for managing pages and content through the REST API with the new message-based architecture. ```bash # List all pages with filtering curl -X GET "https://example.com/admin/api/pages?locale=en&webspace=sulu-test&parentId=null&fields=title,url" \ -H "Authorization: Bearer YOUR_TOKEN" # Response: # { # "_embedded": { # "pages": [ # { # "id": "550e8400-e29b-41d4-a716-446655440000", # "title": "Homepage", # "url": "/", # "template": "homepage", # "publishedState": true, # "published": "2024-01-15T10:30:00+00:00" # } # ] # } # } # Get a single page by UUID curl -X GET "https://example.com/admin/api/pages/550e8400-e29b-41d4-a716-446655440000?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" # Create a new page curl -X POST "https://example.com/admin/api/pages?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "About Us", "template": "default", "url": "/about-us", "article": "<p>Welcome to our company</p>", "parentId": "550e8400-e29b-41d4-a716-446655440000", "webspaceKey": "sulu-test", "navigationContexts": ["main"], "shadowLocale": null, "authored": "2024-01-20T12:00:00+00:00" }' # Update an existing page curl -X PUT "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "About Us - Updated", "article": "<p>Updated content here</p>", "tags": [1, 2, 3], "categories": [5] }' # Delete a page curl -X DELETE "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" # Publish a page curl -X POST "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en&action=publish" \ -H "Authorization: Bearer YOUR_TOKEN" # Unpublish a page curl -X POST "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en&action=unpublish" \ -H "Authorization: Bearer YOUR_TOKEN" # Move a page to a new parent curl -X POST "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en&action=move" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "destinationId": "550e8400-e29b-41d4-a716-446655440000" }' # Copy page content from one locale to another curl -X POST "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=de&action=copy-locale" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "srcLocale": "en", "destLocales": ["de", "fr"] }' # Copy entire page curl -X POST "https://example.com/admin/api/pages/123e4567-e89b-12d3-a456-426614174000?locale=en&action=copy" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "destinationId": "550e8400-e29b-41d4-a716-446655440000" }' ``` ### REST API - Media Endpoint Complete media management including upload, metadata management, collections, and format generation. ```bash # List all media files with filters curl -X GET "https://example.com/admin/api/media?locale=en&collection=1&types=image,document&limit=20&page=1" \ -H "Authorization: Bearer YOUR_TOKEN" # Get a single media file by ID curl -X GET "https://example.com/admin/api/media/42?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" # Response includes all metadata, versions, formats: # { # "id": 42, # "locale": "en", # "type": { # "name": "image", # "id": 1 # }, # "mimeType": "image/jpeg", # "size": 2048576, # "title": "Company Logo", # "description": "Main company logo in high resolution", # "url": "/uploads/media/42/01/company-logo.jpg", # "thumbnails": { # "sulu-100x100": "/uploads/media/42/01/company-logo.jpg?v=1-0", # "sulu-400x400": "/uploads/media/42/01/company-logo.jpg?v=1-1" # }, # "version": 2, # "storageOptions": {} # } # Upload a new media file curl -X POST "https://example.com/admin/api/media?locale=en&collection=1" \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "fileVersion=@/path/to/image.jpg" \ -F "title=New Image" \ -F "description=Image description" \ -F "copyright=Company Name 2024" \ -F "tags[]=10" \ -F "tags[]=15" \ -F "categories[]=5" # Update media metadata curl -X PUT "https://example.com/admin/api/media/42?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Updated Company Logo", "description": "Updated description", "copyright": "Company Name 2024", "credits": "John Doe Photography", "tags": [10, 15, 20], "categories": [5, 8] }' # Upload a new version of existing media curl -X POST "https://example.com/admin/api/media/42?locale=en&action=new-version" \ -H "Authorization: Bearer YOUR_TOKEN" \ -F "fileVersion=@/path/to/updated-image.jpg" # Move media to a different collection curl -X POST "https://example.com/admin/api/media/42?locale=en&action=move" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "destination": 5 }' # Delete a specific version of media curl -X DELETE "https://example.com/admin/api/media/42/versions/1" \ -H "Authorization: Bearer YOUR_TOKEN" # Delete media completely curl -X DELETE "https://example.com/admin/api/media/42" \ -H "Authorization: Bearer YOUR_TOKEN" # List all collections curl -X GET "https://example.com/admin/api/collections?locale=en&depth=1" \ -H "Authorization: Bearer YOUR_TOKEN" # Create a new collection curl -X POST "https://example.com/admin/api/collections?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "title": "Product Images", "description": "All product photography", "parent": 1, "type": { "id": 2 } }' # Get image formats for media curl -X GET "https://example.com/admin/api/media/42/formats?locale=en" \ -H "Authorization: Bearer YOUR_TOKEN" ``` ### Media Manager Service PHP service for programmatic media management with full error handling. ```php <?php use Sulu\Bundle\MediaBundle\Media\Manager\MediaManagerInterface; use Sulu\Bundle\MediaBundle\Media\Exception\MediaNotFoundException; use Symfony\Component\HttpFoundation\File\UploadedFile; public function __construct( private MediaManagerInterface $mediaManager ) {} // Get a media file by ID try { $media = $this->mediaManager->getById(42, 'en'); echo $media->getId(); // 42 echo $media->getTitle(); // "Company Logo" echo $media->getUrl(); // "/uploads/media/42/01/logo.jpg" echo $media->getMimeType(); // "image/jpeg" echo $media->getSize(); // 2048576 // Access thumbnails $thumbnails = $media->getThumbnails(); echo $thumbnails['sulu-400x400']; // URL to 400x400 format } catch (MediaNotFoundException $e) { // Handle media not found throw new NotFoundHttpException('Media not found'); } // Get multiple media by IDs in specific order $mediaList = $this->mediaManager->getByIds([42, 15, 88], 'en'); foreach ($mediaList as $media) { echo $media->getTitle() . "\n"; } // Create or update media with uploaded file $uploadedFile = new UploadedFile( '/tmp/uploaded-image.jpg', 'image.jpg', 'image/jpeg', null, true ); $data = [ 'id' => null, // null for new, ID for update 'locale' => 'en', 'title' => 'New Product Image', 'description' => 'High-resolution product photo', 'copyright' => 'Company 2024', 'credits' => 'Professional Photography Ltd', 'collection' => 5, // Collection ID 'tags' => [10, 15, 20], 'categories' => [8] ]; $userId = 1; // Current user ID $media = $this->mediaManager->save($uploadedFile, $data, $userId); // Move media to another collection try { $media = $this->mediaManager->move( 42, // media ID 'en', // locale 8 // destination collection ID ); } catch (MediaNotFoundException | CollectionNotFoundException $e) { // Handle errors } // Delete media with security check try { $this->mediaManager->delete(42, true); // true = check security permissions } catch (\Exception $e) { // Handle deletion error or permission denied } // Remove a specific file version $this->mediaManager->removeFileVersion(42, 1); // media ID, version number // Get format URLs for multiple media IDs $formatUrls = $this->mediaManager->getFormatUrls([42, 15, 88], 'en'); // Returns: [42 => ['sulu-100x100' => 'url', ...], 15 => [...], ...] // Get direct download URL $downloadUrl = $this->mediaManager->getUrl(42, 'company-logo.jpg', 2); // Increase download counter (for analytics) $this->mediaManager->increaseDownloadCounter(150); // file version ID ``` ### Page Repository Query interface for retrieving page entities with filtering capabilities. ```php <?php use Sulu\Page\Domain\Repository\PageRepositoryInterface; use Sulu\Page\Domain\Model\PageInterface; public function __construct( private PageRepositoryInterface $pageRepository ) {} // Find a page by UUID $page = $this->pageRepository->findById('550e8400-e29b-41d4-a716-446655440000'); if ($page) { echo $page->getId(); // UUID echo $page->getWebspaceKey(); // 'sulu-test' // Access dimension content through ContentManager } // Find pages by webspace $pages = $this->pageRepository->findBy(['webspaceKey' => 'sulu-test']); foreach ($pages as $page) { echo $page->getId() . "\n"; } // Find page by resource segment (URL path) $pages = $this->pageRepository->findByWebspaceKeyAndResourceSegment( 'sulu-test', '/about-us', ['locale' => 'en'] ); // Count pages in webspace $count = $this->pageRepository->countByWebspaceKey('sulu-test'); // Find root pages (pages without parent) $rootPages = $this->pageRepository->findBy([ 'webspaceKey' => 'sulu-test', 'parentId' => null ]); // Advanced filtering with custom query $qb = $this->entityManager->createQueryBuilder(); $qb->select('p') ->from(PageInterface::class, 'p') ->where('p.webspaceKey = :webspace') ->setParameter('webspace', 'sulu-test'); $pages = $qb->getQuery()->getResult(); ``` ### REST API - User Management Complete user management API with roles, permissions, and authentication. ```bash # List all users curl -X GET "https://example.com/admin/api/users?flat=true&limit=20&page=1" \ -H "Authorization: Bearer YOUR_TOKEN" # Get a single user curl -X GET "https://example.com/admin/api/users/5" \ -H "Authorization: Bearer YOUR_TOKEN" # Response: # { # "id": 5, # "username": "john.doe", # "email": "john@example.com", # "contact": { # "id": 10, # "firstName": "John", # "lastName": "Doe" # }, # "locale": "en", # "userRoles": [ # { # "id": 1, # "role": { # "id": 1, # "name": "Administrator", # "system": "Sulu" # }, # "locales": ["en", "de"] # } # ], # "locked": false, # "enabled": true # } # Create a new user curl -X POST "https://example.com/admin/api/users" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "jane.smith", "email": "jane@example.com", "password": "SecurePassword123!", "locale": "en", "contact": { "firstName": "Jane", "lastName": "Smith", "emails": [ { "email": "jane@example.com", "emailType": { "id": 1 } } ] }, "userRoles": [ { "role": { "id": 2 }, "locales": ["en"] } ] }' # Update a user curl -X PUT "https://example.com/admin/api/users/5" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "john.doe", "email": "john.updated@example.com", "contact": { "firstName": "John", "lastName": "Doe-Updated" }, "userRoles": [ { "role": { "id": 1 }, "locales": ["en", "de", "fr"] } ] }' # Enable/disable a user curl -X POST "https://example.com/admin/api/users/5?action=enable" \ -H "Authorization: Bearer YOUR_TOKEN" # Lock/unlock a user curl -X POST "https://example.com/admin/api/users/5?action=lock" \ -H "Authorization: Bearer YOUR_TOKEN" curl -X POST "https://example.com/admin/api/users/5?action=unlock" \ -H "Authorization: Bearer YOUR_TOKEN" # Delete a user curl -X DELETE "https://example.com/admin/api/users/5" \ -H "Authorization: Bearer YOUR_TOKEN" # Manage roles curl -X GET "https://example.com/admin/api/roles" \ -H "Authorization: Bearer YOUR_TOKEN" # Create a role with permissions curl -X POST "https://example.com/admin/api/roles" \ -H "Authorization: Bearer YOUR_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Content Editor", "system": "Sulu", "permissions": [ { "context": "sulu.webspaces.sulu-test", "permissions": { "view": true, "add": true, "edit": true, "delete": false, "live": true } }, { "context": "sulu.media.collections", "permissions": { "view": true, "add": true, "edit": true, "delete": false } } ] }' ``` ### Security Checker Service Programmatic permission checking for access control in custom controllers and services. ```php <?php use Sulu\Component\Security\Authorization\SecurityCheckerInterface; use Sulu\Component\Security\Authorization\SecurityCondition; use Sulu\Component\Security\Authorization\PermissionTypes; use Sulu\Component\Security\Exception\InsufficientPermissionException; public function __construct( private SecurityCheckerInterface $securityChecker ) {} // Check if user has permission $hasPermission = $this->securityChecker->hasPermission( new SecurityCondition( 'sulu.media.collections', null, // locale (null for all) 'Sulu\Bundle\MediaBundle\Entity\Collection', 42 // object ID ), PermissionTypes::EDIT ); if ($hasPermission) { // User can edit collection 42 } // Check permission and throw exception if denied try { $this->securityChecker->checkPermission( new SecurityCondition( 'sulu.webspaces.sulu-test', 'en', 'Sulu\Page\Domain\Model\Page', '550e8400-e29b-41d4-a716-446655440000' ), PermissionTypes::DELETE ); // Permission granted, proceed with deletion $this->pageRepository->remove($page); $this->entityManager->flush(); } catch (InsufficientPermissionException $e) { // User doesn't have permission throw new AccessDeniedException('You cannot delete this page'); } // Check multiple permission types $permissions = [ PermissionTypes::VIEW, PermissionTypes::EDIT, PermissionTypes::DELETE, PermissionTypes::LIVE ]; foreach ($permissions as $permission) { if ($this->securityChecker->hasPermission($securityCondition, $permission)) { echo "User has {$permission} permission\n"; } } // Common security contexts // - sulu.webspaces.{webspace-key} - Page management per webspace // - sulu.media.collections - Media management // - sulu.security.users - User management // - sulu.security.roles - Role management // - sulu.security.groups - Group management // - sulu.contact.people - Contact management // - sulu.contact.organizations - Organization management ``` ### Webspace Configuration XML configuration for multi-site management with localization and URL patterns. ```xml <?xml version="1.0" encoding="utf-8"?> <webspace xmlns="http://schemas.sulu.io/webspace/webspace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/webspace/webspace http://schemas.sulu.io/webspace/webspace-1.1.xsd"> <name>My Website</name> <key>my-website</key> <!-- Enable permission checks --> <security permission-check="true"> <system>My Website</system> </security> <!-- Define supported languages --> <localizations> <localization language="en" default="true"> <localization language="en" country="us"/> <localization language="en" country="gb"/> </localization> <localization language="de"/> <localization language="fr"/> <localization language="es"/> </localizations> <!-- Default templates for page types --> <default-templates> <default-template type="page">default</default-template> <default-template type="home">homepage</default-template> </default-templates> <!-- System templates --> <templates> <template type="search">search/search</template> <template type="error">error/error</template> <template type="error-404">error/error-404</template> </templates> <!-- Navigation contexts --> <navigation> <contexts> <context key="main"> <meta> <title lang="en">Main Navigation</title> <title lang="de">Hauptnavigation</title> </meta> </context> <context key="footer"> <meta> <title lang="en">Footer Navigation</title> <title lang="de">Fußnavigation</title> </meta> </context> </contexts> </navigation> <!-- Content segments for audience targeting --> <segments> <segment key="default" default="true"> <meta> <title lang="en">Default</title> </meta> </segment> <segment key="premium"> <meta> <title lang="en">Premium Customers</title> </meta> </segment> </segments> <!-- Portal definitions with URL patterns --> <portals> <portal> <name>my-website.com</name> <key>my-website-portal</key> <resource-locator> <strategy>tree</strategy> </resource-locator> <environments> <environment type="prod"> <urls> <url language="en">my-website.com/{localization}</url> <url language="de">my-website.de</url> <url language="fr">my-website.fr</url> </urls> </environment> <environment type="stage"> <urls> <url>staging.my-website.com/{localization}</url> </urls> </environment> <environment type="dev"> <urls> <url>localhost:8000/{localization}</url> </urls> </environment> </environments> </portal> </portals> </webspace> ``` ### Twig Functions for Content Loading Sulu 3.0 provides specialized Twig functions for loading pages, articles, and snippets. ```twig {# Load a page with specific properties #} {% set page = sulu_page_load(page_uuid, {'title': true, 'url': true, 'article': true, 'images': true}) %} {{ page.title }} {{ page.article }} {# Load a page in a specific locale #} {% set page = sulu_page_load(page_uuid, {'title': true, 'url': true}, 'de') %} {# Load an article with properties #} {% set article = sulu_article_load(article_uuid, {'title': true, 'author': true, 'content': true}) %} {{ article.title }} {{ article.author }} {# Load an article in a specific locale #} {% set article = sulu_article_load(article_uuid, {'title': true}, 'fr') %} {# Load snippet by area with properties (NEW in 3.0) #} {% set snippet = sulu_snippet_load_by_area('footer', {'title': true, 'content': true}, 'sulu-io', 'en') %} {{ snippet.title }} {{ snippet.content }} {# Access current page content #} {% if content is defined %} <h1>{{ content.title }}</h1> <div>{{ content.article|raw }}</div> {# Access media images #} {% if content.images is defined %} {% for image in content.images %} <img src="{{ image.thumbnails['sulu-400x400'] }}" alt="{{ image.title }}" /> {% endfor %} {% endif %} {% endif %} ``` ### JavaScript Admin Interface Integration React-based admin UI configuration and component registration. ```javascript // index.js - Main entry point import {startAdmin} from 'sulu-admin-bundle'; // Import all bundle JavaScript modules import 'sulu-category-bundle'; import 'sulu-contact-bundle'; import 'sulu-media-bundle'; import 'sulu-page-bundle'; import 'sulu-security-bundle'; import 'sulu-snippet-bundle'; // Start the admin application startAdmin(); ``` ```php // Custom Admin class registration (PHP) <?php namespace App\Admin; use Sulu\Bundle\AdminBundle\Admin\Admin; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItem; use Sulu\Bundle\AdminBundle\Admin\Navigation\NavigationItemCollection; use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface; use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection; class CustomAdmin extends Admin { private ViewBuilderFactoryInterface $viewBuilderFactory; public function __construct(ViewBuilderFactoryInterface $viewBuilderFactory) { $this->viewBuilderFactory = $viewBuilderFactory; } public function configureNavigationItems(NavigationItemCollection $navigationItemCollection): void { // Add custom navigation item $products = new NavigationItem('app.products'); $products->setPosition(30); $products->setIcon('su-shopping-cart'); $products->setView('app.product_list'); $navigationItemCollection->add($products); } public function configureViews(ViewCollection $viewCollection): void { // Add list view $viewCollection->add( $this->viewBuilderFactory->createListViewBuilder('app.product_list', '/products') ->setResourceKey('products') ->setListKey('products') ->setTitle('app.products') ->addListAdapters(['table']) ->setAddView('app.product_add_form') ->setEditView('app.product_edit_form') ); // Add form view for creating $viewCollection->add( $this->viewBuilderFactory->createResourceTabViewBuilder('app.product_add_form', '/products/add') ->setResourceKey('products') ->setBackView('app.product_list') ); $viewCollection->add( $this->viewBuilderFactory->createFormViewBuilder('app.product_add_form.details', '/details') ->setResourceKey('products') ->setFormKey('product_details') ->setTabTitle('sulu_admin.details') ->setEditView('app.product_edit_form') ->addToolbarActions([new ToolbarAction('sulu_admin.save')]) ->setParent('app.product_add_form') ); // Add form view for editing $viewCollection->add( $this->viewBuilderFactory->createResourceTabViewBuilder('app.product_edit_form', '/products/:id') ->setResourceKey('products') ->setBackView('app.product_list') ); $viewCollection->add( $this->viewBuilderFactory->createFormViewBuilder('app.product_edit_form.details', '/details') ->setResourceKey('products') ->setFormKey('product_details') ->setTabTitle('sulu_admin.details') ->addToolbarActions([new ToolbarAction('sulu_admin.save'), new ToolbarAction('sulu_admin.delete')]) ->setParent('app.product_edit_form') ); } public function getSecurityContexts() { return [ 'Sulu' => [ 'Products' => [ 'app.products' => [ PermissionTypes::VIEW, PermissionTypes::ADD, PermissionTypes::EDIT, PermissionTypes::DELETE, ], ], ], ]; } } ``` ### Content Type System Creating custom content types for flexible field definitions in page templates. ```php <?php namespace App\Content\Type; use Sulu\Component\Content\Compat\PropertyInterface; use Sulu\Component\Content\SimpleContentType; // Simple string content type class CustomTextType extends SimpleContentType { public function __construct() { parent::__construct('CustomText', ''); } public function read(PropertyInterface $property): void { $value = $this->getValue($property); $property->setValue($value); } public function write(PropertyInterface $property): void { $value = $property->getValue(); $this->setValue($property, $value); } public function getDefaultValue(): string { return ''; } } ``` ```xml <!-- Page template with content types --> <?xml version="1.0" ?> <template xmlns="http://schemas.sulu.io/template/template" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://schemas.sulu.io/template/template http://schemas.sulu.io/template/template-1.0.xsd"> <key>product_page</key> <view>pages/product</view> <controller>Sulu\Content\UserInterface\Controller\Website\ContentController::indexAction</controller> <cacheLifetime>86400</cacheLifetime> <meta> <title lang="en">Product Page</title> <title lang="de">Produktseite</title> </meta> <properties> <!-- Text line --> <property name="title" type="text_line" mandatory="true"> <meta> <title lang="en">Title</title> <title lang="de">Titel</title> </meta> <params> <param name="headline" value="true"/> </params> <tag name="sulu.rlp.part"/> </property> <!-- Rich text editor --> <property name="description" type="text_editor"> <meta> <title lang="en">Description</title> <title lang="de">Beschreibung</title> </meta> </property> <!-- Media selection --> <property name="images" type="media_selection"> <meta> <title lang="en">Product Images</title> </meta> <params> <param name="types" value="image"/> <param name="displayOptions" value="leftTop,top,rightTop,left,middle,right,leftBottom,bottom,rightBottom"/> </params> </property> <!-- Single media selection --> <property name="featuredImage" type="single_media_selection"> <meta> <title lang="en">Featured Image</title> </meta> </property> <!-- Category selection --> <property name="categories" type="category_selection"> <meta> <title lang="en">Categories</title> </meta> </property> <!-- Tag selection --> <property name="tags" type="tag_selection"> <meta> <title lang="en">Tags</title> </meta> </property> <!-- Block content type for flexible content --> <block name="content_blocks" default-type="text" minOccurs="0"> <meta> <title lang="en">Content Blocks</title> </meta> <types> <type name="text"> <meta> <title lang="en">Text Block</title> </meta> <properties> <property name="text" type="text_editor" mandatory="true"> <meta> <title lang="en">Text</title> </meta> </property> </properties> </type> <type name="image_gallery"> <meta> <title lang="en">Image Gallery</title> </meta> <properties> <property name="images" type="media_selection"> <meta> <title lang="en">Images</title> </meta> <params> <param name="types" value="image"/> </params> </property> </properties> </type> <type name="quote"> <meta> <title lang="en">Quote</title> </meta> <properties> <property name="quote" type="text_area"> <meta> <title lang="en">Quote Text</title> </meta> </property> <property name="author" type="text_line"> <meta> <title lang="en">Author</title> </meta> </property> </properties> </type> </types> </block> <!-- Smart content for dynamic content selection --> <property name="related_products" type="smart_content"> <meta> <title lang="en">Related Products</title> </meta> <params> <param name="provider" value="pages"/> <param name="max_per_page" value="6"/> <param name="page_parameter" value="page"/> </params> </property> <!-- Page selection --> <property name="linked_pages" type="page_selection"> <meta> <title lang="en">Linked Pages</title> </meta> </property> <!-- URL/Link --> <property name="external_link" type="url"> <meta> <title lang="en">External Link</title> </meta> <params> <param name="schemes"> <param name="http"/> <param name="https"/> </param> </params> </property> <!-- Checkbox --> <property name="featured" type="checkbox"> <meta> <title lang="en">Featured Product</title> </meta> </property> <!-- Select dropdown --> <property name="availability" type="select"> <meta> <title lang="en">Availability</title> </meta> <params> <param name="values" type="collection"> <param name="in_stock"> <meta> <title lang="en">In Stock</title> </meta> </param> <param name="out_of_stock"> <meta> <title lang="en">Out of Stock</title> </meta> </param> <param name="pre_order"> <meta> <title lang="en">Pre-Order</title> </meta> </param> </param> </params> </property> </properties> </template> ``` ## Summary and Integration Patterns Sulu 3.0 is designed for enterprise-level content management scenarios requiring multi-language support, sophisticated permission controls, and flexible content modeling. The primary use cases include corporate websites with multiple brands or regions, e-commerce platforms requiring complex product catalogs, intranet portals with role-based access control, and publishing platforms managing large volumes of content across different channels. The webspace system enables managing multiple independent websites within a single installation, each with its own domain configuration, content tree, and security context. The new dimension-based content architecture provides enhanced flexibility for managing content variations across locales, workflow stages, and custom dimensions. Integration patterns in Sulu 3.0 center around the REST API for headless CMS scenarios, the ContentManager for programmatic content manipulation with dimension support, Symfony Messenger for asynchronous operations, and the admin interface extension system for custom entity management. The system supports external storage backends through League Flysystem (AWS S3, Azure Blob, Google Cloud) for media files, integrates with modern search solutions through pluggable adapters, and provides comprehensive event dispatching throughout the content lifecycle for custom business logic. Developers typically extend functionality by creating custom bundles that register new content types, admin views, and REST endpoints, while the Doctrine ORM-based storage provides a robust foundation for hierarchical content structures with full versioning and dimension-based content variants for draft/published workflows and multi-locale support.