Skip to main content

Create a Custom MMDB from Scratch

This guide walks you through building a custom MMDB file from scratch — from designing your dataset to verifying the final output. By the end you will have a working .mmdb file you can query with any MaxMind-compatible library or tool.

Prerequisites​

  • mmdb-cli installed (Installation)
  • Basic familiarity with JSON and CIDR notation

Step 1 — Decide what data to store​

An MMDB file maps IP networks (CIDR ranges) to arbitrary records. Before writing any JSON, answer these questions:

QuestionExample answer
What IP networks will I cover?1.1.1.0/24, 10.0.0.0/8
What fields does each record need?ASN number, organization name, threat score
Do I need strict integer/float types?Yes → use a schema; No → let MMDB CLI infer
IPv4-only, or dual-stack (IPv4 + IPv6)?Dual-stack → "IPVersion": 6 (recommended default)

Step 2 — Create the dataset JSON file​

The dataset file is the single source of truth for your MMDB. Create a file named my-database.json with the following top-level structure:

{
"version": "v1",
"schema": {},
"metadata": {},
"dataset": []
}

2a — Fill in the metadata​

The metadata block controls how the MMDB file identifies itself. Every field shown below is required by the generate command:

"metadata": {
"DatabaseType": "My-Custom-DB",
"Description": {
"en": "My custom IP database"
},
"Languages": ["en"],
"IPVersion": 6,
"RecordSize": 24
}
FieldNotes
DatabaseTypeFree-form string — choose a name that is meaningful to your application.
DescriptionMap of language code → description text.
IPVersionUse 6 for dual-stack (supports both IPv4 and IPv6 lookups). Use 4 for IPv4-only.
RecordSize24 is the most compact option and sufficient for most custom databases.

Without a schema, all JSON numbers are stored as float64. If your records contain integer fields (ASN numbers, port numbers, counts), add a schema to enforce the correct types:

"schema": {
"asn": "uint32",
"organization": "string",
"is_proxy": "bool",
"threat_score": "float64"
}

Supported type keywords: string, bool, float64, int32, uint32, uint16.

Nested objects are supported:

"schema": {
"geo": {
"latitude": "float64",
"longitude": "float64"
}
}

2c — Add records to dataset​

Each entry in dataset maps one CIDR range to a record object:

"dataset": [
{
"network": "1.1.1.0/24",
"record": {
"asn": 13335,
"organization": "CLOUDFLARENET",
"is_proxy": false,
"threat_score": 0.1
}
},
{
"network": "8.8.8.0/24",
"record": {
"asn": 15169,
"organization": "GOOGLE",
"is_proxy": false,
"threat_score": 0.0
}
},
{
"network": "185.220.101.0/24",
"record": {
"asn": 205100,
"organization": "F3 Netze e.V.",
"is_proxy": true,
"threat_score": 8.5
}
}
]

Complete example​

{
"version": "v1",
"schema": {
"asn": "uint32",
"organization": "string",
"is_proxy": "bool",
"threat_score": "float64"
},
"metadata": {
"DatabaseType": "My-Custom-DB",
"Description": {
"en": "My custom IP database"
},
"Languages": ["en"],
"IPVersion": 6,
"RecordSize": 24
},
"dataset": [
{
"network": "1.1.1.0/24",
"record": {
"asn": 13335,
"organization": "CLOUDFLARENET",
"is_proxy": false,
"threat_score": 0.1
}
},
{
"network": "8.8.8.0/24",
"record": {
"asn": 15169,
"organization": "GOOGLE",
"is_proxy": false,
"threat_score": 0.0
}
},
{
"network": "185.220.101.0/24",
"record": {
"asn": 205100,
"organization": "F3 Netze e.V.",
"is_proxy": true,
"threat_score": 8.5
}
}
]
}

Step 3 — Generate the MMDB file​

Run the generate command, pointing it at your dataset file and choosing an output path:

mmdb-cli generate -i my-database.json -o my-database.mmdb

On success the command exits with code 0 and writes the binary MMDB file to my-database.mmdb. You can add -v to see verbose output:

mmdb-cli generate -i my-database.json -o my-database.mmdb -v
IPv4-only datasets

If every network in your dataset is IPv4 and you do not need IPv6 lookups, pass --disable-ipv4-aliasing to produce a leaner file:

mmdb-cli generate -i my-database.json -o my-database.mmdb --disable-ipv4-aliasing

Step 4 — Verify the file​

Check that the generated file is a valid MMDB binary:

mmdb-cli verify -i my-database.mmdb

Expected output:

The MMDB file is valid

If the file is corrupted or malformed, the command will exit with a non-zero code and print an error.


Step 5 — Inspect and test lookups​

Use the inspect command to query the new file and confirm your records were written correctly:

mmdb-cli inspect -i my-database.mmdb 1.1.1.1

Expected output (YAML):

- query: 1.1.1.1
records:
- network: 1.1.1.0/24
record:
asn: 13335
is_proxy: false
organization: CLOUDFLARENET
threat_score: 0.1

Test a few more IPs to cover each network you added:

mmdb-cli inspect -i my-database.mmdb 8.8.8.8 185.220.101.42

For a range query, pass a CIDR prefix:

mmdb-cli inspect -i my-database.mmdb 1.1.1.0/24
note

An IP address that falls outside every network in your dataset will return an empty records list — this is expected behavior.


Step 6 — Iterate and update​

Need to add or change records without regenerating from scratch? Use the update command to merge changes into the existing file. See Update Command for details.


Summary​

StepCommand
1. Design your schema(plan on paper)
2. Write my-database.json(text editor)
3. Generate the MMDBmmdb-cli generate -i my-database.json -o my-database.mmdb
4. Verify integritymmdb-cli verify -i my-database.mmdb
5. Test lookupsmmdb-cli inspect -i my-database.mmdb <IP>
6. Update recordsmmdb-cli update ...

See also​