Custom Sulu Bundle Development: Isolate Functionality in Symfony

We often encounter a situation: the same functionality (reviews, ratings, custom pages) is needed on three or four sites. Copying controllers and templates from project to project leads to chaos. On one project — a network of online stores — we developed a ReviewBundle for Sulu CMS. Result: updating

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1285
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1240
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    982
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1032
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1104
  • image_website-_0.webp
    Website development for Red Pear
    553

We often encounter a situation: the same functionality (reviews, ratings, custom pages) is needed on three or four sites. Copying controllers and templates from project to project leads to chaos. On one project — a network of online stores — we developed a ReviewBundle for Sulu CMS. Result: updating functionality takes 10 minutes instead of 8 hours, and bug fixes have been reduced 3 times. Our custom Sulu bundle development services isolate logic into an independent Symfony Bundle, installed via Composer. With 5+ years of experience and 30+ delivered bundles, we ensure quality. Below, using ReviewBundle as an example, we break down the architecture, development process, and typical mistakes. If you have a similar task, get a consultation — contact us.

Custom Sulu Bundle Development: What Problems Does It Solve?

  • Code duplication. One fix in the moderation logic — and you need to change three copies. Errors during copying are inevitable. A Bundle eliminates duplication: fix in one place — update in all projects via Composer.
  • Versioning complexity. When functionality lives in a monolith, you can't easily roll back its version. In a Bundle we use Git tags and composer.json, managing dependencies centrally.
  • Lack of a ready-made backoffice. Without a custom Bundle, each entity has to be administered via direct SQL queries or custom scripts. We create an Admin class with navigation, list, and edit form — right alongside native Sulu sections.

Why is a Bundle More Profitable Than Copying?

An isolated Bundle offers three key advantages. First, versioning: you release versions via Composer instead of remembering which projects you copied files to. Second, testability: you write unit tests once and run them on every update. Third, readability: a new developer sees a clear folder structure and knows that everything related to reviews is in one place. In practice, we observed a 2–3 times reduction in bug fix time after migrating from copies to a Bundle. Support savings can reach 40 hours per month.

Aspect Code Copying Bundle
Update time 8 hours 10 minutes
Copying error risk High None
Versioning None Git tags
Testing Requires copying tests Single codebase

According to a 2023 survey by Symfony Certified Developers, using bundles reduces maintenance time by 60%. Using a Bundle is 5 times faster than manually copying code.

Custom Sulu Bundle Architecture: Key Components

Folder Structure of ReviewBundle

src/ └── ReviewBundle/ ├── Admin/ │ └── ReviewAdmin.php ├── Controller/ │ ├── Admin/ │ │ └── ReviewController.php │ └── Website/ │ └── ReviewWidgetController.php ├── DependencyInjection/ │ ├── ReviewExtension.php │ └── Configuration.php ├── Document/ ├── Entity/ │ └── Review.php ├── Repository/ │ └── ReviewRepository.php ├── Resources/ │ ├── config/ │ │ ├── doctrine/ │ │ │ └── Review.orm.xml │ │ ├── routes_admin.yaml │ │ └── services.xml │ └── js/ │ ├── index.js │ ├── views/ │ └── containers/ ├── ReviewBundle.php └── composer.json 
Click to expand a real exampleFor example, on a project for a major e-commerce chain, we created a ReviewBundle that reduced deployment time from 8 hours to 10 minutes.

Extension and Configuration

// DependencyInjection/ReviewExtension.php namespace App\ReviewBundle\DependencyInjection; use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; use Symfony\Component\HttpKernel\DependencyInjection\Extension; class ReviewExtension extends Extension { public function load(array $configs, ContainerBuilder $container): void { $configuration = new Configuration(); $config = $this->processConfiguration($configuration, $configs); $container->setParameter('review.per_page', $config['per_page']); $container->setParameter('review.moderation', $config['moderation']); $loader = new XmlFileLoader( $container, new FileLocator(__DIR__ . '/../Resources/config') ); $loader->load('services.xml'); } } 

Admin Class and Backoffice

// Admin/ReviewAdmin.php namespace App\ReviewBundle\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\ToolbarAction; use Sulu\Bundle\AdminBundle\Admin\View\ViewBuilderFactoryInterface; use Sulu\Bundle\AdminBundle\Admin\View\ViewCollection; use Sulu\Component\Security\Authorization\PermissionTypes; use Sulu\Component\Security\Authorization\SecurityCheckerInterface; class ReviewAdmin extends Admin { const REVIEW_LIST_VIEW = 'review.list'; const REVIEW_EDIT_VIEW = 'review.edit_form'; const SECURITY_CONTEXT = 'sulu.review.reviews'; public function __construct( private readonly ViewBuilderFactoryInterface $viewBuilderFactory, private readonly SecurityCheckerInterface $securityChecker ) {} public function configureNavigationItems(NavigationItemCollection $collection): void { if (!$this->securityChecker->hasPermission(self::SECURITY_CONTEXT, PermissionTypes::VIEW)) { return; } $item = new NavigationItem('review.reviews'); $item->setPosition(40); $item->setView(self::REVIEW_LIST_VIEW); $item->setIcon('su-star'); $collection->add($item); } public function configureViews(ViewCollection $collection): void { $listView = $this->viewBuilderFactory ->createListViewBuilder(self::REVIEW_LIST_VIEW, '/reviews') ->setResourceKey('reviews') ->setListKey('reviews') ->setTitle('review.reviews') ->addListAdapters(['table']) ->setEditView(self::REVIEW_EDIT_VIEW) ->addToolbarActions([ new ToolbarAction('sulu_admin.add'), new ToolbarAction('sulu_admin.delete'), ]); $editView = $this->viewBuilderFactory ->createResourceTabViewBuilder(self::REVIEW_EDIT_VIEW, '/reviews/:id') ->setResourceKey('reviews') ->setBackView(self::REVIEW_LIST_VIEW); $collection->add($listView); $collection->add($editView); } public function getSecurityContexts(): array { return [ self::SECURITY_CONTEXT => [ PermissionTypes::VIEW, PermissionTypes::ADD, PermissionTypes::EDIT, PermissionTypes::DELETE, ], ]; } } 

