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:

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">

    FakeGmvReporting
    Fake GMV Reporting
     lang="de-DE">Fake GMV Reporting
    Test app for reading orders to calculate GMV.
    Friendly Hacker
    1.0.0
    MIT



    http://fake-gmv-app:3000/register
    supersecret



     name="appActivated" url="http://fake-gmv-app:3000/activated" event="app.activated"/>



    order
    order_line_item
    currency
    system_config

✅ 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.