| <!doctype html> |
| <html class="docs-version-3.14" lang="en" dir="ltr"> |
| <head> |
| <meta charset="UTF-8"> |
| <meta name="viewport" content="width=device-width,initial-scale=1"> |
| <meta name="ahrefs-site-verification" content="c2f7370ecf46173f4fb25f114e74c97e0a2976d4f02f61c9b00a9d7d34e34698"> |
| <meta name="generator" content="Docusaurus v2.0.0-beta.6"> |
| <link rel="search" type="application/opensearchdescription+xml" title="Apache APISIX® -- Cloud-Native API Gateway and AI Gateway" href="/opensearch.xml"> |
| <script type="application/ld+json">{"@context":"https://schema.org","@type":"WebSite","name":"Apache APISIX","url":"https://apisix.apache.org"}</script> |
| <script src="https://widget.kapa.ai/kapa-widget.bundle.js" data-website-id="24b59d9a-682e-4c3d-9e83-bf2ee85cdc19" data-project-name="APISIX" data-project-color="#E8442E" data-project-logo="https://static.apiseven.com/202202/apache-apisix.png" data-modal-disclaimer="This is a custom LLM for APISIX with access to all developer documentation, GitHub issues and discussions." data-modal-example-questions="How to set up canary release in APISIX?,How to develop a custom APISIX plugin?,How to use custom NGINX configuration in APISIX?,How to configure mTLS between clients and APISIX?,How to only allow a specific APISIX consumer to access special services or routes?" async></script><title data-react-helmet="true">Plugin Develop | Apache APISIX® -- Cloud-Native API Gateway and AI Gateway</title><meta data-react-helmet="true" property="og:image" content="https://static.apiseven.com/202202/apache-apisix.png"><meta data-react-helmet="true" name="twitter:image" content="https://static.apiseven.com/202202/apache-apisix.png"><meta data-react-helmet="true" property="og:url" content="https://apisix.apache.org/docs/apisix/plugin-develop/"><meta data-react-helmet="true" name="docsearch:language" content="en"><meta data-react-helmet="true" name="docsearch:version" content="3.14"><meta data-react-helmet="true" name="docsearch:docusaurus_tag" content="docs-docs-apisix-3.14"><meta data-react-helmet="true" name="robots" content="index,follow"><meta data-react-helmet="true" name="twitter:card" content="summary"><meta data-react-helmet="true" property="og:title" content="Plugin Develop | Apache APISIX® -- Cloud-Native API Gateway and AI Gateway"><meta data-react-helmet="true" name="description" content="This documentation is about developing plugin in Lua. For other languages, |
| see external plugin."><meta data-react-helmet="true" property="og:description" content="This documentation is about developing plugin in Lua. For other languages, |
| see external plugin."><link data-react-helmet="true" rel="shortcut icon" href="https://static.apiseven.com/202202/favicon.png"><link data-react-helmet="true" rel="canonical" href="https://apisix.apache.org/docs/apisix/plugin-develop/"><link data-react-helmet="true" rel="alternate" href="https://apisix.apache.org/docs/apisix/plugin-develop/" hreflang="en"><link data-react-helmet="true" rel="alternate" href="https://apisix.apache.org/zh/docs/apisix/plugin-develop/" hreflang="zh"><link data-react-helmet="true" rel="alternate" href="https://apisix.apache.org/docs/apisix/plugin-develop/" hreflang="x-default"><link data-react-helmet="true" rel="preconnect" href="https://38VC84A2WJ-dsn.algolia.net" crossorigin="anonymous"><link rel="preload" href="https://static.apiseven.com/202202/MaisonNeue-Medium.otf" as="font" type="font/otf" crossorigin> |
| <link rel="preload" href="https://static.apiseven.com/202202/MaisonNeue-Bold.otf" as="font" type="font/otf" crossorigin> |
| <link rel="preload" href="https://static.apiseven.com/202202/MaisonNeue-Light.otf" as="font" type="font/otf" crossorigin> |
| <link rel="preload" href="https://static.apiseven.com/202202/MaisonNeue-Demi.otf" as="font" type="font/otf" crossorigin> |
| <link rel="preload" href="https://static.apiseven.com/202202/MaisonNeue-ExtraBold.otf" as="font" type="font/otf" crossorigin> |
| <link rel="preload" href="https://apisix-website-static.apiseven.com/assets/js/runtime~main.6f5566cc.js" as="script"> |
| <link rel="preload" href="https://apisix-website-static.apiseven.com/assets/js/main.2626d18c.js" as="script"> |
| <link rel="stylesheet" href="https://apisix-website-static.apiseven.com/assets/css/styles.8de0825e.css"> |
| |
| <script>var _paq=window._paq=window._paq||[];_paq.push(["disableCookies"]),_paq.push(["trackPageView"]),_paq.push(["enableLinkTracking"]),function(){var a="https://analytics.apache.org/";_paq.push(["setTrackerUrl",a+"matomo.php"]),_paq.push(["setSiteId","17"]);var e=document,p=e.createElement("script"),t=e.getElementsByTagName("script")[0];p.async=!0,p.src=a+"matomo.js",t.parentNode.insertBefore(p,t)}()</script> |
| |
| </head> |
| <body> |
| <script>!function(){function t(t){document.documentElement.setAttribute("data-theme",t)}var e=function(){var t=null;try{t=localStorage.getItem("theme")}catch(t){}return t}();t(null!==e?e:"light")}(),document.documentElement.setAttribute("data-announcement-bar-initially-dismissed",function(){try{return"true"===localStorage.getItem("docusaurus.announcement.dismiss")}catch(t){}return!1}())</script><div id="__docusaurus"> |
| <div><a href="#" class="skipToContent_OuoZ">Skip to main content</a></div><div class="announcementBar_axC9" style="background-color:#e8433e;color:white" role="banner"><div class="announcementBarPlaceholder_xYHE"></div><div class="announcementBarContent_6uhP">🤔 Introducing APISIX AI Gateway – Built for LLMs and AI workloads. <a target="_blank" rel="noopener noreferrer" href="/ai-gateway/"> Learn More</a></div><button type="button" class="clean-btn close announcementBarClose_A3A1" aria-label="Close"><svg viewBox="0 0 24 24" width="14" height="14" fill="currentColor"><path d="M24 20.188l-8.315-8.209 8.2-8.282-3.697-3.697-8.212 8.318-8.31-8.203-3.666 3.666 8.321 8.24-8.206 8.313 3.666 3.666 8.237-8.318 8.285 8.203z"></path></svg></button></div><nav class="navbar navbar--fixed-top navbarHideable_RReh"><div class="navbar__inner"><div class="navbar__items"><button aria-label="Navigation bar toggle" class="navbar__toggle clean-btn" type="button" tabindex="0"><svg width="30" height="30" viewBox="0 0 30 30" aria-hidden="true"><path stroke="currentColor" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2" d="M4 7h22M4 15h22M4 23h22"></path></svg></button><a target="_parent" class="navbar__brand" href="/"><img src="/img/logo2.svg" alt="Apache APISIX®" class="themedImage_TMUO themedImage--light_4Vu1 navbar__logo"><img src="/img/logo2.svg" alt="Apache APISIX®" class="themedImage_TMUO themedImage--dark_uzRr navbar__logo"><b class="navbar__title">Apache APISIX®</b></a></div><div class="navbar__items navbar__items--right"><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a aria-current="page" class="navbar__link" target="_parent" href="/docs/">Docs</a><ul class="dropdown__menu"><li><a class="dropdown__link" target="_parent" href="/docs/apisix/getting-started/">Apache APISIX®️</a></li><li><a class="dropdown__link" target="_parent" href="/docs/apisix/next/dashboard/">Apache APISIX®️ Dashboard</a></li><li><a class="dropdown__link" target="_parent" href="/docs/ingress-controller/overview/">Apache APISIX®️ Ingress Controller</a></li><li><a class="dropdown__link" target="_parent" href="/docs/helm-chart/apisix/">Apache APISIX®️ Helm Charts</a></li><li><a class="dropdown__link" target="_parent" href="/docs/docker/build/">Apache APISIX®️ Docker</a></li><li><a class="dropdown__link" target="_parent" href="/docs/java-plugin-runner/development/">Apache APISIX®️ Java Plugin Runner</a></li><li><a class="dropdown__link" target="_parent" href="/docs/go-plugin-runner/getting-started/">Apache APISIX®️ Go Plugin Runner</a></li><li><a class="dropdown__link" target="_parent" href="/docs/python-plugin-runner/getting-started/">Apache APISIX®️ Python Plugin Runner</a></li><li><a class="dropdown__link" target="_parent" href="/docs/general/join/">General</a></li></ul></div><a class="navbar__item navbar__link" target="_parent" href="/blog/">Blog</a><a class="navbar__item navbar__link" target="_parent" href="/blog/tags/case-studies/">Case Studies</a><a class="navbar__item navbar__link" target="_parent" href="/downloads/">Downloads</a><a class="navbar__item navbar__link" target="_parent" href="/help/">Help</a><a class="navbar__item navbar__link" target="_parent" href="/team/">Team</a><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a class="navbar__link">Resources</a><ul class="dropdown__menu"><li><a class="dropdown__link" target="_parent" href="/showcase/">Showcase</a></li><li><a class="dropdown__link" target="_parent" href="/docs/general/code-samples/">Code Samples</a></li><li><a class="dropdown__link" target="_parent" href="/plugins/">PluginHub</a></li><li><a class="dropdown__link" target="_parent" href="/docs/general/join/">Community</a></li><li><a class="dropdown__link" target="_parent" href="/docs/general/events/">Events</a></li><li><a href="https://github.com/apache/apisix/milestones" target="_parent" rel="noopener noreferrer" class="dropdown__link">Roadmap</a></li></ul></div><div class="navbar__item dropdown dropdown--hoverable dropdown--right"><a href="#" class="navbar__link"><span><svg viewBox="0 0 20 20" width="20" height="20" aria-hidden="true" class="iconLanguage_zID8"><path fill="currentColor" d="M19.753 10.909c-.624-1.707-2.366-2.726-4.661-2.726-.09 0-.176.002-.262.006l-.016-2.063 3.525-.607c.115-.019.133-.119.109-.231-.023-.111-.167-.883-.188-.976-.027-.131-.102-.127-.207-.109-.104.018-3.25.461-3.25.461l-.013-2.078c-.001-.125-.069-.158-.194-.156l-1.025.016c-.105.002-.164.049-.162.148l.033 2.307s-3.061.527-3.144.543c-.084.014-.17.053-.151.143.019.09.19 1.094.208 1.172.018.08.072.129.188.107l2.924-.504.035 2.018c-1.077.281-1.801.824-2.256 1.303-.768.807-1.207 1.887-1.207 2.963 0 1.586.971 2.529 2.328 2.695 3.162.387 5.119-3.06 5.769-4.715 1.097 1.506.256 4.354-2.094 5.98-.043.029-.098.129-.033.207l.619.756c.08.096.206.059.256.023 2.51-1.73 3.661-4.515 2.869-6.683zm-7.386 3.188c-.966-.121-.944-.914-.944-1.453 0-.773.327-1.58.876-2.156a3.21 3.21 0 011.229-.799l.082 4.277a2.773 2.773 0 01-1.243.131zm2.427-.553l.046-4.109c.084-.004.166-.01.252-.01.773 0 1.494.145 1.885.361.391.217-1.023 2.713-2.183 3.758zm-8.95-7.668a.196.196 0 00-.196-.145h-1.95a.194.194 0 00-.194.144L.008 16.916c-.017.051-.011.076.062.076h1.733c.075 0 .099-.023.114-.072l1.008-3.318h3.496l1.008 3.318c.016.049.039.072.113.072h1.734c.072 0 .078-.025.062-.076-.014-.05-3.083-9.741-3.494-11.04zm-2.618 6.318l1.447-5.25 1.447 5.25H3.226z"></path></svg><span>English</span></span></a><ul class="dropdown__menu"><li><a href="/docs/apisix/plugin-develop/" target="_self" rel="noopener noreferrer" class="dropdown__link dropdown__link--active" style="text-transform:capitalize">English</a></li><li><a href="/zh/docs/apisix/plugin-develop/" target="_self" rel="noopener noreferrer" class="dropdown__link" style="text-transform:capitalize">简体中文</a></li></ul></div><div class="react-toggle toggle_2i4l react-toggle--disabled"><div class="react-toggle-track" role="button" tabindex="-1"><div class="react-toggle-track-check"><span class="toggle_iYfV">🌜</span></div><div class="react-toggle-track-x"><span class="toggle_iYfV">🌞</span></div><div class="react-toggle-thumb"></div></div><input type="checkbox" class="react-toggle-screenreader-only" aria-label="Switch between dark and light mode"></div><div class="searchBox_fBfG"><button type="button" class="DocSearch DocSearch-Button" aria-label="Search"><span class="DocSearch-Button-Container"><svg width="20" height="20" class="DocSearch-Search-Icon" viewBox="0 0 20 20"><path d="M14.386 14.386l4.0877 4.0877-4.0877-4.0877c-2.9418 2.9419-7.7115 2.9419-10.6533 0-2.9419-2.9418-2.9419-7.7115 0-10.6533 2.9418-2.9419 7.7115-2.9419 10.6533 0 2.9419 2.9418 2.9419 7.7115 0 10.6533z" stroke="currentColor" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round"></path></svg><span class="DocSearch-Button-Placeholder">Search</span></span><span class="DocSearch-Button-Keys"></span></button></div></div></div><div role="presentation" class="navbar-sidebar__backdrop"></div></nav><div class="main-wrapper docs-wrapper docs-doc-page"><div class="docPage_GMj9"><button class="clean-btn backToTopButton_i9tI" type="button"><svg viewBox="0 0 24 24" width="28"><path d="M7.41 15.41L12 10.83l4.59 4.58L18 14l-6-6-6 6z" fill="currentColor"></path></svg></button><aside class="docSidebarContainer_k0Pq"><div class="sidebar_LIo8 sidebarWithHideableNavbar_CMI-"><a target="_parent" tabindex="-1" class="sidebarLogo_P87M" href="/"><img src="/img/logo2.svg" alt="Apache APISIX®" class="themedImage_TMUO themedImage--light_4Vu1"><img src="/img/logo2.svg" alt="Apache APISIX®" class="themedImage_TMUO themedImage--dark_uzRr"><b>Apache APISIX®</b></a><div class="sidebarVersionSwitch_0QIZ">Version:<div class="navbar__item dropdown dropdown--hoverable"><a class="navbar__link" href="/docs/apisix/getting-started/README/">3.14</a><ul class="dropdown__menu"><li><a class="dropdown__link" href="/docs/apisix/next/plugin-develop/"><div>Next</div></a></li><li><a aria-current="page" class="dropdown__link dropdown__link--active" href="/docs/apisix/plugin-develop/"><div>3.14<div class="badge_6FVu Latest_oyqS">Latest</div></div></a></li><li><a class="dropdown__link" href="/docs/apisix/3.13/plugin-develop/"><div>3.13</div></a></li><li><a class="dropdown__link" href="/docs/apisix/3.12/plugin-develop/"><div>3.12</div></a></li><li><a class="dropdown__link" href="/docs/apisix/3.11/plugin-develop/"><div>3.11</div></a></li><li><a class="dropdown__link" href="/docs/apisix/3.10/plugin-develop/"><div>3.10</div></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.9/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.9<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.8/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.8<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.7/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.7<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.6/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.6<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.5/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.5<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.4/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.4<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.3/getting-started/readme/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.3<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.2/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.2<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.1/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.1<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/3.0/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>3.0<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.15/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.15<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.14/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.14<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.13/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.13<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.12/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.12<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.11/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.11<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.10/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.10<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.9/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.9<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.8/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.8<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.7/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.7<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.6/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.6<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.5/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.5<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li><a href="https://apache-apisix.netlify.app/docs/apisix/2.4/getting-started/" target="_blank" rel="noopener noreferrer" class="dropdown__link"><span>2.4<svg width="12" height="12" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li></ul></div></div><nav class="menu thin-scrollbar menu_oAhv menuWithAnnouncementBar_IVfW"><ul class="theme-doc-sidebar-menu menu__list"><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">Getting Started</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/installation-guide/">Installation</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/architecture-design/apisix/">Architecture</a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">Tutorials</a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">Terminology</a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">Plugins</a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">API</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/dashboard/">Apache APISIX Dashboard</a></li><li class="theme-doc-sidebar-item-category menu__list-item"><a class="menu__link menu__link--sublist menu__link--active" href="#">Development</a><ul style="display:block;overflow:visible;height:auto" class="menu__list"><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/build-apisix-dev-environment-devcontainers/">Build development environment with Dev Containers</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/building-apisix/">Building APISIX from source</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/build-apisix-dev-environment-on-mac/">Build development environment on Mac</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/support-fips-in-apisix/">Support FIPS in APISIX</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/external-plugin/">External Plugin</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/wasm/">Wasm</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a href="https://github.com/apache/apisix/blob/master/CODE_STYLE.md" target="_blank" rel="noopener noreferrer" class="menu__link" tabindex="0"><span>CODE_STYLE<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#" tabindex="0">internal</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link menu__link--active" aria-current="page" tabindex="0" href="/docs/apisix/plugin-develop/">Plugin Develop</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" tabindex="0" href="/docs/apisix/debug-mode/">Debug mode</a></li></ul></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/deployment-modes/">Deployment modes</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/FAQ/">FAQ</a></li><li class="theme-doc-sidebar-item-category menu__list-item menu__list-item--collapsed"><a class="menu__link menu__link--sublist" href="#">Others</a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a href="https://github.com/apache/apisix/blob/master/CHANGELOG.md" target="_blank" rel="noopener noreferrer" class="menu__link"><span>CHANGELOG<svg width="13.5" height="13.5" aria-hidden="true" viewBox="0 0 24 24" class="iconExternalLink_wgqa"><path fill="currentColor" d="M21 13v10h-21v-19h12v2h-10v15h17v-8h2zm3-12h-10.988l4.035 4-6.977 7.07 2.828 2.828 6.977-7.07 4.125 4.172v-11z"></path></svg></span></a></li><li class="theme-doc-sidebar-item-link menu__list-item"><a class="menu__link" href="/docs/apisix/upgrade-guide-from-2.15.x-to-3.0.0/">Upgrade Guide</a></li></ul></nav><button type="button" title="Collapse sidebar" aria-label="Collapse sidebar" class="button button--secondary button--outline collapseSidebarButton_EBxv"><svg width="20" height="20" aria-hidden="true" class="collapseSidebarButtonIcon_AF9Q"><g fill="#7a7a7a"><path d="M9.992 10.023c0 .2-.062.399-.172.547l-4.996 7.492a.982.982 0 01-.828.454H1c-.55 0-1-.453-1-1 0-.2.059-.403.168-.551l4.629-6.942L.168 3.078A.939.939 0 010 2.528c0-.548.45-.997 1-.997h2.996c.352 0 .649.18.828.45L9.82 9.472c.11.148.172.347.172.55zm0 0"></path><path d="M19.98 10.023c0 .2-.058.399-.168.547l-4.996 7.492a.987.987 0 01-.828.454h-3c-.547 0-.996-.453-.996-1 0-.2.059-.403.168-.551l4.625-6.942-4.625-6.945a.939.939 0 01-.168-.55 1 1 0 01.996-.997h3c.348 0 .649.18.828.45l4.996 7.492c.11.148.168.347.168.55zm0 0"></path></g></svg></button></div></aside><main class="docMainContainer_Q970"><div class="container padding-top--md padding-bottom--lg"><div class="row"><div class="col docItemCol_zHA2"><div class="docItemContainer_oiyr"><article><span class="theme-doc-version-badge badge badge--secondary">Version: 3.14</span><div class="tocCollapsible_aw-L theme-doc-toc-mobile tocMobile_Tx6Y"><button type="button" class="clean-btn tocCollapsibleButton_zr6a">On this page</button></div><div class="theme-doc-markdown markdown"><header><h1>Plugin Develop</h1></header><p>This documentation is about developing plugin in Lua. For other languages, |
| see <a href="/docs/apisix/external-plugin/">external plugin</a>.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="where-to-put-your-plugins"></a>Where to put your plugins<a class="hash-link" href="#where-to-put-your-plugins" title="Direct link to heading">#</a></h2><p>Use the <code>extra_lua_path</code> parameter in <code>conf/config.yaml</code> file to load your custom plugin code (or use <code>extra_lua_cpath</code> for compiled <code>.so</code> or <code>.dll</code> file).</p><p>For example, you can create a directory <code>/path/to/example</code>:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 yaml"><pre tabindex="0" class="prism-code language-yaml codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apisix</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">extra_lua_path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/example/?.lua"</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>The structure of the <code>example</code> directory should look like this:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 bash"><pre tabindex="0" class="prism-code language-bash codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">├── example</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ └── apisix</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ ├── plugins</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ │ └── 3rd-party.lua</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ └── stream</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ └── plugins</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">│ └── 3rd-party.lua</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><div class="admonition admonition-note alert alert--secondary"><div class="admonition-heading"><h5><span class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</h5></div><div class="admonition-content"><p>The directory (<code>/path/to/example</code>) must contain the <code>/apisix/plugins</code> subdirectory.</p></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="enable-the-plugin"></a>Enable the plugin<a class="hash-link" href="#enable-the-plugin" title="Direct link to heading">#</a></h2><p>To enable your custom plugin, add the plugin list to <code>conf/config.yaml</code> and append your plugin name. For instance:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 yaml"><pre tabindex="0" class="prism-code language-yaml codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">plugins</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># See `conf/config.yaml.example` for an example</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">...</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Add existing plugins</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> your</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">plugin </span><span class="token comment" style="color:#999988;font-style:italic"># Add your custom plugin name (name is the plugin name defined in the code)</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><div class="admonition admonition-warning alert alert--danger"><div class="admonition-heading"><h5><span class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" width="12" height="16" viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>warning</h5></div><div class="admonition-content"><p>In particular, most APISIX plugins are enabled by default when the plugins field configuration is not defined (The default enabled plugins can be found in <a href="https://github.com/apache/apisix/blob/master/apisix/cli/config.lua" rel="noopener noreferrer">apisix/cli/config.lua</a>).</p><p>Once the plugins configuration is defined in <code>conf/config.yaml</code>, the new plugins list will replace the default configuration instead of merging. Therefore, when defining the <code>plugins</code> field, make sure to include the built-in plugins that are being used. To maintain consistency with the default behavior, you can include all the default enabled plugins defined in <code>apisix/cli/config.lua</code>.</p></div></div><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="writing-plugins"></a>Writing plugins<a class="hash-link" href="#writing-plugins" title="Direct link to heading">#</a></h2><p>The <a href="https://github.com/apache/apisix/blob/master/apisix/plugins/example-plugin.lua" rel="noopener noreferrer"><code>example-plugin</code></a> plugin in this repo provides an example.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="naming-and-priority"></a>Naming and priority<a class="hash-link" href="#naming-and-priority" title="Direct link to heading">#</a></h3><p>Specify the plugin name (the name is the unique identifier of the plugin and cannot be duplicate) and priority in the code.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">local plugin_name = "example-plugin"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">local _M = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> version = 0.1,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> priority = 0,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> name = plugin_name,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> schema = schema,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> metadata_schema = metadata_schema,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>Note: The priority of the new plugin cannot be same to any existing ones, you can use the <code>/v1/schema</code> method of <a href="/docs/apisix/control-api/#get-v1schema">control API</a> to view the priority of all plugins. In addition, plugins with higher priority value will be executed first in a given phase (see the definition of <code>phase</code> in <a href="#choose-phase-to-run">choose-phase-to-run</a>). For example, the priority of example-plugin is 0 and the priority of ip-restriction is 3000. Therefore, the ip-restriction plugin will be executed first, then the example-plugin plugin. It's recommended to use priority 1 ~ 99 for your plugin unless you want it to run before some builtin plugins.</p><p>Note: the order of the plugins is not related to the order of execution.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="schema-and-check"></a>Schema and check<a class="hash-link" href="#schema-and-check" title="Direct link to heading">#</a></h3><p>Write <a href="https://json-schema.org" target="_blank" rel="noopener noreferrer">JSON Schema</a> descriptions and check functions. Similarly, take the example-plugin plugin as an example to see its |
| configuration data:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 json"><pre tabindex="0" class="prism-code language-json codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"example-plugin"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"i"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"s"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"s"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"t"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>Let's look at its schema description :</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">local schema = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> type = "object",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> properties = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> i = {type = "number", minimum = 0},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> s = {type = "string"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> t = {type = "array", minItems = 1},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ip = {type = "string"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> port = {type = "integer"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> required = {"i"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>The schema defines a non-negative number <code>i</code>, a string <code>s</code>, a non-empty array of <code>t</code>, and <code>ip</code> / <code>port</code>. Only <code>i</code> is required.</p><p>At the same time, we need to implement the <strong>check_schema(conf, schema_type)</strong> method to complete the specification verification.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function _M.check_schema(conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return core.schema.check(schema, conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><div class="admonition admonition-note alert alert--secondary"><div class="admonition-heading"><h5><span class="admonition-icon"><svg xmlns="http://www.w3.org/2000/svg" width="14" height="16" viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</h5></div><div class="admonition-content"><p>Note: the project has provided the public method "<strong>core.schema.check</strong>", which can be used directly to complete JSON |
| verification.</p></div></div><p>The input parameter <strong>schema_type</strong> is used to distinguish between different schemas types. For example, many plugins need to use some <a href="/docs/apisix/terminology/plugin-metadata/">metadata</a>, so they define the plugin's <code>metadata_schema</code>.</p><div class="codeBlockContainer_EiTO"><div style="color:#393A34;background-color:#f6f8fa" class="codeBlockTitle_PQMO">example-plugin.lua</div><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">-- schema definition for metadata</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">local metadata_schema = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> type = "object",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> properties = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ikey = {type = "number", minimum = 0},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> skey = {type = "string"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> required = {"ikey", "skey"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">function _M.check_schema(conf, schema_type)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> --- check schema for metadata</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if schema_type == core.schema.TYPE_METADATA then</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return core.schema.check(metadata_schema, conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return core.schema.check(schema, conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>Another example, the <a href="https://github.com/apache/apisix/blob/master/apisix/plugins/key-auth.lua" rel="noopener noreferrer">key-auth</a> plugin needs to provide a <code>consumer_schema</code> to check the configuration of the <code>plugins</code> attribute of the <code>consumer</code> resource in order to be used with the <a href="/docs/apisix/admin-api/#consumer">Consumer</a> resource.</p><div class="codeBlockContainer_EiTO"><div style="color:#393A34;background-color:#f6f8fa" class="codeBlockTitle_PQMO">key-auth.lua</div><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">local consumer_schema = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> type = "object",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> properties = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> key = {type = "string"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> },</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> required = {"key"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">function _M.check_schema(conf, schema_type)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if schema_type == core.schema.TYPE_CONSUMER then</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return core.schema.check(consumer_schema, conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> else</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return core.schema.check(schema, conf)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="choose-phase-to-run"></a>Choose phase to run<a class="hash-link" href="#choose-phase-to-run" title="Direct link to heading">#</a></h3><p>Determine which <a href="/docs/apisix/terminology/plugin/#plugins-execution-lifecycle">phase</a> to run, generally access or rewrite. If you don't know the <a href="https://github.com/openresty/lua-nginx-module/blob/master/README.markdown#directives" target="_blank" rel="noopener noreferrer">OpenResty lifecycle</a>, it's |
| recommended to learn about it in advance. For example <code>key-auth</code> is an authentication plugin, thus the authentication should be completed |
| before forwarding the request to any upstream service. Therefore, the plugin must be executed in the rewrite phases. |
| Similarly, if you want to modify or process the response body or headers you can do that in the <code>body_filter</code> or in the <code>header_filter</code> phases respectively.</p><p>The following code snippet shows how to implement any logic relevant to the plugin in the OpenResty log phase.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function _M.log(conf, ctx)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">-- Implement logic here</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p><strong>Note : we can't invoke <code>ngx.exit</code>, <code>ngx.redirect</code> or <code>core.respond.exit</code> in rewrite phase and access phase. if need to exit, just return the status and body, the plugin engine will make the exit happen with the returned status and body. <a href="https://github.com/apache/apisix/blob/35269581e21473e1a27b11cceca6f773cad0192a/apisix/plugins/limit-count.lua#L177" rel="noopener noreferrer">example</a></strong></p><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="extra-phase"></a>extra phase<a class="hash-link" href="#extra-phase" title="Direct link to heading">#</a></h3><p>Besides OpenResty's phases, we also provide extra phases to satisfy specific purpose:</p><ul><li><code>delayed_body_filter</code></li></ul><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function _M.delayed_body_filter(conf, ctx)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -- delayed_body_filter is called after body_filter</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -- it is used by the tracing plugins to end the span right after body_filter</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="implement-the-logic"></a>Implement the logic<a class="hash-link" href="#implement-the-logic" title="Direct link to heading">#</a></h3><p>Write the logic of the plugin in the corresponding phase. There are two parameters <code>conf</code> and <code>ctx</code> in the phase method, take the <code>limit-conn</code> plugin configuration as an example.</p><h4><a aria-hidden="true" tabindex="-1" class="anchor anchor__h4 anchorWithHideOnScrollNavbar_3ly5" id="conf-parameter"></a>conf parameter<a class="hash-link" href="#conf-parameter" title="Direct link to heading">#</a></h4><p>The <code>conf</code> parameter is the relevant configuration information of the plugin, you can use <code>core.log.warn(core.json.encode(conf))</code> to output it to <code>error.log</code> for viewing, as shown below:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function _M.access(conf, ctx)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> core.log.warn(core.json.encode(conf))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ......</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>conf:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 json"><pre tabindex="0" class="prism-code language-json codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"rejected_code"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">503</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"burst"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"default_conn_delay"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">0.1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"conn"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token property" style="color:#36acaa">"key"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"remote_addr"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><h4><a aria-hidden="true" tabindex="-1" class="anchor anchor__h4 anchorWithHideOnScrollNavbar_3ly5" id="ctx-parameter"></a>ctx parameter<a class="hash-link" href="#ctx-parameter" title="Direct link to heading">#</a></h4><p>The <code>ctx</code> parameter caches data information related to the request. You can use <code>core.log.warn(core.json.encode(ctx, true))</code> to output it to <code>error.log</code> for viewing, as shown below :</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function _M.access(conf, ctx)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> core.log.warn(core.json.encode(ctx, true))</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ......</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="others"></a>Others<a class="hash-link" href="#others" title="Direct link to heading">#</a></h3><p>If your plugin has a new code directory of its own, and you need to redistribute it with the APISIX source code, you will need to modify the <code>Makefile</code> to create directory, such as:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 bash"><pre tabindex="0" class="prism-code language-bash codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token variable" style="color:#36acaa">$(</span><span class="token variable" style="color:#36acaa">INSTALL</span><span class="token variable" style="color:#36acaa">)</span><span class="token plain"> -d </span><span class="token variable" style="color:#36acaa">$(</span><span class="token variable" style="color:#36acaa">INST_LUADIR</span><span class="token variable" style="color:#36acaa">)</span><span class="token plain">/apisix/plugins/skywalking</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token variable" style="color:#36acaa">$(</span><span class="token variable" style="color:#36acaa">INSTALL</span><span class="token variable" style="color:#36acaa">)</span><span class="token plain"> apisix/plugins/skywalking/*.lua </span><span class="token variable" style="color:#36acaa">$(</span><span class="token variable" style="color:#36acaa">INST_LUADIR</span><span class="token variable" style="color:#36acaa">)</span><span class="token plain">/apisix/plugins/skywalking/</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>There are other fields in the <code>_M</code> which affect the plugin's behavior.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">local _M = {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> type = 'auth',</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> run_policy = 'prefer_route',</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p><code>run_policy</code> field can be used to control the behavior of the plugin execution. |
| When this field set to <code>prefer_route</code>, and the plugin has been configured both |
| in the global and at the route level, only the route level one will take effect.</p><p><code>type</code> field is required to be set to <code>auth</code> if your plugin needs to work with consumer.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="load-plugin-and-replace-plugin"></a>Load plugin and replace plugin<a class="hash-link" href="#load-plugin-and-replace-plugin" title="Direct link to heading">#</a></h2><p>Using <code>require "apisix.plugins.3rd-party"</code> will load your plugin, just like <code>require "apisix.plugins.jwt-auth"</code> will load the <code>jwt-auth</code> plugin.</p><p>Sometimes you may want to override a method instead of a whole file. In this case, you can configure <code>lua_module_hook</code> in <code>conf/config.yaml</code> |
| to introduce your hook.</p><p>Assume that your configuration is as follows:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 yaml"><pre tabindex="0" class="prism-code language-yaml codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apisix</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">...</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">extra_lua_path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"/path/to/example/?.lua"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">lua_module_hook</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"my_hook"</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>The <code>example/my_hook.lua</code> will be loaded when APISIX starts, and you can use this hook to replace a method in APISIX. |
| The example of <a href="https://github.com/apache/apisix/blob/master/example/my_hook.lua" rel="noopener noreferrer">my_hook.lua</a> can be found under the <code>example</code> directory of this project.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="check-external-dependencies"></a>Check external dependencies<a class="hash-link" href="#check-external-dependencies" title="Direct link to heading">#</a></h2><p>If you have dependencies on external libraries, check the dependent items. If your plugin needs to use shared memory, it |
| needs to declare via <a href="/docs/apisix/customize-nginx-configuration/">customizing Nginx configuration</a>, for example :</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 yaml"><pre tabindex="0" class="prism-code language-yaml codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># put this in config.yaml:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">nginx_config</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">http_configuration_snippet</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> # for openid-connect plugin</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> lua_shared_dict discovery 1m; # cache for discovery metadata documents</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> lua_shared_dict jwks 1m; # cache for JWKs</span><br></span><span class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c"> lua_shared_dict introspection 10m; # cache for JWT verification results</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>The plugin itself provides the init method. It is convenient for plugins to perform some initialization after |
| the plugin is loaded. If you need to clean up the initialization, you can put it in the corresponding destroy method.</p><p>Note : if the dependency of some plugin needs to be initialized when Nginx start, you may need to add logic to the initialization |
| method "http_init" in the file <code>apisix/init.lua</code>, and you may need to add some processing on generated part of Nginx |
| configuration file in <code>apisix/cli/ngx_tpl.lua</code> file. But it is easy to have an impact on the overall situation according to the |
| existing plugin mechanism, <strong>we do not recommend this unless you have a complete grasp of the code</strong>.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="encrypted-storage-fields"></a>Encrypted storage fields<a class="hash-link" href="#encrypted-storage-fields" title="Direct link to heading">#</a></h2><p>Some plugins require parameters to be stored encrypted, such as the <code>password</code> parameter of the <code>basic-auth</code> plugin. This plugin needs to specify in the <code>schema</code> which parameters need to be stored encrypted.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">encrypt_fields = {"password"}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>If it is a nested parameter, such as the <code>clickhouse.password</code> parameter of the <code>error-log-logger</code> plugin, it needs to be separated by <code>.</code>:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">encrypt_fields = {"clickhouse.password"}</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>Currently not supported yet:</p><ol><li>more than two levels of nesting</li><li>fields in arrays</li></ol><p>Parameters can be stored encrypted by specifying <code>encrypt_fields = {"password"}</code> in the <code>schema</code>. APISIX will provide the following functionality.</p><ul><li>When adding and updating resources, APISIX automatically encrypts the parameters declared in <code>encrypt_fields</code> and stores them in etcd</li><li>When fetching resources and when running the plugin, APISIX automatically decrypts the parameters declared in <code>encrypt_fields</code></li></ul><p>By default, APISIX has <code>data_encryption</code> enabled with <a href="https://github.com/apache/apisix/blob/85563f016c35834763376894e45908b2fb582d87/apisix/cli/config.lua#L75" rel="noopener noreferrer">two default keys</a>, you can modify them in <code>config.yaml</code>.</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 yaml"><pre tabindex="0" class="prism-code language-yaml codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apisix</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">data_encryption</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">enable</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">keyring</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">...</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>APISIX will try to decrypt the data with keys in the order of the keys in the keyring (only for parameters declared in <code>encrypt_fields</code>). If the decryption fails, the next key will be tried until the decryption succeeds.</p><p>If none of the keys in <code>keyring</code> can decrypt the data, the original data is used.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="register-public-api"></a>Register public API<a class="hash-link" href="#register-public-api" title="Direct link to heading">#</a></h2><p>A plugin can register API which exposes to the public. Take batch-requests plugin as an example, this plugin registers <code>POST /apisix/batch-requests</code> to allow developers to group multiple API requests into a single HTTP request/response cycle:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">function batch_requests()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -- ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">function _M.api()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> -- ...</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> methods = {"POST"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> uri = "/apisix/batch-requests",</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> handler = batch_requests,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>Note that the public API will not be exposed by default, you will need to use the <a href="/docs/apisix/plugins/public-api/">public-api plugin</a> to expose it.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="register-control-api"></a>Register control API<a class="hash-link" href="#register-control-api" title="Direct link to heading">#</a></h2><p>If you only want to expose the API to the localhost or intranet, you can expose it via <a href="/docs/apisix/control-api/">Control API</a>.</p><p>Take a look at example-plugin plugin:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 lua"><pre tabindex="0" class="prism-code language-lua codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">local function hello()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> local args = ngx.req.get_uri_args()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if args["json"] then</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return 200, {msg = "world"}</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> else</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return 200, "world\n"</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">function _M.control_api()</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> return {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> methods = {"GET"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> uris = {"/v1/plugin/example-plugin/hello"},</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> handler = hello,</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>If you don't change the default control API configuration, the plugin will be expose <code>GET /v1/plugin/example-plugin/hello</code> which can only be accessed via <code>127.0.0.1</code>. Test with the following command:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 shell"><pre tabindex="0" class="prism-code language-shell codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token function" style="color:#d73a49">curl</span><span class="token plain"> -i -X GET </span><span class="token string" style="color:#e3116c">"http://127.0.0.1:9090/v1/plugin/example-plugin/hello"</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p><a href="/docs/apisix/control-api/">Read more about control API introduction</a></p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="register-custom-variables"></a>Register custom variables<a class="hash-link" href="#register-custom-variables" title="Direct link to heading">#</a></h2><p>We can use variables in many places of APISIX. For example, customizing log format in http-logger, using it as the key of <code>limit-*</code> plugins. In some situations, the builtin variables are not enough. Therefore, APISIX allows developers to register their variables globally, and use them as normal builtin variables.</p><p>For instance, let's register a variable called <code>a6_labels_zone</code> to fetch the value of the <code>zone</code> label in a route:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 bash"><pre tabindex="0" class="prism-code language-bash codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token builtin class-name">local</span><span class="token plain"> core </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> require </span><span class="token string" style="color:#e3116c">"apisix.core"</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">core.ctx.register_var</span><span class="token punctuation" style="color:#393A34">(</span><span class="token string" style="color:#e3116c">"a6_labels_zone"</span><span class="token plain">, function</span><span class="token punctuation" style="color:#393A34">(</span><span class="token plain">ctx</span><span class="token punctuation" style="color:#393A34">)</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token builtin class-name">local</span><span class="token plain"> route </span><span class="token operator" style="color:#393A34">=</span><span class="token plain"> ctx.matched_route and ctx.matched_route.value</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token keyword" style="color:#00009f">if</span><span class="token plain"> route and route.labels </span><span class="token keyword" style="color:#00009f">then</span><span class="token plain"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token builtin class-name">return</span><span class="token plain"> route.labels.zone</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> </span><span class="token builtin class-name">return</span><span class="token plain"> nil</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">end</span><span class="token punctuation" style="color:#393A34">)</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>After that, any get operation to <code>$a6_labels_zone</code> will call the registered getter to fetch the value.</p><p>Note that the custom variables can't be used in features that depend on the Nginx directive, like <code>access_log_format</code>.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="write-test-cases"></a>Write test cases<a class="hash-link" href="#write-test-cases" title="Direct link to heading">#</a></h2><p>For functions, write and improve the test cases of various dimensions, do a comprehensive test for your plugin! The |
| test cases of plugins are all in the "<strong>t/plugin</strong>" directory. You can go ahead to find out. APISIX uses |
| <a href="https://github.com/openresty/test-nginx" target="_blank" rel="noopener noreferrer">*<strong>*test-nginx**</strong></a> as the test framework. A test case (.t file) is usually |
| divided into prologue and data parts by __data__. Here we will briefly introduce the data part, that is, the part |
| of the real test case. For example, the key-auth plugin:</p><div class="codeBlockContainer_EiTO"><div class="codeBlockContent_X2I6 perl"><pre tabindex="0" class="prism-code language-perl codeBlock_UxnK thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_W6UD"><span class="token-line" style="color:#393A34"><span class="token plain">=== TEST 1: sanity</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">--- config</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> location /t {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> content_by_lua_block {</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> local plugin = require("apisix.plugins.key-auth")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> local ok, err = plugin.check_schema({key = 'test-key'}, core.schema.TYPE_CONSUMER)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> if not ok then</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ngx.say(err)</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> end</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> ngx.say("done")</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain"> }</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">--- request</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">GET /t</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">--- response_body</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">done</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">--- no_error_log</span><br></span><span class="token-line" style="color:#393A34"><span class="token plain">[error]</span><br></span></code></pre><button type="button" aria-label="Copy code to clipboard" class="copyButton_V-PD clean-btn">Copy</button></div></div><p>A test case consists of three parts :</p><ul><li><strong>Program code</strong> : configuration content of Nginx location</li><li><strong>Input</strong> : http request information</li><li><strong>Output check</strong> : status, header, body, error log check</li></ul><p>When we request <strong>/t</strong>, which config in the configuration file, the Nginx will call "<strong>content_by_lua_block</strong>" instruction to |
| complete the Lua script, and finally return. The assertion of the use case is response_body return "done", |
| "<strong>no_error_log</strong>" means to check the "<strong>error.log</strong>" of Nginx. There must be no ERROR level record. The log files for the unit test |
| are located in the following folder: 't/servroot/logs'.</p><p>The above test case represents a simple scenario. Most scenarios will require multiple steps to validate. To do this, create multiple tests <code>=== TEST 1</code>, <code>=== TEST 2</code>, and so on. These tests will be executed sequentially, allowing you to break down scenarios into a sequence of atomic steps.</p><p>Additionally, there are some convenience testing endpoints which can be found <a href="https://github.com/apache/apisix/blob/master/t/lib/server.lua#L36" rel="noopener noreferrer">here</a>. For example, see <a href="https://github.com/apache/apisix/blob/master/t/plugin/proxy-rewrite.t" rel="noopener noreferrer">proxy-rewrite</a>. In test 42, the upstream <code>uri</code> is made to redirect <code>/test?new_uri=hello</code> to <code>/hello</code> (which always returns <code>hello world</code>). In test 43, the response body is confirmed to equal <code>hello world</code>, meaning the proxy-rewrite configuration added with test 42 worked correctly.</p><p>Refer the following <a href="/docs/apisix/building-apisix/">document</a> to setup the testing framework.</p><h3><a aria-hidden="true" tabindex="-1" class="anchor anchor__h3 anchorWithHideOnScrollNavbar_3ly5" id="attach-the-test-nginx-execution-process"></a>Attach the test-nginx execution process:<a class="hash-link" href="#attach-the-test-nginx-execution-process" title="Direct link to heading">#</a></h3><p>According to the path we configured in the makefile and some configuration items at the front of each <strong>.t</strong> file, the |
| framework will assemble into a complete nginx.conf file. "<strong>t/servroot</strong>" is the working directory of Nginx and start the |
| Nginx instance. according to the information provided by the test case, initiate the http request and check that the |
| return items of HTTP include HTTP status, HTTP response header, HTTP response body and so on.</p><h2><a aria-hidden="true" tabindex="-1" class="anchor anchor__h2 anchorWithHideOnScrollNavbar_3ly5" id="additional-resources"></a>Additional Resource(s)<a class="hash-link" href="#additional-resources" title="Direct link to heading">#</a></h2><ul><li>Key Concepts - <a href="https://apisix.apache.org/docs/apisix/terminology/plugin/" rel="noopener noreferrer">Plugins</a></li><li><a href="https://apisix.apache.org/blog/2021/10/29/extension-guide/" rel="noopener noreferrer">Apache APISIX Extensions Guide</a></li><li><a href="https://docs.api7.ai/apisix/how-to-guide/custom-plugins/create-plugin-in-lua" rel="noopener noreferrer">Create a Custom Plugin in Lua</a></li><li><a href="https://github.com/apache/apisix/blob/master/apisix/plugins/example-plugin.lua" rel="noopener noreferrer">example-plugin code</a></li></ul></div><footer class="theme-doc-footer docusaurus-mt-lg"><div class="theme-doc-footer-edit-meta-row row"><div class="col"><a href="/edit#https://github.com/apache/apisix/edit/release/3.14/docs/en/latest/plugin-develop.md" target="_blank" rel="noreferrer noopener" class="theme-edit-this-page"><svg fill="currentColor" height="20" width="20" viewBox="0 0 40 40" class="iconEdit_mS5F" aria-hidden="true"><g><path d="m34.5 11.7l-3 3.1-6.3-6.3 3.1-3q0.5-0.5 1.2-0.5t1.1 0.5l3.9 3.9q0.5 0.4 0.5 1.1t-0.5 1.2z m-29.5 17.1l18.4-18.5 6.3 6.3-18.4 18.4h-6.3v-6.2z"></path></g></svg>Edit this page</a></div><div class="col lastUpdated_mt2f"></div></div></footer></article><nav class="pagination-nav docusaurus-mt-lg" aria-label="Docs pages navigation"><div class="pagination-nav__item"><a class="pagination-nav__link" href="/docs/apisix/internal/testing-framework/"><div class="pagination-nav__sublabel">Previous</div><div class="pagination-nav__label">« Introducing APISIX's testing framework</div></a></div><div class="pagination-nav__item pagination-nav__item--next"><a class="pagination-nav__link" href="/docs/apisix/debug-mode/"><div class="pagination-nav__sublabel">Next</div><div class="pagination-nav__label">Debug mode »</div></a></div></nav></div></div><div class="col col--3"><div class="tableOfContents_vrFS thin-scrollbar"><ul class="table-of-contents table-of-contents__left-border"><li><a href="#where-to-put-your-plugins" class="table-of-contents__link">Where to put your plugins</a></li><li><a href="#enable-the-plugin" class="table-of-contents__link">Enable the plugin</a></li><li><a href="#writing-plugins" class="table-of-contents__link">Writing plugins</a><ul><li><a href="#naming-and-priority" class="table-of-contents__link">Naming and priority</a></li><li><a href="#schema-and-check" class="table-of-contents__link">Schema and check</a></li><li><a href="#choose-phase-to-run" class="table-of-contents__link">Choose phase to run</a></li><li><a href="#extra-phase" class="table-of-contents__link">extra phase</a></li><li><a href="#implement-the-logic" class="table-of-contents__link">Implement the logic</a></li><li><a href="#others" class="table-of-contents__link">Others</a></li></ul></li><li><a href="#load-plugin-and-replace-plugin" class="table-of-contents__link">Load plugin and replace plugin</a></li><li><a href="#check-external-dependencies" class="table-of-contents__link">Check external dependencies</a></li><li><a href="#encrypted-storage-fields" class="table-of-contents__link">Encrypted storage fields</a></li><li><a href="#register-public-api" class="table-of-contents__link">Register public API</a></li><li><a href="#register-control-api" class="table-of-contents__link">Register control API</a></li><li><a href="#register-custom-variables" class="table-of-contents__link">Register custom variables</a></li><li><a href="#write-test-cases" class="table-of-contents__link">Write test cases</a><ul><li><a href="#attach-the-test-nginx-execution-process" class="table-of-contents__link">Attach the test-nginx execution process:</a></li></ul></li><li><a href="#additional-resources" class="table-of-contents__link">Additional Resource(s)</a></li></ul></div></div></div></div></main></div></div><footer class="container_MP5Z"><div class="linksRow_iwpv"><div class="linksCol_a1ec"><div>ASF</div><ul><li class="footer__item"><a href="https://www.apache.org/" target="_blank" rel="noopener noreferrer"><span></span><span>Foundation</span></a></li><li class="footer__item"><a href="https://www.apache.org/licenses/" target="_blank" rel="noopener noreferrer"><span></span><span>License</span></a></li><li class="footer__item"><a href="https://www.apache.org/events/" target="_blank" rel="noopener noreferrer"><span></span><span>Events</span></a></li><li class="footer__item"><a href="https://www.apache.org/security/" target="_blank" rel="noopener noreferrer"><span></span><span>Security</span></a></li><li class="footer__item"><a href="https://www.apache.org/foundation/sponsorship.html" target="_blank" rel="noopener noreferrer"><span></span><span>Sponsorship</span></a></li><li class="footer__item"><a href="https://www.apache.org/foundation/thanks.html" target="_blank" rel="noopener noreferrer"><span></span><span>Thanks</span></a></li></ul></div><div class="linksCol_a1ec"><div>Community</div><ul><li class="footer__item"><a href="https://github.com/apache/apisix/issues" target="_blank" rel="noopener noreferrer"><span></span><span>GitHub</span></a></li><li class="footer__item"><a href="/docs/general/join/"><span></span><span>Slack</span></a></li><li class="footer__item"><a href="https://twitter.com/ApacheAPISIX" target="_blank" rel="noopener noreferrer"><span></span><span>Twitter</span></a></li><li class="footer__item"><a href="https://www.youtube.com/channel/UCgPD18cMhOg5rmPVnQhAC8g" target="_blank" rel="noopener noreferrer"><span></span><span>YouTube</span></a></li></ul></div><div class="linksCol_a1ec"><div>More</div><ul><li class="footer__item"><a target="_parent" href="/blog/"><span></span><span>Blog</span></a></li><li class="footer__item"><a target="_parent" href="/showcase/"><span></span><span>Showcase</span></a></li><li class="footer__item"><a target="_parent" href="/plugins/"><span></span><span>Plugin Hub</span></a></li><li class="footer__item"><a href="https://github.com/apache/apisix/milestones" target="_parent" rel="noopener noreferrer"><span></span><span>Roadmap</span></a></li></ul></div></div><div class="copyright_ZfFh"><a href="https://www.apache.org/" target="_blank" rel="noopener noreferrer"><span style="display:inline-block;width:231.25px;height:40px"></span></a><div>Copyright © 2019-2025 The Apache Software Foundation. Apache APISIX, APISIX®, Apache, the Apache feather logo, and the Apache APISIX project logo are either registered trademarks or trademarks of the Apache Software Foundation.</div></div></footer></div> |
| <script src="https://apisix-website-static.apiseven.com/assets/js/runtime~main.6f5566cc.js"></script> |
| <script src="https://apisix-website-static.apiseven.com/assets/js/main.2626d18c.js"></script> |
| </body> |
| </html> |