{"ast":{"$$mdtype":"Tag","name":"article","attributes":{},"children":[{"$$mdtype":"Tag","name":"Heading","attributes":{"level":1,"id":"decorators-in-plugins","__idx":0},"children":["Decorators in plugins"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Decorators transform API descriptions by adding, removing, or changing elements of the document. Before you build your own decorators:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/decorators"},"children":["Learn about Redocly decorators"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":[{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/decorators#list-of-decorators"},"children":["Check the list of built-in decorators"]},"."]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you can't find an existing decorator that fits your needs, you can add a decorator in a plugin."]},{"$$mdtype":"Tag","name":"Admonition","attributes":{"type":"warning","name":"Preprocessors"},"children":[{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Decorators and preprocessors are the same in structure, but preprocessors are run ",{"$$mdtype":"Tag","name":"em","attributes":{},"children":["before"]}," linting, and decorators are run after. We always recommend using decorators where possible, since the document might not be valid or structured as expected if the linting step hasn't run yet."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"plugin-structure","__idx":1},"children":["Plugin structure"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To create a preprocessor or decorator, the function that is exported from your module has to conform to an interface such as the following example:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"export default function myLocalPlugin() {\n  return {\n    id: 'my-local-plugin',\n    preprocessors: {\n      oas3: {\n        'processor-id': () => {\n          // ...\n        },\n      },\n    },\n    decorators: {\n      oas3: {\n        'decorator-id': () => {\n          // ...\n        },\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Each decorator or preprocessor is a function that returns an object. The object's keys are the node types in the document, and each of those can contain any or all of the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["enter()"]},", ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["leave()"]}," and ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["skip()"]}," functions for that node type. Find more information and examples on the ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/custom-plugins/visitor"},"children":["visitor pattern page"]},"."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To find the exact type of a place in your API description, either:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Run the ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/commands/inspect-node-types"},"children":[{"$$mdtype":"Tag","name":"code","attributes":{},"children":["inspect-node-types"]}," command"]}," with a pointer to that place."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Hover over it in the ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"https://redocly.com/docs/redocly-openapi/"},"children":["Redocly OpenAPI VS Code extension"]}," to see the same type hints."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"decorator-example","__idx":2},"children":["Decorator example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To give a small (but fun) example, here is a decorator that adds a sparkle emoji ✨ at the start of every operation description."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To help keep the plugin code organized, this example uses one file per decorator. In this example, this is the file ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugins/decorators/operation-sparkle.js"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"export default function OperationSparkle() {\n  console.log('adding sparkles ... ');\n  return {\n    Operation: {\n      leave(target) {\n        if (target.description) {\n          target.description = '✨ ' + String(target.description);\n        }\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Decorators use the ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/custom-plugins/visitor"},"children":["visitor pattern"]}," to run an operation on every node in the document. In this example, when the code executes the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["leave()"]}," function on the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["Operation"]}," node, it checks if the node (passed as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["target"]}," in this example) has a description, and updates it if it does."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["To use this decorator, add it to a plugin. In this example the main decorator file is ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugins/sparkle.js"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"import OperationSparkle from './decorators/operation-sparkle.js';\n\nexport default function sparklePlugin() {\n  return {\n    id: 'sparkle',\n    decorators: {\n      oas3: {\n        'operation-sparkle': OperationSparkle,\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The plugin is good to go. For a user to include it in their Redocly configuration, edit the configuration file to look something like this:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"plugins:\n  - plugins/sparkle.js\n\ndecorators:\n  sparkle/operation-sparkle: on\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"decorator-example-with-parameters","__idx":3},"children":["Decorator example with parameters"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["A common use case is a decorator that can accept input values to be used during processing. This example decorator adds a suffix to all OperationIds in the document. Since every use case is different, the user can configure what should be used for the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["suffix"]}," value."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Here's the decorator code, in a file named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugins/decorations/add-suffix.js"]}," and it expects a configuration option named ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["suffix"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"export default function OpIdSuffix({ suffix }) {\n  console.log('updating OperationIds ... ');\n  return {\n    Operation: {\n      leave(target) {\n        if (target.operationId) {\n          target.operationId = target.operationId + suffix;\n        }\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["suffix"]}," configuration option is automatically passed in, and it can be used in the function."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Now extend the decorator from the previous example to add this to the existing plugin in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugins/sparkle.js"]},":"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"import OpIdSuffix from './decorators/add-suffix.js';\nimport OperationSparkle from './decorators/operation-sparkle.js';\n\nexport default function sparklePlugin() {\n  return {\n    id: 'sparkle',\n    decorators: {\n      oas3: {\n        'operation-sparkle': OperationSparkle,\n        'add-opid-suffix': OpIdSuffix,\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["All that remains is for a user to configure this decorator in their ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," configuration file to take advantage of the new decorator functionality. Here's an example of the configuration file:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"plugins:\n  - plugins/sparkle.js\n\ndecorators:\n  sparkle/operation-sparkle: on\n  sparkle/add-opid-suffix:\n    suffix: ButShinier\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["With this configuration, an ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["operationId"]}," called ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GetAllItems"]}," would be rewritten as ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["GetAllItemsButShinier"]},". You can choose a more sensible suffix for your use case as appropriate."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"further-examples-of-custom-decorators","__idx":4},"children":["Further examples of custom decorators"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["See some more examples of decorators:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["There's a ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"https://github.com/Redocly/redocly-cli-cookbook"},"children":["Redocly CLI cookbook"]}," containing many more examples and ready-to-use scripts from our community."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Follow our ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/guides/replace-servers-url"},"children":["replace-servers-url tutorial"]},"."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["Change your ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/guides/change-token-url"},"children":["OAuth2 token URL"]},"."]}]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"decorator-execution-order","__idx":5},"children":["Decorator execution order"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The order in which decorators are executed is important and can affect the final output of your API description."," ","Here are the key points to understand:"]},{"$$mdtype":"Tag","name":"ul","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["For each decorator, the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["enter"]}," function is always executed before the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["leave"]}," function."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The order of decorator execution is determined by:",{"$$mdtype":"Tag","name":"ol","attributes":{},"children":[{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The order of plugins as listed in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugins"]}," array in ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," configuration file."]},{"$$mdtype":"Tag","name":"li","attributes":{},"children":["The order of decorators as defined in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["decorators"]}," object of each plugin."]}]}]}]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["Note that the built-in decorators are considered to be part of a special default plugin which is always executed last."]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The order in the ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["decorators"]}," section of ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," ",{"$$mdtype":"Tag","name":"strong","attributes":{},"children":["DOES NOT"]}," affect the order in which the decorators are executed."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":3,"id":"example","__idx":6},"children":["Example"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["If you have two plugins defined as follows:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"// plugins/plugin1.js\nexport default function plugin1() {\n  return {\n    id: 'plugin1',\n    decorators: {\n      oas3: {\n        decoratorB,\n        decoratorA,\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"js","header":{"controls":{"copy":{}}},"source":"// plugins/plugin2.js\nexport default function plugin2() {\n  return {\n    id: 'plugin2',\n    decorators: {\n      oas3: {\n        decoratorC,\n      },\n    },\n  };\n}\n","lang":"js"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["And your ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["redocly.yaml"]}," has this configuration:"]},{"$$mdtype":"Tag","name":"CodeBlock","attributes":{"data-language":"yaml","header":{"controls":{"copy":{}}},"source":"plugins:\n  - plugins/plugin2.js\n  - plugins/plugin1.js\n\ndecorators:\n  plugin1/decoratorA: on\n  plugin1/decoratorB: on\n  plugin2/decoratorC: on\n","lang":"yaml"},"children":[]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["The execution order in this case is: ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugin2/decoratorC"]}," -> ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugin1/decoratorB"]}," -> ",{"$$mdtype":"Tag","name":"code","attributes":{},"children":["plugin1/decoratorA"]},"."]},{"$$mdtype":"Tag","name":"Heading","attributes":{"level":2,"id":"preprocessors","__idx":7},"children":["Preprocessors"]},{"$$mdtype":"Tag","name":"p","attributes":{},"children":["For detailed information about preprocessors, see the ",{"$$mdtype":"Tag","name":"Link","attributes":{"href":"/docs/cli/v2/configuration/reference/preprocessors"},"children":["preprocessors configuration reference"]},"."]}]},"frontmatter":{},"tagList":["admonition"],"title":"Decorators in plugins","lastModified":"2026-10-06T13:52:58.000Z"}