Skip to content
← All articles
Tutorial

A Method to Install Python Packages for Add-ons & Plugins in Blender (Windows, Blender 4.2+)

Listen to the audio story

Why pip does nothing inside Blender · 1:54 · AI-written script, synthetic voices

Transcript

Ava: Picture this. You write a Blender add-on, you type pip install, it works fine, and then Blender says it has never heard of the module. Annoying, right?

Andrew: Honestly yes. I'd assume pip was broken. What's actually going on?

Ava: Blender ships its own Python. So the pip on your system installs packages for a completely different interpreter. Add-ons that need NumPy, meshio or requests all hit that same wall.

Andrew: So the package is installed, just in the wrong house.

Ava: Exactly. Antoine wrote a small installer for this, for Blender 4.2 and up on Windows. It calls pip through Blender's own interpreter, so the package lands where Blender will actually look.

Andrew: And where does it put the files? I don't love add-ons that need admin rights.

Ava: No admin needed. It uses a per-user scripts modules folder that's writable, and it survives add-on reloads. It also skips packages that already import, so a second run is quick.

Andrew: What about the interface freezing while pip downloads things?

Ava: That's the other half of the trick. The install runs in a background thread, so Blender stays responsive. But only the main thread may touch the interface, so the popups go through Blender's timers and show up when it's done.

Andrew: Smart. How do people run it?

Ava: Two ways. Paste it into the Scripting workspace and hit Run Script, or call it from your add-on's register function so dependencies ship with the add-on.

Andrew: Any gotchas?

Ava: A few. Restart Blender after the first install, pin your versions because Blender's Python isn't your system's 3.12, and on locked-down corporate PCs pip may be blocked, so you'd bundle wheels by hand.

Andrew: So users stop asking which Python to use.

Ava: That's the takeaway. You own the dependency list, and users just click. The full write-up has the script and the troubleshooting table.

Blender Scripting workspace — Text Editor and Run Script
Table of contents

Blender add-ons that need NumPy, meshio, or requests hit the same wall: Blender ships its own Python, so system pip does nothing useful. I wrote a small installer that targets Blender’s sys.executable, drops wheels under scripts/modules, and runs in a background thread so the UI stays responsive (Blender 4.2+, Windows).

1. The problem in one sentence

You cannot pip install globally and expect import meshio inside Blender — the interpreter path is Blender’s bundled runtime.

2. Strategy

PieceRole
sys.executableInvoke pip against Blender’s Python
bpy.utils.user_resource("SCRIPTS", path="modules")Writable install dir per user/version
site.addsitedirMake imports resolve next launch
Background thread + bpy.app.timersAvoid freezing the UI; show popups when done

3. The installer script

import bpy
import sys
import site
import logging
import subprocess
import threading

# Set up logging
logger = logging.getLogger(__name__)
logging.basicConfig(level=logging.INFO)

# List of packages required by the add-on/plugin
REQUIRED_PACKAGES = {
    "fileseq": "fileseq==1.15.2",
    "meshio": "meshio==5.3.4",
    "rich": "rich==13.7.0",
    "requests": "requests==2.31.0"
}

def get_blender_python_path():
    """Returns the path of Blender's embedded Python interpreter."""
    return sys.executable

def get_modules_path():
    """Return a writable directory for installing Python packages."""
    return bpy.utils.user_resource("SCRIPTS", path="modules", create=True)

def append_modules_to_sys_path(modules_path):
    """Ensure Blender can find installed packages."""
    if modules_path not in sys.path:
        sys.path.append(modules_path)
    site.addsitedir(modules_path)

def display_message(message, title="Notification", icon='INFO'):
    """Show a popup message in Blender."""
    def draw(self, context):
        self.layout.label(text=message)
    def show_popup():
        bpy.context.window_manager.popup_menu(draw, title=title, icon=icon)
        return None  # Stops timer
    bpy.app.timers.register(show_popup)

def install_package(package, modules_path):
    """Install a single package using Blender's Python."""
    try:
        logger.info(f"Installing {package}...")
        subprocess.check_call([
            get_blender_python_path(),
            "-m",
            "pip",
            "install",
            "--upgrade",
            "--target",
            modules_path,
            package
        ])
        logger.info(f"{package} installed successfully.")
    except subprocess.CalledProcessError as e:
        logger.error(f"Failed to install {package}. Error: {e}")
        display_message(f"Failed to install {package}. Check console for details.", icon='ERROR')

def background_install_packages(packages, modules_path):
    """Install missing packages in a background thread."""
    def install_packages():
        wm = bpy.context.window_manager
        wm.progress_begin(0, len(packages))
        for i, (module_name, pip_spec) in enumerate(packages.items()):
            try:
                __import__(module_name)
                logger.info(f"'{module_name}' is already installed.")
            except ImportError:
                install_package(pip_spec, modules_path)
            wm.progress_update(i + 1)
        wm.progress_end()
        display_message("All required packages installed successfully.")
    threading.Thread(target=install_packages, daemon=True).start()

# Setup
modules_path = get_modules_path()
append_modules_to_sys_path(modules_path)

# Start package installation
background_install_packages(REQUIRED_PACKAGES, modules_path)

Four details do the work:

  • sys.executable inside Blender is Blender’s own interpreter, so pip installs for the Python that will import the package.
  • --target writes into the per-user scripts/modules folder, which is writable without admin rights and survives add-on reloads.
  • The __import__ check skips packages that are already importable, so a second run is quick.
  • Popups go through bpy.app.timers because only the main thread may touch the UI, not the install thread.

4. How to run it

Option A — Scripting workspace: paste in Text Editor → Run Script → wait for popup.

Blender Scripting workspace with the installer in the Text Editor

Option B — Add-on register():

def register():
    modules_path = get_modules_path()
    append_modules_to_sys_path(modules_path)
    background_install_packages(REQUIRED_PACKAGES, modules_path)

Ship dependencies with your add-on so users are not guessing which Python to use.

5. Troubleshooting

  • Restart Blender after first install.
  • print(sys.path) if imports fail — confirm modules is listed.
  • Version pin wheels — Blender’s Python version is not your system 3.12.

6. When not to use this pattern

CaseWhy
Add-on targets many Blender versionsPin and test per minor version
Heavy CUDA stacksMay exceed user GPU/driver setup
Corporate locked-down PCspip may be blocked — vendor wheels manually

7. Takeaway

Automate dependency install inside Blender’s sandbox; users stop emailing “which Python?” — you own the matrix in REQUIRED_PACKAGES.

9. References


Originally published on Medium[4].

References

  1. Blender API. (). [Web page] docs.blender.org.
  2. Managing Python in Blender. (). [Web page] wiki.blender.org.
  3. Python packaging guide. (). [Web page] packaging.python.org.
  4. Medium. (). [Article] medium.com.

References use this article’s publication date.