Shopware 6 provides two powerful ways to extend and customize your shop: Apps and Plugins. Both offer ways to interact with the platform, but they serve very different purposes. In this post, we’ll explore the differences through a simple (and slightly sneaky) example involving the GMV (Gross Merchandise Volume) of a shop.
What We’re Building
We’ll create a Shopware App that queries the total order value of the shop (GMV) via the Shopware Admin API.
Then, we’ll implement a Plugin that intercepts those order values and manipulates them – effectively feeding fake data back to the app. This is not a practical use case, but it's perfect to show how these two extension types operate.
Shopware Apps: External Logic with API Access
Apps in Shopware 6 are external applications that run on a separate server (not inside Shopware) and communicate with the shop via a secure API integration.
When an app is installed, it registers an integration with specific access scopes (permissions), which the shop admin has to approve. It’s important to note: the app only gets access to the data that the API makes available, and only if the proper permissions were granted.
Here’s an example manifest.xml for our app:
<?xml version="1.0" encoding="UTF-8"?> <manifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/shopware/shopware/trunk/src/Core/Framework/App/Manifest/Schema/manifest-2.0.xsd"> <meta> <name>FakeGmvReporting</name> <label>Fake GMV Reporting</label> <label lang="de-DE">Fake GMV Reporting</label> <description>Test app for reading orders to calculate GMV.</description> <author>Friendly Hacker</author> <version>1.0.0</version> <license>MIT</license> </meta> <setup> <registrationUrl>http://fake-gmv-app:3000/register</registrationUrl> <secret>supersecret</secret> </setup> <webhooks> <webhook name="appActivated" url="http://fake-gmv-app:3000/activated" event="app.activated"/> </webhooks> <permissions> <read>order</read> <read>order_line_item</read> <read>currency</read> <read>system_config</read> </permissions> </manifest> ✅ Shopware generates an integration with the exact set of API scopes you declare in the manifest.
Apps cannot access more than what’s explicitly listed – unless, of course, the scopes are overly broad.
👉 As a shop owner, always review which data tables are requested – system_config is commonly used to extract sensitive config and credentials!
🛡 OAuth Flow: /register, /confirm, /activated
The app follows the standard Shopware app lifecycle:
/register
Shopware calls this during installation. The app responds with a cryptographic proof.
app.get('/register', (req, res) => { const shopUrl = req.query['shop-url']; const shopId = req.query['shop-id']; const rawData = `${shopId}${shopUrl}${APP_NAME}`; const proof = crypto .createHmac('sha256', APP_SECRET) .update(rawData) .digest('hex'); res.json({ proof: proof, secret: APP_SECRET, confirmation_url: 'http://fake-gmv-app:3000/confirm' }); }); /confirm
Shopware sends client credentials so the app can request access tokens.
app.post('/confirm', express.text({ type: 'application/json' }), (req, res) => { const body = req.body; const tokenData = { shopId: body.shopId, clientId: body.apiKey, clientSecret: body.secretKey, shopUrl: 'http://shopware' }; fs.writeFileSync('./shop-token.json', JSON.stringify(tokenData, null, 2)); res.sendStatus(204); }); ⚠️ In this example, we store the API credentials in a local file – don’t do this in production!
This is just for demonstration. In a real app, use a secure key vault and avoid persisting credentials as plain text.
/activated
app.post('/activated', (req, res) => { console.log('✅ App was activated'); res.sendStatus(200); }); ℹ️ Note: Our Shopware instance is assumed to be running at http://shopware, and our app lives at http://fake-gmv-app.
📊 API Endpoint: /gmv
This is the core logic of our app – fetch orders and calculate GMV.
app.get('/gmv', async (req, res) => { const tokenData = JSON.parse(fs.readFileSync('./shop-token.json', 'utf-8')); const token = await fetchAccessToken(tokenData.clientId, tokenData.clientSecret, tokenData.shopUrl); const response = await axios.get(`${tokenData.shopUrl}/api/order`, { headers: { Authorization: `Bearer ${token}` } }); const orders = response.data.data; const gmv = orders.reduce((sum, order) => sum + order.amountTotal, 0); res.json({ orders: orders.length, gmv: gmv.toFixed(2), currency: orders[0]?.currency?.isoCode || 'EUR' }); }); Shopware Plugins: Internal Logic, Full Power
Plugins, unlike apps, run inside the Shopware core and have access to everything – services, database, events, etc.
Let’s modify our GMV example:
We’ll create a plugin that recognizes requests made by our app – and manipulates the data being returned.
public function onOrderLoaded(EntityLoadedEvent $event): void { $source = $event->getContext()->getSource(); if (!$source instanceof AdminApiSource) return; $criteria = new Criteria(); $criteria->addFilter(new EqualsFilter('name', 'FakeGmvReporting')); $apps = $this->appRepository->search($criteria, $event->getContext())->getEntities(); $app = $apps->first(); if ($app && $source->getIntegrationId() === $app->getIntegrationId()) { foreach ($event->getEntities() as $entity) { if (!$entity instanceof OrderEntity) continue; $entity->setAmountTotal(1.00); $entity->setAmountNet(0.84); $entity->setShippingTotal(0.00); $entity->setTaxStatus('gross'); } } } 🤯 The plugin detects API requests made by the app and changes the order data on the fly!
Summary
| Shopware App | Shopware Plugin | |
|---|---|---|
| Runs where? | External server | Inside the Shopware environment |
| Deployment | No deployment on Shopware required | Installed as plugin |
| Security | Needs explicit permissions | Has full access |
| Use Cases | External services, integrations | Deep customizations |
| Example | Read orders via Admin API | Modify data before it's returned |
Need Help with Your Own Shopware App or Plugin?
If you're planning to develop your own Shopware 6 app or plugin and could use some expert support, I’m happy to help.
From figuring out the Shopware API to building a clean plugin structure — I’ve got your back.
You can find more info about my services here: Shopware 6 plugin development.
Top comments (0)