# Lua configuration (`init.lua`) yay can optionally load a Lua configuration file, `init.lua`. `init.lua` overlays whatever is in `config.json`, and any command-line flag you pass still wins over `init.lua`. yay uses the [Lua 5.1 interpreter](https://www.lua.org/manual/5.1/) to run `init.lua`. The Lua standard library is available. ## Location `init.lua` is looked up, in order: 1. `$XDG_CONFIG_HOME/yay/init.lua` 2. `$HOME/.config/yay/init.lua` [Example init.lua](./init.lua) ## Editor support

Available from yay v13.0.2

yay ships a [lua-language-server](https://luals.github.io/) type definition file at [`meta/yay.d.lua`](../meta/yay.d.lua). It declares the `yay` global, every `yay.opt` field, and all autocmd event payloads so your editor can provide completion, hover documentation, and catch typos such as unknown option names or misspelled event fields. ### Setup Point lua-language-server at the `meta/` directory via `workspace.library` in your [configuration file](https://luals.github.io/wiki/configuration/). With a `.luarc.json` in your project root, next to `init.lua`: ```json { "runtime": { "version": "Lua 5.1" }, "workspace": { "library": ["/usr/share/yay/meta"] } } ``` Replace `/usr/share/yay/meta` with the path to the `meta/` directory in your local yay checkout or installation. The runtime version should be `Lua 5.1` because yay runs `init.lua` through the [gopher-lua](https://github.com/yuin/gopher-lua) Lua 5.1 interpreter. lua-language-server is available for [VS Code](https://marketplace.visualstudio.com/items?itemName=sumneko.lua), [Neovim](https://luals.github.io/#neovim-install), [JetBrains](https://plugins.jetbrains.com/plugin/22315-sumnekolua), and [other editors](https://luals.github.io/#other-install) that support the Language Server Protocol. ## Setting options with `yay.opt`

Available from yay v13.0.0

Assign to keys on the `yay.opt` table using the exact option names shown below. ```lua yay.opt.bottom_up = false yay.opt.clean_after = true yay.opt.sort_by = "votes" yay.opt.request_split_n = 150 yay.opt.editor = os.getenv("EDITOR") or "vi" ``` Unknown keys and values of the wrong type are treated as errors. yay aborts startup and reports the offending keys/values so misconfigurations fail fast. ### Available options **Strings** `aururl`, `aurrpcurl`, `build_dir`, `editor`, `editor_flags`, `pager`, `makepkg_bin`, `makepkg_conf`, `pacman_bin`, `pacman_conf`, `redownload`, `rebuild`, `git_bin`, `gpg_bin`, `gpg_flags`, `mflags`, `sort_by`, `search_by`, `git_flags`, `remove_make`, `sudo_bin`, `sudo_flags` **Strings (Available from yay v13.0.1)** `answer_clean`, `answer_diff`, `answer_edit` **Integers** `request_split_n`, `completion_refresh_time`, `max_concurrent_downloads` **Booleans** `bottom_up`, `sudo_loop`, `devel`, `clean_after`, `keep_src`, `provides`, `pgp_fetch`, `clean_menu`, `diff_menu`, `edit_menu`, `combined_upgrade`, `use_ask`, `batch_install`, `single_line_results`, `separate_sources`, `debug`, `rpc`, `double_confirm` A ready-to-copy example lives at [`doc/init.lua`](init.lua). ## Logging with `yay.log`

Available from yay v13.0.0

Lua config and hooks can write through yay's normal logger: ```lua yay.log.debug("build dir:", yay.opt.build_dir) yay.log.info("loaded init.lua") yay.log.warn("skipping", "pkgname") yay.log.error("policy check failed") ``` `debug` only prints when debug logging is enabled. `error` logs an error-level message and does not stop execution; use `yay.abort("message")` for controlled hook stops. ## Requiring modules with `require()`

Available from yay v13.0.1

`init.lua` can pull in other Lua files with the standard `require()` function. yay prepends the directory that contains `init.lua` to `package.path`, so modules resolve relative to your yay config directory rather than the directory you run yay from. Given this layout: ``` $XDG_CONFIG_HOME/yay/ init.lua hooks/ maintainer_change.lua ``` `init.lua` can do: ```lua require("hooks.maintainer_change") ``` `require("name")` looks up, in order: 1. `/name.lua` 2. `/name/init.lua` Dotted module names map onto the filesystem, so `require("hooks.maintainer_change")` loads `/hooks/maintainer_change.lua`. This lets you split hooks and helpers across multiple files and keep `init.lua` small. Required modules run in the same Lua state as `init.lua`, so anything they register through `yay.create_autocmd` or assign to `yay.opt` takes effect just as if it were written inline. ## Upgrade selection hooks

Available from yay v13.0.0

`UpgradeSelect` runs during `yay -Syu` after yay has built and sorted the upgrade graph, and before the native "Packages to exclude" menu is printed. The hook can return package names to exclude. By default, yay still shows the native menu after applying hook exclusions. ```lua yay.create_autocmd("UpgradeSelect", { desc = "skip recently modified AUR upgrades", callback = function(event) local exclude = {} local recent_cutoff = os.time() - (3 * 24 * 60 * 60) for _, pkg in ipairs(event.data.upgrades) do if pkg.repository == "aur" and pkg.last_modified >= recent_cutoff then yay.log.debug("pre-excluding recently modified AUR package:", pkg.name) table.insert(exclude, pkg.name) end end return { exclude = exclude, skip_menu = false } end, }) ``` Multiple `UpgradeSelect` hooks run in registration order. Their `exclude` lists are unioned. If any hook returns `skip_menu = true`, yay applies all hook exclusions and skips the native menu. With `skip_menu = false` or no return value, hook exclusions are applied first and then the native menu is shown. More examples: [`doc/examples/recently_modified.lua`](https://github.com/Jguer/yay/blob/next/doc/examples/recently_modified.lua) (pre-exclude recently modified AUR upgrades) and [`doc/examples/maintainer_change.lua`](https://github.com/Jguer/yay/blob/next/doc/examples/maintainer_change.lua) (warn on AUR maintainer changes). Returned exclusions must name packages from `event.data.upgrades`. Unknown names are treated as hook errors so typos do not silently upgrade the wrong package. Pulled dependencies are visible in `event.data.pulled_dependencies`, but they are removed only when pruning an excluded upgrade candidate requires it. ### UpgradeSelect event The callback receives this table: ```lua { event = "UpgradeSelect", data = { upgrades = { { id = 3, name = "pkgname", base = "pkgbase", repository = "aur", local_version = "1.2.3-3", remote_version = "1.2.3-4", reason = "explicit", last_modified = 1700000000, maintainer = "username", }, }, pulled_dependencies = { { id = 0, name = "depname", base = "", repository = "core", local_version = "", remote_version = "1.0-1", reason = "dependency", last_modified = 0, maintainer = "", }, }, }, } ``` For selectable `data.upgrades` entries, `id` matches the number shown in the native menu. `pulled_dependencies` entries are shown separately by yay and use `id = 0` because they are not directly selectable. ## AUR pre-install hooks

Available from yay v13.0.0

`init.lua` can register hooks with a small autocmd API: ```lua yay.create_autocmd("AURPreInstall", { desc = "inspect or modify AUR package files", callback = function(event) -- event.match is the package base. -- event.data has package metadata and local file paths. end, }) ``` `AURPreInstall` runs once per AUR package base, in sorted package-base order, after the AUR PKGBUILD repositories are downloaded and merged. It runs before the clean, diff, and edit menus, and before source downloads or builds. Use `yay.abort("message")` for controlled policy stops without a Lua traceback. If a callback raises a Lua error, yay aborts the install before build work starts and includes the Lua traceback for debugging. Changing fields in the Lua `event` table does not change yay's internal package state. Hooks can still edit files through Lua's normal `io` and `os` libraries; later menus and build steps will see those file changes. ### AURPreInstall event The callback receives this table: ```lua { event = "AURPreInstall", match = "pkgbase", data = { base = "pkgbase", dir = "/path/to/build/pkgbase", pkgbuild_path = "/path/to/build/pkgbase/PKGBUILD", srcinfo_path = "/path/to/build/pkgbase/.SRCINFO", pkgbuild = "...PKGBUILD contents...", version = "1:1.2.3-4", last_modified = 1700000000, installed = true, packages = { { name = "pkgname", version = "1:1.2.3-4", local_version = "1:1.2.3-3", reason = "explicit", upgrade = true, devel = false, }, }, srcinfo = { pkgbase = "pkgbase", pkgver = "1.2.3", pkgrel = "4", epoch = "1", version = "1:1.2.3-4", pkgdesc = "description", url = "https://example.invalid", arch = { "x86_64" }, license = { "MIT" }, depends = { "glibc" }, makedepends = { "go" }, checkdepends = { "bats" }, optdepends = { "pkg: optional feature" }, provides = { "virtual-pkg" }, conflicts = { "old-pkg" }, replaces = { "older-pkg" }, }, }, } ``` `data.packages` contains the target packages for that base. Split packages are listed separately. `reason` is one of `explicit`, `dependency`, `make_dependency`, `check_dependency`, or `unknown`. ### Example ```lua yay.create_autocmd("AURPreInstall", { desc = "block forbidden sources and patch a PKGBUILD", callback = function(event) if event.data.pkgbuild:match("forbidden.example") then yay.log.warn(event.match .. ": forbidden source URL") yay.abort(event.match .. ": forbidden source URL") end if event.match == "demo-pkg" then local path = event.data.pkgbuild_path local f = assert(io.open(path, "r")) local body = f:read("*a") f:close() body = body:gsub("options=%('strip'%)", "options=('!strip')") f = assert(io.open(path, "w")) f:write(body) f:close() end end, }) ``` ## AUR post-download hooks

Available from yay v13.0.0

`AURPostDownload` runs once per AUR package base, in sorted package-base order, after yay runs `makepkg --verifysource` for package sources and before compatibility checks, PGP key import prompts, builds, or package installs. Use `yay.abort("message")` to stop the operation without a Lua traceback. `AURPostDownload` receives the same payload shape as `AURPreInstall`; only the `event` value differs. ### AURPostDownload event The callback receives this table: ```lua { event = "AURPostDownload", match = "pkgbase", data = { base = "pkgbase", dir = "/path/to/build/pkgbase", pkgbuild_path = "/path/to/build/pkgbase/PKGBUILD", srcinfo_path = "/path/to/build/pkgbase/.SRCINFO", pkgbuild = "...PKGBUILD contents...", version = "1:1.2.3-4", last_modified = 1700000000, installed = true, packages = { ... }, srcinfo = { ... }, }, } ``` ### Example ```lua yay.create_autocmd("AURPostDownload", { desc = "block forbidden source URLs after download", callback = function(event) if event.data.pkgbuild:match("forbidden.example") then yay.abort(event.match .. ": forbidden source URL") end end, }) ``` ## Post-install hooks

Available from yay v13.0.0

`PostInstall` fires once after a successful install/upgrade transaction, before yay exits. It is skipped when `--downloadonly` (`-w`) is used. Because the installation is already complete when the callback runs, calling `yay.abort` logs the message but cannot roll back anything. ### PostInstall event ```lua { event = "PostInstall", data = { packages = { { name = "pkgname", version = "1.2.3-1", -- resolved version local_version = "1.0.0-1", -- previously installed ("" if not installed) source = "aur", -- "aur" | "sync" | "local" | "srcinfo" | "missing" reason = "explicit", -- "explicit" | "dependency" | "make_dependency" | "check_dependency" | "unknown" }, -- one entry per package yay resolved; sorted alphabetically }, }, } ``` The `packages` array covers every node yay resolved into the transaction (all sources, all topo layers). Transitive repo dependencies pulled in by pacman but not explicitly tracked by yay are **not** included. The callback is fire-and-forget: no return value is read. More examples: [`doc/examples/install_log.lua`](https://github.com/Jguer/yay/blob/next/doc/examples/install_log.lua) (append every installed/upgraded package to a persistent log file). ### Example ```lua yay.create_autocmd("PostInstall", { desc = "log every package yay installed", callback = function(event) for _, pkg in ipairs(event.data.packages) do yay.log.info(pkg.name .. " " .. pkg.version .. " (" .. pkg.source .. ")") end end, }) ``` ## Search-filter hooks `SearchFilter` runs during `yay -Ss` and the `yay -S` number menu, after results are ranked and sorted but before they are displayed. The callback receives the full ordered result list and may return a filtered or reordered subset. Returning `nil` (or nothing) leaves the list unchanged. Multiple `SearchFilter` hooks **chain**: each hook receives the output of the previous hook. An unknown `(source, name)` pair in the return table is a hard error; duplicate refs are deduplicated first-wins. Hook errors are logged and the **unfiltered** results are shown rather than aborting the command. ### SearchFilter event

Available from yay v13.0.0

```lua { event = "SearchFilter", data = { results = { { source = "aur", -- "aur" or the pacman DB name (e.g. "core", "extra") name = "pkgname", description = "A useful package", base = "pkgbase", votes = 123, -- -1 for sync packages popularity = 1.23, -- -1 for sync packages first_submitted = 1700000000, -- -1 for sync packages last_modified = 1700000001, -- -1 for sync packages }, -- … }, }, } ``` The callback must return `nil` or an array of `{source=, name=}` tables. Every `(source, name)` pair must exist in the input; unknown pairs are an error. More examples: [`doc/examples/hide_first_submitted.lua`](https://github.com/Jguer/yay/blob/next/doc/examples/hide_first_submitted.lua) (hide AUR packages submitted in the last 14 days). ### Example ```lua yay.create_autocmd("SearchFilter", { desc = "show only AUR results", callback = function(event) local out = {} for _, r in ipairs(event.data.results) do if r.source == "aur" then out[#out + 1] = { source = r.source, name = r.name } end end return out end, }) ``` ## Render hooks `RenderAUR` and `RenderSync` fire once per result row during `yay -Ss` and the `yay ` number menu, after results are ranked, sorted, and (optionally) filtered. The callback receives per-package metadata and may return a `string` that fully replaces what yay would print for that row. Returning `nil` (or nothing) falls back to yay's built-in formatting. The menu number prefix (magenta index shown in the number menu) is always prepended by yay itself; the callback controls only the package portion of the line. `-q`/`--quiet` (minimal verbosity) is unaffected — name-only output never calls these hooks. Hook errors are logged and that row falls back to default rendering rather than aborting the command. Multiple hooks of the same event **run in registration order**; the **last** hook that returns a non-`nil` string wins. A later hook returning `nil` does not undo an earlier hook's string. More examples: [`doc/examples/single_line.lua`](https://github.com/Jguer/yay/blob/next/doc/examples/single_line.lua) (reproduces `yay.opt.single_line_results` with colored, single-line AUR and sync results). ### RenderAUR event

Available from yay v13.1.0

```lua { event = "RenderAUR", data = { name = "pkgname", version = "1.0-1", description = "A useful package", base = "pkgbase", votes = 42, popularity = 1.23, maintainer = "alice", out_of_date = 0, -- 0 if current; Unix timestamp if flagged first_submitted = 1700000000, last_modified = 1700000001, local_version = "", -- installed version, or "" when not installed }, } ``` ### RenderSync event

Available from yay v13.1.0

```lua { event = "RenderSync", data = { repository = "extra", -- pacman DB name (e.g. "core", "extra") name = "pkgname", description = "A useful package", version = "2.0-1", groups = {}, -- array of group strings, often empty local_version = "", -- installed version, or "" when not installed }, } ``` ### Example ```lua yay.create_autocmd("RenderAUR", { desc = "compact AUR search lines", callback = function(event) local d = event.data local tag = d.local_version ~= "" and (" [installed: " .. d.local_version .. "]") or "" return string.format("aur/%s %s (+%d)%s\n %s", d.name, d.version, d.votes, tag, d.description) end, }) yay.create_autocmd("RenderSync", { desc = "compact sync search lines", callback = function(event) local d = event.data local tag = d.local_version ~= "" and (" [installed: " .. d.local_version .. "]") or "" return string.format("%s/%s %s%s\n %s", d.repository, d.name, d.version, tag, d.description) end, }) ```