---
title: Plugin Lifecycle Management | Shopware Community Hub
description: >-
  Learn the Shopware plugin lifecycle, including installation, activation,
  updates, uninstallation, deactivation and database migrations.
canonical_url: 'https://hub.shopware.com/learn/unit/plugin-lifecycle-management'
---

# Plugin Lifecycle Management

<LearningObjectives>

- Understand the Shopware plugin lifecycle and its core lifecycle events.
- Learn how to create, execute, and manage database migrations.
- Understand plugin version management and its impact on updates.
- Understand the principle behind safe and forward-compatible update strategies.
- Handle plugin activation and deactivation correctly.

</LearningObjectives>

# Plugin Lifecycle Management

Proper plugin lifecycle management is crucial for maintaining and updating Shopware plugins. This learning unit will guide you through the various stages of a plugin's lifecycle and how to handle them effectively.

## Plugin Lifecycle Methods

Shopware plugins follow a well-defined lifecycle managed by the platform. At each phase of the lifecycle, Shopware calls specific methods on your plugin class so that you can perform setup, cleanup, and migration tasks as needed.

### Plugin Installation and Uninstallation

The `install` method is called when a plugin is installed. Typical responsibilities here include:

1. The plugin will be registered in the `plugin` database table.
2. Running database migrations and creating required tables.
3. Initial data setup.
4. Installing required assets.

---

The `uninstall` method is called when a plugin is uninstalled.

Shopware passes an `UninstallContext` that allows you to check whether user data should be preserved.

If you or a merchant uninstall the plugin **via the administration**, a confirmation dialog appears asking whether all plugin data should be removed permanently.

This user decision is exposed through the `keepUserData` method:

- If `keepUserData` returns `true`, the user wants to **keep existing plugin data**.
- If `keepUserData` returns `false`, the user explicitly allowed **data removal**.

In each plugin lifecycle method, you can add custom logic as needed. Ensure that you call the parent method to ensure proper lifecycle management.

```php
<?php declare(strict_types=1);

namespace Swag\ExamplePlugin;

use Shopware\Core\Framework\Plugin;
use Shopware\Core\Framework\Plugin\Context\InstallContext;
use Shopware\Core\Framework\Plugin\Context\UninstallContext;

class ExamplePlugin extends Plugin
{
    public function install(InstallContext $installContext): void
    {
        parent::install($installContext);
        
        // For example, create a payment method entity and a shipping method here
        $this->createPaymentMethodIfNotExists($installContext->getContext());
        $this->createShippingMethodIfNotExists($installContext->getContext());
        
        // Your other custom logic here
    }

    public function uninstall(UninstallContext $uninstallContext): void
    {
        parent::uninstall($uninstallContext);
        
        // If the user chose to keep plugin data, do nothing destructive
        // (toggle in the administration is inactive)
        if ($uninstallContext->keepUserData()) {
            return;
        }
        
        // If the user chose to remove plugin data, remove it here
        // (toggle in the administration is active)
        
        // Clean up and your custom logic here 
    }
}
```

This pattern allows you or the merchants to decide whether uninstalling a plugin should preserve or remove existing plugin data.

### Data Handling on Uninstall

Be aware that in some cases, you **should not remove plugin data** when uninstalling a plugin.

For example, plugins that integrate with external systems or payment providers may rely on persisted data that should remain available even after the plugin is uninstalled.

**Always evaluate carefully before performing destructive operations!**

Typical use cases where you have to be especially careful about data removal include:

- Payment methods that rely on external payment providers
- Shipping methods that rely on external shipping providers
- Order-related external integrations
- Customer-related external integrations

Rule of thumb: **If data has business value or is required for compliance, do not delete it on uninstallation unless the merchant (or company agrees) to it!**

### Plugin Activation and Deactivation

Once a plugin is installed, it can be activated or deactivated independently of installation and uninstallation.

The `activate` method is called when a plugin is enabled (by toggling it on in the administration or via the CLI).

This is a good place to perform any initialization tasks, such as:

- Activate payment methods (common use case) or shipping methods.
- Enabling features that depend on the installed state of the plugin.

```php
public function activate(ActivateContext $activateContext): void
{
    parent::activate($activateContext);
    
    $this->activatePaymentMethod($activateContext); // Your method, which activates your related payment method
    $this->activateShippingMethod($activateContext); // Your method, which activates your related payment method
    
    // Clear caches
    // Enable features
    // Initialize services
}
```

---

The `deactivate` method is called when a plugin is disabled (by toggling it off in the administration or via the CLI).

Use this method to perform actions needed when the plugin stops working without removing its data, such as:

- Clean up temporary data
- Deactivate payment methods or other entities created by the `install` method.

```php
public function deactivate(DeactivateContext $deactivateContext): void
{
    parent::deactivate($deactivateContext);
    
    $this->deactivatePaymentMethod($activateContext); // Your method, which deactivates your related payment method
    $this->deactivateShippingMethod($activateContext); // Your method, which deactivates your related shipping method
    
    // Clean up temporary data
    // Disable features
    // Preserve data
}
```

