# How to Add a New Website Module to holehe: Complete Developer Guide

> Learn to add a new website module to holehe with this developer guide. Discover how to integrate new services by creating a single async function in your Python module.

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

---

**holehe automatically discovers new website modules by dynamically importing all Python files in the `holehe.modules` package, requiring only a single async function with a specific signature to integrate a new service.**

Adding a new website module to holehe extends the tool’s ability to investigate email addresses across additional platforms. The megadose/holehe repository leverages a dynamic import system in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) that eliminates the need for manual registration or configuration files. By following the established pattern for async check functions and result formatting, you can integrate any website that exposes an email validation endpoint through registration or login forms.

## How the Dynamic Module Discovery Works

The holehe CLI relies on a recursive package walker to find check functions at runtime. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the `import_submodules()` function (lines 37-47) traverses the `holehe.modules` directory tree and imports every Python file it encounters.

When you add a new file containing an `async def <sitename>(email, client, out)` function, the loader automatically registers it. The CLI then calls this function for each target email address, passing in an `httpx.AsyncClient` instance for HTTP requests and a list `out` to collect results. No additional configuration or registry edits are required—placement in the correct subdirectory is sufficient.

## Step-by-Step Implementation Guide

### 1. Select the Appropriate Category

Modules are organized by theme within `holehe/modules/`. Choose the folder that best represents the target website:

- `social_media/` for platforms like Instagram, Twitter, or Facebook
- `programing/` for developer services like GitHub or Stack Overflow
- `forum/` for community discussion boards
- `shopping/` for e-commerce sites
- `mail/` for email providers

If no category fits, create a new folder inside `holehe/modules/`; the dynamic loader will recurse into it automatically.

### 2. Create the Module File

Create a new Python file named after the website (e.g., [`targetsite.py`](https://github.com/megadose/holehe/blob/main/targetsite.py)) inside your chosen category folder. For example:

```

holehe/modules/social_media/targetsite.py

```

### 3. Implement the Async Check Function

Every module must define an async function with the exact signature:

```python
async def targetsite(email, client, out):

```

The parameters are:
- **`email`** (`str`): The email address to investigate
- **`client`** (`httpx.AsyncClient`): A shared async HTTP client with connection pooling
- **`out`** (`list`): A list to which you append result dictionaries

Inside the function, define these metadata variables before making HTTP requests:

```python
name = "targetsite"                  # Internal identifier

domain = "targetsite.com"            # Domain name

method = "register"                  # Usually "register" or "login"

frequent_rate_limit = False          # Set True if site aggressively rate-limits

```

## Required Data Structure and Result Formatting

Your function must append a dictionary to the `out` list with specific keys that the `print_result()` function expects. The structure is:

```python
out.append({
    "name": name,                    # String: module identifier

    "domain": domain,                # String: domain name

    "method": method,                # String: check method used

    "frequent_rate_limit": frequent_rate_limit,  # Boolean: rate limit tendency

    "rateLimit": False,              # Boolean: hit rate limit this run?

    "exists": True,                  # Boolean: email found on site?

    "emailrecovery": None,           # String or None: recovery email if exposed

    "phoneNumber": None,             # String or None: phone number if exposed

    "others": None                   # Dict or None: additional metadata

})

```

If the check encounters an unrecoverable error (e.g., missing CSRF token or service downtime), append a result with `rateLimit: True` or appropriate error state and return `None` early to halt further processing.

## Complete Module Template

Copy this template to `holehe/modules/<category>/yourservice.py` and adapt the request logic:

```python

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

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

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

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
        "Accept-Language": "en-US,en;q=0.5",
        "Origin": f"https://{domain}",
        "DNT": "1",
        "Connection": "keep-alive",
    }

    # Example: fetch CSRF token if required by the site

    try:
        resp = await client.get(f"https://{domain}/signup", headers=headers)
        token = resp.text.split('csrf_token":"')[1].split('"')[0]
    except Exception:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
        return None

    # Submit email to validation endpoint

    data = {"email": email, "username": "tmpuser"}
    headers["x-csrftoken"] = token
    check = await client.post(f"https://{domain}/api/check", data=data, headers=headers)
    
    try:
        result = check.json()
        exists = result.get("error") == "email_taken"
    except Exception:
        exists = False

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

```

## Key Source Files for Reference

Understanding these core files helps ensure your module follows holehe conventions:

- **[`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)** – Contains the `import_submodules()` function (lines 37-47) responsible for dynamic module discovery and the result printing logic
- **[`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py)** – A production-ready reference showing header preparation, token extraction, error handling, and result formatting
- **[`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py)** – Provides the `ua` dictionary containing browser user-agent strings; import this to rotate identities and avoid detection

## Summary

- **Dynamic discovery**: Place any Python file under `holehe/modules/` and the `import_submodules()` loader in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) automatically registers it
- **Required signature**: Implement `async def sitename(email, client, out)` with exact parameter names
- **Mandatory metadata**: Define `name`, `domain`, `method`, and `frequent_rate_limit` variables at the function start
- **Result contract**: Append a dictionary to `out` with keys `name`, `domain`, `method`, `frequent_rate_limit`, `rateLimit`, `exists`, `emailrecovery`, `phoneNumber`, and `others`
- **Error handling**: Return `None` early after appending an error state to `out` when encountering unrecoverable failures
- **HTTP client**: Use the passed `httpx.AsyncClient` for all requests to enable connection reuse and proper async execution

## Frequently Asked Questions

### Do I need to manually register my new module in a configuration file?

No. According to the `import_submodules()` implementation in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the tool walks the entire `holehe.modules` package tree at startup. As long as your file exists under `holehe/modules/` (in any subfolder) and contains the properly named async function, holehe discovers it automatically.

### What is the exact function signature required for a holehe module?

The function must be declared as `async def <sitename>(email, client, out):` where `email` is the string to check, `client` is an `httpx.AsyncClient` instance for HTTP requests, and `out` is a list where you append the result dictionary. The function name should match the filename and the `name` variable defined inside.

### How should I handle websites that frequently rate-limit requests?

Set `frequent_rate_limit = True` in your module's metadata variables. This flag signals to users that the service aggressively limits requests. If you encounter a rate limit during the check, set `"rateLimit": True` in the result dictionary you append to `out`, and return `None` to stop further processing for that email.

### Can I use custom HTTP headers or user agents in my module?

Yes. Import `from holehe.localuseragent import *` to access the `ua` dictionary, then select a random browser user agent with `random.choice(ua["browsers"]["chrome"])`. Construct your headers dictionary using standard browser headers to mimic legitimate traffic, and pass these headers to all `client.get()` or `client.post()` calls.