# easy-invoice/2.3.1/includes/Addons/AddonRegistry.php

Easy Invoice – Invoice Generator, PDF Quotes &amp; Payments, version 2.3.1. 437 lines.

- Page: https://pluginprobe.com/plugins/easy-invoice/2.3.1/code/includes/Addons/AddonRegistry.php
- Raw: https://pluginprobe.com/plugins/easy-invoice/2.3.1/raw/includes/Addons/AddonRegistry.php
- Modified: 2026-05-21T08:29:24+00:00

Line numbers below start at 1. Link to a line or a range by appending a fragment to the
page URL, for example `https://pluginprobe.com/plugins/easy-invoice/2.3.1/code/includes/Addons/AddonRegistry.php#L10-L20`.

```php
<?php
/**
 * Catalogue of available Easy Invoice addons.
 *
 * The Free plugin owns the metadata so the Addons page renders even when Pro
 * is inactive. The Pro plugin owns the executable code at the `pro_file` path.
 *
 * @package EasyInvoice
 */

namespace EasyInvoice\Addons;

if (!defined('ABSPATH')) {
    exit;
}

class AddonRegistry {

    /**
     * @return array<int,array<string,mixed>>
     */
    public static function all(): array {
        return apply_filters('easy_invoice_addons_registry', [

            // ─── PROFESSIONAL ─────────────────────────────────────────────
            [
                'id'            => 'time_tracking',
                'name'          => __('Time Tracking & Project Billing', 'easy-invoice'),
                'short_name'    => __('Time Tracking', 'easy-invoice'),
                'tagline'       => __('Track billable hours and convert them to invoice lines in one click.', 'easy-invoice'),
                'description'   => __('Built-in start/stop timer with per-project and per-client entries. Convert any block of tracked time into invoice line items in one click — replaces your standalone time-tracking tool.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PROFESSIONAL,
                'icon'          => 'dashicons-clock',
                'category'      => 'productivity',
                'badge'         => 'popular',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/time-tracking/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-time-tracking'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\TimeTracking\\TimeTracking',
            ],

            [
                'id'            => 'expense_tracking',
                'name'          => __('Expense Tracking & Reimbursable Items', 'easy-invoice'),
                'short_name'    => __('Expenses', 'easy-invoice'),
                'tagline'       => __('Log billable expenses, mark them up, and convert to invoice lines in one click.', 'easy-invoice'),
                'description'   => __('Track project expenses (software, mileage, materials, contractor costs) with receipt uploads. Apply markup and roll selected expenses into invoice line items — the same pattern as Time Tracking, for everything that isn\'t billable hours.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PROFESSIONAL,
                'icon'          => 'dashicons-money',
                'category'      => 'billing',
                'badge'         => 'new',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/expense-tracking/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-expense-tracking'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\ExpenseTracking\\ExpenseTracking',
            ],

            [
                'id'            => 'dunning',
                'name'          => __('Smart Reminders & Late Fees', 'easy-invoice'),
                'short_name'    => __('Smart Reminders', 'easy-invoice'),
                'tagline'       => __('Recover overdue invoices automatically — without the awkward chase emails.', 'easy-invoice'),
                'description'   => __('Configure a multi-step reminder cadence with per-step templates that escalate in tone. Auto-apply late fees and offer early-payment discounts — typically recovers 30%+ of overdue invoices.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PROFESSIONAL,
                'icon'          => 'dashicons-email-alt',
                'category'      => 'billing',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/smart-reminders/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-dunning'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\SmartReminders\\SmartReminders',
            ],

            // ─── AGENCY ───────────────────────────────────────────────────
            [
                'id'            => 'white_label',
                'name'          => __('White-Label & Brand Override', 'easy-invoice'),
                'short_name'    => __('White-Label', 'easy-invoice'),
                'tagline'       => __('Rebrand Easy Invoice as your own product — every label, footer, and PDF.', 'easy-invoice'),
                'description'   => __('Replace the plugin name, admin menu, dashboard icon, PDF footer, and email signature with your own brand. Hide every upgrade prompt — essential for agencies reselling Easy Invoice as a managed service.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_AGENCY,
                'icon'          => 'dashicons-businesswoman',
                'category'      => 'branding',
                'badge'         => 'new',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/white-label/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-white-label'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\WhiteLabel\\WhiteLabel',
            ],

            [
                'id'            => 'team_roles',
                'name'          => __('Team Members & Audit Log', 'easy-invoice'),
                'short_name'    => __('Team & Audit', 'easy-invoice'),
                'tagline'       => __('Invite staff with scoped roles. See who did what, when, and why.', 'easy-invoice'),
                'description'   => __('Scoped roles (Manager / Accountant / Sales / Viewer) so staff can work without touching refunds or settings. Every action lands in a searchable audit log — required for compliance and accountant hand-offs.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_AGENCY,
                'icon'          => 'dashicons-groups',
                'category'      => 'team',
                'badge'         => 'new',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/team-roles/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-team-roles'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\TeamRoles\\TeamRoles',
            ],

            [
                'id'            => 'accounting_sync',
                'name'          => __('Accounting Sync (QuickBooks / Xero / FreshBooks)', 'easy-invoice'),
                'short_name'    => __('Accounting Sync', 'easy-invoice'),
                'tagline'       => __('Two-way sync with QuickBooks Online, Xero, and FreshBooks — invoices out, payments back.', 'easy-invoice'),
                'description'   => __('Push invoices to your accounting tool when created; pull payment status back when reconciled there. Chart-of-accounts mapping, customer mirroring, and bank-rec friendly. Pluggable provider layer for QBO, Xero, FreshBooks (and adding more).', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_AGENCY,
                'icon'          => 'dashicons-update-alt',
                'category'      => 'integrations',
                'badge'         => 'new',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/accounting-sync/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-accounting-sync'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\AccountingSync\\AccountingSync',
            ],

            [
                'id'            => 'webhooks',
                'name'          => __('Webhooks & Zapier Bridge', 'easy-invoice'),
                'short_name'    => __('Webhooks', 'easy-invoice'),
                'tagline'       => __('Push invoice events into Zapier, your CRM, Slack — anywhere with a URL.', 'easy-invoice'),
                'description'   => __('Subscribe any URL to invoice events (paid, accepted, failed, recurring run). HMAC-SHA256 signed payloads, automatic retries. Drops into Zapier, Make, n8n, your CRM — connect to 5,000+ tools.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_AGENCY,
                'icon'          => 'dashicons-rest-api',
                'category'      => 'integrations',
                'badge'         => 'new',
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/webhooks/',
                'settings_url'  => admin_url('admin.php?page=easy-invoice-addon-webhooks'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\Webhooks\\Webhooks',
            ],

            // ─── PRO-FEATURE ADDONS (migrated from monolithic Pro plugin) ────
            //
            // These ten addons wrap features that previously loaded
            // unconditionally inside easy-invoice-pro.php. They're enabled
            // by default on EXISTING installs via the migration in
            // ProAddonMigration::migrate(), and disabled by default on
            // FRESH installs so new users can opt in only to what they need.
            //
            // None of these have settings pages of their own (they reuse
            // existing Pro settings pages); the Addons grid only exposes
            // the enable/disable toggle. The settings_url field points at
            // the closest relevant settings tab.

            [
                'id'            => 'recurring_invoices',
                'name'          => __('Recurring Invoices & Subscriptions', 'easy-invoice'),
                'short_name'    => __('Recurring', 'easy-invoice'),
                'tagline'       => __('Auto-bill clients on autopilot — weekly, monthly, or any custom schedule.', 'easy-invoice'),
                'description'   => __('Set the frequency, end date, and payment method once; Easy Invoice generates and sends each invoice on schedule. Optional auto-charge through your gateway closes the loop — predictable revenue, zero admin.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-update',
                'category'      => 'billing',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/recurring-invoices/',
                // No dedicated settings page. Recurring data lives as
                // post-meta on regular easy_invoice posts (META_ENABLED,
                // META_FREQUENCY, etc. in RecurringInvoices.php) — the
                // merchant toggles "Make this recurring" on the invoice
                // editor. The legacy easy_invoice_recurring CPT was
                // registered with show_in_menu=false + create_posts=
                // do_not_allow, so linking to its list page from the
                // addon card just dumped users on a WP-styled, read-only,
                // dead-end screen. Empty settings_url hides that link.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-settings&section=invoice'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\RecurringInvoices\\RecurringInvoices',
            ],
            [
                'id'            => 'partial_payments',
                'name'          => __('Partial Payments & Deposits', 'easy-invoice'),
                'short_name'    => __('Partial Payments', 'easy-invoice'),
                'tagline'       => __('Get paid sooner with deposits and flexible installment plans.', 'easy-invoice'),
                'description'   => __('Require a deposit (% or flat) before work begins, then let clients pay the balance in installments. Tracks every part-payment and shows the running balance — no spreadsheets, no manual reconciliation.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-money-alt',
                'category'      => 'billing',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/partial-payments/',
                // PartialPaymentsSettingsController appends its fields to the
                // main Settings page via the easy_invoice_settings_fields_config
                // filter — no dedicated page slug. Leave settings_url empty
                // so the addon card doesn't render a misleading "Settings →"
                // link that just dumps the user on the main Settings page.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-settings&section=invoice'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\PartialPayments\\PartialPayments',
            ],
            [
                'id'            => 'client_portal',
                'name'          => __('Client Portal', 'easy-invoice'),
                'short_name'    => __('Client Portal', 'easy-invoice'),
                'tagline'       => __('Give clients a branded self-service area for invoices, quotes, and receipts.', 'easy-invoice'),
                'description'   => __('Clients log in to a branded portal to view invoices, download PDFs, see payment history, and accept or decline quotes — on their own time. Stops the back-and-forth email asking for old documents.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-admin-users',
                'category'      => 'clients',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/client-portal/',
                // ClientPortal extension stores config at
                // easy_invoice_pro_client_portal_settings; UI lives as a tab
                // on the main Settings page. No dedicated slug → leave
                // settings_url empty so the card doesn't render a misleading
                // "Settings →" link.
                'settings_url'  => '',
                'pro_class'     => 'EasyInvoicePro\\Addons\\ClientPortal\\ClientPortal',
            ],
            [
                'id'            => 'pdf_toolkit',
                'name'          => __('PDF Toolkit', 'easy-invoice'),
                'short_name'    => __('PDF Toolkit', 'easy-invoice'),
                'tagline'       => __('Customise every detail of your invoice and quote PDFs.', 'easy-invoice'),
                'description'   => __('Custom headers and footers, watermark stamps (PAID / DRAFT / OVERDUE / VOID), color and font controls, and per-document branding. Make every PDF look enterprise-grade — not stock WordPress.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-pdf',
                'category'      => 'branding',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/pdf-toolkit/',
                // PdfCustomizationSettingsController appends fields to the
                // main Settings page via filter; no dedicated slug. Empty
                // settings_url hides the misleading "Settings →" link.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-settings&section=invoice'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\PdfToolkit\\PdfToolkit',
            ],
            [
                'id'            => 'bulk_operations',
                'name'          => __('Bulk Email & Export', 'easy-invoice'),
                'short_name'    => __('Bulk Ops', 'easy-invoice'),
                'tagline'       => __('Send and export hundreds of invoices in a single click.', 'easy-invoice'),
                'description'   => __('Bulk actions on every list screen — email batches of invoices in one click, or export selected rows to CSV, Excel, or PDF. \"Export All\" downloads your entire history, perfect for year-end accounting.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-download',
                'category'      => 'productivity',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/bulk-operations/',
                'settings_url'  => '',
                'pro_class'     => 'EasyInvoicePro\\Addons\\BulkOperations\\BulkOperations',
            ],
            [
                'id'            => 'item_library',
                'name'          => __('Item Library', 'easy-invoice'),
                'short_name'    => __('Item Library', 'easy-invoice'),
                'tagline'       => __('Build a reusable catalog of services and products.', 'easy-invoice'),
                'description'   => __('Save your common services and products with name, description, price, and SKU. Insert into any invoice or quote in one click — stop retyping and keep pricing consistent across every document.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-products',
                'category'      => 'productivity',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/item-library/',
                // Verified slug: ItemLibraryController::addAdminMenu()
                // registers 'easy-invoice-pro-item-library'.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-pro-item-library'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\ItemLibrary\\ItemLibrary',
            ],
            [
                'id'            => 'custom_templates',
                'name'          => __('Custom Invoice & Quote Templates', 'easy-invoice'),
                'short_name'    => __('Templates', 'easy-invoice'),
                'tagline'       => __('Design pixel-perfect invoice and quote layouts with a drag-and-drop builder.', 'easy-invoice'),
                'description'   => __('Build your own invoice and quote layouts with the visual editor — drag in logos, company info, item tables, totals, signatures, and footers. Save multiple templates and assign different layouts per client.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-layout',
                'category'      => 'branding',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/custom-templates/',
                // Verified slug: TemplateBuilderController registers
                // 'easy-invoice-templates' (the listing) — that's the
                // canonical entry point. settings_url here is used by
                // the per-addon sidebar loop in main-template.php to
                // render a sidebar entry BELOW the Addons menu. The
                // addons-page.php card does NOT render a "Settings →"
                // link anymore (the sidebar is the navigation).
                'settings_url'  => admin_url('admin.php?page=easy-invoice-templates'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\CustomTemplates\\CustomTemplates',
            ],
            [
                'id'            => 'additional_tax',
                'name'          => __('Additional Tax Lines', 'easy-invoice'),
                'short_name'    => __('Additional Tax', 'easy-invoice'),
                'tagline'       => __('Multiple tax lines per invoice — VAT + duty, GST + PST, federal + state.', 'easy-invoice'),
                'description'   => __('Add unlimited named tax lines per invoice — VAT + duty, GST + PST, federal + state — each with its own rate. Renders every tax as a separate line on the PDF so clients see exactly what they\'re paying.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-chart-bar',
                'category'      => 'billing',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/additional-tax/',
                // AdditionalTax extension appends "Enable Additional Tax"
                // + per-tax-rate fields via the main Settings page; no
                // dedicated slug → empty settings_url hides the
                // misleading "Settings →" link.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-settings&section=tax'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\AdditionalTax\\AdditionalTax',
            ],
            [
                'id'            => 'email_enhancements',
                'name'          => __('Email Enhancements', 'easy-invoice'),
                'short_name'    => __('Email Tools', 'easy-invoice'),
                'tagline'       => __('Send branded HTML emails with PDF attached, CC / BCC, and Reply-To.', 'easy-invoice'),
                'description'   => __('Branded HTML emails with your colors and logo. Add CC / BCC, auto-attach the PDF, customise the subject and body per invoice, and set a Reply-To so client replies reach the right inbox.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-email',
                'category'      => 'productivity',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/email-enhancements/',
                // EmailEnhancements appends Reply-To / advanced email
                // fields to the main Settings page; no dedicated slug →
                // empty settings_url hides the misleading "Settings →"
                // link.
                'settings_url'  => '',
                'pro_class'     => 'EasyInvoicePro\\Addons\\EmailEnhancements\\EmailEnhancements',
            ],
            [
                'id'            => 'secure_links',
                'name'          => __('Secure Links for Invoices & Quotes', 'easy-invoice'),
                'short_name'    => __('Secure Links', 'easy-invoice'),
                'tagline'       => __('Share invoices and quotes with unguessable, signed URLs instead of plain post slugs.', 'easy-invoice'),
                'description'   => __('Replace predictable invoice/quote permalinks with cryptographically signed, expiring URLs. Stops competitors and customers from incrementing IDs to view documents that aren\'t theirs. Already wired into emails, the [easy_invoice_links] shortcode, and recurring child invoices.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-lock',
                'category'      => 'compliance',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/secure-links/',
                // Settings live inside Settings → Advanced, injected
                // by SecureLinks::registerAdvancedFields() (the same
                // pattern Payment Reminder uses for the Email tab).
                // The toggle there stays in lock-step with the
                // addon-enabled state via the addon's
                // update_option_* hooks.
                'settings_url'  => admin_url('admin.php?page=easy-invoice-settings&section=advanced'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\SecureLinks\\SecureLinks',
            ],
            [
                'id'            => 'privacy_tools',
                'name'          => __('Privacy & GDPR', 'easy-invoice'),
                'short_name'    => __('Privacy', 'easy-invoice'),
                'tagline'       => __('Comply with GDPR, CCPA, and other data-protection laws in one click.', 'easy-invoice'),
                'description'   => __('Wires Easy Invoice into WordPress\'s Personal Data Export and Erase tools. Client invoice, quote, and payment history is included automatically in data-subject requests — required for EU and California compliance.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-shield',
                'category'      => 'compliance',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/privacy-tools/',
                // The privacy_tools addon hooks WordPress's built-in
                // personal-data exporter / eraser registries — those
                // registries are managed by WP core under
                // Tools → Export/Erase Personal Data (a WP-native page,
                // not our UI). There is no addon-specific settings page
                // to surface from the addons grid, so leave settings_url
                // empty — the addon card just shows Active/Inactive.
                'settings_url'  => '',
                'pro_class'     => 'EasyInvoicePro\\Addons\\PrivacyTools\\PrivacyTools',
            ],
            [
                'id'            => 'reports',
                'name'          => __('Reports & Analytics', 'easy-invoice'),
                'short_name'    => __('Reports', 'easy-invoice'),
                'tagline'       => __('See your revenue, outstanding balances, and best clients at a glance.', 'easy-invoice'),
                'description'   => __('Visual dashboard for invoice revenue, outstanding balances, payment status, and per-client performance. Filter by any date range, export for your accountant, and decide on data instead of guessing.', 'easy-invoice'),
                'plan'          => LicensePlanResolver::PLAN_PERSONAL,
                'icon'          => 'dashicons-chart-line',
                'category'      => 'analytics',
                'badge'         => null,
                'docs_url'      => 'https://easy-invoice.matrixaddons.com/docs/addons/reports/',
                // ReportController lives in the Free plugin (the class
                // existed before addonization) but the page is a Pro
                // feature shown to Free users only as an upsell badge.
                // Gating its sidebar link + page dispatch on this addon
                // makes "Reports" opt-in for Pro users too. The slug
                // 'easy-invoice-reports' is registered as an EI admin
                // submenu by EasyInvoiceAdmin::registerMenuPages().
                'settings_url'  => admin_url('admin.php?page=easy-invoice-reports'),
                'pro_class'     => 'EasyInvoicePro\\Addons\\Reports\\Reports',
            ],

        ]);
    }

    /**
     * IDs of every addon that wraps a previously-monolithic Pro feature.
     * Used by ProAddonMigration to know which flags to set for existing
     * Pro installs (so their currently-active features keep working) and
     * to leave disabled for fresh installs.
     *
     * @return array<int,string>
     */
    public static function migratedProAddonIds(): array {
        return [
            'recurring_invoices',
            'partial_payments',
            'client_portal',
            'pdf_toolkit',
            'bulk_operations',
            'item_library',
            'custom_templates',
            'additional_tax',
            'email_enhancements',
            'privacy_tools',
            // Reports was previously gated only by easy_invoice_has_pro();
            // any Pro install was using it. The migration falls through
            // to isExistingProInstall() for this id, so existing Pro
            // sites keep Reports active after the addon-ization migration.
            'reports',
            // Secure Links was always-loaded Pro infrastructure
            // gated only by easy_invoice_pro_enable_secure_links.
            // Migration auto-enables the addon when that legacy
            // option was 'yes' so signed URLs already in the wild
            // (emails, shortcodes) keep resolving.
            'secure_links',
        ];
    }

    /**
     * Find a single addon definition by id.
     *
     * @return array<string,mixed>|null
     */
    public static function find(string $id): ?array {
        foreach (self::all() as $addon) {
            if ($addon['id'] === $id) {
                return $addon;
            }
        }
        return null;
    }

    /**
     * Distinct categories present in the registry — drives the filter chips in the UI.
     *
     * @return array<int,string>
     */
    public static function categories(): array {
        $cats = [];
        foreach (self::all() as $addon) {
            $cats[$addon['category']] = true;
        }
        return array_keys($cats);
    }
}

```
