JTL Shop Plugin entwickeln – Praxisleitfaden (mit PHP-Beispielen)

4 Min. Lesezeit

Für wen ist dieser Artikel? Für JTL Shop-Entwickler:innen, die ein robustes, wartbares und wiederverwendbares Plugin-Setup suchen.

Überblick

In diesem Leitfaden entwickeln Sie ein JTL Shop Plugin mit produktionsreifen Mustern:

  • Skalierbare Projektstruktur
  • Manifeste für JTL Shop 4/5
  • Hooks registrieren & sauber kapseln
  • Admin-Einstellungen inkl. Validierung
  • Template-Integration (Smarty)
  • Datenbank-Migrationen
  • Versionierung & Deployment

Alle Beispiele sind in PHP. Bei Versionsunterschieden weise ich explizit darauf hin.


Voraussetzungen

  • PHP 7.4+ (abhängig von Ihrer JTL Shop Version)
  • Zugriff auf eine JTL Shop Entwicklungs-/Staging-Umgebung
  • Composer (optional, empfohlen)
  • Grundkenntnisse in Smarty

Projektstruktur

So bleibt Ihr Plugin auch in Teams und CI/CD gut wartbar:

my-jtl-plugin/
├─ src/
│  ├─ Plugin.php
│  ├─ Services/
│  ├─ Hooks/
│  ├─ Admin/
│  └─ Templates/
├─ templates/
├─ migrations/
├─ vendor/
├─ plugin.json                 # JTL Shop 5 (bevorzugt)
├─ info.xml                    # JTL Shop 4 (nur wenn nötig)
├─ config.xml                  # Admin-Einstellungen
├─ README.md
└─ composer.json

plugin.json für JTL Shop 5 verwenden. info.xml nur für Alt-Installationen (v4).


Manifest (JTL Shop 5: plugin.json)

{
  "name": "my-jtl-plugin",
  "displayName": "My JTL Plugin",
  "version": "1.0.0",
  "author": "Ihr Unternehmen",
  "url": "https://ideployed.com",
  "minShopVersion": "5.0.0",
  "description": "Fügt Feature X zum JTL Shop hinzu.",
  "hooks": [
    { "id": 0, "file": "src/Hooks/OnPageLoaded.php" },
    { "id": 99, "file": "src/Hooks/BeforeCheckout.php" }
  ],
  "adminMenu": [
    { "name": "My Plugin", "url": "plugin.php?plugin=my-jtl-plugin", "rights": ["VIEW"] }
  ],
  "install": "src/Plugin.php",
  "uninstall": "src/Plugin.php",
  "update": "src/Plugin.php"
}
hljs json

Hook-IDs sind versionsabhängig. Vermeiden Sie „Magic Numbers“ und kapseln Sie IDs in Konstanten. Prüfen Sie die offizielle Hook-Liste Ihrer Zielversion.

Legacy (JTL Shop 4: info.xml)

<?xml version="1.0" encoding="UTF-8"?>
<jtlshopplugin>
  <Name>My JTL Plugin</Name>
  <Author>Ihr Unternehmen</Author>
  <Version>1.0.0</Version>
  <MinShopVersion>4.0.0</MinShopVersion>
  <Hooks>
    <Hook>
      <HookID>0</HookID>
      <File>src/Hooks/OnPageLoaded.php</File>
    </Hook>
  </Hooks>
</jtlshopplugin>
hljs xml

Bootstrap (src/Plugin.php)

Schlanker, testbarer Lebenszyklus:

<?php
declare(strict_types=1);

namespace MyJtlPlugin;

final class Plugin
{
    public static function install(): void
    {
        self::runMigrations(__DIR__ . '/../migrations');
    }

    public static function update(string $fromVersion, string $toVersion): void
    {
        self::runMigrations(__DIR__ . '/../migrations');
    }

    public static function uninstall(): void
    {
        // Daten nach Möglichkeit behalten (Opt-In für Hard-Delete)
    }

    private static function runMigrations(string $dir): void
    {
        foreach (glob($dir . '/*.sql') as $file) {
            $sql = file_get_contents($file);
            if ($sql !== false && trim($sql) !== '') {
                self::db()->exec($sql);
            }
        }
    }

    private static function db(): \PDO
    {
        return new \PDO('mysql:host=localhost;dbname=shop','user','pass', [
            \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION
        ]);
    }
}
hljs php

Hooks registrieren (Beispiele)

src/Hooks/OnPageLoaded.php — sicher mit Smarty arbeiten:

<?php
declare(strict_types=1);

namespace MyJtlPlugin\Hooks;

final class OnPageLoaded
{
    public function __invoke(array $args): void
    {
        $smarty = $args['smarty'] ?? null;
        if ($smarty instanceof \Smarty) {
            $smarty->assign('MY_PLUGIN_GREETING', 'Hallo vom My JTL Plugin!');
        }
    }
}
hljs php

src/Hooks/BeforeCheckout.php — Beispiel für Checkout-Validierung:

<?php
declare(strict_types=1);

namespace MyJtlPlugin\Hooks;