### Plugin Update Process

Plugins can be updated when a new version is released. Shopware detects a plugin update based on the version defined in the plugin's `composer.json` file.

When the version changes, and you run the `plugin:update` command, Shopware executes the update process. During an update database **migrations** with higher timestamps than previously applied ones are executed automatically.

The update process should be designed to preserve existing data and ensure forward compatibility. Database schema changes should be handled via migrations, not inside `update()`.

Handle updates carefully to ensure data integrity and make sure your version-specific code respects existing data.

```php
public function update(UpdateContext $updateContext): void
{
    parent::update($updateContext);
    
    // Update necessary stuff, mostly non-database related
    
    // Your custom logic here
}
```

## Best Practices

1. **Installation**
   - Create necessary database tables using migrations
   - Set up default configurations
   - Install required assets
   - Handle errors gracefully

2. **Updates**
   - Test updates thoroughly
   - Provide database migration scripts for schema changes
   - Document changes
   - Support rollback

3. **Uninstallation**
   - Respect user data preservation settings
   - Clean up properly
   - Remove temporary files
   - Consider dependencies and external integrations

## Version Management

### Versioning Strategy

```json
{
    "name": "swag/example-plugin",
    "version": "1.2.0",
    "description": "Example plugin for Shopware",
    "type": "shopware-platform-plugin",
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "Swag\\ExamplePlugin\\": "src/"
        }
    },
    "extra": {
        "shopware-plugin-class": "Swag\\ExamplePlugin\\ExamplePlugin",
        "label": {
            "de-DE": "Beispiel Plugin",
            "en-GB": "Example Plugin"
        }
    }
}
```

The `"version": "1.2.0"` field in your `composer.json` defines your plugin's version and is used by Shopware and Composer to manage updates and compatibility.

