# How to Create a New Custom Module for Holehe: A Step-by-Step Developer's Guide

> Learn how to create a new custom module for Holehe with this developer's guide. Follow our step-by-step instructions to add new services and expand Holehe's capabilities.

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

---

**To create a custom module for Holehe, add an async function with the signature `async def <service_name>(email, client, out):` to a new Python file under `holehe/modules/<category>/`, following the standardized result dictionary schema used by existing modules.**

Holehe discovers whether an email address has been used on various online services through a modular architecture. Each service is implemented as a **module** that Holehe dynamically loads at runtime. According to the `megadose/holehe` source code, you can extend this email reconnaissance tool by creating properly structured modules that the framework automatically discovers and executes.

## Understanding Holehe's Module System

Holehe's dynamic loading mechanism is implemented in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). When the tool starts, it executes two key functions:

```python
modules = import_submodules("holehe.modules")        # holehe/core.py

websites = get_functions(modules, args)               # holehe/core.py

```

The `import_submodules` function uses `pkgutil.walk_packages` to traverse the package tree and load every Python file. Then `get_functions` extracts the callable (async function) from each module. These callables are scheduled for concurrent execution using Trio.

This architecture means **no registration step is required**—simply placing a correctly structured file in the modules directory makes your service immediately available.

## Module Requirements and Structure

### File Location

Place your module in the appropriate category folder under `holehe/modules/`:

- `holehe/modules/social_media/` — platforms like Instagram, Twitter, Facebook
- `holehe/modules/shopping/` — e-commerce sites
- `holehe/modules/programing/` — developer-focused services
- `holehe/modules/forum/` — community forums
- `holehe/modules/mail/` — email services
- `holehe/modules/porn/` — adult content platforms

The folder name becomes part of the import path (e.g., `holehe.modules.social_media.yourservice`).

### Required Function Signature

Every module must define **exactly one async function** matching the filename:

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

```

| Parameter | Type | Description |
|-----------|------|-------------|
| `email` | `str` | The target email address to check |
| `client` | `httpx.AsyncClient` | Shared HTTP client for making requests |
| `out` | `list` | List to which you append the result dictionary |

### Standardized Result Dictionary

Your function must append a dictionary to `out` with these exact keys (as used by `print_result` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py)):

| Key | Type | Purpose |
|-----|------|---------|
| `name` | `str` | Service identifier (matching function name) |
| `domain` | `str` | The service's primary domain |
| `method` | `str` | Optional endpoint description (e.g., "register", "login") |
| `frequent_rate_limit` | `bool` | `True` if the service often throttles requests |
| `rateLimit` | `bool` | `True` if this specific request was rate-limited |
| `exists` | `bool` | `True` if the email is registered on the service |
| `emailrecovery` | `str` or `None` | Recovered recovery email (if exposed) |
| `phoneNumber` | `str` or `None` | Recovered phone number (if exposed) |
| `others` | `dict` or `None` | Additional extracted metadata |

## Complete Custom Module Example

This template mirrors the structure of the built-in Instagram module ([`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py)):

```python

# holehe/modules/social_media/example_service.py

from holehe.core import *          # Optional: imports helpers

from holehe.localuseragent import *  # Random user-agent list

async def example_service(email, client, out):
    """Check if <email> is registered on ExampleService."""
    name = "example_service"
    domain = "example.com"
    method = "register"
    frequent_rate_limit = False

    # Build request headers with randomized browser fingerprint

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Accept": "application/json, text/plain, */*",
    }

    # Handle network failures gracefully

    try:
        resp = await client.get(
            f"https://api.example.com/users/exists?email={email}",
            headers=headers,
        )
    except Exception:
        # Network error → mark as rate-limited/unknown

        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

    # Parse response and populate result

    if resp.status_code == 200 and resp.json().get("exists"):
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": True,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })
    else:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": False,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

## POST-Based Module Example

For services requiring form submission:

```python

# holehe/modules/forum/myforum.py

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

async def myforum(email, client, out):
    name = "myforum"
    domain = "myforum.org"
    method = "login"
    frequent_rate_limit = False

    headers = {
        "User-Agent": random.choice(ua["browsers"]["chrome"]),
        "Content-Type": "application/x-www-form-urlencoded",
    }

    payload = {"login": email, "password": "random"}

    try:
        resp = await client.post(
            "https://myforum.org/api/check_user",
            data=payload,
            headers=headers,
        )
    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

    exists = resp.json().get("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,
    })

```

## Essential Implementation Guidelines

**Use the shared HTTP client.** Always use the provided `client` parameter (an `httpx.AsyncClient` instance) rather than creating your own. This ensures proper connection pooling and Trio compatibility.

**Randomize user agents.** Import from [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) and select randomly: `random.choice(ua["browsers"]["chrome"])`. This mimics real browser traffic and reduces blocking.

**Handle all exceptions.** Wrap network calls in `try/except` and append a result with `"rateLimit": True` on failure. See lines 71-78 of [`instagram.py`](https://github.com/megadose/holehe/blob/main/instagram.py) for the pattern used when unexpected exceptions occur.

**Avoid import side effects.** Your module should not execute code during import—only the async function runs during Holehe's execution phase.

**Match function and filename names exactly.** The `get_functions` loader extracts `module.__dict__[site]` where `site` is the filename without extension. Mismatched names cause the module to be silently skipped.

## Verifying Your Custom Module

After creating your file, run Holehe with your target email:

```bash
holehe target@example.com

```

Your service appears automatically—no rebuild or configuration required. The dynamic loader in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) discovers it on the next execution.

## Key Source Files Reference

| File | Role |
|------|------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Entry point with `import_submodules`, `get_functions`, and result printing |
| [`holehe/modules/social_media/instagram.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/instagram.py) | Reference implementation showing error handling and response parsing |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | Random user-agent strings for request headers |

## Summary

- **Location matters**: Place files under `holehe/modules/<category>/` with matching function and filename names.
- **Signature is strict**: Use `async def <name>(email, client, out):` exactly.
- **Result dict is standardized**: Include all required keys, especially `rateLimit`, `exists`, and `frequent_rate_limit`.
- **No registration needed**: The dynamic importer in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) discovers modules automatically.
- **Defensive programming**: Handle network exceptions, randomize headers, and set appropriate boolean flags for UI display.

## Frequently Asked Questions

### What happens if my function name doesn't match the filename?

Holehe will not detect your module. The `get_functions` function in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) looks up `module.__dict__[site]` where `site` is derived from the filename. A mismatch causes silent skipping—your module simply won't appear in results.

### Can I use synchronous HTTP libraries like requests?

No. Holehe uses Trio for async concurrency, so your module must use the provided `httpx.AsyncClient` via `await client.get()` or `await client.post()`. Synchronous calls would block the entire event loop and break concurrent execution.

### How do I indicate that a service frequently rate-limits requests?

Set `"frequent_rate_limit": True` in your result dictionary. This flag appears in Holehe's output to warn users that results may be unreliable. Distinguish this from `"rateLimit": True`, which indicates the current specific request was throttled.

### Why does my module need to append to `out` instead of returning a value?

Holehe schedules modules concurrently using Trio nurseries. The `out` list acts as a thread-safe collector for results from all running checks. Appending ensures your result is captured even when multiple services execute simultaneously.