{"id":3741,"date":"2026-04-22T15:04:08","date_gmt":"2026-04-22T22:04:08","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/azure-sdk\/?p=3741"},"modified":"2026-04-24T11:13:17","modified_gmt":"2026-04-24T18:13:17","slug":"azd-multi-language-hooks","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/azure-sdk\/azd-multi-language-hooks\/","title":{"rendered":"Write azd hooks in Python, JavaScript, TypeScript, or .NET"},"content":{"rendered":"<p><em>Hooks are one of the most popular features in <code>azd<\/code>, and now you can write them in Python, JavaScript, TypeScript, or .NET, not just Bash and PowerShell.<\/em><\/p>\n<h2>What&#8217;s new?<\/h2>\n<p>The Azure Developer CLI (<code>azd<\/code>) hook system now supports four more languages beyond Bash and PowerShell. You can write hook scripts in <strong>Python<\/strong>, <strong>JavaScript<\/strong>, <strong>TypeScript<\/strong>, or <strong>.NET<\/strong>. <code>azd<\/code> automatically detects the language from the file extension, manages dependencies, and runs the script with no extra configuration required.<\/p>\n<h2>Why it matters<\/h2>\n<p>Hooks let you run custom logic at key points in the <code>azd<\/code> lifecycle before provisioning, after deployment, and more. Previously, hooks supported scripts written in Bash and PowerShell, which meant developers had to context-switch to a shell scripting language even if their project was entirely Python or TypeScript. Now you can write hooks in the same language as your application, reuse existing libraries, and skip the shell scripting.<\/p>\n<h2>How to use it<\/h2>\n<p>Point a hook at a script file in <code>azure.yaml<\/code>, and <code>azd<\/code> infers the language from the extension. If the extension is ambiguous or missing, you can specify the language explicitly with the <code>kind<\/code> field:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  preprovision:\r\n    run: .\/hooks\/setup\r\n    run: .\/hooks\/setup.py\r\n    kind: python    # explicit \u2014 overrides extension inference<\/code><\/pre>\n<p>Here&#8217;s what a typical setup looks like:<\/p>\n<pre><code class=\"language-yaml\"># azure.yaml\r\nhooks:\r\n  preprovision:\r\n    run: .\/hooks\/setup.py\r\n\r\n  postdeploy:\r\n    run: .\/hooks\/seed.ts\r\n\r\n  postprovision:\r\n    run: .\/hooks\/migrate.cs<\/code><\/pre>\n<h3>Python hooks<\/h3>\n<p>Place a <code>requirements.txt<\/code> or <code>pyproject.toml<\/code> in the same directory as your script or a parent directory. <code>azd<\/code> walks up the directory tree from the script location to find the nearest project file, creates a virtual environment, installs dependencies, and runs the script.<\/p>\n<pre><code>hooks\/\r\n\u251c\u2500\u2500 setup.py\r\n\u2514\u2500\u2500 requirements.txt<\/code><\/pre>\n<p>Then reference the script in your <code>azure.yaml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  preprovision:\r\n    run: .\/hooks\/setup.py<\/code><\/pre>\n<h3>JavaScript and TypeScript hooks<\/h3>\n<p>Place a <code>package.json<\/code> in the same directory as your script or a parent directory. <code>azd<\/code> runs <code>npm install<\/code> (or the package manager specified in <code>config<\/code>) and executes the script. TypeScript scripts run via <code>npx tsx<\/code> with no compile step or <code>tsconfig.json<\/code> needed:<\/p>\n<pre><code>hooks\/\r\n\u251c\u2500\u2500 seed.ts\r\n\u2514\u2500\u2500 package.json<\/code><\/pre>\n<p>Then reference the script in your <code>azure.yaml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  postdeploy:\r\n    run: .\/hooks\/seed.ts<\/code><\/pre>\n<h3>.NET hooks<\/h3>\n<p>Two modes are supported:<\/p>\n<ul>\n<li><strong>Project mode<\/strong>: If a <code>.csproj<\/code>, <code>.fsproj<\/code>, or <code>.vbproj<\/code> exists in the same directory as the script, <code>azd<\/code> runs <code>dotnet restore<\/code> and <code>dotnet build<\/code> automatically.<\/li>\n<li><strong>Single-file mode<\/strong>: On .NET 10+, standalone <code>.cs<\/code> files run directly via <code>dotnet run script.cs<\/code> without a project file.<\/li>\n<\/ul>\n<pre><code>hooks\/\r\n\u251c\u2500\u2500 migrate.cs\r\n\u2514\u2500\u2500 migrate.csproj   # optional \u2014 omit for single-file mode on .NET 10+<\/code><\/pre>\n<p>Then reference the script in your <code>azure.yaml<\/code>:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  postprovision:\r\n    run: .\/hooks\/migrate.cs<\/code><\/pre>\n<h3>Override the working directory<\/h3>\n<p>Use the <code>dir<\/code> field to set the working directory for a hook. This configuration is useful when the project root differs from the script location:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  preprovision:\r\n    run: main.py\r\n    dir: hooks\/preprovision<\/code><\/pre>\n<h3>Executor-specific configuration<\/h3>\n<p>Each language supports an optional <code>config<\/code> block for executor-specific settings:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  preprovision:\r\n    run: .\/hooks\/setup.ts\r\n    config:\r\n      packageManager: pnpm    # npm | pnpm | yarn\r\n\r\n  postdeploy:\r\n    run: .\/hooks\/seed.py\r\n    config:\r\n      virtualEnvName: .venv   # override default naming\r\n\r\n  postprovision:\r\n    run: .\/hooks\/migrate.cs\r\n    config:\r\n      configuration: Release  # Debug | Release\r\n      framework: net10.0      # target framework<\/code><\/pre>\n<h3>Mixed formats<\/h3>\n<p>You can mix single-hook and multi-hook formats in the same <code>hooks:<\/code> block, including platform-specific overrides:<\/p>\n<pre><code class=\"language-yaml\">hooks:\r\n  preprovision:\r\n    run: .\/hooks\/setup.py\r\n  predeploy:\r\n    windows:\r\n      run: .\/hooks\/build.ps1\r\n    posix:\r\n      run: .\/hooks\/build.sh<\/code><\/pre>\n<h2>Try it out<\/h2>\n<p>To make sure you have this feature, update to the latest <code>azd<\/code> version:<\/p>\n<pre><code class=\"language-bash\">azd update<\/code><\/pre>\n<p>For a fresh install, see <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/install-azd\">Install azd<\/a>.<\/p>\n<p>We hope you like this new feature for writing <code>azd<\/code> hooks in your preferred language! Let us know what you think and how you&#8217;re using it.<\/p>\n<h2>Feedback<\/h2>\n<p>Have questions or ideas? File an issue or start a discussion on <a href=\"https:\/\/github.com\/Azure\/azure-dev\">GitHub<\/a>. Want to help shape the future of <code>azd<\/code>? <a href=\"https:\/\/aka.ms\/azd-user-research-signup\">Sign up for user research<\/a>.<\/p>\n<h2>\ud83d\ude4b\u200d\u2640\ufe0f New to azd?<\/h2>\n<p>If you&#8217;re new to the Azure Developer CLI, <code>azd<\/code> is an open-source command-line tool that takes your application from local development environment to Azure. <code>azd<\/code> provides best practice, developer-friendly commands that map to key stages in your workflow, whether you&#8217;re working in the terminal, your editor or CI\/CD.<\/p>\n<ul>\n<li><a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/install-azd\">Install azd<\/a><\/li>\n<li>Explore templates: Browse the <a href=\"https:\/\/azure.github.io\/awesome-azd\/\">Awesome azd template gallery<\/a> and <a href=\"https:\/\/aka.ms\/ai-gallery\">AI App Templates<\/a><\/li>\n<li>Learn more: Visit the <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/\">official documentation<\/a> and <a href=\"https:\/\/learn.microsoft.com\/azure\/developer\/azure-developer-cli\/troubleshoot\">troubleshooting guide<\/a><\/li>\n<li>Get help: Visit the <a href=\"https:\/\/github.com\/Azure\/azure-dev\">GitHub repository<\/a> to file issues or start discussions<\/li>\n<li>Share your feedback: We&#8217;re interested in learning how you&#8217;re using <code>azd<\/code>! <a href=\"https:\/\/aka.ms\/azd-user-research-signup\">Sign up for user research<\/a> to help shape the future of the Azure Developer CLI<\/li>\n<\/ul>\n<hr \/>\n<p><em>This feature was introduced across several PRs: <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/7451\">#7451<\/a> (Python), <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/7626\">#7626<\/a> (JS\/TS), <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/7652\">#7652<\/a> (.NET\/C#\/F#\/VB.NET), <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/7690\">#7690<\/a> (config), <a href=\"https:\/\/github.com\/Azure\/azure-dev\/pull\/7618\">#7618<\/a> (mixed formats).<\/em><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Hooks are one of the most popular features in azd, and now you can write them in Python, JavaScript, TypeScript, or .NET, not just Bash and PowerShell. What&#8217;s new? The Azure Developer CLI (azd) hook system now supports four more languages beyond Bash and PowerShell. You can write hook scripts in Python, JavaScript, TypeScript, or [&hellip;]<\/p>\n","protected":false},"author":107449,"featured_media":3743,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1],"tags":[908,864,161,976,159,162,733],"class_list":["post-3741","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-azure-sdk","tag-azd","tag-azure-developer-cli","tag-dotnet","tag-hooks","tag-javascript","tag-python","tag-typescript"],"acf":[],"blog_post_summary":"<p>Hooks are one of the most popular features in azd, and now you can write them in Python, JavaScript, TypeScript, or .NET, not just Bash and PowerShell. What&#8217;s new? The Azure Developer CLI (azd) hook system now supports four more languages beyond Bash and PowerShell. You can write hook scripts in Python, JavaScript, TypeScript, or [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/3741","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/users\/107449"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/comments?post=3741"}],"version-history":[{"count":3,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/3741\/revisions"}],"predecessor-version":[{"id":3772,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/posts\/3741\/revisions\/3772"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media\/3743"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/media?parent=3741"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/categories?post=3741"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/azure-sdk\/wp-json\/wp\/v2\/tags?post=3741"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}