ecosyste-ms/package-manager-hooks

A reference for lifecycle hooks across different package manager ecosystems.

5

6 commits

updated Jun 28, 2026

See the code

README

Package Manager Lifecycle Hooks

A reference for lifecycle hooks across different package manager ecosystems.

This is part of a larger effort to document package manager data for the ecosyste.ms project.

Overview

Package manager hooks fall into two distinct categories:

  1. Package-Defined Hooks: Scripts or code that package authors define to run during their own package's lifecycle (install, build, publish, uninstall). These execute when someone installs or uses your package.

  2. System/Plugin Hooks: Extension points that let users, tools, or plugins modify the package manager's behavior globally across all operations, not tied to any specific package.

This distinction matters for security: package-defined hooks represent supply chain attack surface (third-party code runs on your machine), while system hooks are user-controlled.


Package-Defined Hooks

JavaScript (npm, Yarn, pnpm, Bun, Deno)

HookWhen it runs
preinstallBefore package installation
installDuring installation
postinstallAfter installation
preuninstallBefore uninstallation
uninstallDuring uninstallation
postuninstallAfter uninstallation
prepublishBefore publish and local install (deprecated behavior)
prepublishOnlyBefore publish only
prepackBefore tarball is packed
postpackAfter tarball is packed
prepareAfter install (both local and from registry) and before publish
dependenciesAfter any dependency changes

Mechanism: scripts field in package.json

{
  "scripts": {
    "preinstall": "node check-env.js",
    "postinstall": "node build-native.js"
  }
}

Documentation:

pnpm-specific hooks:

  • pnpm:devPreinstall - Runs only on local pnpm install, before any dependency is installed (root package.json only)

Security notes:

  • postinstall is the most common vector for supply chain attacks
  • npm 7+ runs lifecycle scripts in a sandbox by default (can be disabled with --ignore-scripts=false)
  • Yarn Berry disables lifecycle scripts by default for non-root packages
  • pnpm v10+ blocks dependency lifecycle scripts by default - packages must be explicitly approved via onlyBuiltDependencies in package.json or pnpm approve-builds command (prompted by the Rspack supply chain attack)
  • pnpm settings: ignoreScripts, ignoreDepScripts, onlyBuiltDependencies, neverBuiltDependencies, dangerouslyAllowAllBuilds
  • Bun runs lifecycle scripts by default but supports --ignore-scripts
  • Consider using npm audit, socket.dev, or similar tools to detect suspicious install scripts

Deprecation notes:

  • prepublish behavior is confusing (runs on install too); use prepublishOnly or prepare instead
  • npm deprecated running prepublish on install in npm 5+

Rust (Cargo)

HookWhen it runs
build.rsBefore compilation, generates code or sets build flags

Mechanism: Build script at crate root

// build.rs
fn main() {
    // Generate code, link native libraries, set cfg flags
    println!("cargo:rustc-link-lib=foo");
}

Documentation: https://doc.rust-lang.org/cargo/reference/build-scripts.html

Capabilities:

  • Generate source code (OUT_DIR)
  • Link native libraries
  • Set compiler flags via cargo:rustc-* directives
  • Rerun conditionally via cargo:rerun-if-changed

Security notes:

  • Build scripts run arbitrary code at compile time
  • cargo-vet and cargo-crev can audit dependencies
  • No sandbox by default
  • Build scripts cannot run during cargo install from crates.io (only during source builds)

Additional hooks (via Cargo configuration, not per-package):

  • Custom runners for tests/benches
  • Custom linkers

Python (pip, uv, Poetry, pipenv, PDM, Hatch)

Wheel packages (PEP 427): Hooks are explicitly forbidden. Wheels are designed to be "unpack and go" with no arbitrary code execution at install time.

Source distributions (sdist): Can run setup.py which executes arbitrary Python code.

HookWhen it runs
setup.pyDuring pip install from source

Mechanism: setup.py at package root (legacy) or build backend

# setup.py
from setuptools import setup
from setuptools.command.install import install

class PostInstall(install):
    def run(self):
        install.run(self)
        # custom code here

setup(cmdclass={'install': PostInstall})

Documentation:

Security notes:

  • Prefer wheels over sdists to avoid arbitrary code execution
  • pip install --only-binary :all: forces wheel-only installation
  • Modern PEP 517/518 build backends (pyproject.toml) are sandboxed better than raw setup.py
  • uv and pip both support --no-build-isolation which can expose the build environment

Poetry/PDM/Hatch: These tools primarily use PEP 517 build backends and don't add their own hook mechanisms for package authors.


PHP (Composer)

HookWhen it runs
pre-install-cmdBefore install command
post-install-cmdAfter install command
pre-update-cmdBefore update command
post-update-cmdAfter update command
pre-autoload-dumpBefore autoloader is generated
post-autoload-dumpAfter autoloader is generated
post-root-package-installAfter root package is installed
post-create-project-cmdAfter create-project command
pre-archive-cmdBefore archive command
post-archive-cmdAfter archive command
pre-package-installBefore a package is installed
post-package-installAfter a package is installed
pre-package-updateBefore a package is updated
post-package-updateAfter a package is updated
pre-package-uninstallBefore a package is uninstalled
post-package-uninstallAfter a package is uninstalled

Mechanism: scripts field in composer.json

{
  "scripts": {
    "post-install-cmd": "MyVendor\\MyClass::postInstall",
    "post-package-install": ["php scripts/install.php"]
  }
}

Documentation: https://getcomposer.org/doc/articles/scripts.md

Critical security note: Only the root package's scripts are executed. Scripts defined in dependencies are NOT run. This is a deliberate security design decision.

Additional notes:

  • Scripts can be PHP callbacks or shell commands
  • Custom script names are supported
  • @composer and @php are available as portable binary references

Ruby (RubyGems, Bundler)

HookWhen it runs
extconf.rbDuring native extension compilation
Rakefile tasksVarious build/test tasks

Mechanism: Extensions in gemspec

# example.gemspec
Gem::Specification.new do |s|
  s.extensions = ['ext/example/extconf.rb']
end

Documentation:

Security notes:

  • extconf.rb runs arbitrary Ruby code during gem install
  • Native extensions require compilation (runs make/gcc)
  • No sandbox by default
  • bundle config set --local force_ruby_platform true can help avoid precompiled binaries

Bundler: Does not add additional hook mechanisms for package authors; uses RubyGems' extension system.


.NET (NuGet, dotnet)

HookWhen it runsStatus
install.ps1After package installDeprecated in NuGet 3.x
uninstall.ps1Before package uninstallDeprecated in NuGet 3.x
init.ps1First time package is loaded in solutionStill works in VS

Mechanism (legacy): PowerShell scripts in package tools/ folder

Documentation: https://learn.microsoft.com/en-us/nuget/concepts/package-installation-process

Current approach: NuGet moved away from install scripts in favor of:

  • MSBuild props/targets for build-time integration
  • Source generators for compile-time code generation
<!-- In .nuspec or package -->
<files>
  <file src="build\MyPackage.props" target="build" />
  <file src="build\MyPackage.targets" target="build" />
</files>

Security notes:

  • PowerShell scripts only ran in Visual Studio, not dotnet restore
  • Modern NuGet is essentially "unpack only" like Python wheels
  • MSBuild targets do run at build time, which is a potential vector

Java (Maven)

Maven uses a build lifecycle with phases, not traditional hooks.

