SyliusCon 2025 in Lyon
Join Us!
LogoLogo
🛣️ Roadmap💻 Sylius Demo💬 Community Slack
  • Sylius Documentation
  • Sylius Plugins
  • Sylius Stack
  • 📖Sylius Documentation
  • Organization
    • Sylius Team
  • Release Cycle
    • Backwards Compatibility Promise
  • Getting Started with Sylius
    • Installation
    • Basic Configuration
    • Shipping & Payment
    • First Product
    • Customizing the Shop
    • Customizing Business Logic
    • Using API
    • Installing Plugins
    • Deployment
    • Summary
  • The Book
    • Introduction to Sylius
    • Installation
      • System Requirements
      • Sylius CE Installation
        • Sylius CE Installation with Docker
      • ➕Sylius Plus Installation
      • Upgrading Sylius CE
      • Upgrading Sylius Plus
    • Architecture
      • Architecture Overview
      • Architectural Drivers
      • Resource Layer
      • State Machine
      • Translations
      • E-Mails
      • Contact
      • Fixtures
      • Events
    • Configuration
      • Channels
      • Locales
      • Currencies
    • Customers
      • Customer & ShopUser
      • ➕Customer Pools
      • AdminUser
      • Addresses
        • Countries
        • Zones
        • Addresses
        • Address Book
    • Products
      • Products
      • Product Reviews
      • Product Associations
      • Attributes
      • Pricing
      • Catalog Promotions
      • Taxons
      • Inventory
      • ➕Multi-Source Inventory
      • Search
    • Carts & Orders
      • Orders
      • Cart flow
      • Taxation
      • Adjustments
      • Cart Promotions
      • Coupons
      • Payments
      • 🧩Invoices
      • Shipments
    • 🎨Frontend & Themes
    • Support
    • Contributing
      • Contributing Code
        • Submitting a Patch
        • ⚠️Security Issues
        • Coding Standards
        • Conventions
        • Sylius License and Trademark
      • Contributing Translations
      • Key Contributors
  • The Customization Guide
    • Customizing Models
      • How to add a custom model?
      • How to add a custom translatable model?
    • Customizing Forms
      • How to add a live form for a custom model?
    • Customizing Templates
    • Customizing Styles
    • Customizing Dynamic Elements
    • Customizing Validation
    • Customizing Menus
    • Customizing Translations
    • Customizing Flashes
    • Customizing State Machines
    • Customizing Grids
    • Customizing Fixtures
    • Customizing API
    • Customizing Serialization of API
    • Customizing Payments
      • How to integrate a Payment Gateway as a Plugin?
  • 🧑‍🍳The Cookbook
  • How to resize images?
  • How to add one image to an entity?
  • How to add multiple images to an entity?
  • How to add a custom cart promotion action?
  • How to add a custom cart promotion rule?
  • Sylius 1.X Documentation
    • 📓Sylius 1.x Documentation
Powered by GitBook
LogoLogo

Developer

  • Community
  • Online Course

About

  • Team

© 2025 Sylius. All Rights Reserved

On this page
  • Prerequisites
  • Step 1: Create the Image Entity
  • Step 2: Extend the Owner Entity
  • Step 3: Configure Resources
  • Step 4: Create the Image Form Type
  • Step 5: Extend the Form for Shipping Method
  • Step 6: Enable Image Upload via Listener
  • Step 7: (Optional) Add Validation Constraints
  • Step 8: Customize the Shipping Method twig hooks
  • Step 9: Generate and Run Migrations
  • Step 10: Result

Was this helpful?

Edit on GitHub

How to add multiple images to an entity?

This guide explains how to associate multiple images with a single entity in Sylius 2.x using a one-to-many relationship. We'll use the ShippingMethod entity as an example, but this applies to any entity.


Prerequisites

  • Sylius 2.x is installed

  • LiipImagineBundle is configured

  • Doctrine is configured with migrations

  • The media path (public/media/image/) is writable


Step 1: Create the Image Entity

<?php

// src/Entity/Shipping/ShippingMethodImage.php

