Status: π‘ More capable than it looks β but with hard gaps Β· Audience: GoBuild staff (ops + onboarding) moving a new Client off their old tool.
Contrary to the older note on Client onboarding Β§6 ("data import has no tooling"), there is a real, wired importer: a generic uploadβmapβpreviewβcommitβundo engine and a one-shot competitor-migration flow with presets for Buildertrend, CoConstruct and QuickBooks. What's genuinely missing is bulk import for cost codes / price books and historical financials. This page is the honest map of both.
Badge legend: π’ supported (wired, works today) Β· π‘ partial / caveat Β· β manual or gap (no tooling).
House terms: a Client is the builder company we onboard (a tenant Organization). A Job is a project inside their account. The builder's own customers are also called Clients/Customers in the product β where ambiguous we say "the builder's Customers."
Access: every import surface is internal back-office only β owner / pm / office roles (app/routers/data_io.py:38). Whole-account backup/restore is owner-only (data_io.py:40). There is no self-serve import for the builder's end customers; a GoBuild operator or the Client's office staff drives it.
/portal/data/import)A single generic pipeline handles any registered resource: upload CSV/XLSX β auto-map columns β preview with row validation β commit β undo.
app/routers/data_io.py:53-124.MAX_ROWS = 5000 rows β app/services/resources/__init__.py:150-193, 29.resources/__init__.py:97-109.DataTransfer with the created row ids, so import_undo deletes exactly what that run created β data_io.py:117, resources/__init__.py:241-268.key_field exists (e.g. email) β resources/__init__.py + spec key_field.Importable resources today (those with a from_row β app/services/resources/specs.py):
| Resource | Registry key | Import? | Notes |
|---|---|---|---|
| Contacts (People directory) | contact |
π’ | dedupe on email β specs.py:67 |
| Leads | lead |
π’ | source / estimated value fields β specs.py:122 |
| Crew & Subs / Vendors | crew |
π’ | dedupe on email β specs.py:161 |
| Customers | client |
π’ | dedupe on email β specs.py:203 |
| Jobs / Projects | job |
π’ | links to an imported Customer by name/email β specs.py:248-293 |
| Tasks / Schedule items | task |
β | export-only (importable=False) β specs.py:307-319 |
β The module docstring at
specs.py:3-6still says "Job and task are export-only" β that's stale for Job (it now hasfrom_row=_job_from_row,specs.py:279). Task is genuinely export-only.
/portal/data/migrate)A thin layer over the importer that ingests a whole account in one shot from another tool β accepts a ZIP bundle or several files, matches each file to a resource, and commits in dependency order (customers before projects) so a Job links to the Customer that just came in.
data_io.py:170-223.app/services/migration.py.__MACOSX junk β migration.py:160-172.migration.py:175-187.migration.py:47-55._MAX_BUNDLE = 8 MB (data_io.py:27); over that, import files one at a time.Presets (migration.py:70-148):
| Vendor | Loads | Key |
|---|---|---|
| π’ Buildertrend | Customers, Leads, Subs & Vendors, Jobs | buildertrend |
| π’ CoConstruct | Clients, Partners, Projects (First/Last auto-merged) | coconstruct |
| π’ QuickBooks | Customers, Vendors, ProjectsβJobs | quickbooks |
β These presets are best-known schemas, not validated against real exports (
migration.py:11-13). The header maps are the only thing to touch when a real file's columns disagree β adjust thePRESETSfile'sheadersdict. Always validate against the Client's actual export before trusting a big commit.
Every new org is seeded day-one with starter libraries β the Client is not starting from a blank slate (app/routers/admin_api.py:526-532, same path as self-serve registration and app/services/instance_reset.py:107-110):
costcodes.seed_default_cost_codes (app/services/costcodes.py:146). Idempotent.templates.seed_starter_templates (app/services/templates.py:1130).admin_api.py:531-532).| Data type | State | Why |
|---|---|---|
| Cost codes / the Client's own scheme | β | No from_row spec. Org gets the ~100 default library; the builder's own numbering can't be bulk-loaded β edit in-app or ask eng for a one-off. |
| Price book / catalog items | π‘ | No file importer. The catalog grows from estimate history only β catalog.seed_from_history mines past EstimateLines (app/services/catalog.py:21). Nothing to mine on day one. |
| Historical financials (invoices, payments, POs, change orders, budgets, estimates) | β | No importer of any kind. Start fresh; carry forward via QBO sync going forward. |
| Job tasks / schedules | β | task spec is export-only (specs.py:310). Jobs import, their task lists don't. |
| Documents, photos, plan files | β | No bulk file import. |
| Open A/R, job costs-to-date, WIP | β | Not modelled for import. |
Also available (not import, but related): π’ export any resource to CSV/XLSX/PDF (data_io.py:128-154), π’ full-account backup & restore ZIP, owner-only (data_io.py:227-257), π’ version history & restore per entity (data_io.py:264-284).
| Source | Typical export | Best path |
|---|---|---|
| QuickBooks (Online/Desktop) | Customers, Vendors, (sub-customers as Projects) CSV/XLSX | π’ quickbooks migration preset, then QBO sync for going-forward financials |
| Buildertrend | Contacts, Jobs, Subs, Leads β one CSV each or a ZIP | π’ buildertrend migration preset |
| CoConstruct | Clients, Partners, Projects | π’ coconstruct migration preset |
| Spreadsheets (Excel/Google Sheets) | Ad-hoc contact/customer/job lists | π’ Unified importer, one resource at a time |
| Other PM tools (JobTree, Contractor Foreman, etc.) | Varies | π‘ Export to CSV, import per-resource via unified importer; add a preset if it recurs |
| Paper / QB Desktop no export | β | β Start fresh; type the handful of active jobs by hand |
Pick the lightest path that works. Order matters: customers/subs before jobs (jobs link back to them).
/portal/data/migrate). It loads customers, subs and jobs in one dependency-ordered pass./portal/data/import), one resource at a time: Customers first, then Crew & Subs.email; rows without an email create fresh records β clean the sheet first if you're re-importing.client_name/client_email (specs.py:287-290).costcodes.py:146) β for most Clients that's enough; map their estimates onto it.catalog.seed_from_history, catalog.py:21) β don't try to pre-load it.Settings β Integrations β Connect QuickBooks, onboarding checklist step 8.2) so new GoBuild financials flow to their books β no back-fill.Copy this per Client. See also docs/client_onboarding_checklist.csv for the full provisioning sequence.
/portal/data/migrate review step. Adjust migration.py PRESETS.headers if columns disagree (eng, migration.py:70-148).data_io.py:117)./portal/data/backup, owner-only) right after the migration commit so there's a restore point.app/routers/data_io.py (surface), app/services/migration.py (presets), app/services/resources/specs.py (what's importable), app/services/costcodes.py + app/services/templates.py (seeds).