Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/own-pay/OwnPay-Documentation/llms.txt

Use this file to discover all available pages before exploring further.

Every OwnPay plugin declares a set of capabilities in its manifest.json. Capabilities serve two related purposes: they document what the plugin does and which platform subsystems it interacts with, and they map to RBAC permission keys that are checked against the platform’s role-permission system before certain operations execute. Declaring capabilities accurately is both a security obligation and a requirement for marketplace review.

The Trust Model

OwnPay uses a full-trust plugin model similar to WordPress: plugins uploaded by an operator run with full application trust. Under this model, capabilities are declarative metadata and an intent contract. They document what your plugin does and drive UI presentation in the admin panel.
Capabilities are not a runtime sandbox that restricts PHP execution. The PluginSandbox enforces a narrower set of hard security rules — blocked PHP functions, path containment, and SQL table access policies — independently of capability declarations. Declaring "db_write" does not magically grant you write access; it tells the platform and marketplace reviewers that your plugin performs writes, which triggers the appropriate review.

Declaring Capabilities

Declare capabilities in the capabilities array in manifest.json:
{
  "slug": "my-addon",
  "capabilities": ["addon", "hooks", "cron", "http_outbound", "db_write"]
}
The entrypoint class’s capabilities() method must return the matching Capability enum cases. Both declarations must be consistent — the manifest drives discovery and database storage; the class method is used for runtime introspection.
use OwnPay\Plugin\Capability;

public function capabilities(): array
{
    return [
        Capability::ADDON,
        Capability::HOOKS,
        Capability::CRON,
        Capability::HTTP_OUTBOUND,
        Capability::DB_WRITE,
    ];
}
Over-declaring capabilities causes the marketplace security review to flag your submission. Declare only what your plugin actually uses. A plugin that reads a settings value does not need Capability::ANALYTICS just because it touches transactional data.

Full Capability Reference

The following table documents every value in the OwnPay\Plugin\Capability enum (src/Plugin/Capability.php).
CapabilityJSON ValueRequired Permission KeysScope and Intent
Capability::GATEWAY"gateway"gateway.process, gateway.configPayment gateway adapter. The entrypoint must also implement GatewayAdapterInterface. Hooks into the payment processing pipeline via GatewayBridge.
Capability::THEME"theme"theme.renderCheckout theme. Authorises overriding Twig checkout template paths and injecting CSS/JS into checkout pages.
Capability::ADDON"addon"(none — base platform access)General-purpose extension. The baseline capability for any plugin that does not process payments or replace checkout templates.
Capability::COMMUNICATION"communication"comm.sendSMS, email, or chat delivery. Authorises connecting to outbound messaging providers (Twilio, Vonage, SMTP, Telegram, etc.).
Capability::ANALYTICS"analytics"analytics.readReporting and BI. Grants read access to transactional datasets for rendering custom analytics dashboards.
Capability::WEBHOOK"webhook"(none)Custom inbound webhook handler. Allows the plugin to register listeners under the dynamic gateway.webhook.{slug} hook.
Capability::NOTIFICATION"notification"(none)Push and system notifications. Grants authority to post visual alert panels to the admin control panel.
Capability::EXPORT"export"(none)Data export. Grants permission to produce CSV, PDF, or Excel output from reporting datasets.
Capability::AUTHENTICATION"authentication"(none)SSO and OAuth. Authorises implementing external identity provider (OAuth2, SAML, LDAP) authentication layers.
Capability::STORAGE"storage"storage.read, storage.writeExternal file storage. Grants permission to integrate with block storage providers (S3, GCS, Cloudflare R2).
Capability::CRON"cron"(none)Scheduled background jobs. Required when declaring cron entries in manifest.json. The cron class must implement CronJobInterface.
Capability::DASHBOARD"dashboard"(none)Admin dashboard widgets. Authorises injecting HTML/JS panels into the dashboard via admin.dashboard.before and admin.dashboard.bottom hooks.
Capability::DB_READ"db_read"(none)Database read. Read-query permissions scoped exclusively to op_plugin_* prefixed tables. Queries to core op_* tables are blocked by PluginSandbox::validateSql().
Capability::DB_WRITE"db_write"(none)Database write. Insert/update/delete permissions scoped exclusively to op_plugin_* prefixed tables.
Capability::FILE_READ"file_read"(none)Filesystem read. Sandboxed to the plugin’s own directory via PluginSandbox::validateFilePath().
Capability::FILE_WRITE"file_write"(none)Filesystem write. Confined to the plugin’s storage/ subdirectory via PluginSandbox::storagePath().
Capability::HTTP_OUTBOUND"http_outbound"(none)Outbound HTTP. Declares that the plugin makes curl requests to remote APIs.
Capability::HOOKS"hooks"(none)Event hook subscriptions. Declares that the plugin subscribes to EventManager action and filter hooks.
Capability::CHECKOUT_UI"checkout_ui"(none)Checkout UI injection. Grants permission to filter Twig context variables and inject UI elements into the checkout via checkout.render, checkout.head, checkout.footer.

Capability-to-Permission Mapping