final class BeforeCheckout
{
    public function __invoke(array $args): void
    {
        $cart = $args['cart'] ?? null;

        if ($cart && $this->hasDisallowedItems($cart)) {
            $smarty = $args['smarty'] ?? null;
            if ($smarty instanceof \Smarty) {
                $smarty->assign('MY_PLUGIN_CHECKOUT_WARNING', 'Bestimmte Artikel erfordern eine manuelle Prüfung.');
            }
        }
    }

    private function hasDisallowedItems($cart): bool
    {
        return false;
    }
}
hljs php

Halten Sie Ihre Hook-Handler idempotent und performant. Das verhindert doppelte Ausführung und unnötige Seiteneffekte.


Admin-Einstellungen (config.xml)

Funktionen ein-/ausschalten und Werte speichern:

<?xml version="1.0" encoding="utf-8"?>
<config>
  <section key="my_jtl_plugin" label="My JTL Plugin">
    <setting key="enabled" type="selectbox" label="Plugin aktivieren">
      <option value="0">Deaktiviert</option>
      <option value="1" selected="selected">Aktiviert</option>
    </setting>
    <setting key="greeting_text" type="text" label="Begrüßungstext" default="Hallo vom My JTL Plugin!" />
  </section>
</config>
hljs xml

Im Hook lesen:

$enabled = (int)($_ENV['MY_JTL_PLUGIN_ENABLED'] ?? 1);
$greeting = $_ENV['MY_JTL_PLUGIN_GREETING'] ?? 'Hallo vom My JTL Plugin!';
hljs php

Nutzen Sie in Produktion die offizielle Konfig-Schnittstelle Ihrer JTL-Version statt direktem Zugriff auf $_ENV.


Template-Integration (Smarty)

Kleine Komponente einblenden:

templates/snippets/my_plugin_banner.tpl

{if $MY_PLUGIN_GREETING}
<div class="alert alert-info my-plugin-banner">
  {$MY_PLUGIN_GREETING|escape}
</div>
{/if}
hljs smarty

Einbinden wo nötig:

{include file="snippets/my_plugin_banner.tpl"}
hljs smarty

Checkout-Warnungen:

templates/snippets/my_plugin_checkout_warning.tpl

{if $MY_PLUGIN_CHECKOUT_WARNING}
<div class="alert alert-warning my-plugin-warning">
  {$MY_PLUGIN_CHECKOUT_WARNING|escape}
</div>
{/if}
hljs smarty

Datenbank-Migrationen

migrations/001_create_flags.sql

CREATE TABLE IF NOT EXISTS my_plugin_flags (
  id INT AUTO_INCREMENT PRIMARY KEY,
  name VARCHAR(64) NOT NULL,
  enabled TINYINT(1) NOT NULL DEFAULT 1,
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
hljs sql

migrations/002_add_description.sql

ALTER TABLE my_plugin_flags
  ADD COLUMN description VARCHAR(255) NULL AFTER name;
hljs sql

Nummerierte Einzeldateien helfen, nur notwendige Migrationen beim Update auszuführen.


Versionierung & Kompatibilität

  • SemVer verwenden (MAJOR.MINOR.PATCH)
  • minShopVersion im Manifest aktuell halten
  • CHANGELOG.md pflegen
  • Bei mehreren Major-Versionen: Kompatibilitätsschichten kapseln

Beispiel CHANGELOG.md:

## 1.1.0 - 2025-06-10
- Checkout-Warnung hinzugefügt (konfigurierbar)
- Feature-Flag-Tabelle eingeführt (Migration 001)
- Getestet mit JTL Shop 5.2.x

Test-Checkliste

  • ✅ Hooks sind idempotent
  • ✅ Admin-Einstellungen validieren/speichern korrekt
  • ✅ Keine Smarty-Konflikte
  • ✅ Staging-Test mit realistischen Daten
  • ✅ Rollback möglich
  • ✅ Performance unter Produktionslast getestet

Nutzen Sie reale Testdaten im Staging, um frühzeitig Probleme zu erkennen.


Deployment

  • Nur notwendige Artefakte packen
  • Keine Secrets im Repo – Umgebungsvariablen nutzen
  • Staging ≈ Produktion
  • Automatisches Deployment bevorzugen

Beispiel Build-Skript:

#!/usr/bin/env bash
set -euo pipefail

PLUGIN_NAME="my-jtl-plugin"
OUT="dist/${PLUGIN_NAME}.zip"

rm -f "$OUT"
mkdir -p dist

zip -r "$OUT"   src/ templates/ migrations/   plugin.json info.xml config.xml README.md   -x "**/.DS_Store" "**/node_modules/**" "**/vendor/**"
hljs bash

⚡️ Ein-Klick-Deployment mit iDeployed. Mit iDeployed erstellen Sie isolierte Staging-Shops, testen Plugins mit produktionsnahen Daten und deployen sicher. Jetzt kostenlos testen →

Share this article

SOC 2 Typ II zertifiziert
DSGVO-konform
HIPAA-konform

Alle Kommunikationen sind mit AES-256 verschlüsselt und durch unser Zero-Trust-Sicherheitskonzept geschützt. Wir geben Ihre Daten niemals an Dritte weiter.