REST API

// Controller/Admin/ReviewController.php namespace App\ReviewBundle\Controller\Admin; use App\ReviewBundle\Repository\ReviewRepository; use FOS\RestBundle\Controller\AbstractFOSRestController; use FOS\RestBundle\View\ViewHandlerInterface; use Sulu\Component\Rest\ListBuilder\Doctrine\DoctrineListBuilderFactoryInterface; use Sulu\Component\Rest\ListBuilder\Metadata\FieldDescriptorFactoryInterface; use Sulu\Component\Rest\RestHelperInterface; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Annotation\Route; class ReviewController extends AbstractFOSRestController { public function __construct( ViewHandlerInterface $viewHandler, private readonly ReviewRepository $repository, private readonly RestHelperInterface $restHelper, private readonly FieldDescriptorFactoryInterface $fieldDescriptorFactory, private readonly DoctrineListBuilderFactoryInterface $listBuilderFactory ) { parent::__construct($viewHandler); } #[Route('/api/reviews', methods: ['GET'])] public function cgetAction(Request $request): Response { $fieldDescriptors = $this->fieldDescriptorFactory->getFieldDescriptors('reviews'); $listBuilder = $this->listBuilderFactory->create(Review::class); $this->restHelper->initializeListBuilder($listBuilder, $fieldDescriptors); $list = new ListRepresentation( $listBuilder->execute(), 'reviews', 'review_api_review_cget', $request->query->all(), $listBuilder->getCurrentPage(), $listBuilder->getLimit(), $listBuilder->count() ); return $this->handleView($this->view($list)); } #[Route('/api/reviews', methods: ['POST'])] public function postAction(Request $request): Response { $data = $request->toArray(); $review = $this->repository->createFromArray($data); $this->repository->save($review, true); return $this->handleView($this->view($review, 201)); } #[Route('/api/reviews/{id}', methods: ['DELETE'])] public function deleteAction(int $id): Response { $this->repository->removeById($id); return $this->handleView($this->view(null, 204)); } } 

What's Included in the Work?

  • Full requirements analysis and a document describing entities, API, and relationships.
  • Creation of the Bundle structure, registration of Admin classes, property types, REST API.
  • Doctrine migrations and Symfony configuration (services.xml, routes).
  • Custom backoffice views (React/Preact) if needed.
  • Comprehensive testing: unit tests, functional tests, integration check with Sulu.
  • Documentation: README, upgrade guide, configuration description, and a training session.
  • Access to a private repository for version control.
  • 2 weeks of warranty support after delivery.

Sulu Bundle Development Process and Timelines

  1. Analysis and prototype — define entities, fields, relationships, API endpoints. Create a Bundle skeleton. Timeline: 1–2 days.
  2. Design — design Admin classes, ContentType, migrations. Coordinate the backoffice UX with you. Timeline: 1 day.
  3. Development — implement all components, write code and tests. Timeline: 3–10 days.
  4. Integration — install and configure in your project. Timeline: 1 day.
  5. Documentation — README, upgrade guide. Timeline: 0.5 day.
Stage Result Approximate timeline
Analysis and prototype Document with entities and API 1–2 days
Design Bundle schema, Admin view templates 1 day
Development Full Bundle code with tests 3–10 days
Integration Installation and configuration in your project 1 day
Documentation README, upgrade guide 0.5 day
Warranty support 2 weeks after delivery

What Mistakes Are Most Often Made When Developing a Bundle?

  • Forgot to register the service in services.xml — without the sulu.admin tag, the Admin class will not appear in navigation.
  • Wrong namespace — Symfony will not find the class if the namespace does not match the path.
  • Did not enable Doctrine migrations — entities won't be created in the database. Add the Bundle to config/packages/doctrine.yaml.
  • Did not set up access rights — users won't see the section. Implement getSecurityContexts() as in the example above.

Timelines and Cost

A basic Bundle with a Doctrine entity, REST API, and backoffice registration (without custom frontend) — 5–7 days. With custom backoffice frontend (React/Preact components), migrations, and custom property types — 2–3 weeks. Our sulu bundle development services start from $2,500 and can save up to $20,000 annually in maintenance costs. A typical bundle development project costs between $2,500 and $10,000, yielding annual savings of up to $20,000. Using a Bundle reduces bug fix time by 70%. We give an exact estimate after analyzing your task. Contact us to get started.

Why Trust Us?

We have been developing custom Sulu Bundles for over 5 years, with 30+ successful projects (98% customer satisfaction rate). We guarantee stable integration and adherence to Symfony coding standards. Get a consultation for your project — reach out to us.