The plugin descriptor schema defines a plugin.yaml file that lives in the root of each CRS plugin repository. The file must be named plugin.yaml and must sit at the repository root: tooling locates it by appending that path to the repository URL, so any other name or location makes the plugin undiscoverable. It provides machine-readable metadata about the plugin, its configuration variables, and compatibility requirements.
The registry table itself is generated from registry.yaml, not from these descriptors — see registry-schema.md for why the two files have different owners. Downstream tooling (such as a CRS configurator) parses plugin.yaml to build preconfigured CRS deployments based on plugin selection.
- Allow each plugin repository to be the single source of truth for its own metadata.
- Enable automated tooling to discover, validate, and configure plugins.
- Provide enough information for a configurator to present a UI for plugin selection and variable tuning.
The schema is defined as JSON Schema (2020-12) in plugin-schema.json. A plugin.yaml file has the following top-level sections:
Required. Integer, currently fixed at 1.
Allows future evolution of the schema without breaking existing parsers. Tooling should check this field and handle unknown versions gracefully.
Required. Plugin identity and metadata.
| Field | Required | Description |
|---|---|---|
name |
yes | Plugin name following the CRS convention: <name>-plugin. Validated by regex. |
description |
yes | One-line summary of the plugin's purpose. |
long_description |
no | Multi-line extended description for documentation and UI display. |
type |
yes | official (coreruleset-maintained) or 3rd-party. |
category |
no | Functional category: rule-exclusion, detection, protection, utility, logging, or performance. |
status |
yes | Maturity level: tested, being-tested, untested, or draft. |
license |
yes | SPDX license identifier (e.g., Apache-2.0, GPL-2.0-only). |
authors |
yes | List of author objects with name (required), email and url (optional). |
repository |
yes | URL of the plugin GitHub repository (only GitHub repositories are currently supported). |
homepage |
no | URL to documentation or project site. |
keywords |
no | YAML list of lowercase tags for discovery and categorization (e.g., - wordpress). |
Required. The registered rule ID block from the plugin registry.
| Field | Description |
|---|---|
start |
First rule ID in the allocated range (integer, 9500000-9999999). |
end |
Last rule ID in the allocated range (integer, 9500000-9999999). |
Plugins typically receive a 1,000-ID range. The schema validates that values fall within the CRS plugin namespace (9,500,000 - 9,999,999).
Optional. WAF engine and CRS version requirements.
| Field | Description |
|---|---|
crs_version |
Version constraint string (e.g., >=4.0.0). |
engines |
List of compatible WAF engines, one of: modsecurity2, modsecurity3, coraza, all. all is exclusive: when used it must be the only entry. |
When omitted, no compatibility constraints are assumed. Tooling should treat missing engines field as if engines were set to all.
Optional. List of other CRS plugins this plugin depends on.
Each entry has:
| Field | Required | Description |
|---|---|---|
name |
yes | Name of the required plugin. |
version |
no | Version constraint for the dependency. |
Most plugins have no dependencies. This field exists for plugins that build on top of other plugins.
Required. The key section for automated tooling. Describes the config file and all user-tunable variables.
| Field | Description |
|---|---|
file |
Path to the -config.conf file relative to the plugin root. |
variables |
YAML sequence of transaction variable definitions (see Variable Definition below). |
Each entry in variables describes a single tx.* variable from the config file:
| Field | Required | Description |
|---|---|---|
name |
yes | Full ModSecurity variable name (e.g., tx.myplugin_enabled). |
type |
yes | Data type: boolean, integer, string, list, indexed, or enum. |
default |
no | Default value if not set by the user. |
description |
yes | Human-readable explanation of the variable. |
required |
no | Whether the user must explicitly set this variable (default: false). |
allowed_values |
yes (for enum) |
List of valid values when type is enum. |
separator |
yes (for list) |
String used to separate list items when type is list. |
prefix |
no | String prepended to each list item when type is list (e.g., | in |a/ |b/). |
suffix |
no | String appended to each list item when type is list (e.g., / in |a/ |b/). |
example |
no | Example value for documentation. |
min |
no | Minimum value when type is integer. |
max |
no | Maximum value when type is integer. |
max_index |
yes (for indexed) |
Highest numeric suffix the plugin reads when type is indexed. |
The six types cover all patterns found across existing CRS plugins:
boolean— Enable/disable flags. Every plugin has at leasttx.<name>_enabled. Values are integers:0(disabled) or1(enabled), following the ModSecurity convention for boolean flags.integer— Numeric thresholds and limits (e.g.,tx.body-decompress-plugin_max_data_size_bytes). Supportsmin/maxconstraints.string— Freeform text values. Theexamplefield helps users understand the expected format.list— Structured list of strings (e.g.,tx.google-oauth2-plugin_whitelisted_parameters). The fieldsseparator,prefix,suffixdefine the format. For example, the list|a/ |b/ |c/would be defined as follows:separator:" "(space character)prefix:"|"suffix:"/"
indexed— A numbered series of sibling variables that the plugin reads until it finds an unset one, up to a fixed bound (e.g.,tx.false-positive-report-plugin_smtp_cc_1through_5). Unlikelist, each entry is its owntx.*variable rather than one delimited value. Thenamefield holds the base name without the numeric suffix andmax_indexgives the upper bound, so tooling emits<name>_1…<name>_<n>for as many entries as the user supplies:name:tx.false-positive-report-plugin_smtp_ccmax_index:5
enum— Constrained choices (e.g.,tx.phpmyadmin-rule-exclusions-plugin_url_formatwith valuesv51,v52). Must includeallowed_values.
YAML supports comments, multi-line strings, and is widely used in the CRS ecosystem (test files use FTW YAML format). Plugin authors already work with YAML for regression tests, so it is familiar.
Embedding versioning in the schema itself allows parsers to detect incompatible changes before attempting to parse. This is simpler than relying on file naming or external version tracking.
A configurator project needs to know more than just variable names and defaults. By declaring types, allowed values, and numeric bounds, tooling can:
- Render appropriate input widgets (toggle for boolean, slider for bounded integer, dropdown for enum).
- Validate user input before generating configuration.
- Generate documentation automatically.
Every CRS plugin has at least one configuration variable (tx.<name>_enabled). Making this section required ensures consistency and guarantees that tooling always has something to work with, even for minimal rule-exclusion plugins.
The plugin version is derived from GitHub release tags at query time. Embedding a version in plugin.yaml would inevitably drift because developers forget to bump it. Tooling should fetch the latest release tag from the repository URL instead. This keeps plugin.yaml as a static descriptor that rarely needs updating.
The rule ID range is a registry-level concern (namespace coordination), not a plugin identity attribute. Keeping it separate makes it clear that this range is allocated by the registry and should not be changed unilaterally.
The six categories cover the existing plugin landscape:
| Category | Examples |
|---|---|
rule-exclusion |
wordpress, drupal, nextcloud, phpbb, cpanel |
detection |
fake-bot |
protection |
dos-protection |
utility |
template, body-decompress, auto-decoding |
logging |
database-logging, false-positive-report |
performance |
performance-plugin |
New categories can be added to the schema's enum as the ecosystem grows.
The supported WAF engines are:
| Engine | Description |
|---|---|
modsecurity2 |
ModSecurity 2.x (Apache module) |
modsecurity3 |
ModSecurity 3.x (connector-based, e.g. nginx) |
coraza |
Coraza WAF |
all |
Compatible with all supported engines |
New engines can be added to the schema's enum as the ecosystem grows.
The primary consumer of plugin.yaml files is a configurator tool that:
- Fetches
plugin.yamlfrom each registered plugin repository. - Presents available plugins grouped by category, with descriptions and compatibility info.
- Collects user choices: which plugins to enable and how to tune their variables.
- Validates input against variable constraints (type, min/max, allowed_values).
- Resolves dependencies between plugins.
- Generates a complete CRS configuration with all selected plugins and their tuned
tx.*variables.
- Add
plugin.yamltocoreruleset/template-pluginas the reference implementation. - Get feedback from the CRS team and iterate on the schema.
- Once the schema is accepted, create PRs adding
plugin.yamlto all registered plugins. - Update the registry to validate incoming plugin registrations against the schema.
- Build the configurator project.
Plugin authors can check a descriptor against the schema before opening a PR:
uvx check-jsonschema --schemafile plugin-schema.json plugin.yamlCI runs the same check over examples/, plugin.yaml.template, and the fixtures in
tests/valid/, and asserts that every fixture in tests/invalid/ is rejected.