This version should follow [semantic versioning](https://semver.org/), which is a standard for version numbers that helps communicate the nature of changes in each release.

You can check your current plugin version via the `plugin:list` command:

```bash
bin/console plugin:list
```

<Callout title="Checking the Shopware Version" type="info">

Did you know you can quickly check your current Shopware version by using `bin/console -V`?

</Callout>

### Update Strategies

1. **Minor Updates**
   - Backward compatible
   - No data migration required
   - Simple version bump

2. **Major Updates**
   - May include breaking changes
   - Often require data migrations
   - Require careful planning

## Database Migrations

### Creating Migrations

Migrations are used to manage database schema changes in a controlled way:

```php
<?php declare(strict_types=1);

namespace Swag\ExamplePlugin\Migration;

use Doctrine\DBAL\Connection;
use Shopware\Core\Framework\Migration\MigrationStep;

class Migration1612345678Example extends MigrationStep
{
    public function getCreationTimestamp(): int
    {
        return 1612345678;
    }

    public function update(Connection $connection): void
    {
        $sql = <<<SQL
CREATE TABLE IF NOT EXISTS `swag_example` (
    `id` BINARY(16) NOT NULL,
    `name` VARCHAR(255) NOT NULL,
    `created_at` DATETIME(3) NOT NULL,
    `updated_at` DATETIME(3) NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;

        $connection->executeStatement($sql);
    }

    public function updateDestructive(Connection $connection): void
    {
        // Optional: Only use this for destructive changes
    }
}
```

### Migration Execution and Status

#### When Migrations Are Applied

Migrations are automatically executed by Shopware in the following scenarios:

1. **During Plugin Installation**
   - When a plugin is installed for the first time
   - All migrations with timestamps are executed in order
   - Only the `update()` method is executed automatically
   - Destructive migrations (`updateDestructive()`) must be triggered manually

2. **During Plugin Updates**
   - When a plugin version changes (detected via `composer.json`)
   - Only new migrations (with higher timestamps) are executed
   - Ensures incremental database updates

3. **Manual Execution**
   - You can manually trigger migrations using console commands
   - Non-destructive migrations can be executed using `database:migrate`
   - Destructive migrations can be executed using `database:migrate-destructive`
   - Useful for debugging or applying migrations in specific environments

#### Checking Migration Status

**Using Console Commands:**

```bash
# Check which migrations are pending
bin/console database:migrate --all

# Run pending migrations
bin/console database:migrate --all Shopware\\Core\\Framework\\Migration

# Run destructive migrations
bin/console database:migrate-destructive --all
```

**Using the Database:**

Shopware tracks all applied migrations in the `migration` table. You can query this table to see which migrations have been executed:

```sql
SELECT * FROM `migration` 
WHERE `class_name` LIKE 'AcademyReviewExtension%' 
ORDER BY `creation_timestamp` DESC;
```

The `LIKE` condition is optional and can be adjusted to filter migrations for a specific plugin namespace.

#### The Migration Tracking Table

The `migration` table structure:

- `class_name`: Full class name of the migration (e.g., `AcademyReviewExtension\Migration\Migration1747312800CreateReviewMediaTable`)
- `creation_timestamp`: The timestamp returned by `getCreationTimestamp()`
- `update`: Timestamp when the `update()` method was executed
- `update_destructive`: Timestamp when the `updateDestructive()` method was executed

**Understanding the Status:**

- If a migration exists in the `migration` table with an `update` timestamp, the `update()` method has been executed.
- If it has an `update_destructive` timestamp, the `updateDestructive()` method has been executed.
- If a migration class exists but isn't in the table, it hasn't been applied yet.
- If a migration exists in the table but neither `update` nor `update_destructive` contains a timestamp, the migration was started but not completed successfully (for example, due to an error during execution).

### Migration Best Practices

1. **Version Control**
   - Use timestamps for migration versions
   - Keep migrations atomic
   - Document changes

2. **Data Safety**
   - Backup data before destructive changes
   - Plan destructive changes carefully, as automatic rollbacks are not supported
   - Test migrations thoroughly

3. **Performance**
   - Use efficient SQL
   - Consider large datasets
   - Add appropriate indexes

### Example Database Migration

The plugin includes a migration that creates the necessary database tables:

```php
<?php declare(strict_types=1);

namespace AcademyReviewExtension\Migration;

use Doctrine\DBAL\Connection;
use Doctrine\DBAL\Exception;
use Shopware\Core\Framework\Migration\MigrationStep;

class Migration1747312800CreateReviewMediaTable extends MigrationStep
{
    public function getCreationTimestamp(): int
    {
        return 1747312800;
    }

    /**
     * @throws Exception
     */
    public function update(Connection $connection): void
    {
        // Create review images table
        $sql = <<<SQL
CREATE TABLE IF NOT EXISTS `academy_review_image` (
    `review_id` BINARY(16) NOT NULL,
    `media_id` BINARY(16) NOT NULL,
    `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updated_at` DATETIME(3) NULL ON UPDATE CURRENT_TIMESTAMP(3),
    PRIMARY KEY (`review_id`, `media_id`),
    CONSTRAINT `fk.academy_review_image.review_id` FOREIGN KEY (`review_id`)
        REFERENCES `product_review` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk.academy_review_image.media_id` FOREIGN KEY (`media_id`)
        REFERENCES `media` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;
        $connection->executeStatement($sql);

        // Create review videos table
        $sql = <<<SQL
CREATE TABLE IF NOT EXISTS `academy_review_video` (
    `review_id` BINARY(16) NOT NULL,
    `media_id` BINARY(16) NOT NULL,
    `created_at` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    `updated_at` DATETIME(3) NULL ON UPDATE CURRENT_TIMESTAMP(3),
    PRIMARY KEY (`review_id`, `media_id`),
    CONSTRAINT `fk.academy_review_video.review_id` FOREIGN KEY (`review_id`)
        REFERENCES `product_review` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
    CONSTRAINT `fk.academy_review_video.media_id` FOREIGN KEY (`media_id`)
        REFERENCES `media` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
SQL;
        $connection->executeStatement($sql);
    }
}
```

### Plugin Configuration

The plugin's `composer.json` defines essential metadata:

```json
{
  "name": "academy/review-extension",
  "description": "Extension for product reviews with additional functionality.",
  "type": "shopware-platform-plugin",
  "version": "1.0.0",
  "license": "MIT",
  "authors": [
    {
      "name": "Shopware Academy",
      "email": "academy@shopware.com"
    }
  ],
  "require": {
    "shopware/core": "*"
  },
  "autoload": {
    "psr-4": {
      "AcademyReviewExtension\\": "src/"
    }
  },
  "extra": {
    "shopware-plugin-class": "AcademyReviewExtension\\AcademyReviewExtension",
    "label": {
      "en-GB": "Academy Review Extension",
      "de-DE": "Academy Review Erweiterung"
    }
  }
}
```

### Key Features of This Plugin

1. **Clean Lifecycle Management**: Simple, focused lifecycle methods that call parent methods
2. **Automatic Migration Handling**: Database tables are created automatically during installation
3. **Proper Foreign Key Constraints**: Tables reference existing Shopware entities with CASCADE rules
4. **Service-Based Architecture**: Entity definitions and services are registered via services.xml

### What Happens During Each Lifecycle Event

- **Install**: Migration creates database tables, entities are registered.
- **Activate**: Plugin becomes active, services and subscribers are available.
- **Deactivate**: Plugin is disabled but data remains intact.
- **Uninstall**: Tables can be dropped via destructive migration if user data is not preserved.

This example demonstrates how a well-structured plugin can leverage Shopware's automatic lifecycle management while maintaining clean, maintainable code.

## Summary

Well done! In this learning unit you learned:

- How the Shopware plugin lifecycle works and which lifecycle methods are called during each stage.
- How to properly handle plugin installation, activation, deactivation, updates, and uninstallation.
- How version changes in the `composer.json` file trigger plugin updates.
- How database migrations are created, executed, and tracked in Shopware.
- When and how to handle destructive changes safely using destructive migrations.
- Best practices for managing data responsibly.

With this solid foundation, you are now able to manage plugin lifecycles in a clean, predictable, and maintainable way while respecting data integrity and business requirements.

Congratulations! By completing this learning unit, you have also successfully finished this course.