The Capability::requiredPermissions() method returns the RBAC permission keys required for each capability. These keys are matched against the permissions array in manifest.json and the platform’s role-permission system (op_roles, op_role_permissions):
public function requiredPermissions(): array
{
    return match ($this) {
        self::GATEWAY       => ['gateway.process', 'gateway.config'],
        self::THEME         => ['theme.render'],
        self::COMMUNICATION => ['comm.send'],
        self::ANALYTICS     => ['analytics.read'],
        self::STORAGE       => ['storage.read', 'storage.write'],
        default             => [],
    };
}
When a capability requires permission keys, declare them in manifest.json:
{
  "capabilities": ["gateway"],
  "permissions": [
    "gateway.process",
    "gateway.refund"
  ]
}

Common Capability Combinations

Most plugins fall into one of these well-understood archetypes. Use the combination that matches your plugin’s actual behavior.
"capabilities": ["gateway"]
The entrypoint must also implement GatewayAdapterInterface. PluginLoader auto-registers it with GatewayBridge after the boot phase.
public function capabilities(): array
{
    return [Capability::GATEWAY];
}

Checking Capabilities at Runtime

The PluginManifest class provides methods for introspecting declared capabilities:
// Check if a manifest declares a specific capability
$manifest->hasCapability(Capability::GATEWAY); // bool

// Get all declared capabilities as enum cases
$capabilities = $manifest->getCapabilities(); // Capability[]
The PluginSandbox class exposes the same check alongside its hard security enforcement methods:
// Check a declared capability string
$sandbox->hasCapability('db_write'); // bool

// Validate a file path is within the plugin's own directory
$sandbox->validateFilePath($path); // bool — uses realpath() containment check

// Validate a SQL query against the core table access policy
$sandbox->validateSql($sql); // bool — blocks DDL and core op_* table access

// Get the plugin's sandboxed storage directory
$storagePath = $sandbox->storagePath(); // string — {plugin_dir}/storage/

Security-Relevant Capability Notes

db_read / db_write — SQL Isolation

When a plugin’s hook callback modifies a database query through the db.query.before filter, EventManager::applyFilter() automatically invokes PluginSandbox::validateSql() on the resulting SQL. This check rejects:
  • DDL statements: DROP, TRUNCATE, ALTER, CREATE TABLE, CREATE DATABASE, CREATE USER
  • Privilege statements: GRANT, REVOKE
  • File operations: LOAD_FILE, INTO OUTFILE, INTO DUMPFILE
  • Access to any op_* table not prefixed with op_plugin_
A blocked query raises a RuntimeException that is re-thrown — unlike other hook errors which are swallowed, SQL sandbox violations halt the request immediately.
-- Allowed: plugin-owned table
SELECT * FROM op_plugin_my_addon_logs WHERE merchant_id = :mid;

-- Blocked: core OwnPay table
SELECT * FROM op_transactions WHERE merchant_id = :mid; -- RuntimeException

file_read / file_write — Path Containment

PluginSandbox::validateFilePath() uses realpath() to resolve the target path and verifies it begins with the plugin directory path followed by a directory separator. This prevents directory traversal attacks:
// Safe — inside plugin directory
$sandbox->validateFilePath('/app/modules/addons/my-addon/data/config.json'); // true

// Blocked — escapes plugin directory
$sandbox->validateFilePath('/app/modules/addons/my-addon/../other-plugin/secrets.php'); // false

Blocked PHP Functions

Regardless of capabilities, PluginLoader token-scans every PHP file before loading it. The following direct function calls cause an immediate load failure:
Blocked FunctionReason
evalDynamic code evaluation
exec()Direct OS command execution
shell_exec()Shell command via backtick-equivalent
system()OS command with direct output
passthru()Raw OS command execution
popen()Process pipe opener
proc_open()Full process control
pcntl_exec()Process image replacement
dl()Dynamic extension loading
assert()Code evaluation via assertion
create_function()Runtime lambda code generation
Only bare function invocations are blocked. Method calls ($obj->system()), static calls (MyClass::exec()), and function declarations (function exec() {}) are not flagged by the token scanner.

Principle of Least Privilege

Declare only the capabilities your plugin genuinely requires. This is both a security best practice and a marketplace submission requirement.

Under-declare

Missing a required capability causes PluginManifest::validate() to throw during load, or causes admin panel grouping to be incorrect.

Over-declare

Unnecessary capabilities trigger additional security review, mislead operators about what the plugin does, and may block marketplace approval.
Practical checklist:
  • Does your plugin call curl? → Add "http_outbound".
  • Does your plugin read from an op_plugin_* table? → Add "db_read".
  • Does your plugin write to an op_plugin_* table? → Add "db_write".
  • Does your plugin register addAction() or addFilter()? → Add "hooks".
  • Does your plugin declare a cron entry in manifest.json? → Add "cron".
  • Does your plugin send SMS, email, or chat messages? → Add "communication".
  • Does your plugin override checkout templates? → Use "theme" and "checkout_ui", not "addon".
  • Does your plugin integrate with S3 or another block storage service? → Add "storage".

Build docs developers (and LLMs) love