with one click
add-model
Add a Doctrine entity as a Sylius Resource
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
Add a Doctrine entity as a Sylius Resource
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
Implement the fix described in a GitHub issue and open a Pull Request that closes it
Apply the latest review feedback to a Pull Request and push it to the PR branch
Add an admin autocomplete form type for a Sylius Resource (translatable or not), optionally injected into another form via an extension
Add an admin FormType for an existing Sylius Resource
Add a Sylius admin grid for an existing Sylius Resource
Add a multiple images collection (OneToMany) to an existing Sylius Resource
| name | add-model |
| description | Add a Doctrine entity as a Sylius Resource |
| argument-hint | [ModelName] |
| allowed-tools | AskUserQuestion, Bash, Read, Edit, Write, Glob, Grep |
Ask the user for the ModelName if not provided, the list of fields with their types, and any relations to other resources.
Group by domain like Sylius-Standard (src/Entity/Product/, src/Entity/Customer/):
{Parent}Image, {Parent}Translation, …) → src/Entity/{Parent}/src/Entity/{ModelName}/src/Entity/{ModelName}/{ModelName}Interface.php:
<?php
declare(strict_types=1);
namespace $SYLIUS_NAMESPACE\Entity\{ModelName};
use Sylius\Resource\Model\ResourceInterface;
interface {ModelName}Interface extends ResourceInterface
{
public function getId(): ?int;
// declare the getter (and setter if mutable) for every field and relation added in step 2
}
src/Entity/{ModelName}/{ModelName}.php:
<?php
declare(strict_types=1);
namespace $SYLIUS_NAMESPACE\Entity\{ModelName};
use Doctrine\ORM\Mapping as ORM;
#[ORM\Entity]
#[ORM\Table(name: '${SYLIUS_PREFIX}_{model_snake}')]
class {ModelName} implements {ModelName}Interface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column(type: 'integer')]
protected ?int $id = null;
public function getId(): ?int
{
return $this->id;
}
// add your fields here
}
For fields that require validation, add Symfony constraints directly on the property:
use Symfony\Component\Validator\Constraints as Assert;
#[Assert\NotBlank]
#[Assert\Length(max: 255)]
#[ORM\Column(length: 255)]
private ?string $title = null;
For relations to another resource in the same project, always use the target's interface as targetEntity — Sylius's ResolveTargetEntityListener resolves it at runtime from the interface: key in the resource config. In forms, EntityType does not go through this listener — pass the concrete class or use /sylius-plugin:add-autocomplete.
ManyToOne — {ModelName} belongs to one {RelatedModel}:
#[ORM\ManyToOne(targetEntity: {RelatedModel}Interface::class)]
private ?{RelatedModel}Interface ${related_model} = null;
OneToMany (inverse side) — {ModelName} has many {RelatedModel}. Initialize the collection in the constructor. Requires the owning side ({RelatedModel} entity) to declare a matching ManyToOne field named {model_snake}:
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
#[ORM\OneToMany(mappedBy: '{model_snake}', targetEntity: {RelatedModel}Interface::class)]
private Collection ${related_model_plural};
public function __construct()
{
$this->{related_model_plural} = new ArrayCollection();
}
Add the canonical Sylius adder/remover pair so LiveCollectionType and other form types sync the inverse side correctly:
public function add{RelatedModel}({RelatedModel}Interface ${related_model}): void
{
if (!$this->{related_model_plural}->contains(${related_model})) {
$this->{related_model_plural}->add(${related_model});
${related_model}->set{ModelName}($this);
}
}
public function remove{RelatedModel}({RelatedModel}Interface ${related_model}): void
{
if ($this->{related_model_plural}->removeElement(${related_model})) {
${related_model}->set{ModelName}(null);
}
}
Same adder/remover pattern for ManyToMany — omit the inverse sync call (no owning-side field to update on the other entity).
ManyToMany:
#[ORM\ManyToMany(targetEntity: {RelatedModel}Interface::class)]
private Collection ${related_model_plural};
public function __construct()
{
$this->{related_model_plural} = new ArrayCollection();
}
To reach a Sylius core entity from a plugin (e.g. attach plugin data to Product), do not add a relation on the plugin entity — use /sylius-plugin:extends-model + a plugin-provided trait instead (the FK lives on the extended core entity).
Create config/resources/{model_snake}.yaml at the plugin root (one file per resource — matches ProductBundlePlugin / MolliePlugin convention). Add the glob import once to the plugin's config/config.yaml: - { resource: "resources/*.yaml" }. The test app loads @{PluginBundle}/config/config.yaml via its own tests/TestApplication/config/config.yaml, which cascades the import.
sylius_resource:
resources:
${SYLIUS_PREFIX}.{model_snake}:
driver: doctrine/orm
classes:
model: $SYLIUS_NAMESPACE\Entity\{ModelName}\{ModelName}
interface: $SYLIUS_NAMESPACE\Entity\{ModelName}\{ModelName}Interface
The plugin's DI extension declares the DoctrineMigrations namespace via PrependDoctrineMigrationsTrait, so generate into it:
vendor/bin/console doctrine:migrations:diff --namespace=DoctrineMigrations
Always review the generated migration before applying. doctrine:migrations:diff captures every difference between mapping and DB — including pre-existing schema drift unrelated to your model (e.g. ALTER TABLE messenger_messages ... from a Sylius update never migrated locally). The migration should only contain CREATE TABLE ${SYLIUS_PREFIX}_{model_snake} + its indexes/FKs.
If unrelated SQL is present, manually trim the migration up()/down() to keep only your table — the drift belongs to the test app baseline, not your plugin.
Then apply:
vendor/bin/console doctrine:migrations:migrate --no-interaction
vendor/bin/console cache:clear
vendor/bin/console sylius:debug:resource '$SYLIUS_NAMESPACE\Entity\{ModelName}\{ModelName}' prints the resource metadata (alias ${SYLIUS_PREFIX}.{model_snake}, model + interface classes)vendor/bin/console doctrine:query:sql "DESCRIBE ${SYLIUS_PREFIX}_{model_snake}" lists the expected columns (id, plus user fields)/sylius-plugin:add-form to add an admin form/sylius-plugin:add-translatable-model if the model needs translations/sylius-plugin:add-grid, then /sylius-plugin:add-routes, then /sylius-plugin:add-menu