
There are many ways to install Shopware 6 plugins. You can download them directly in the admin panel or install them with composer.
There is a detailed comparison in the official documentation.
As a developer and maintainer of themes, customizations, third part API's, etc., I'm focused on Static Plugins.
The workflow is simple:
- Create a plugin with
bin/console plugin:create --static - Require it with composer
- Build the project with shopware-cli
When we need the same plugin in more than one shop, we could create the same plugin more than ones, but this wouldn't be great for maintenance.
Extracting the plugin
Let's move the source code of our plugin to a separate repository. To make things easier for now, we make the repository public.
Download with git
We just need to tell composer where to find our plugin
<project-root>/composer.json{
"name": "shopware/production",
"license": "MIT",
"type": "project",
"require": {
"composer-runtime-api": "^2.0",
"acme/sample-plugin": "^1.0",
"shopware/administration": "*",
"shopware/core": "6.6.10.2",
"shopware/elasticsearch": "*",
"shopware/storefront": "*",
"symfony/flex": "~2"
},
"repositories": [
{
"type": "path",
"url": "custom/plugins/*",
"options": {
"symlink": true
}
},
{
"type": "path",
"url": "custom/plugins/*/packages/*",
"options": {
"symlink": true
}
},
{
"type": "path",
"url": "custom/static-plugins/*",
"options": {
"symlink": true
}
},
{
"type": "git",
"url": "https://<DOMAIN-NAME>/<group>/<repo>.git"
}
],
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"prefer-stable": true,
"config": {
"allow-plugins": {
"symfony/flex": true,
"symfony/runtime": true
},
"optimize-autoloader": true,
"sort-packages": true
},
"scripts": {
"auto-scripts": {
"assets:install": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"extra": {
"symfony": {
"allow-contrib": true,
"endpoint": [
"https://raw.githubusercontent.com/shopware/recipes/flex/main/index.json",
"flex://defaults"
]
}
}
}
and require it with
composer req acme/sample-plugin
Could not find a version of package acme/sample-plugin matching your minimum-stability (stable). Require it with an explicit version constraint allowing its desired stability.
Yes... this is the downside. We need to use dev-master as a version
composer req acme/sample-plugin:dev-master
./composer.json has been updated
Running composer update acme/sample-plugin
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking acme/sample-plugin (dev-master 294414d)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Syncing acme/sample-plugin (dev-master 294414d) into cache
- Installing acme/sample-plugin (dev-master 294414d): Cloning 294414deb2 from cache
Generating optimized autoload files
Run composer recipes at any time to see the status of your Symfony recipes.
Executing script assets:install [OK]
Composer will use git to clone our repo and use the default branch and the commit hash to track the release.
This works, but we can do better.
Git tags
Let's tag our plugin with v1.0.0.
Make sure to set the version in composer.json.
<plugin-root>/composer.json{
"name": "acme/sample-plugin",
"description": "acme/sample-plugin",
"type": "shopware-platform-plugin",
"version": "1.0.0",
"license": "MIT",
"require": {
"shopware/core": "~6.6.0"
},
"extra": {
"shopware-plugin-class": "Acme\\SamplePlugin",
"label": {
"de-DE": "Skeleton plugin",
"en-GB": "Skeleton plugin"
}
},
"autoload": {
"psr-4": {
"Acme\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"Acme\\Tests\\": "tests/"
}
}
}
git tag v1.0.0
git push --tags
Now this will work:
composer req acme/sample-plugin
./composer.json has been updated
Running composer update acme/sample-plugin
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking acme/sample-plugin (1.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Syncing acme/sample-plugin (1.0.0) into cache
- Installing acme/sample-plugin (1.0.0): Cloning 294414deb2 from cache
Generating optimized autoload files
Run composer recipes at any time to see the status of your Symfony recipes.
Executing script assets:install [OK]
Using version ^1.0 for acme/sample-plugin
This is better, but we are still using git to fetch the plugin. We can do better.
GitLab Package registry
Here is where the GitLab part starts. For more details refer to the official documentation.
At this point, it doesn't matter if our project in public or not, because we will need to authenticate with the package registry anyway.
Let's release our v1.0.0 tag as a composer package.
curl --fail-with-body --data tag=v1.0.0 "https://__token__:<personal-access-token>@<DOMAIN-NAME>/api/v4/projects/<project_id>/packages/composer"
Now we need to update the repository information:
<project-root>/composer.json{
"name": "shopware/production",
"license": "MIT",
"type": "project",
"require": {
"composer-runtime-api": "^2.0",
"acme/sample-plugin": "^1.0",
"shopware/administration": "*",
"shopware/core": "6.6.10.2",
"shopware/elasticsearch": "*",
"shopware/storefront": "*",
"symfony/flex": "~2"
},
"repositories": [
{
"type": "path",
"url": "custom/plugins/*",
"options": {
"symlink": true
}
},
{
"type": "path",
"url": "custom/plugins/*/packages/*",
"options": {
"symlink": true
}
},
{
"type": "path",
"url": "custom/static-plugins/*",
"options": {
"symlink": true
}
},
{
"type": "composer",
"url": "https://<DOMAIN-NAME>/api/v4/api/v4/group/<group_id>/-/packages/composer/packages.json"
}
],
"autoload": {
"psr-4": {
"App\\": "src/"
}
},
"prefer-stable": true,
"config": {
"allow-plugins": {
"symfony/flex": true,
"symfony/runtime": true
},
"optimize-autoloader": true,
"sort-packages": true
},
"scripts": {
"auto-scripts": {
"assets:install": "symfony-cmd"
},
"post-install-cmd": [
"@auto-scripts"
],
"post-update-cmd": [
"@auto-scripts"
]
},
"extra": {
"symfony": {
"allow-contrib": true,
"endpoint": [
"https://raw.githubusercontent.com/shopware/recipes/flex/main/index.json",
"flex://defaults"
]
}
}
}
or by using the CLI:
composer config repositories.<group_id> composer https://<DOMAIN-NAME>/api/v4/group/<group_id>/-/packages/composer/packages.json
And setup GitLab credentials:
composer config gitlab-token.<DOMAIN-NAME> <personal_access_token>
You can read more about this process in the official documentation.
Now we require our package us usual:
composer req acme/sample-plugin
./composer.json has been updated
Running composer update acme/sample-plugin
Loading composer repositories with package information
Updating dependencies
Lock file operations: 1 install, 0 updates, 0 removals
- Locking acme/sample-plugin (1.0.0)
Writing lock file
Installing dependencies from lock file (including require-dev)
Package operations: 1 install, 0 updates, 0 removals
- Downloading acme/sample-plugin (1.0.0)
- Installing acme/sample-plugin (1.0.0): Extracting archive
Generating optimized autoload files
Run composer recipes at any time to see the status of your Symfony recipes.
Executing script assets:install [OK]
Using version ^1.0 for acme/sample-plugin
Awesome. Direct package download!
Why bother?
This is an excellent question. The main reason is package caching. When running in a CI/CD environment or docker build,
caching packages can give you a massive performance boost.
Release pipeline
With manual tagging
This is a simple pipeline when you need to manually create and push a git tag.
Make sure to always update the version in composer.json
git tag <version>
git push --tags
.gitlab-ci.ymlstages:
- release
deploy:
image: alpine/curl
stage: release
script:
- 'curl --fail-with-body --header "Job-Token: $CI_JOB_TOKEN" --data tag=$CI_COMMIT_TAG "${CI_API_V4_URL}/projects/$CI_PROJECT_ID/packages/composer"'
environment: production
rules:
if: $CI_COMMIT_TAG
With semantic-release
It would be a lot easier if we could just push our changes and don't care about versioning and tagging.
semantic-release automates the whole package release workflow.
Please follow the GitLab authentication instruction before you continue to read.
<plugin-root>/.gitlab-ci.ymlstages:
- release
release:
stage: release
image:
name: ghcr.io/voxpupuli/semantic-release:25.0.0-latest
entrypoint: [""]
interruptible: true
script:
- /container-entrypoint.sh
rules:
- if: $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH
when: never
- if: $CI_COMMIT_BRANCH
<plugin-root>/.releaserc.json{
"plugins": [
"@semantic-release/commit-analyzer",
[
"semantic-release-replace-plugin",
{
"replacements": [
{
"files": ["composer.json"],
"from": "version\": \".*\"",
"to": "version\": \"${nextRelease.version}\""
}
]
}
],
[
"@semantic-release/git",
{
"assets": ["composer.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/exec",
{
"publishCmd": "curl --fail-with-body --header \"Job-Token: ${process.env.CI_JOB_TOKEN}\" --data tag=${nextRelease.gitTag} ${process.env.CI_API_V4_URL}/projects/${process.env.CI_PROJECT_ID}/packages/composer"
}
]
]
}
This will:
- Analise the commits from the last release to decide if a new version should be released
- Update the version in
composer.json - Commit the
composer.jsonback into the repo - Create a tag
- Release a composer package from this tag
