# Parts Platform Plugin Development Guide

This guide explains how developers extend the Parts Platform with file-based plugins.

Plugins are loaded from the configured plugin path, can register a Laravel service provider, can expose web and API routes, and can ship migrations. They are intended for integrations, custom operational screens, private API extensions, import/export jobs, reporting, and project-specific behavior that should not live in the core application.

## 1. Runtime Model

At boot, `App\Providers\PluginServiceProvider` does this:

1. Reads `config/plugins.php`.
2. Discovers `plugin.json` files one or two directory levels below the plugin path.
3. Filters enabled plugins using `PARTS_ENABLED_PLUGINS`.
4. Requires each plugin provider file when configured.
5. Registers each plugin provider class when it exists.
6. Loads plugin web routes with the `web` middleware.
7. Loads plugin API routes with the `api` middleware.
8. Loads plugin migrations from the configured plugin migration path.

The default plugin root is:

```text
plugins/
```

Override it with:

```env
PARTS_PLUGINS_PATH=/absolute/path/to/plugins
```

By default, every manifest with `"enabled": true` is loaded:

```env
PARTS_ENABLED_PLUGINS=*
```

To allow only specific plugins, use exact manifest names:

```env
PARTS_ENABLED_PLUGINS=Vendor/InventorySync,Vendor/AnotherPlugin
```

## 2. Scaffold a Plugin

Create a plugin with:

```bash
php artisan parts:make-plugin Vendor/InventorySync
```

The command creates:

```text
plugins/
└── Vendor/
    └── InventorySync/
        ├── README.md
        ├── plugin.json
        ├── database/
        │   └── migrations/
        ├── routes/
        │   ├── api.php
        │   └── web.php
        └── src/
            └── PluginServiceProvider.php
```

The generated API health route is:

```http
GET /api/v1/plugins/vendor-inventory-sync/health
```

The generated web route is:

```http
GET /plugins/vendor-inventory-sync
```

## 3. Manifest Reference

Each plugin is described by `plugin.json`.

```json
{
  "name": "Vendor/InventorySync",
  "description": "Parts platform plugin.",
  "version": "0.1.0",
  "enabled": true,
  "provider": "PartsPlugins\\Vendor\\InventorySync\\PluginServiceProvider",
  "provider_file": "src/PluginServiceProvider.php",
  "routes": {
    "api": "routes/api.php",
    "web": "routes/web.php"
  },
  "migrations": "database/migrations",
  "api_prefix": "api/v1/plugins/vendor-inventory-sync",
  "web_prefix": "plugins/vendor-inventory-sync",
  "route_name_prefix": "plugins.vendor.inventory.sync."
}
```

| Field | Required | Meaning |
| --- | --- | --- |
| `name` | Yes | Stable manifest name. Use `Vendor/PluginName`. |
| `description` | No | Human-readable plugin summary. |
| `version` | No | Plugin version. Use semantic versions when possible. |
| `enabled` | No | `true` loads the plugin. `false` disables it. Defaults to `true`. |
| `provider` | No | Fully qualified service provider class. |
| `provider_file` | No | Relative path to the provider file. |
| `routes.api` | No | Relative path to API routes. |
| `routes.web` | No | Relative path to web routes. |
| `migrations` | No | Relative path to plugin migrations. |
| `api_prefix` | No | API URL prefix. Defaults to `api/v1/plugins/{plugin-slug}`. |
| `web_prefix` | No | Web URL prefix. Defaults to `plugins/{plugin-slug}`. |
| `route_name_prefix` | No | Route name prefix. Defaults to `plugins.{plugin.slug}.`. |

The plugin slug is generated from the manifest name. `Vendor/InventorySync` becomes `vendor-inventory-sync`.

## 4. Service Provider

Use the plugin service provider for container bindings, event listeners, commands, view namespaces, configuration merging, and other Laravel bootstrapping.

```php
<?php

namespace PartsPlugins\Vendor\InventorySync;

use Illuminate\Support\ServiceProvider;

class PluginServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Bind services or merge plugin config.
    }

    public function boot(): void
    {
        // Register commands, listeners, views, or other boot-time behavior.
    }
}
```

Keep provider boot logic lightweight. Long-running integration work should run through queued jobs or commands.

## 5. API Routes

Plugin API routes are grouped with the `api` middleware under the manifest `api_prefix`.

Example `routes/api.php`:

```php
<?php

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;

Route::middleware(['auth:sanctum', 'abilities:products.read'])
    ->get('/inventory-status', function (Request $request): array {
        return [
            'status' => 'ok',
            'user_id' => $request->user()?->id,
        ];
    });
```