PhaseDescription
validateValidate project
compileCompile source
testRun tests
packagePackage into JAR/WAR
verifyRun integration tests
installInstall to local repo
deployDeploy to remote repo

Mechanism: Plugin executions bound to phases in pom.xml

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <phase>install</phase>
      <goals><goal>run</goal></goals>
      <configuration>
        <target>
          <!-- arbitrary Ant tasks -->
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

Documentation: https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

Security notes:

  • Plugin code executes during build
  • No install-time hooks when adding as dependency (unlike npm)
  • Plugins are code—audit your plugin dependencies
  • Maven Central has signing requirements

Java/Kotlin (Gradle)

HookWhen it runs
beforeEvaluateBefore project is evaluated
afterEvaluateAfter project is evaluated
Task lifecycle hooksBefore/after specific tasks

Mechanism: Build scripts (build.gradle or build.gradle.kts)

// build.gradle
afterEvaluate {
    println "Project evaluated"
}

tasks.named('jar') {
    doFirst { println "Before jar" }
    doLast { println "After jar" }
}

Documentation: https://docs.gradle.org/current/userguide/build_lifecycle.html

Security notes:

  • Build scripts are code (Groovy or Kotlin)
  • --no-daemon can help with reproducibility
  • Gradle wrapper (gradlew) should be committed to ensure version consistency
  • Init scripts (system hooks, see below) can inject behavior

Debian/Ubuntu (dpkg/apt)

HookWhen it runs
preinstBefore package files are unpacked
postinstAfter package files are unpacked
prermBefore package is removed
postrmAfter package is removed
configFor debconf configuration (before preinst)

Mechanism: Shell scripts in DEBIAN/ directory of .deb package

#!/bin/bash
# DEBIAN/postinst
case "$1" in
    configure)
        systemctl enable myservice
        ;;
esac

Arguments passed to scripts:

  • preinst: install, upgrade, abort-upgrade
  • postinst: configure, abort-upgrade, abort-remove, abort-deconfigure
  • prerm: remove, upgrade, deconfigure
  • postrm: remove, purge, upgrade, disappear, abort-install, abort-upgrade

Documentation: https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html

Security notes:

  • Scripts run as root
  • dpkg verifies package signatures but scripts still execute
  • Maintainer scripts are reviewed for official Debian packages
  • Third-party repos may have less oversight

Red Hat/Fedora (RPM/dnf/yum)

HookWhen it runs
%pretransBefore any packages in transaction
%preBefore package files are installed
%postAfter package files are installed
%preunBefore package files are removed
%postunAfter package files are removed
%posttransAfter all packages in transaction
%triggerpreinTrigger before other package installed
%triggerinTrigger when other package installed
%triggerunTrigger before other package removed
%triggerpostunTrigger after other package removed

Mechanism: Scriptlets in .spec file

%post
systemctl daemon-reload
systemctl enable myservice

%preun
if [ $1 -eq 0 ]; then
    systemctl stop myservice
fi

Documentation: https://rpm-software-management.github.io/rpm/manual/scriptlet_expansion.html

Security notes:

  • Scripts run as root
  • $1 argument indicates install (1), upgrade (2), or uninstall (0)
  • RPM supports signature verification
  • Fedora/RHEL packages go through review process

macOS/iOS (CocoaPods)

HookWhen it runs
prepare_commandAfter pod is downloaded, before integration

Mechanism: In podspec

Pod::Spec.new do |s|
  s.prepare_command = <<-CMD
    sed -i '' 's/old/new/g' SomeFile.m
  CMD
end

Documentation: https://guides.cocoapods.org/syntax/podspec.html#prepare_command

Security notes:

  • prepare_command runs shell commands
  • Only runs on initial install or when pod changes
  • Consider using --no-prepare-command during install
  • CocoaPods specs are centrally hosted and can be audited

Swift (Swift Package Manager)

Swift Package Manager intentionally does not support lifecycle hooks for security reasons.

Documentation: https://www.swift.org/documentation/package-manager/

Build-time customization:

  • Package plugins (Swift 5.6+) can extend build process
  • Plugins are sandboxed and require explicit permissions
// Package.swift
.plugin(
    name: "MyPlugin",
    capability: .buildTool()
)

Plugin documentation: https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-extensible-build-tools.md


Go

Go modules do not support lifecycle hooks.

go generate is a separate command that runs directives in source files:

//go:generate stringer -type=Pill

This is opt-in and explicit, not automatic during install.

Documentation:

Security notes:

  • No arbitrary code execution on go get or go install
  • go generate must be explicitly run
  • This is a deliberate security design decision

Haskell (Cabal, Stack)

HookWhen it runs
Setup.hsCustom build phases

Mechanism: Custom Setup.hs with build type Custom

-- Setup.hs
import Distribution.Simple
main = defaultMainWithHooks simpleUserHooks
    { postBuild = \args flags desc lbi -> do
        putStrLn "Post-build hook"
    }

Documentation:

Security notes:

  • Setup.hs runs at build time
  • Modern Cabal encourages build-type: Simple to avoid custom setup
  • Stack sandboxes builds by default

Elixir (Mix)

HookWhen it runs
Compiler callbacksDuring compilation

Mechanism: Mix tasks and compiler hooks

# mix.exs
defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      compilers: [:custom_compiler] ++ Mix.compilers()
    ]
  end
end

Documentation: https://hexdocs.pm/mix/Mix.html

Security notes:

  • Mix tasks run Elixir code
  • Dependencies compile on first use
  • Hex.pm packages are checksummed

Erlang (rebar3)

HookWhen it runs
Pre/post hooksAround compile, clean, etc.

Mechanism: rebar.config

{pre_hooks, [{compile, "echo pre-compile"}]}.
{post_hooks, [{compile, "echo post-compile"}]}.

Documentation: https://rebar3.readme.io/docs/configuration#hooks


Dart/Flutter (pub)

While pub (as a package manager) does not support lifecycle hooks for packages, most Dart embedders (Flutter, dart run and dart build) do.

HookWhen it runs
Build hooksDuring compilation

Documentation: https://dart.dev/tools/hooks

Security notes:

  • hook/build.dart runs arbitrary Dart code.

Perl (CPAN, cpanm)

HookWhen it runs
Makefile.PLBefore build
Build.PLBefore build (Module::Build)

Mechanism: Perl scripts that generate makefiles

# Makefile.PL
use ExtUtils::MakeMaker;
WriteMakefile(
    NAME => 'My::Module',
    # custom code can run here
);

Documentation: https://metacpan.org/pod/ExtUtils::MakeMaker

Security notes:

  • Makefile.PL runs arbitrary Perl code
  • Consider cpanm --notest to skip test phase but not install scripts
  • CPAN Testers provides community vetting

Lua (LuaRocks)

HookWhen it runs
build.install.binInstall executables
Build type hooksDuring build phase

Mechanism: Rockspec file

-- myrock-1.0-1.rockspec
build = {
    type = "command",
    build_command = "make",
    install_command = "make install"
}

Documentation: https://github.com/luarocks/luarocks/wiki/Rockspec-format


C/C++ (Conan)

HookWhen it runs
build()During package build
package()During package creation

Mechanism: conanfile.py

class MyConan(ConanFile):
    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

Documentation: https://docs.conan.io/2/reference/conanfile.html


OCaml (opam)

HookWhen it runs
buildBuild commands
installInstall commands
removeUninstall commands

Mechanism: opam file

build: ["dune" "build" "-p" name "-j" jobs]
install: ["dune" "install" "-p" name]

