# How to Add a Custom Module for a New Website to the Holehe Framework

> Learn to add custom modules for new websites to the Holehe framework. Create async Python functions in holehe modules and let the dynamic import system handle discovery and execution.

- Repository: [Palenath/holehe](https://github.com/megadose/holehe)
- Tags: how-to-guide
- Published: 2026-08-15

---

**To add a custom module for a new website to Holehe, create an async Python function with the signature `async def <site>(email, client, out)` inside the `holehe/modules/` package hierarchy; the framework's dynamic import system automatically discovers and runs it at startup.**

Holehe uses a dynamic import system to discover and execute every site-specific check at runtime. When Holehe launches, `import_submodules("holehe.modules")` traverses the package tree in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), imports every submodule, and extracts public functions via `get_functions`. This architecture makes extending Holehe straightforward—no central registry or configuration file updates are required.

## Understanding the Module Structure

Holehe organizes website checks by category. The `holehe/modules/` directory contains subdirectories like `social_media`, `forum`, `software`, and others. Each category holds Python files implementing checks for specific sites.

The core mechanism lives in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). The `import_submodules` function walks the entire package hierarchy, making any new module file immediately available without explicit registration.

## Required Function Signature

Every Holehe module must define a single public async function with this exact signature:

```python
async def <site>(email, client, out):

```

The parameters are:

- **`email`** — The target email address being investigated
- **`client`** — An `httpx.AsyncClient` instance provided by the core for making HTTP requests
- **`out`** — A list to which you append result dictionaries

## Result Dictionary Format

Your function must populate `out` with a dictionary following the established schema. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the `get_functions` utility extracts these functions, and the orchestration code processes the standardized output.

The required result structure:

```python
out.append({
    "name": name,                   # Internal identifier (same as function name)

    "domain": domain,               # Human-readable domain (e.g., "example.com")

    "method": method,               # Usually "register"

    "frequent_rate_limit": False,
    "rateLimit": False,             # Set True when rate-limit response detected

    "exists": exists,               # True if email is registered on the site

    "emailrecovery": None,          # Optional recovered email address

    "phoneNumber": None,            # Optional recovered phone number

    "others": None                  # Additional data (e.g., full name)

})

```

## Step-by-Step Implementation

### Step 1: Choose the Appropriate Category

Navigate to `holehe/modules/` and select or create the relevant category folder. Existing categories include `social_media`, `forum`, `software`, `transport`, `shopping`, and others.

### Step 2: Create the Module File

Create a new Python file named after the website. For example, to add support for `example.com` under social media:

```

holehe/modules/social_media/example.py

```

### Step 3: Implement the Async Check

Use this template adapted from existing modules like [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py):

```python

# File: holehe/modules/social_media/example.py

from holehe.core import *
from holehe.localuseragent import *

async def example(email, client, out):
    name = "example"
    domain = "example.com"
    method = "register"
    frequent_rate_limit = False

    try:
        # Replace with actual endpoint that tests email availability

        req = await client.get(
            "https://api.example.com/v1/users/email_available",
            params={"email": email},
            headers={"User-Agent": ua}
        )
        
        # Parse response—adjust logic based on actual API behavior

        exists = not req.json().get("available", True)

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": exists,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
    except Exception:
        # Handle network errors or rate limits gracefully

        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

### Step 4: Optional — Update [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py)

Most category folders include an [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py) with an `__all__` list. While not strictly required for Holehe's dynamic import system, adding your module name enables explicit imports:

```python

# holehe/modules/social_media/__init__.py

__all__ = [
    "twitter",
    "example",  # Add your module here

    # ... other modules

]

```

## Testing Your Custom Module

No additional command-line flags are needed. Simply run Holehe normally:

```bash
holehe user@example.com

```

Your new check appears automatically in the output. The `import_submodules` call in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) loads every `.py` file under `holehe/modules/` recursively, making your module available immediately.

## Error Handling Best Practices

Follow the pattern established in `launch_module` and existing site implementations:

- **Wrap all network operations** in `try/except Exception` blocks
- **Set `rateLimit: True`** when catching exceptions that likely indicate rate limiting
- **Always append a result to `out`** — never leave the output list empty

This ensures Holehe continues operating even when individual checks fail, and users receive consistent feedback about which sites encountered problems.

## Key Files in the Holehe Source

| File | Purpose |
|------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Core driver containing `import_submodules` and orchestration logic |
| [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | Reference implementation showing production-grade error handling and response parsing |
| `holehe/modules/<category>/<your_module>.py` | Your custom module implementing the new website check |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | User-agent strings available for request headers |

## Summary

- **Holehe automatically discovers modules** through `import_submodules("holehe.modules")` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)
- **Create a file** in the appropriate category under `holehe/modules/`
- **Define one async function** with signature `async def <name>(email, client, out)`
- **Append standardized result dictionaries** to the `out` parameter
- **No registration step required** — save the file and run `holehe`

## Frequently Asked Questions

### Can I add a module without modifying the main Holehe package?

Yes, if you install Holehe in development mode with `pip install -e .`, your custom modules in the local `holehe/modules/` hierarchy are loaded alongside the core modules. For fully external modules, you would need to fork the repository, as the dynamic import system is hardcoded to scan `holehe.modules`.

### What HTTP client should I use inside my module?

Use the `client` parameter provided to your function — it is an `httpx.AsyncClient` instance managed by Holehe. This ensures connection pooling, timeout handling, and retry policies remain consistent across all checks. Do not create your own client instances.

### How do I determine if an email is registered on a site?

Each site exposes registration flows differently. Common techniques include: checking email availability during signup, testing password reset flows, or examining API responses for profile existence indicators. Study [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) and similar modules to see how existing implementations detect registration status for specific platforms.

### Why does my new module not appear in the output?

Verify that: (1) your Python file is saved in a subdirectory of `holehe/modules/`, (2) the function name matches the filename, (3) the function is `async`, and (4) the signature exactly matches `async def name(email, client, out)`. Run with debugging enabled to see import errors: `python -c "from holehe.core import import_submodules; import_submodules('holehe.modules')"`.