# How to Add a New Website Module to Holehe: Step-by-Step Guide

> Easily add a new website module to Holehe by creating an async function in holehe modules and populating a standard result dictionary. Follow this step-by-step guide.

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

---

**Adding a website module to Holehe requires creating an async function with a specific signature in `holehe/modules/` and populating a standard result dictionary—no registration step is needed.**

Holehe uses a dynamic import system to discover and execute email checks across hundreds of websites. To extend its capabilities, you create a Python module in the appropriate category folder that follows the established pattern. This guide walks through the exact implementation details found in the megadose/holehe source code, from signature requirements to result formatting.

## Understanding Holehe's Module Discovery System

Holehe discovers all available checks at runtime. In [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py), the function `import_submodules("holehe.modules")` recursively walks the package tree, imports every submodule, and extracts public functions using `get_functions`.

Because of this automatic discovery, **no manual registration is required**. Save your module file in the correct location and Holehe will include it in the next scan.

## Step 1: Choose the Right Category Folder

Website modules are organized by category under `holehe/modules/`. Common categories include:

- `social_media` — platforms like Twitter, Instagram, LinkedIn
- `forum` — discussion boards and community sites
- `software` — developer tools and SaaS platforms
- `ecommerce` — online stores and marketplaces
- `entertainment` — streaming and gaming services

Place your new module in the most appropriate existing folder, or create a new category if none fit.

## Step 2: Create the Module File with Required Function Signature

Every Holehe module defines a single **async function** with this exact signature:

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

```

The parameters are:

| Parameter | Type | Purpose |
|-----------|------|---------|
| `email` | `str` | The target email address to check |
| `client` | `httpx.AsyncClient` | Shared async HTTP client provided by the core |
| `out` | `list` | Result accumulator—append your dictionary here |

Each site-specific check is a **coroutine** that performs one HTTP request (or sequence) to determine if the email is registered.

## Step 3: Populate the Standard Result Dictionary

Every module appends a dictionary to `out` with exactly these keys:

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

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

    "method": method,                  # Usually "register"

    "frequent_rate_limit": False,      # True if site often rate-limits

    "rateLimit": False,                # Set True when rate-limit detected in this request

    "exists": exists,                  # Boolean: True if email is registered

    "emailrecovery": None,             # Optional recovered email address

    "phoneNumber": None,               # Optional recovered phone number

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

})

```

**Critical:** Use `None` for optional fields rather than omitting them. The core expects this exact structure.

## Step 4: Implement Error Handling

Wrap your request logic in a generic exception handler. When errors occur, append a result with `rateLimit: True` (or follow the `launch_module` pattern with `error: True`):

```python
try:
    # Your request logic here

    ...
except Exception:
    out.append({
        "name": name,
        "domain": domain,
        "method": method,
        "frequent_rate_limit": frequent_rate_limit,
        "rateLimit": True,              # Mark as rate-limited/errored

        "exists": False,
        "emailrecovery": None,
        "phoneNumber": None,
        "others": None,
    })

```

This ensures Holehe continues checking other sites even when one fails.

## Complete Example: Adding a Module for example.com

Here's a production-ready template based on patterns from [`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 for checking email availability

        req = await client.get(
            "https://api.example.com/v1/users/email_available",
            params={"email": email},
            headers={"User-Agent": ua},
            timeout=5
        )
        
        # Parse response—logic varies by service

        data = req.json()
        exists = not data.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:
        out.append({
            "name": name,
            "domain": domain,
            "method": method,
            "frequent_rate_limit": frequent_rate_limit,
            "rateLimit": True,
            "exists": False,
            "emailrecovery": None,
            "phoneNumber": None,
            "others": None,
        })

```

Save this file and run Holehe immediately:

```bash
holehe target@example.com

```

Your new check appears automatically in the output.

## Optional: Update [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py) for Clean Imports

Most category folders expose an `__all__` list in their [`__init__.py`](https://github.com/megadose/holehe/blob/main/__init__.py). Adding your module name here enables direct imports:

```python

# holehe/modules/social_media/__init__.py

__all__ = ["twitter", "instagram", "example"]  # Add your module

```

This step is **optional**—the dynamic importer works regardless.

## Key Source Files to Reference

| File | Purpose |
|------|---------|
| [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py) | Contains `import_submodules()` and orchestration logic |
| [`holehe/modules/social_media/twitter.py`](https://github.com/megadose/holehe/blob/main/holehe/modules/social_media/twitter.py) | Canonical example of a well-implemented module |
| [`holehe/localuseragent.py`](https://github.com/megadose/holehe/blob/main/holehe/localuseragent.py) | Provides `ua` constant for request headers |

Study existing modules in your target category to understand site-specific detection patterns. Each service exposes email registration status differently—some through registration forms, others through password reset flows or API endpoints.

## Summary

- **Location matters**: Place modules in `holehe/modules/<category>/`
- **Signature is fixed**: `async def name(email, client, out)`
- **Structure is strict**: Use the exact result dictionary format with all 9 keys
- **No registration needed**: The `import_submodules` system discovers your file automatically
- **Defensive coding required**: Wrap requests in `try/except` and set `rateLimit: True` on failures

## Frequently Asked Questions

### Do I need to restart Holehe after adding a module?

No. Holehe performs module discovery on every execution via `import_submodules("holehe.modules")` in [`holehe/core.py`](https://github.com/megadose/holehe/blob/main/holehe/core.py). Save your file and run `holehe` immediately—the new check is automatically included.

### Can I add multiple website checks in one file?

Technically yes, but conventionally no. Each `.py` file in `holehe/modules/` typically contains one site function. This keeps the codebase organized and makes debugging easier. If you need multiple related checks, consider whether they belong as separate modules or in a shared utility file.

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

Every service exposes this differently. Common techniques include:
- **Registration availability APIs** — endpoints that return `available: true/false`
- **Password reset flows** — submitting the email and parsing error messages
- **Login form responses** — distinguishing "account not found" from "wrong password"
- **Profile lookup endpoints** — checking if a public profile exists for that email

Study the target site's network traffic during legitimate use to identify the simplest detection method.