Documentation: https://opam.ocaml.org/doc/Manual.html


Clojure (Leiningen)

HookWhen it runs
Various hooksAround any Leiningen task

Mechanism: project.clj or plugins

;; project.clj
:hooks [leiningen.hooks.my-hook]

Documentation: https://codeberg.org/leiningen/leiningen/src/branch/main/doc/PLUGINS.md


Alpine Linux (apk)

HookWhen it runs
pre-installBefore package install
post-installAfter package install
pre-deinstallBefore package removal
post-deinstallAfter package removal
pre-upgradeBefore package upgrade
post-upgradeAfter package upgrade

Mechanism: Scripts in .INSTALL file within package

Documentation: https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package


Arch Linux (pacman)

HookWhen it runs
pre_installBefore install
post_installAfter install
pre_upgradeBefore upgrade
post_upgradeAfter upgrade
pre_removeBefore removal
post_removeAfter removal

Mechanism: Install scriptlet in PKGBUILD

post_install() {
    systemctl daemon-reload
}

Documentation: https://wiki.archlinux.org/title/PKGBUILD#install


FreeBSD (pkg)

HookWhen it runs
pre-installBefore installation
post-installAfter installation
pre-deinstallBefore removal
post-deinstallAfter removal

Mechanism: Shell scripts in package

Documentation: https://docs.freebsd.org/en/books/porters-handbook/pkg-files/


Homebrew (brew)

HookWhen it runs
post_installAfter installation

Mechanism: Ruby block in formula

class MyFormula < Formula
  def post_install
    system "some-command"
  end
end

Documentation: https://docs.brew.sh/Formula-Cookbook

Security notes:

  • Formulas are Ruby code
  • Core formulas are reviewed
  • Third-party taps may have less oversight

Nix

Nix derivations are pure by design—no hooks that run arbitrary code outside the sandbox.

Build phases exist but run in isolated environments:

{
  postInstall = ''
    wrapProgram $out/bin/foo
  '';
}

Documentation: https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases

Security notes:

  • Builds are sandboxed and reproducible
  • No network access during build by default
  • This is a deliberate security design decision

Containers (Docker, Podman)

Docker images don't have "install hooks" but have build-time instructions:

InstructionWhen it runs
RUNDuring image build
ENTRYPOINTWhen container starts
CMDWhen container starts (if no command given)
HEALTHCHECKPeriodically after container starts

Mechanism: Dockerfile / Containerfile

Documentation:


Terraform

Terraform providers don't have lifecycle hooks in the traditional sense. Resources have:

LifecyclePurpose
create_before_destroyOrder of operations
prevent_destroySafety mechanism
ignore_changesDrift handling
ProvisionersRun scripts (discouraged)

Mechanism: lifecycle block and provisioners

resource "aws_instance" "example" {
  lifecycle {
    create_before_destroy = true
  }

  provisioner "local-exec" {
    command = "echo created"
  }
}

Documentation: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax

Security notes:

  • Provisioners are discouraged; use configuration management instead
  • Provisioners run locally or over SSH

Helm (Kubernetes)

HookWhen it runs
pre-installBefore resources are loaded
post-installAfter all resources are loaded
pre-deleteBefore resources are deleted
post-deleteAfter resources are deleted
pre-upgradeBefore resources are updated
post-upgradeAfter resources are updated
pre-rollbackBefore rollback
post-rollbackAfter rollback
testWhen helm test is run

Mechanism: Annotations on Kubernetes resources

apiVersion: batch/v1
kind: Job
metadata:
  annotations:
    "helm.sh/hook": post-install

Documentation: https://helm.sh/docs/topics/charts_hooks/


Ansible Galaxy

Ansible roles/collections don't have install-time hooks. Execution happens when playbooks run.

Documentation: https://docs.ansible.com/ansible/latest/galaxy/user_guide.html


Snap

HookWhen it runs
configureOn install, refresh, or snap set
installAfter snap install
pre-refreshBefore snap refresh
post-refreshAfter snap refresh
removeBefore snap removal

Mechanism: Scripts in snap/hooks/

Documentation: https://snapcraft.io/docs/supported-snap-hooks

Security notes:

  • Snaps run confined by default
  • Hooks run with snap's confinement level

Void Linux (xbps)

HookWhen it runs
pre_installBefore package install
post_installAfter package install
pre_removeBefore package removal
post_removeAfter package removal

Mechanism: Functions in package template

Documentation: https://github.com/void-linux/void-packages/blob/master/Manual.md


Gentoo (Portage/emerge)

HookWhen it runs
pkg_pretendCheck system requirements
pkg_setupBefore compilation
pkg_preinstBefore merge
pkg_postinstAfter merge
pkg_prermBefore unmerge
pkg_postrmAfter unmerge
pkg_configUser-triggered configuration

Mechanism: Functions in ebuild file

pkg_postinst() {
    elog "Run 'emerge --config ${PN}' to configure"
}

Documentation: https://devmanual.gentoo.org/ebuild-writing/functions/


Slackware (pkgtools, slackpkg)

HookWhen it runs
doinst.shAfter package extraction

Mechanism: Script in package install/ directory

Documentation: https://www.slackbook.org/html/package-management.html


openSUSE (zypper)

Zypper uses RPM packages, so RPM scriptlets apply. See RPM section above.

Additional zypper hooks: Zypper itself supports commit plugins and service hooks.

Documentation: https://doc.opensuse.org/documentation/leap/reference/html/book-reference/cha-sw-cl.html


Mageia (urpmi)

Uses RPM packages. See RPM section above.

Documentation: https://wiki.mageia.org/en/URPMI


PCLinuxOS (smart)

Uses RPM packages. See RPM section above.

Documentation: https://www.pclinuxos.com/forum/


Conda / Mamba

HookWhen it runs
pre-linkBefore package is linked
post-linkAfter package is linked
pre-unlinkBefore package is unlinked

Mechanism: Scripts in <package>/Scripts/ (Windows) or <package>/bin/ (Unix)

#!/bin/bash
# bin/.mypackage-post-link.sh
echo "Package installed"

Documentation:

Security notes:

  • Scripts run during environment modification
  • Conda-forge has review process

Other System Package Managers

Most system package managers (zypper, dnf, yum, urpmi, etc.) defer to the underlying package format (RPM, deb) for scriptlets. They add repository-level and transaction-level hooks (see System/Plugin Hooks section).


Summary Table: Package-Defined Hooks Support

Package ManagerHooks SupportedSecurity Model
npm/Yarn/pnpm/BunYesScripts run, can be disabled
Cargobuild.rs onlyCompile-time only
pip (wheel)NoBy design
pip (sdist)Yessetup.py runs
ComposerRoot package onlyDependencies' scripts ignored
RubyGemsExtensions onlyextconf.rb for native code
NuGetDeprecatedMSBuild targets only
MavenBuild pluginsNo install-time hooks
GradleBuild scriptsNo install-time hooks
dpkg/RPMYesRun as root
CocoaPodsprepare_commandShell command
SwiftPMNoBy design
GoNoBy design
NixSandboxedPure builds
CondaYesScripts in package

System/Plugin Hooks

These hooks let users extend package manager behavior globally, independent of specific packages.

npm

Hook TypeDescription
.npmrc scriptsNot supported
npm hooks (deprecated)Webhooks for registry events

npm does not have a local plugin/hook system for extending CLI behavior. The npm hook command was for registry webhooks (deprecated).

