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