namespace App\Entity\Shipping;

use Sylius\Component\Core\Model\Image;
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
#[ORM\Table(name: 'app_shipping_method_image')]
class ShippingMethodImage extends Image
{
    #[ORM\ManyToOne(
        targetEntity: ShippingMethod::class,
        inversedBy: 'images'
    )]
    #[ORM\JoinColumn(
        name: 'owner_id',
        referencedColumnName: 'id',
        nullable: false,
        onDelete: 'CASCADE'
    )]
    protected $owner = null;
}

Step 2: Extend the Owner Entity

<?php

// src/Entity/Shipping/ShippingMethod.php

namespace App\Entity\Shipping;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Sylius\Component\Core\Model\ImageInterface;
use Sylius\Component\Core\Model\ImagesAwareInterface;
use Sylius\Component\Core\Model\ShippingMethod as BaseShippingMethod;

#[ORM\Entity]
#[ORM\Table(name: 'sylius_shipping_method')]
class ShippingMethod extends BaseShippingMethod implements ImagesAwareInterface
{
    #[ORM\OneToMany(
        targetEntity: ShippingMethodImage::class,
        mappedBy: 'owner',
        orphanRemoval: true,
        cascade: ['persist', 'remove', 'merge', 'detach']
    )]
    private Collection $images;

    public function __construct()
    {
        parent::__construct();
        $this->images = new ArrayCollection();
    }

    public function getImages(): Collection
    {
        return $this->images;
    }

    public function getImagesByType(string $type): Collection
    {
        return $this->images->filter(fn(ImageInterface $image) => $image->getType() === $type);
    }

    public function hasImages(): bool
    {
        return !$this->images->isEmpty();
    }

    public function hasImage(ImageInterface $image): bool
    {
        return $this->images->contains($image);
    }

    public function addImage(ImageInterface $image): void
    {
        if (!$this->hasImage($image)) {
            $image->setOwner($this);
            $this->images->add($image);
        }
    }

    public function removeImage(ImageInterface $image): void
    {
        if ($this->hasImage($image)) {
            $image->setOwner(null);
            $this->images->removeElement($image);
        }
    }
}

Step 3: Configure Resources

# config/packages/_sylius.yaml
sylius_resource:
    resources:
        app.shipping_method_image:
            classes:
                model: App\Entity\Shipping\ShippingMethodImage
                form: App\Form\Type\ShippingMethodImageType

Step 4: Create the Image Form Type

<?php

// src/Form/Type/ShippingMethodImageType.php

namespace App\Form\Type;

use App\Entity\Shipping\ShippingMethodImage;
use Sylius\Bundle\CoreBundle\Form\Type\ImageType;

final class ShippingMethodImageType extends ImageType
{
    public function __construct()
    {
        parent::__construct(ShippingMethodImage::class, ['sylius']);
    }

    public function getBlockPrefix(): string
    {
        return 'app_shipping_method_image';
    }
}

Register the form type if necessary:

# config/services.yaml
services:
    App\Form\Type\ShippingMethodImageType:
        tags:
            - { name: form.type }

Step 5: Extend the Form for Shipping Method

<?php

// src/Form/Extension/ShippingMethodTypeExtension.php

namespace App\Form\Extension;

use App\Form\Type\ShippingMethodImageType;
use Sylius\Bundle\ShippingBundle\Form\Type\ShippingMethodType;
use Symfony\Component\Form\AbstractTypeExtension;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\UX\LiveComponent\Form\Type\LiveCollectionType;

final class ShippingMethodTypeExtension extends AbstractTypeExtension
{
    public function buildForm(FormBuilderInterface $builder, array $options): void
    {
        $builder->add('images', LiveCollectionType::class, [
            'entry_type' => ShippingMethodImageType::class,
            'allow_add' => true,
            'allow_delete' => true,
            'by_reference' => false,
            'label' => 'sylius.form.shipping_method.images',
        ]);
    }

    public static function getExtendedTypes(): iterable
    {
        return [ShippingMethodType::class];
    }
}

Register the extension:

# config/services.yaml
services:
    App\Form\Extension\ShippingMethodTypeExtension:
        tags:
            - { name: form.type_extension }

Step 6: Enable Image Upload via Listener

# config/services.yaml
services:
    app.listener.images_upload:
        class: Sylius\Bundle\CoreBundle\EventListener\ImagesUploadListener
        parent: sylius.listener.images_upload
        autowire: true
        public: false
        tags:
            - { name: kernel.event_listener, event: sylius.shipping_method.pre_create, method: uploadImages }
            - { name: kernel.event_listener, event: sylius.shipping_method.pre_update, method: uploadImages }

Step 7: (Optional) Add Validation Constraints

// src/Entity/Shipping/ShippingMethodImage.php

use Symfony\Component\Validator\Constraints as Assert;
​
    #[Assert\Image(
        groups: ['sylius'],
        mimeTypes: ['image/png', 'image/jpeg', 'image/gif'],
        maxSize: '10M'
    )]
    protected $file;
// App\Entity\Shipping\ShippingMethod.php

use Symfony\Component\Validator\Constraints as Assert;

#[Assert\Valid]
private Collection $images;

Step 8: Customize the Shipping Method twig hooks

Inspect the shipping method form, let's assume you want to add new new section Images that is between the general and configuration.

  1. Configure hooks for your new images section

# config/packages/_sylius.yaml
sylius_twig_hooks:
    hooks:
        'sylius_admin.shipping_method.update.content.form#left':
            images:
                template: '/admin/shipping_method/form/sections/images.html.twig'
                priority: 150 # to place it between general and configuration sections
                
        'sylius_admin.shipping_method.update.content.form.images':
            content:
                template: '/admin/shipping_method/form/sections/images/content.html.twig'
                priority: 100
            add_button:
                template: '/admin/shipping_method/form/sections/images/add_button.html.twig'
                priority: 0
                
        'sylius_admin.shipping_method.create.content.form#left':
            images:
                template: '/admin/shipping_method/form/sections/images.html.twig'
                priority: 150

        'sylius_admin.shipping_method.create.content.form.images':
            content:
                template: '/admin/shipping_method/form/sections/images/content.html.twig'
                priority: 100
            add_button:
                template: '/admin/shipping_method/form/sections/images/add_button.html.twig'
                priority: 0
  1. Create templates for your hooks

{# templates/admin/shipping_method/form/sections/images.html.twig #}

<div class="card mb-3">
    <div class="card-header">
        <div class="card-title">
            {{ 'sylius.ui.images'|trans }}
        </div>
    </div>
    <div class="card-body">
        {% hook 'images' %}
    </div>
</div>
{# templates/admin/shipping_method/form/sections/images/content.html.twig #}

{% set images = hookable_metadata.context.form.images %}

<div class="row">
    {% for image_form in images %}
        <div class="col-12 col-md-6 row mb-4">
            <div class="col-auto">
                <div>
                    {% if image_form.vars.value.path is not null %}
                        <span class="avatar avatar-xl" style="background-image: url('{{ image_form.vars.value.path|imagine_filter('sylius_small') }}')"></span>
                    {% else %}
                        <span class="avatar avatar-xl"></span>
                    {% endif %}
                </div>
                <div class="mt-3 d-flex items-center">
                    {{ form_widget(image_form.vars.button_delete, { label: 'sylius.ui.delete'|trans, attr: { class: 'btn btn-outline-danger w-100' }}) }}
                </div>
            </div>
            <div class="col">
                <div class="mb-3">
                    {{ form_row(image_form.file) }}
                </div>
            </div>
        </div>
    {% endfor %}
</div>
{# templates/admin/shipping_method/form/sections/images/add_button.html.twig #}

<div class="d-grid gap-2">
    {{ form_widget(hookable_metadata.context.form.images.vars.button_add) }}
</div>

Step 9: Generate and Run Migrations

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

Step 10: Result

PreviousHow to add one image to an entity?NextHow to add a custom cart promotion action?

Last updated 7 days ago

Was this helpful?

The Shipping method has now collection of images !

🎉