Workaround: Use shell aliases or wrapper scripts.

Documentation: https://docs.npmjs.com/cli/v10/commands/npm-hook


Yarn

Yarn Classic (1.x): No plugin system.

Yarn Berry (2+): Full plugin system.

HookDescription
afterAllInstalledAfter all packages installed
validateProjectValidate project configuration
resolveStepCustom resolution logic
fetchStepCustom fetch logic
linkStepCustom linking logic

Mechanism: Yarn plugins (JavaScript)

module.exports = {
  name: 'my-plugin',
  factory: require => ({
    hooks: {
      afterAllInstalled() {
        console.log('Done!');
      },
    },
  }),
};

Documentation: https://yarnpkg.com/advanced/plugin-tutorial


pnpm

HookDescriptionVersion
readPackageMutate dependency's package.json after parsing-
afterAllResolvedMutate lockfile after dependencies resolved-
preResolutionModify lockfile before resolution-
importPackageCustomize how packages are written to node_modules-
fetchersOverride fetchers for different dependency types-
updateConfigModify pnpm configurationv10.8.0

Mechanism: .pnpmfile.cjs hook file at project root

// .pnpmfile.cjs
module.exports = {
  hooks: {
    readPackage(pkg) {
      // Modify pkg before resolution
      return pkg;
    },
    afterAllResolved(lockfile) {
      // Mutate lockfile before serialization
      return lockfile;
    },
  },
};

Documentation: https://pnpm.io/pnpmfile


Cargo

HookDescription
[alias]Command aliases
Custom subcommandscargo-* binaries in PATH

Mechanism: .cargo/config.toml and PATH

# .cargo/config.toml
[alias]
b = "build"
mycommand = ["run", "--bin", "mytool"]

Custom subcommands: Any cargo-foo binary in PATH becomes cargo foo.

Documentation: https://doc.rust-lang.org/cargo/reference/config.html


pip / uv

No plugin or hook system for extending pip/uv behavior.

Documentation:


Poetry

HookDescription
PluginsExtend Poetry functionality

Mechanism: Poetry plugins (Python packages)

from poetry.plugins.plugin import Plugin

class MyPlugin(Plugin):
    def activate(self, poetry, io):
        pass

Documentation: https://python-poetry.org/docs/plugins/


Bundler

HookDescription
Bundler pluginsExtend Bundler functionality

Mechanism: Plugins via gems

# Register hooks
Bundler::Plugin.add_hook('after-install') do |deps|
  puts "Installed: #{deps.map(&:name)}"
end

Documentation: https://bundler.io/guides/bundler_plugins.html


Composer

HookDescription
Composer pluginsExtend Composer

Mechanism: Packages of type composer-plugin

class MyPlugin implements PluginInterface, EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            'post-install-cmd' => 'onPostInstall',
        ];
    }
}

Documentation: https://getcomposer.org/doc/articles/plugins.md


Maven

HookDescription
ExtensionsModify Maven core
settings.xml profilesGlobal configuration

Mechanism: .mvn/extensions.xml

<extensions>
  <extension>
    <groupId>com.example</groupId>
    <artifactId>my-extension</artifactId>
    <version>1.0</version>
  </extension>
</extensions>

Documentation: https://maven.apache.org/guides/mini/guide-using-extensions.html


Gradle

HookDescription
Init scriptsRun before every build
Settings pluginsModify settings
Convention pluginsReusable build logic

Mechanism: ~/.gradle/init.gradle or ~/.gradle/init.d/*.gradle

// ~/.gradle/init.gradle
allprojects {
    repositories {
        mavenLocal()
    }
}

Documentation: https://docs.gradle.org/current/userguide/init_scripts.html


apt

HookDescription
APT::Update::Pre-InvokeBefore update
APT::Update::Post-InvokeAfter update
DPkg::Pre-InvokeBefore dpkg runs
DPkg::Post-InvokeAfter dpkg runs

Mechanism: /etc/apt/apt.conf.d/

// /etc/apt/apt.conf.d/99custom
DPkg::Post-Invoke { "echo 'dpkg finished'"; };

Documentation: https://manpages.debian.org/stable/apt/apt.conf.5.en.html


dnf / yum

HookDescription
PluginsExtend dnf functionality
Transaction hooksBefore/after transactions

Mechanism: Python plugins in /etc/dnf/plugins/ or /usr/lib/python*/site-packages/dnf-plugins/

Documentation: https://dnf.readthedocs.io/en/latest/api_plugins.html


pacman

HookDescription
Alpm hooksRun scripts on file triggers

Mechanism: /etc/pacman.d/hooks/*.hook

[Trigger]
Type = File
Operation = Install
Target = usr/lib/modules/*/vmlinuz

[Action]
When = PostTransaction
Exec = /usr/bin/mkinitcpio -P

Documentation: https://man.archlinux.org/man/alpm-hooks.5


Homebrew

HookDescription
TapsThird-party formula repos
External commandsbrew-* executables in PATH

Mechanism: Any brew-foo script in PATH becomes brew foo.

Documentation: https://docs.brew.sh/External-Commands


Nix

HookDescription
OverlaysModify package set
FlakesComposable configurations

Mechanism: ~/.config/nixpkgs/overlays/

# ~/.config/nixpkgs/overlays/my-overlay.nix
self: super: {
  myPackage = super.myPackage.override { ... };
}

Documentation: https://nixos.wiki/wiki/Overlays


Docker / Podman

HookDescription
BuildKit frontendsCustom Dockerfile syntax
Registry webhooksExternal notification

Documentation:


Helm

HookDescription
PluginsExtend Helm CLI
Post-renderersModify manifests before apply

Mechanism: Plugins installed via helm plugin install

Documentation: https://helm.sh/docs/topics/plugins/


Terraform

HookDescription
Provider pluginsAdd resource types
External data sourcesCall external programs

Mechanism: Provider binaries in plugin cache

Documentation: https://developer.hashicorp.com/terraform/plugin


Portage (Gentoo)

HookDescription
bashrc hooksRun before/after phases
Portage hooksGlobal event hooks

Mechanism: /etc/portage/bashrc and /etc/portage/env/

# /etc/portage/bashrc
post_src_install() {
    einfo "Custom post-install hook"
}

Documentation: https://wiki.gentoo.org/wiki//etc/portage/bashrc


Summary Table: System/Plugin Hooks

Package ManagerPlugin SystemConfig HooksExternal Commands
npmNoNoNo
Yarn BerryYesYesNo
pnpmYes (.pnpmfile.cjs)Yes (v10.8.0+)No
CargoNoYes (config)Yes (cargo-*)
pip/uvNoNoNo
PoetryYesNoNo
BundlerYesNoNo
ComposerYesNoNo
MavenYes (extensions)YesNo
GradleYesYes (init scripts)No
aptNoYesNo
dnf/yumYesYesNo
pacmanNoYes (alpm hooks)No
HomebrewNoNoYes (brew-*)
NixYes (overlays)YesNo
HelmYesNoNo
PortageNoYes (bashrc)No

License

This document is released under CC0 1.0 Universal (Public Domain).

ecosyste-ms/package-manager-hooks

A reference for lifecycle hooks across different package manager ecosystems.

5

6 commits

updated Jun 28, 2026

See the code

README

Package Manager Lifecycle Hooks

A reference for lifecycle hooks across different package manager ecosystems.

This is part of a larger effort to document package manager data for the ecosyste.ms project.

Overview

