How to Add a Custom Module to Holehe: Complete Developer Guide
Holehe discovers every available check by walking the holehe.modules package at runtime using import_submodules() in holehe/core.py, allowing you to add new capabilities by simply creating a Python file with a properly named async function and no core code changes.
Holehe is an open-source email OSINT tool that identifies where an email address is registered across hundreds of services. When you need to probe a site that isn't in the default module library, adding a custom module to Holehe lets you extend the tool's reach without forking or patching the central execution logic.
How Holehe Discovers Custom Modules
The module discovery system relies on two key functions in [holehe/core.py](https://github.com/megadose/holehe/blob/master/holehe/core.py).
First, import_submodules() (lines 37–48) uses pkgutil.walk_packages to recursively import every sub-package under holehe.modules. This means any Python file you add to the modules directory automatically becomes part of the runtime environment.
Second, get_functions() (lines 50–63) inspects the imported modules and extracts any callable that matches the filename. For example, a file named example.py must expose an async function named example.
The system requires no registration files or configuration updates—simply placing a valid module file in the correct directory makes it available the next time you run the CLI.
Step-by-Step Guide to Creating a Custom Module
Choose a Category and Create the File
Create a new Python file under holehe/modules/<category>/. Use an existing category like shopping or social, or create a new directory such as custom.
Ensure the directory contains an __init__.py file. This marker file is essential because import_submodules() relies on Python's package structure to locate submodules.
The filename determines the function name you must use later. For a service named example, create holehe/modules/custom/example.py.
Define the Async Function Signature
Each module must expose a single asynchronous function whose name matches the filename exactly. The function signature must accept three parameters:
async def example(email, client, out):
email: The target email address string.client: Anhttpx.AsyncClientinstance for making HTTP requests.out: A list that your function must populate with a result dictionary.
Populate the Output Dictionary
Your function must append a dictionary to the out list following the standard Holehe schema. The dictionary requires these specific keys:
name: Service identifier (e.g.,"example").domain: The service domain (e.g.,"example.com").method: HTTP method used (e.g.,"login","register").frequent_rate_limit: Boolean indicating if the site frequently rate-limits.rateLimit: Boolean set toTrueif the current request hit a rate limit.exists: Boolean indicating if the email is registered (True/False).emailrecovery: String with recovery email hint, orNone.phoneNumber: String with recovery phone hint, orNone.others: Dictionary for additional metadata, orNone.
Complete Custom Module Example
Below is a production-ready template you can adapt. This module checks a fictional example.com service by attempting a login request and parsing the error response.
# File: holehe/modules/custom/example.py
from holehe.core import *
from holehe.localuseragent import *
async def example(email, client, out):
"""
Checks whether the supplied email exists on example.com.
"""
name = "example"
domain = "example.com"
method = "login"
frequent_rate_limit = False
headers = {"User-agent": random.choice(ua["browsers"]["chrome"])}
try:
# Load the login page to obtain any hidden fields
resp = await client.get("https://example.com/login", headers=headers)
soup = BeautifulSoup(resp.text, "html.parser")
# Build the POST payload
data = {"email": email}
for hidden in soup.select('form input[type="hidden"]'):
if hidden.get("name") and hidden.get("value"):
data[hidden["name"]] = hidden["value"]
# Submit the login request
resp = await client.post("https://example.com/login", data=data, headers=headers)
body = BeautifulSoup(resp.text, "html.parser")
# Determine existence based on error messages
if body.find("div", {"class": "error"}):
exists = False
else:
exists = 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-level problems (rate-limits, timeouts, etc.)
out.append({
"name": name,
"domain": domain,
"method": method,
"frequent_rate_limit": frequent_rate_limit,
"rateLimit": True,
"exists": False,
"emailrecovery": None,
"phoneNumber": None,
"others": None,
})
Running Your Custom Module
After saving the file, execute Holehe normally:
holehe user@example.com
The CLI automatically imports holehe.modules.custom.example and includes its results alongside built-in checks. As implemented in megadose/holehe, the get_functions() loader will discover your new module during the initialization phase and queue it for execution with the other probes.
Summary
- No core modifications required: The
import_submodules()function inholehe/core.pyautomatically discovers new files placed underholehe/modules/. - Strict naming convention: The async function name must match the filename (e.g.,
example.pycontainsasync def example). - Three required parameters: All module functions must accept
email,client, andout. - Standard output schema: Populate the
outlist with a dictionary containingname,domain,method,frequent_rate_limit,rateLimit,exists,emailrecovery,phoneNumber, andothers. - Helper utilities available: Import
from holehe.core import *andfrom holehe.localuseragent import *to access shared HTTP utilities and random user-agent lists.
Frequently Asked Questions
Do I need to modify holehe/core.py to add a new module?
No. According to the Holehe source code, the import_submodules() function dynamically walks the holehe.modules package at runtime using pkgutil.walk_packages. As long as your file resides in the modules directory and follows the naming convention, it will be imported automatically without any changes to the core files or setup configuration.
What is the exact function signature required for a custom module?
Your module must expose an async function with the signature async def <module_name>(email, client, out), where <module_name> matches the filename. For example, a file named twitter.py must contain async def twitter(email, client, out). The parameters provide the target email, an HTTP client for requests, and a list to populate with the results.
Can I reuse Holehe's existing utilities in my custom module?
Yes. You can import shared utilities using from holehe.core import * to access base functions and from holehe.localuseragent import * to utilize the ua dictionary containing randomized browser user-agents. This ensures your custom check behaves consistently with built-in modules regarding request headers and error handling patterns.
Where should I create a new category for my custom modules?
Create a new directory under holehe/modules/ (e.g., holehe/modules/enterprise/) and place an __init__.py file inside it to make it a valid Python package. The import_submodules() logic in holehe/core.py recursively searches all subdirectories, so your new category will be discovered alongside existing ones like shopping or social.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →