zellij-org/zellij · error

failed to load plugin from disk

Error message

failed to load plugin from disk

What it means

Seed error of PluginConfig::resolve_wasm_bytes (zellij-utils/src/input/plugins.rs): the lookup tries, in order, the raw path, path + '.wasm', plugin_dir/path.wasm, and the system data dir's plugins/path.wasm (plus the builtin ASSET_MAP for 'zellij:' plugins unless built with disable_automatic_asset_installation). This message is the initial value of last_err; the surfaced error chains each failed path's io::Error as context, so it means NO candidate location yielded readable wasm bytes.

Solutions

  1. Check the chained per-path io errors in the full error message — they list exactly which locations were tried; make your wasm exist at one of them (usually the plugin dir, e.g. ~/.local/share/zellij/plugins/).
  2. Fix the plugin name/path in your layout or config: builtins must match exactly (e.g. "zellij:tab-bar"), custom plugins should be the file stem or path relative to the plugin dir.
  3. On builds without automatic asset installation, run `zellij setup --dump-plugins` to materialize builtin wasm into the plugin dir.
  4. Verify file permissions on the wasm and that it is a real wasm module, not an HTML error page or empty file.

Example fix

// before: name matches no builtin and no file
layout {
    pane {
        plugin "tabbar" --flag
    }
}

// after: exact builtin name (loaded from ASSET_MAP)
layout {
    pane {
        plugin "zellij:tab-bar" --flag
    }
}
Defensive patterns

Strategy: validation

Validate before calling

// pre-check every location resolve_wasm_bytes will try
use zellij_utils::home::system_data_dir;

fn plugin_resolvable(path: &Path, plugin_dir: &Path) -> bool {
    let candidates = [
        path.to_path_buf(),
        path.with_extension("wasm"),
        plugin_dir.join(path).with_extension("wasm"),
        system_data_dir().join("plugins").join(path).with_extension("wasm"),
    ];
    candidates.iter().any(|p| p.is_file())
}

Try / catch

match plugin.resolve_wasm_bytes(&plugin_dir) {
    Ok(bytes) => bytes,
    Err(e) => {
        // the context chain lists each attempted path — surface it to the user
        anyhow::bail!("plugin '{}' could not be loaded. Checked: {e:#}", plugin.path.display());
    },
}

Prevention

When it happens

Trigger: A `run "name"` / plugin entry whose wasm exists nowhere: typo'd builtin name (so the ASSET_MAP lookup misses and disk fallbacks fail), custom plugin path not relative to the plugin dir nor absolute-and-existing, unreadable file (permissions), or a distro build with disable_automatic_asset_installation where builtin wasm was never dumped to disk (then you get BuiltinPluginMissing wrapping this).

Common situations: Config/layout referencing 'tab-bar' as 'tabbar'; custom plugin placed in the wrong directory; plugins not installed via `zellij setup --dump-plugins` on feature-restricted builds; upgrading zellij renamed/moved a builtin plugin.

Related errors


AI-assisted analysis of zellij-org/zellij@5cb5df5cce (2026-08-19). Data as JSON: /api/errors/5d42c4b9cad1b7fa. Report an issue: GitHub.

Appendix: source

Thrown at zellij-utils/src/input/plugins.rs:129

            plugin_dir.join(&self.path).with_extension("wasm"),
        ];
        #[cfg(not(target_family = "wasm"))]
        paths.push(
            crate::home::system_data_dir()
                .join("plugins")
                .join(&self.path)
                .with_extension("wasm"),
        );
        // Throw out dupes, because it's confusing to read that zellij checked the same plugin
        // location multiple times. Do NOT sort the vector here, because it will break the lookup!
        paths.dedup();

        // This looks weird and usually we would handle errors like this differently, but in this
        // case it's helpful for users and developers alike. This way we preserve all the lookup
        // errors and can report all of them back. We must initialize `last_err` with something,
        // and since the user will only get to see it when loading a plugin failed, we may as well
        // spell it out right here.
        let mut last_err: Result<Vec<u8>> = Err(anyhow!("failed to load plugin from disk"));
        for path in paths {
            // Check if the plugin path matches an entry in the asset map. If so, load it directly
            // from memory, don't bother with the disk.
            #[cfg(not(target_family = "wasm"))]
            if !cfg!(feature = "disable_automatic_asset_installation") && self.is_builtin() {
                let asset_path = PathBuf::from("plugins").join(&path);
                if let Some(bytes) = ASSET_MAP.get(&asset_path) {
                    log::debug!("Loaded plugin '{}' from internal assets", path.display());

                    if plugin_dir.join(&path).with_extension("wasm").exists() {
                        log::info!(
                            "Plugin '{}' exists in the 'PLUGIN DIR' at '{}' but is being ignored",
                            path.display(),
                            plugin_dir.display()
                        );
                    }

                    return Ok(bytes.to_vec());

View on GitHub (pinned to 5cb5df5cce)