Package manager hooks fall into two distinct categories:

  1. Package-Defined Hooks: Scripts or code that package authors define to run during their own package's lifecycle (install, build, publish, uninstall). These execute when someone installs or uses your package.

  2. System/Plugin Hooks: Extension points that let users, tools, or plugins modify the package manager's behavior globally across all operations, not tied to any specific package.

This distinction matters for security: package-defined hooks represent supply chain attack surface (third-party code runs on your machine), while system hooks are user-controlled.


Package-Defined Hooks

JavaScript (npm, Yarn, pnpm, Bun, Deno)

HookWhen it runs
preinstallBefore package installation
installDuring installation
postinstallAfter installation
preuninstallBefore uninstallation
uninstallDuring uninstallation
postuninstallAfter uninstallation
prepublishBefore publish and local install (deprecated behavior)
prepublishOnlyBefore publish only
prepackBefore tarball is packed
postpackAfter tarball is packed
prepareAfter install (both local and from registry) and before publish
dependenciesAfter any dependency changes

Mechanism: scripts field in package.json

{
  "scripts": {
    "preinstall": "node check-env.js",
    "postinstall": "node build-native.js"
  }
}

Documentation:

pnpm-specific hooks:

  • pnpm:devPreinstall - Runs only on local pnpm install, before any dependency is installed (root package.json only)

Security notes:

  • postinstall is the most common vector for supply chain attacks
  • npm 7+ runs lifecycle scripts in a sandbox by default (can be disabled with --ignore-scripts=false)
  • Yarn Berry disables lifecycle scripts by default for non-root packages
  • pnpm v10+ blocks dependency lifecycle scripts by default - packages must be explicitly approved via onlyBuiltDependencies in package.json or pnpm approve-builds command (prompted by the Rspack supply chain attack)
  • pnpm settings: ignoreScripts, ignoreDepScripts, onlyBuiltDependencies, neverBuiltDependencies, dangerouslyAllowAllBuilds
  • Bun runs lifecycle scripts by default but supports --ignore-scripts
  • Consider using npm audit, socket.dev, or similar tools to detect suspicious install scripts

Deprecation notes:

  • prepublish behavior is confusing (runs on install too); use prepublishOnly or prepare instead
  • npm deprecated running prepublish on install in npm 5+

Rust (Cargo)

HookWhen it runs
build.rsBefore compilation, generates code or sets build flags

Mechanism: Build script at crate root

// build.rs
fn main() {
    // Generate code, link native libraries, set cfg flags
    println!("cargo:rustc-link-lib=foo");
}

Documentation: https://doc.rust-lang.org/cargo/reference/build-scripts.html

Capabilities:

  • Generate source code (OUT_DIR)
  • Link native libraries
  • Set compiler flags via cargo:rustc-* directives
  • Rerun conditionally via cargo:rerun-if-changed

Security notes:

  • Build scripts run arbitrary code at compile time
  • cargo-vet and cargo-crev can audit dependencies
  • No sandbox by default
  • Build scripts cannot run during cargo install from crates.io (only during source builds)

Additional hooks (via Cargo configuration, not per-package):

  • Custom runners for tests/benches
  • Custom linkers

Python (pip, uv, Poetry, pipenv, PDM, Hatch)

Wheel packages (PEP 427): Hooks are explicitly forbidden. Wheels are designed to be "unpack and go" with no arbitrary code execution at install time.

Source distributions (sdist): Can run setup.py which executes arbitrary Python code.

HookWhen it runs
setup.pyDuring pip install from source

Mechanism: setup.py at package root (legacy) or build backend

# setup.py
from setuptools import setup
from setuptools.command.install import install

class PostInstall(install):
    def run(self):
        install.run(self)
        # custom code here

setup(cmdclass={'install': PostInstall})

Documentation:

Security notes:

  • Prefer wheels over sdists to avoid arbitrary code execution
  • pip install --only-binary :all: forces wheel-only installation
  • Modern PEP 517/518 build backends (pyproject.toml) are sandboxed better than raw setup.py
  • uv and pip both support --no-build-isolation which can expose the build environment

Poetry/PDM/Hatch: These tools primarily use PEP 517 build backends and don't add their own hook mechanisms for package authors.


PHP (Composer)

HookWhen it runs
pre-install-cmdBefore install command
post-install-cmdAfter install command
pre-update-cmdBefore update command
post-update-cmdAfter update command
pre-autoload-dumpBefore autoloader is generated
post-autoload-dumpAfter autoloader is generated
post-root-package-installAfter root package is installed
post-create-project-cmdAfter create-project command
pre-archive-cmdBefore archive command
post-archive-cmdAfter archive command
pre-package-installBefore a package is installed
post-package-installAfter a package is installed
pre-package-updateBefore a package is updated
post-package-updateAfter a package is updated
pre-package-uninstallBefore a package is uninstalled
post-package-uninstallAfter a package is uninstalled

Mechanism: scripts field in composer.json

{
  "scripts": {
    "post-install-cmd": "MyVendor\\MyClass::postInstall",
    "post-package-install": ["php scripts/install.php"]
  }
}

Documentation: https://getcomposer.org/doc/articles/scripts.md

Critical security note: Only the root package's scripts are executed. Scripts defined in dependencies are NOT run. This is a deliberate security design decision.

Additional notes:

  • Scripts can be PHP callbacks or shell commands
  • Custom script names are supported
  • @composer and @php are available as portable binary references

Ruby (RubyGems, Bundler)

HookWhen it runs
extconf.rbDuring native extension compilation
Rakefile tasksVarious build/test tasks

Mechanism: Extensions in gemspec

# example.gemspec
Gem::Specification.new do |s|
  s.extensions = ['ext/example/extconf.rb']
end

Documentation:

Security notes:

  • extconf.rb runs arbitrary Ruby code during gem install
  • Native extensions require compilation (runs make/gcc)
  • No sandbox by default
  • bundle config set --local force_ruby_platform true can help avoid precompiled binaries

Bundler: Does not add additional hook mechanisms for package authors; uses RubyGems' extension system.


.NET (NuGet, dotnet)

HookWhen it runsStatus
install.ps1After package installDeprecated in NuGet 3.x
uninstall.ps1Before package uninstallDeprecated in NuGet 3.x
init.ps1First time package is loaded in solutionStill works in VS

Mechanism (legacy): PowerShell scripts in package tools/ folder

Documentation: https://learn.microsoft.com/en-us/nuget/concepts/package-installation-process

Current approach: NuGet moved away from install scripts in favor of:

  • MSBuild props/targets for build-time integration
  • Source generators for compile-time code generation
<!-- In .nuspec or package -->
<files>
  <file src="build\MyPackage.props" target="build" />
  <file src="build\MyPackage.targets" target="build" />
</files>

Security notes:

  • PowerShell scripts only ran in Visual Studio, not dotnet restore
  • Modern NuGet is essentially "unpack only" like Python wheels
  • MSBuild targets do run at build time, which is a potential vector

Java (Maven)

Maven uses a build lifecycle with phases, not traditional hooks.

PhaseDescription
validateValidate project
compileCompile source
testRun tests
packagePackage into JAR/WAR
verifyRun integration tests
installInstall to local repo
deployDeploy to remote repo

Mechanism: Plugin executions bound to phases in pom.xml

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-antrun-plugin</artifactId>
  <executions>
    <execution>
      <phase>install</phase>
      <goals><goal>run</goal></goals>
      <configuration>
        <target>
          <!-- arbitrary Ant tasks -->
        </target>
      </configuration>
    </execution>
  </executions>
</plugin>

Documentation: https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html

Security notes:

  • Plugin code executes during build
  • No install-time hooks when adding as dependency (unlike npm)
  • Plugins are code—audit your plugin dependencies
  • Maven Central has signing requirements

Java/Kotlin (Gradle)

HookWhen it runs
beforeEvaluateBefore project is evaluated
afterEvaluateAfter project is evaluated
Task lifecycle hooksBefore/after specific tasks

Mechanism: Build scripts (build.gradle or build.gradle.kts)

// build.gradle
afterEvaluate {
    println "Project evaluated"
}

tasks.named('jar') {
    doFirst { println "Before jar" }
    doLast { println "After jar" }
}

Documentation: https://docs.gradle.org/current/userguide/build_lifecycle.html

Security notes:

  • Build scripts are code (Groovy or Kotlin)
  • --no-daemon can help with reproducibility
  • Gradle wrapper (gradlew) should be committed to ensure version consistency
  • Init scripts (system hooks, see below) can inject behavior

Debian/Ubuntu (dpkg/apt)

HookWhen it runs
preinstBefore package files are unpacked
postinstAfter package files are unpacked
prermBefore package is removed
postrmAfter package is removed
configFor debconf configuration (before preinst)

Mechanism: Shell scripts in DEBIAN/ directory of .deb package

#!/bin/bash
# DEBIAN/postinst
case "$1" in
    configure)
        systemctl enable myservice
        ;;
esac

Arguments passed to scripts:

  • preinst: install, upgrade, abort-upgrade
  • postinst: configure, abort-upgrade, abort-remove, abort-deconfigure
  • prerm: remove, upgrade, deconfigure
  • postrm: remove, purge, upgrade, disappear, abort-install, abort-upgrade

Documentation: https://www.debian.org/doc/debian-policy/ch-maintainerscripts.html

Security notes:

  • Scripts run as root
  • dpkg verifies package signatures but scripts still execute
  • Maintainer scripts are reviewed for official Debian packages
  • Third-party repos may have less oversight

Red Hat/Fedora (RPM/dnf/yum)

HookWhen it runs
%pretransBefore any packages in transaction
%preBefore package files are installed
%postAfter package files are installed
%preunBefore package files are removed
%postunAfter package files are removed
%posttransAfter all packages in transaction
%triggerpreinTrigger before other package installed
%triggerinTrigger when other package installed
%triggerunTrigger before other package removed
%triggerpostunTrigger after other package removed

Mechanism: Scriptlets in .spec file

%post
systemctl daemon-reload
systemctl enable myservice

%preun
if [ $1 -eq 0 ]; then
    systemctl stop myservice
fi

Documentation: https://rpm-software-management.github.io/rpm/manual/scriptlet_expansion.html

Security notes:

  • Scripts run as root
  • $1 argument indicates install (1), upgrade (2), or uninstall (0)
  • RPM supports signature verification
  • Fedora/RHEL packages go through review process

macOS/iOS (CocoaPods)

HookWhen it runs
prepare_commandAfter pod is downloaded, before integration

Mechanism: In podspec

Pod::Spec.new do |s|
  s.prepare_command = <<-CMD
    sed -i '' 's/old/new/g' SomeFile.m
  CMD
end

Documentation: https://guides.cocoapods.org/syntax/podspec.html#prepare_command

Security notes:

  • prepare_command runs shell commands
  • Only runs on initial install or when pod changes
  • Consider using --no-prepare-command during install
  • CocoaPods specs are centrally hosted and can be audited

Swift (Swift Package Manager)

Swift Package Manager intentionally does not support lifecycle hooks for security reasons.

Documentation: https://www.swift.org/documentation/package-manager/

Build-time customization:

  • Package plugins (Swift 5.6+) can extend build process
  • Plugins are sandboxed and require explicit permissions
// Package.swift
.plugin(
    name: "MyPlugin",
    capability: .buildTool()
)

Plugin documentation: https://github.com/apple/swift-evolution/blob/main/proposals/0303-swiftpm-extensible-build-tools.md


Go

Go modules do not support lifecycle hooks.

go generate is a separate command that runs directives in source files:

//go:generate stringer -type=Pill

This is opt-in and explicit, not automatic during install.

Documentation:

Security notes:

  • No arbitrary code execution on go get or go install
  • go generate must be explicitly run
  • This is a deliberate security design decision

Haskell (Cabal, Stack)

HookWhen it runs
Setup.hsCustom build phases

Mechanism: Custom Setup.hs with build type Custom

-- Setup.hs
import Distribution.Simple
main = defaultMainWithHooks simpleUserHooks
    { postBuild = \args flags desc lbi -> do
        putStrLn "Post-build hook"
    }

Documentation:

Security notes:

  • Setup.hs runs at build time
  • Modern Cabal encourages build-type: Simple to avoid custom setup
  • Stack sandboxes builds by default

Elixir (Mix)

HookWhen it runs
Compiler callbacksDuring compilation

Mechanism: Mix tasks and compiler hooks

# mix.exs
defmodule MyApp.MixProject do
  use Mix.Project

  def project do
    [
      compilers: [:custom_compiler] ++ Mix.compilers()
    ]
  end
end

Documentation: https://hexdocs.pm/mix/Mix.html

Security notes:

  • Mix tasks run Elixir code
  • Dependencies compile on first use
  • Hex.pm packages are checksummed

Erlang (rebar3)

HookWhen it runs
Pre/post hooksAround compile, clean, etc.

Mechanism: rebar.config

{pre_hooks, [{compile, "echo pre-compile"}]}.
{post_hooks, [{compile, "echo post-compile"}]}.

Documentation: https://rebar3.readme.io/docs/configuration#hooks


Dart/Flutter (pub)

While pub (as a package manager) does not support lifecycle hooks for packages, most Dart embedders (Flutter, dart run and dart build) do.

HookWhen it runs
Build hooksDuring compilation

Documentation: https://dart.dev/tools/hooks

Security notes:

  • hook/build.dart runs arbitrary Dart code.

Perl (CPAN, cpanm)

HookWhen it runs
Makefile.PLBefore build
Build.PLBefore build (Module::Build)

Mechanism: Perl scripts that generate makefiles

# Makefile.PL
use ExtUtils::MakeMaker;
WriteMakefile(
    NAME => 'My::Module',
    # custom code can run here
);

Documentation: https://metacpan.org/pod/ExtUtils::MakeMaker

Security notes:

  • Makefile.PL runs arbitrary Perl code
  • Consider cpanm --notest to skip test phase but not install scripts
  • CPAN Testers provides community vetting

Lua (LuaRocks)

HookWhen it runs
build.install.binInstall executables
Build type hooksDuring build phase

Mechanism: Rockspec file

-- myrock-1.0-1.rockspec
build = {
    type = "command",
    build_command = "make",
    install_command = "make install"
}

Documentation: https://github.com/luarocks/luarocks/wiki/Rockspec-format


C/C++ (Conan)

HookWhen it runs
build()During package build
package()During package creation

Mechanism: conanfile.py

class MyConan(ConanFile):
    def build(self):
        cmake = CMake(self)
        cmake.configure()
        cmake.build()

Documentation: https://docs.conan.io/2/reference/conanfile.html


OCaml (opam)

HookWhen it runs
buildBuild commands
installInstall commands
removeUninstall commands

Mechanism: opam file

