Pillar

Migrating Messy Client Data Without the Nightmare

30 June 2026 · Dejan Hervol

English only.Blog posts stay in English so they stay carefully written - not bulk-translated. Product pages are available in 15 languages from the menu.

There is a specific kind of dread that comes from opening a five-year-old client export. Half the phone numbers have a +386 prefix and half don’t. A column called “Notes” mashes together three different kinds of information from whoever was in a hurry that day. Somebody’s company name is spelled two different ways on two different rows, and you genuinely don’t know if that’s the same client.

Wouldn’t it be nice if you could just upload the mess exactly as it is, no cleanup pass first, no reformatting, and have the system tell you honestly what it found, what it guessed, and what it couldn’t fill in? Now you can. We built Craft11 to adapt to how real businesses actually keep their records, not the other way around.

How it actually works

When you upload a file (CSV, Excel, PDF, even a scanned price list), it goes to Neo, the part of Craft11 that reads documents. Neo doesn’t try to match your spreadsheet against a rigid schema. It reads the actual content and proposes structured records: this looks like a name, this looks like an email, this looks like a vertical or category. Each proposal carries a confidence level (high, medium, low), so you’re never left guessing which fields it’s sure about and which ones it’s taking a reasonable swing at.

Nothing gets created yet. You see a review table first. Confidence dots next to each row, inline editing if something’s wrong, a clear count of what’s complete and what isn’t. You approve what’s good. You can fix a typo right there if you want, or just leave it and fix it later. Both are fine.

This is the part that surprises people: it never blocks you on imperfect data. If 47 of your 300 contacts are missing a phone number, you still get the other 253 imported cleanly, and you get an honest, specific list of the 47, not a vague “some records may be incomplete” warning, an actual count that doesn’t go away until it’s actually resolved. Stark can help you track those down conversationally whenever you have a few minutes. The gap is informational, not a gate.

The dedup problem nobody mentions

Here’s something that doesn’t show up in most pitches for import tools: what happens when you upload the same list twice? Or when a teammate already half-entered a client manually, and now the bulk import is about to create a second copy of the same person under a slightly different spelling?

We handle this with a deliberately simple rule, applied automatically before you ever see the review screen. For client imports, it’s a case-insensitive email match against your existing records. For a resource catalogue, it’s a case-insensitive name match. Any row that looks like a likely duplicate gets flagged and defaults to unselected in the review table: not blocked, not auto-merged, just unchecked. You can still tick the box and import it anyway if you genuinely meant to. The system doesn’t get to decide that for you. It just refuses to let an accident slip through silently.

We tested this directly: uploaded a file containing a contact whose email exactly matched an existing client, alongside a genuinely new one. The duplicate got flagged and stayed unchecked. The new contact imported cleanly. Re-running the same file a second time created nothing extra. That’s the whole point: re-uploading shouldn’t be something you have to be careful about.

A real bug, found the honest way

I want to tell you about something that actually went wrong while we were building this, because the alternative, pretending it shipped perfectly, would be exactly the kind of marketing dishonesty we’re trying not to do.

The resource catalogue importer has a VAT rate field. Our database requires it. It has a default of 25%, the standard rate, so a resource without an explicit rate should just fall back to that default automatically. Except it didn’t. If you uploaded a price list with no VAT column at all (which is extremely common, plenty of price lists just don’t have one), the import would crash with a raw server error the moment you tried to activate it.

The reason was subtle: the database’s own default only kicks in when a field is omitted entirely from the insert. Our code was explicitly writing null into that field instead of leaving it out, which meant the database’s safety net never had a chance to catch it. We only found this because we ran a real end-to-end test, with a realistic file that genuinely had no VAT column (not a hypothetical, an actual upload), and watched it fail. The fix was one line: default to 25% in code, the same number the database itself documents as standard, only when the source file is genuinely silent on the matter.

I’m telling you this not because it’s a flattering story, but because it’s the actual texture of building something like this honestly. The instinct to find that bug by actually testing a messy real file, rather than assuming the happy path, is the same instinct behind the whole feature. Software that’s confident about data it’s never actually seen is exactly the kind of software we’re trying not to build.

We tested the whole thing on a company that doesn’t exist yet

Before this shipped, we ran a full simulated month of usage for a four-person consulting team: real inquiries arriving, real client conversations, real catalogue imports, against a dedicated test tenant, using a real AI model the whole way through, not a script. We could run it this thoroughly because we’d already seeded a large, realistic chunk of data from day one and built Velvet, our own testing simulator, specifically so we weren’t limited to checking one edge case at a time. Multiple messy scenarios ran together in the same month: a CSV with a contact that already existed mixed in with genuinely new ones, alongside everything else that month threw at the system.

The total AI cost for that entire simulated month, four people, was under a dollar. We mention this because “AI-powered” import tools have a reputation, sometimes deserved, for becoming an expensive subscription-behind-the-subscription. At this scale, that fear just doesn’t hold up.

What this doesn’t do

In the spirit of not overselling it: it isn’t OCR magic for genuinely illegible scans, and it won’t guess a field that was never in the source file. Structured PDFs and clean spreadsheets work well; a photographed, handwritten invoice from 2019 needs more human review, and the system says so honestly instead of inventing a guess.

It also doesn’t merge duplicates automatically, on purpose. That stays a human’s call, not software’s. Flagging clearly and defaulting to “don’t import this twice” is the right amount of help.

If you’re staring at your own messy export right now

The honest answer to “how long will this take” is: probably an afternoon, not a week. Upload the file. Review what Neo found. Approve what’s right, fix what’s wrong, leave the gaps for later if you want. They’ll still be there, clearly counted, whenever you’re ready. The point was never to make your data perfect before you start. It was to stop perfect data from being the price of admission.


Craft11’s Migration Engine handles both Clients and Resources imports with the same architecture described above: confidence-scored extraction, dedup detection, and honest gap tracking. See it in the product walkthrough →

Questions people actually ask

Will switching to Craft11 mean re-entering all my client data by hand?

No. Upload the file you already have (a spreadsheet, a CRM export, even a supplier's PDF price list) and Neo (Craft11's import agent) proposes structured records from it. You review and approve; nothing gets created without you seeing it first.

What happens to the fields it can't figure out?

They're left blank and flagged clearly, not guessed at. You get a specific list, '47 contacts missing a phone number,' not a vague warning, and you can fix them whenever you actually have five minutes, not before you're allowed to start using the system.

What if I accidentally upload the same list twice?

Craft11 checks for likely duplicates against your existing records (by email for clients, by name for catalogue items) and leaves those rows unchecked by default. You decide whether to import anyway. It never silently creates a second copy of someone you already have.

Start free trial →All posts

Also: answers · use cases · compare · proof