rolodexter · v2.11.1 · python + typescript
Every export names the same field differently. Map them all to one schema.
Point RoloDexter at a CRM payload, a form post, or a CSV nobody has looked at since 2019, and get back clean canonical fields with a confidence score on every match.
from rolodexter import ContactMapper
mapper = ContactMapper()
result = mapper.map_payload({
"fname": "jane",
"surname": "doe",
"mobile": "+1-650-253-0000",
"employer": "Tech Corp",
"Column 1": "jane.doe@example.com",
})
# result.normalized
{
"first_name": "Jane",
"last_name": "Doe",
"phone": "+16502530000",
"company": "Tech Corp",
"email": "jane.doe@example.com"
}
// the problem
Nobody agrees on what to call a phone number.
Every CRM, email platform, and CSV export invents its own names for the same five pieces of information. Writing the mapping by hand works right up until the next integration.
| Service | First name | Phone | Company |
|---|---|---|---|
| HubSpot | firstname | mobilephone | company |
| Salesforce | FirstName | MobilePhone | Company |
| Mailchimp | FNAME | PHONE | COMPANY |
| Google CSV | Given Name | Phone 1 - Value | Organization 1 - Name |
| Random CSV | Column A | Column B | Column C |
| RoloDexter | first_name | phone | company |
Even Column A is recoverable. When a header carries no meaning at all, RoloDexter reads the value's shape instead, and an email address in an unnamed column still lands on email.
// four-layer matching
Four strategies, tried in order of certainty.
Every field walks the chain until something matches, and the strategy that caught it sets the confidence you get back.
Exact
An O(1) lookup against 600+ known aliases across 62 canonical fields. This is where most real payloads land.
confidence 1.0Normalized
Handles CamelCase, dot.path, spaces to underscores, and the rest of the casing zoo.
Fuzzy
Typos survive. phne_nmbr still resolves to phone, and you can see that it was a guess.
Heuristic
No usable header? It reads the data instead, detecting emails, phones, URLs, and postal codes by shape.
confidence 0.6Nothing is silent. Non-fatal issues come back as warnings rather than disappearing, and strict mode turns a low-confidence guess or an unparseable value into a loud failure instead of a quietly wrong row.
// value normalization
Matching the field is only half of it.
The value gets cleaned too, so what comes out the other side is ready to store rather than ready for another round of regexes.
- Phones to E.164 via libphonenumber, and phone numbers buried inside a free-text field can be extracted on their own.
-
Names title-cased with particle awareness, so
jane van der bergbecomesJane van der Berg, notJane Van Der Berg. - Emails lowercased and trimmed. Addresses collapsed and title-cased. Tags coerced to a real list.
-
Your own names too. Per-caller overrides map a vendor's
MMERGE6orcf_lead_scoreonto a canonical field without patching the alias table.
mapper.identify("fname")
FieldMatch(canonical='first_name',
confidence=1.0, strategy='exact')
mapper.identify("phne")
FieldMatch(canonical='phone',
confidence=0.70, strategy='fuzzy')
mapper.identify("Column X",
value="jane@test.com")
FieldMatch(canonical='email',
confidence=0.6, strategy='heuristic')
// at scale
One contact, or the whole export.
The same mapper handles a single webhook payload and a multi-gigabyte CSV, from a script, a notebook, or a terminal.
Batch and streaming
Map a list in one call, or stream a huge CSV or JSONL export in constant memory. There is a preview mode that reports import readiness without retaining a single mapped row.
DataFrames
Hand it a DataFrame and get the columns renamed to canonical fields with values normalized. Columns it does not recognise are kept, not dropped.
Command line
Map a CSV, JSON, or JSONL export straight from a shell, quarantine bad rows to a JSONL file instead of stopping, and ask it to explain exactly how one header resolved.
Compile once
Resolve a source's headers into a mapping profile a single time, then reuse it for every row that follows.
40 languages
Alias tables for 40 languages generate on demand and cache locally, with bounded network behaviour while they build.
Python and TypeScript
The Python package is the canonical implementation and owns the shared alias table. The npm package syncs that same table at build time, so both ecosystems agree.
// the schema
62 canonical fields, and one honest unknown.
Exposed as a string enum, so it drops straight into JSON without a conversion step.
// how it compares
How RoloDexter compares
Where RoloDexter sits next to the tools and workarounds people already reach for on this exact problem.
Hand-written mapping code
The default: a dict or regex per integration, expanded by hand every time a new CRM or vendor field shows up. Works fine until the next unexpected header, then it silently drops or misroutes a value with nothing to warn you.
OpenRefine
A free, open-source GUI for interactively cleaning one dataset at a time — clustering similar values, editing cells by hand. It isn't a library you call from code, and it doesn't ship a maintained cross-CRM alias table.
Zapier / Make field mapping
No-code automation tools let you map one app's fields to another by hand, inside a single workflow step. Great for a one-off automation; it doesn't give you a reusable alias table, confidence scores, or a library you can call from your own code.
// faq
Questions people ask
Is RoloDexter free to use?
Yes — RoloDexter is free, open-source software released under the MIT license. The Python package installs with pip install rolodexter and the JavaScript/TypeScript package installs with npm install rolodexter; both are maintained as one dual-package, MIT-licensed project.
What does RoloDexter do?
RoloDexter routes messy, inconsistent contact data from any CRM, form, or CSV export to one clean canonical schema. It resolves 600+ known field aliases to 62 canonical fields — like first_name, email, and phone — and returns a confidence score on every match, from exact hits down to heuristic guesses on unnamed columns.
How does RoloDexter figure out what a messy column name means?
Every incoming field is tried against four strategies in order of certainty: an exact lookup against the alias table (confidence 1.0), normalized matching that handles CamelCase and dot.path variants, fuzzy matching that catches typos like phne_nmbr, and a heuristic pass that detects emails, phones, and URLs by the shape of the value itself.
Does RoloDexter work with pandas DataFrames?
Yes. Calling map_dataframe() on a pandas DataFrame renames its columns to the canonical schema and normalizes the values in place, while columns RoloDexter doesn't recognize are kept rather than dropped. The same mapper also handles a single dict payload, a batch of payloads, or a streamed CSV/JSONL export in constant memory.
Does RoloDexter support languages other than English?
Yes. English ships by default, and alias tables for 40 languages — including Spanish, French, German, and Japanese — generate on demand and cache locally, so runtime loading is cache-only and never translates on the fly. Network behavior during generation is bounded with configurable timeouts, retries, and worker counts.
Does RoloDexter fail silently on bad data?
No — non-fatal issues, like a phone number that can't be normalized to E.164, come back as warnings on the result instead of disappearing silently. Enabling strict mode turns any low-confidence match or unparseable value into a loud failure instead of a quietly wrong row, and each warning carries a category for grouping instead of matching message text.
Does RoloDexter work offline?
Yes — matching and normalization for the default English alias table run entirely locally, with no network calls. Only generating alias caches for the other 40 supported languages needs a one-time network fetch, via the optional i18n-generate extra; once cached, those load and match offline too.
How is RoloDexter different from a no-code tool like Zapier?
Zapier and Make let you map one app's fields to another by hand, inside a single workflow step. RoloDexter is a library: call it from Python or TypeScript to resolve any header automatically against 600+ known aliases, with a confidence score on every match — no per-workflow mapping UI required.
// pick your ecosystem
Stop writing the same field map again.
Install it, hand it the payload nobody wants to look at, and read what comes back.