build: ["dune" "build" "-p" name "-j" jobs]
install: ["dune" "install" "-p" name]

Documentation: https://opam.ocaml.org/doc/Manual.html


Clojure (Leiningen)

HookWhen it runs
Various hooksAround any Leiningen task

Mechanism: project.clj or plugins

;; project.clj
:hooks [leiningen.hooks.my-hook]

Documentation: https://codeberg.org/leiningen/leiningen/src/branch/main/doc/PLUGINS.md


Alpine Linux (apk)

HookWhen it runs
pre-installBefore package install
post-installAfter package install
pre-deinstallBefore package removal
post-deinstallAfter package removal
pre-upgradeBefore package upgrade
post-upgradeAfter package upgrade

Mechanism: Scripts in .INSTALL file within package

Documentation: https://wiki.alpinelinux.org/wiki/Creating_an_Alpine_package


Arch Linux (pacman)

HookWhen it runs
pre_installBefore install
post_installAfter install
pre_upgradeBefore upgrade
post_upgradeAfter upgrade
pre_removeBefore removal
post_removeAfter removal

Mechanism: Install scriptlet in PKGBUILD

post_install() {
    systemctl daemon-reload
}

Documentation: https://wiki.archlinux.org/title/PKGBUILD#install


FreeBSD (pkg)

HookWhen it runs
pre-installBefore installation
post-installAfter installation
pre-deinstallBefore removal
post-deinstallAfter removal

Mechanism: Shell scripts in package

Documentation: https://docs.freebsd.org/en/books/porters-handbook/pkg-files/


Homebrew (brew)

HookWhen it runs
post_installAfter installation

Mechanism: Ruby block in formula

class MyFormula < Formula
  def post_install
    system "some-command"
  end
end

Documentation: https://docs.brew.sh/Formula-Cookbook

Security notes:

  • Formulas are Ruby code
  • Core formulas are reviewed
  • Third-party taps may have less oversight

Nix

Nix derivations are pure by design—no hooks that run arbitrary code outside the sandbox.

Build phases exist but run in isolated environments:

{
  postInstall = ''
    wrapProgram $out/bin/foo
  '';
}

Documentation: https://nixos.org/manual/nixpkgs/stable/#sec-stdenv-phases

Security notes:

  • Builds are sandboxed and reproducible
  • No network access during build by default
  • This is a deliberate security design decision

Containers (Docker, Podman)

Docker images don't have "install hooks" but have build-time instructions:

InstructionWhen it runs
RUNDuring image build
ENTRYPOINTWhen container starts
CMDWhen container starts (if no command given)
HEALTHCHECKPeriodically after container starts

Mechanism: Dockerfile / Containerfile

Documentation:


Terraform

Terraform providers don't have lifecycle hooks in the traditional sense. Resources have:

LifecyclePurpose
create_before_destroyOrder of operations
prevent_destroySafety mechanism
ignore_changesDrift handling
ProvisionersRun scripts (discouraged)

Mechanism: lifecycle block and provisioners

resource "aws_instance" "example" {
  lifecycle {
    create_before_destroy = true
  }

  provisioner "local-exec" {
    command = "echo created"
  }
}

Documentation: https://developer.hashicorp.com/terraform/language/resources/provisioners/syntax

Security notes:

  • Provisioners are discouraged; use configuration management instead
  • Provisioners run locally or over SSH

Helm (Kubernetes)

HookWhen it runs
pre-installBefore resources are loaded
post-installAfter all resources are loaded
pre-deleteBefore resources are deleted
post-deleteAfter resources are deleted
pre-upgradeBefore resources are updated
post-upgradeAfter resources are updated
pre-rollbackBefore rollback
post-rollbackAfter rollback
testWhen helm test is run

Mechanism: Annotations on Kubernetes resources

apiVersion: batch/v1
kind: Job
metadata:
  annotations:
    "helm.sh/hook": post-install

Documentation: https://helm.sh/docs/topics/charts_hooks/


Ansible Galaxy

Ansible roles/collections don't have install-time hooks. Execution happens when playbooks run.

Documentation: https://docs.ansible.com/ansible/latest/galaxy/user_guide.html


Snap

HookWhen it runs
configureOn install, refresh, or snap set
installAfter snap install
pre-refreshBefore snap refresh
post-refreshAfter snap refresh
removeBefore snap removal

Mechanism: Scripts in snap/hooks/

Documentation: https://snapcraft.io/docs/supported-snap-hooks

Security notes:

  • Snaps run confined by default
  • Hooks run with snap's confinement level

Void Linux (xbps)

HookWhen it runs
pre_installBefore package install
post_installAfter package install
pre_removeBefore package removal
post_removeAfter package removal

Mechanism: Functions in package template

Documentation: https://github.com/void-linux/void-packages/blob/master/Manual.md


Gentoo (Portage/emerge)

HookWhen it runs
pkg_pretendCheck system requirements
pkg_setupBefore compilation
pkg_preinstBefore merge
pkg_postinstAfter merge
pkg_prermBefore unmerge
pkg_postrmAfter unmerge
pkg_configUser-triggered configuration

Mechanism: Functions in ebuild file

pkg_postinst() {
    elog "Run 'emerge --config ${PN}' to configure"
}

Documentation: https://devmanual.gentoo.org/ebuild-writing/functions/


Slackware (pkgtools, slackpkg)

HookWhen it runs
doinst.shAfter package extraction

Mechanism: Script in package install/ directory

Documentation: https://www.slackbook.org/html/package-management.html


openSUSE (zypper)

Zypper uses RPM packages, so RPM scriptlets apply. See RPM section above.

Additional zypper hooks: Zypper itself supports commit plugins and service hooks.

Documentation: https://doc.opensuse.org/documentation/leap/reference/html/book-reference/cha-sw-cl.html


Mageia (urpmi)

Uses RPM packages. See RPM section above.

Documentation: https://wiki.mageia.org/en/URPMI


PCLinuxOS (smart)

Uses RPM packages. See RPM section above.

Documentation: https://www.pclinuxos.com/forum/


Conda / Mamba

HookWhen it runs
pre-linkBefore package is linked
post-linkAfter package is linked
pre-unlinkBefore package is unlinked

Mechanism: Scripts in <package>/Scripts/ (Windows) or <package>/bin/ (Unix)

#!/bin/bash
# bin/.mypackage-post-link.sh
echo "Package installed"

Documentation:

Security notes:

  • Scripts run during environment modification
  • Conda-forge has review process

Other System Package Managers

Most system package managers (zypper, dnf, yum, urpmi, etc.) defer to the underlying package format (RPM, deb) for scriptlets. They add repository-level and transaction-level hooks (see System/Plugin Hooks section).


Summary Table: Package-Defined Hooks Support

Package ManagerHooks SupportedSecurity Model
npm/Yarn/pnpm/BunYesScripts run, can be disabled
Cargobuild.rs onlyCompile-time only
pip (wheel)NoBy design
pip (sdist)Yessetup.py runs
ComposerRoot package onlyDependencies' scripts ignored
RubyGemsExtensions onlyextconf.rb for native code
NuGetDeprecatedMSBuild targets only
MavenBuild pluginsNo install-time hooks
GradleBuild scriptsNo install-time hooks
dpkg/RPMYesRun as root
CocoaPodsprepare_commandShell command
SwiftPMNoBy design
GoNoBy design
NixSandboxedPure builds
CondaYesScripts in package

System/Plugin Hooks