With the default scaffold prefix, this route becomes:

```http
GET /api/v1/plugins/vendor-inventory-sync/inventory-status
```

Security rules:

- Plugin API routes do not automatically require authentication.
- Add `auth:sanctum` to routes that require an API token.
- Add `abilities:*` middleware for existing token abilities where possible.
- Add plugin-specific policies or middleware for sensitive behavior.
- Validate every write request with Form Requests or validator logic.
- Use throttling for integration endpoints that can be called frequently.

## 6. Web Routes

Plugin web routes are grouped with the `web` middleware under the manifest `web_prefix`.

Example `routes/web.php`:

```php
<?php

use Illuminate\Support\Facades\Route;

Route::middleware(['auth', 'role:admin'])
    ->get('/dashboard', fn () => response('Inventory sync dashboard'));
```

With the default scaffold prefix, this route becomes:

```http
GET /plugins/vendor-inventory-sync/dashboard
```

Use admin middleware for operational screens. Do not expose internal integration tools publicly.

## 7. Migrations

Plugin migrations live under the manifest `migrations` path:

```text
database/migrations/
```

They are loaded with Laravel's migration system when the plugin is enabled. Use normal Laravel migration files:

```text
2026_07_06_000001_create_inventory_sync_tables.php
```

Migration rules:

- Treat plugin migrations as production-sensitive.
- Do not rename or edit already-run plugin migrations casually.
- Keep destructive migration steps explicit and reviewed.
- Prefer additive migrations for production changes.
- Test migrations with `php artisan migrate` before release.

## 8. Access to Core Models and Services

Plugins run inside the Laravel application and can use core models and services:

```php
use App\Models\Product;

$publishedProducts = Product::query()
    ->where('status', 'published')
    ->latest()
    ->limit(10)
    ->get();
```

Keep plugin behavior behind stable application boundaries when possible:

- Use public models, actions, services, and events that already exist.
- Avoid duplicating core business rules in plugin code.
- Avoid modifying core database tables directly unless the change is explicitly part of the plugin's contract.
- Keep plugin-specific tables prefixed or named clearly.

## 9. Configuration and Secrets

Do not store secrets in `plugin.json`.

Use environment variables for credentials:

```env
INVENTORY_SYNC_BASE_URL=https://erp.example.com
INVENTORY_SYNC_API_KEY=secret
```

A plugin provider may merge a plugin config file if the plugin ships one:

```php
public function register(): void
{
    $this->mergeConfigFrom(__DIR__.'/../config/inventory-sync.php', 'inventory-sync');
}
```

## 10. Testing Plugins

Recommended checks while developing a plugin:

```bash
php artisan route:list
php artisan migrate
php artisan test
```

For the full platform gate before release:

```bash
composer lint:check
php artisan test
npm run format:check
npm run lint:check
npm run types:check
npm run build
```

When a plugin adds API routes, test:

- unauthenticated requests return `401` when authentication is required
- tokens without required abilities return `403`
- validation failures return `422`
- success responses have stable JSON shapes
- routes appear under the expected prefix in `php artisan route:list`

## 11. Deployment

Deploy plugin changes through the normal branch flow:

```text
development -> testing -> release -> deployment
```

Production deployment runs:

```text
composer install --no-dev --optimize-autoloader --no-interaction --prefer-dist --no-progress --no-scripts
php artisan package:discover --ansi
npm ci --no-audit --no-fund
npm run build
php artisan migrate --force
php artisan optimize:clear
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan queue:restart
```

Plugin routes and migrations are loaded during this process when the plugin is enabled and present in the configured plugin path.

## 12. Troubleshooting

If a plugin does not load:

1. Confirm `PARTS_PLUGINS_PATH` points to the directory containing plugin folders.
2. Confirm `plugin.json` exists one or two levels below the plugin path.
3. Confirm `plugin.json` is valid JSON.
4. Confirm `name` is non-empty.
5. Confirm `"enabled": true`.
6. Confirm `PARTS_ENABLED_PLUGINS` is `*` or contains the exact manifest name.
7. Confirm `provider_file` exists when configured.
8. Confirm the provider class namespace matches the `provider` value.
9. Run `php artisan route:list` to verify routes.
10. Run `php artisan config:clear` and `php artisan optimize:clear` after configuration changes.

If plugin API routes return `404`, check the manifest `api_prefix` and route file path.

If plugin API routes return `401` or `403`, check Sanctum authentication, token abilities, and route middleware.

If migrations do not run, check the manifest `migrations` path and whether the plugin is enabled.