These hooks let users extend package manager behavior globally, independent of specific packages.

npm

Hook TypeDescription
.npmrc scriptsNot supported
npm hooks (deprecated)Webhooks for registry events

npm does not have a local plugin/hook system for extending CLI behavior. The npm hook command was for registry webhooks (deprecated).

Workaround: Use shell aliases or wrapper scripts.

Documentation: https://docs.npmjs.com/cli/v10/commands/npm-hook


Yarn

Yarn Classic (1.x): No plugin system.

Yarn Berry (2+): Full plugin system.

HookDescription
afterAllInstalledAfter all packages installed
validateProjectValidate project configuration
resolveStepCustom resolution logic
fetchStepCustom fetch logic
linkStepCustom linking logic

Mechanism: Yarn plugins (JavaScript)

module.exports = {
  name: 'my-plugin',
  factory: require => ({
    hooks: {
      afterAllInstalled() {
        console.log('Done!');
      },
    },
  }),
};

Documentation: https://yarnpkg.com/advanced/plugin-tutorial


pnpm

HookDescriptionVersion
readPackageMutate dependency's package.json after parsing-
afterAllResolvedMutate lockfile after dependencies resolved-
preResolutionModify lockfile before resolution-
importPackageCustomize how packages are written to node_modules-
fetchersOverride fetchers for different dependency types-
updateConfigModify pnpm configurationv10.8.0

Mechanism: .pnpmfile.cjs hook file at project root

// .pnpmfile.cjs
module.exports = {
  hooks: {
    readPackage(pkg) {
      // Modify pkg before resolution
      return pkg;
    },
    afterAllResolved(lockfile) {
      // Mutate lockfile before serialization
      return lockfile;
    },
  },
};

Documentation: https://pnpm.io/pnpmfile


Cargo

HookDescription
[alias]Command aliases
Custom subcommandscargo-* binaries in PATH

Mechanism: .cargo/config.toml and PATH

# .cargo/config.toml
[alias]
b = "build"
mycommand = ["run", "--bin", "mytool"]

Custom subcommands: Any cargo-foo binary in PATH becomes cargo foo.

Documentation: https://doc.rust-lang.org/cargo/reference/config.html


pip / uv

No plugin or hook system for extending pip/uv behavior.

Documentation:


Poetry

HookDescription
PluginsExtend Poetry functionality

Mechanism: Poetry plugins (Python packages)

from poetry.plugins.plugin import Plugin

class MyPlugin(Plugin):
    def activate(self, poetry, io):
        pass

Documentation: https://python-poetry.org/docs/plugins/


Bundler

HookDescription
Bundler pluginsExtend Bundler functionality

Mechanism: Plugins via gems

# Register hooks
Bundler::Plugin.add_hook('after-install') do |deps|
  puts "Installed: #{deps.map(&:name)}"
end

Documentation: https://bundler.io/guides/bundler_plugins.html


Composer

HookDescription
Composer pluginsExtend Composer

Mechanism: Packages of type composer-plugin

class MyPlugin implements PluginInterface, EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            'post-install-cmd' => 'onPostInstall',
        ];
    }
}

Documentation: https://getcomposer.org/doc/articles/plugins.md


Maven

HookDescription
ExtensionsModify Maven core
settings.xml profilesGlobal configuration

Mechanism: .mvn/extensions.xml

<extensions>
  <extension>
    <groupId>com.example</groupId>
    <artifactId>my-extension</artifactId>
    <version>1.0</version>
  </extension>
</extensions>

Documentation: https://maven.apache.org/guides/mini/guide-using-extensions.html


Gradle

HookDescription
Init scriptsRun before every build
Settings pluginsModify settings
Convention pluginsReusable build logic

Mechanism: ~/.gradle/init.gradle or ~/.gradle/init.d/*.gradle

// ~/.gradle/init.gradle
allprojects {
    repositories {
        mavenLocal()
    }
}

Documentation: https://docs.gradle.org/current/userguide/init_scripts.html


apt

HookDescription
APT::Update::Pre-InvokeBefore update
APT::Update::Post-InvokeAfter update
DPkg::Pre-InvokeBefore dpkg runs
DPkg::Post-InvokeAfter dpkg runs

Mechanism: /etc/apt/apt.conf.d/

// /etc/apt/apt.conf.d/99custom
DPkg::Post-Invoke { "echo 'dpkg finished'"; };

Documentation: https://manpages.debian.org/stable/apt/apt.conf.5.en.html


dnf / yum

HookDescription
PluginsExtend dnf functionality
Transaction hooksBefore/after transactions

Mechanism: Python plugins in /etc/dnf/plugins/ or /usr/lib/python*/site-packages/dnf-plugins/

Documentation: https://dnf.readthedocs.io/en/latest/api_plugins.html


pacman

HookDescription
Alpm hooksRun scripts on file triggers

Mechanism: /etc/pacman.d/hooks/*.hook

[Trigger]
Type = File
Operation = Install
Target = usr/lib/modules/*/vmlinuz

[Action]
When = PostTransaction
Exec = /usr/bin/mkinitcpio -P

Documentation: https://man.archlinux.org/man/alpm-hooks.5


Homebrew

HookDescription
TapsThird-party formula repos
External commandsbrew-* executables in PATH

Mechanism: Any brew-foo script in PATH becomes brew foo.

Documentation: https://docs.brew.sh/External-Commands


Nix

HookDescription
OverlaysModify package set
FlakesComposable configurations

Mechanism: ~/.config/nixpkgs/overlays/

# ~/.config/nixpkgs/overlays/my-overlay.nix
self: super: {
  myPackage = super.myPackage.override { ... };
}

Documentation: https://nixos.wiki/wiki/Overlays


Docker / Podman

HookDescription
BuildKit frontendsCustom Dockerfile syntax
Registry webhooksExternal notification

Documentation:


Helm

HookDescription
PluginsExtend Helm CLI
Post-renderersModify manifests before apply

Mechanism: Plugins installed via helm plugin install

Documentation: https://helm.sh/docs/topics/plugins/


Terraform

HookDescription
Provider pluginsAdd resource types
External data sourcesCall external programs

Mechanism: Provider binaries in plugin cache

Documentation: https://developer.hashicorp.com/terraform/plugin


Portage (Gentoo)

HookDescription
bashrc hooksRun before/after phases
Portage hooksGlobal event hooks

Mechanism: /etc/portage/bashrc and /etc/portage/env/

# /etc/portage/bashrc
post_src_install() {
    einfo "Custom post-install hook"
}

Documentation: https://wiki.gentoo.org/wiki//etc/portage/bashrc


Summary Table: System/Plugin Hooks

Package ManagerPlugin SystemConfig HooksExternal Commands
npmNoNoNo
Yarn BerryYesYesNo
pnpmYes (.pnpmfile.cjs)Yes (v10.8.0+)No
CargoNoYes (config)Yes (cargo-*)
pip/uvNoNoNo
PoetryYesNoNo
BundlerYesNoNo
ComposerYesNoNo
MavenYes (extensions)YesNo
GradleYesYes (init scripts)No
aptNoYesNo
dnf/yumYesYesNo
pacmanNoYes (alpm hooks)No
HomebrewNoNoYes (brew-*)
NixYes (overlays)YesNo
HelmYesNoNo
PortageNoYes (bashrc)No

License

This document is released under CC0 1.0 Universal (Public Domain).

Significant stargazers

Alexander Todorov

187 followers · starred Sep 2026