Migrating off Salesforce — phase by phase.
Salesforce migrations live or die on two technical details: Bulk API 2.0 for the data extraction and a deliberate strategy for the 18-character record IDs that all your historical integrations point at. We preserve those IDs as a column in the new schema so reports, webhooks and saved searches keep resolving.
The phases, tuned to Salesforce
Each phase below is the Salesforce-specific version of our standard playbook. Same shape every engagement, different specifics per SaaS.
Audit & feature scoping
1-2 weeksWe map the 10-20% of Salesforce your team actually uses — every object, every active workflow, every report your CRO opens. The 80% nobody touches becomes the cost-saving case.
✓Shadow sales, ops and leadership on real tasks✓Pull a 90-day login + feature-usage report from Salesforce Optimizer✓Inventory custom objects, fields, validation rules, Apex triggers, Flows✓Identify integrations and inbound webhooks we need to preserve✓Map who-can-see-what (Sharing Rules + Profiles + Permission Sets) into a tractable RBAC modelYou walk away withA Scope Audit naming every object that stays, every workflow that goes, and a mapping of Sharing Rules → roles.
Schema design & ID strategy
1 weekWe model your data in Postgres, preserving the 18-character Salesforce IDs as a column on every record so historical links and external systems keep resolving. Custom fields get sensible types instead of strings-everywhere.
✓Design the Postgres schema (named tables, real types, real foreign keys)✓Add a `salesforce_id` column on every migrated table (indexed)✓Document the picklist mapping (Schema.PicklistEntry API names vs. labels)✓Decide RBAC: 3-4 named roles in code, replacing the Sharing Rules + Profiles + Permission Sets stack✓Architecture Decision Record + DPA signedYou walk away withA signed ADR, schema migrations checked into git, and a documented ID-preservation strategy.
Extract via Bulk API 2.0
1-2 weeksWe pull every record you care about through Bulk API 2.0 — chunked, async, restartable. Soft-deleted records inside the 15-day Recycle Bin window get a separate pass.
✓Authenticate via OAuth 2.0 (client-credentials or JWT bearer for unattended)✓Submit Bulk API 2.0 query jobs per object, poll for completion, download CSV✓Pull Einstein Activity Capture data separately (it lives on AWS, not in the Salesforce core)✓Backfill custom-object relationships via REST Composite✓Extract metadata (custom fields, picklists, validation rules) via the Tooling API for referenceYou walk away withA re-runnable extract script + a reconciliation report comparing record counts to Salesforce dashboards.
Build + weekly demos
5-8 weeksWorking software every Friday. Your reps log in to staging from week two, use it on real deals from week four. By cutover they've used the new product more than they used Salesforce.
✓Build the leads, opportunities and accounts views — the 8-10 fields your team actually fills in, no more✓Pipeline as a Kanban with stage transitions as plain buttons (no validation-rule mazes)✓Activity logging baked into the views (no tab-switch, no record search)✓Email sync via Gmail / Microsoft Graph (replaces Salesforce Inbox)✓Dashboards rendered server-side from real SQL — no 2,000-row cap, no cache stalenessYou walk away withA production-grade application running on your own infrastructure, with you as the GitHub owner.
Parallel run + reconciliation
2-3 weeksBoth systems live. Your team works in the new product; Salesforce stays read-only as a fallback. Nightly reconciliation reports compare totals and flag drift.
✓Daily incremental sync from Salesforce into the new system (last-modified watermark)✓Nightly reconciliation: record counts, sum-of-£-by-stage, won-lost-by-rep✓Train reps in two 60-minute sessions, recorded for the rest✓Re-route inbound webhooks (Stripe, Calendly, etc.) one at a time with a kill switch✓Confirm every external integration resolves against the preserved `salesforce_id`You walk away withA reconciliation dashboard showing zero drift for seven consecutive days + a signed sign-off from sales leadership.
Cutover + Salesforce read-only
1 day + 90-day fallbackDNS and SSO flip on a Tuesday morning. We sit on a war-room call. Salesforce stays read-only for 90 days as a safety net; auto-renewal cancelled the same day.
✓Smoke-test every critical workflow before flipping✓Cut SSO to the new app; redirect bookmarks via a one-line proxy✓Lock Salesforce to read-only (revoke write profiles)✓Cancel Salesforce auto-renewal in writing✓Hold a 90-day read-only window for compliance / dispute / regretYou walk away withA Cutover Runbook + a Day-1 Status Report + a quiet inbox where the renewal email used to land.
The Salesforce APIs we'd actually hit
Concrete endpoints we use to extract your data during the parallel-run phase. We keep the extract scripts — they're useful at every renewal.
POST /services/oauth2/tokenPOST /services/data/v62.0/jobs/queryGET /services/data/v62.0/jobs/query/{jobId}/resultsGET /services/data/v62.0/query/?q={SOQL}GET /services/data/v62.0/sobjects/{SObject}/{id}POST /services/data/v62.0/composite/tree/{SObject}GET /services/data/v62.0/tooling/sobjects/CustomField/{id}GET /services/data/v62.0/chatter/feeds/record/{recordId}/feed-elementsThe Salesforce-specific gotchas
Things we've learnt the hard way (or that the documentation understates). We'll surface these on the kickoff call so nobody is surprised in week 6.
- ⚠15-character vs 18-character record IDs — Salesforce returns 15 in some contexts and 18 in others; integrations break silently if you don't normalise. We preserve the 18-character form everywhere.
- ⚠Recycle Bin holds soft-deleted records for 15 days only — extract twice, near the start and end of the build, to catch deletions that happened mid-project.
- ⚠Picklist API names diverge from labels and from translated values. Schema.PicklistEntry via the Tooling API is the source of truth; the REST sObject endpoint returns the label.
- ⚠Custom field API names (with __c suffix) vs. labels — labels can change without notice; pin everything to API names in the extract scripts.
- ⚠Einstein Activity Capture data lives on AWS outside the Salesforce core. If your team relies on EAC-captured emails, plan a separate extract (EAC's own export path) — they're not in Bulk API 2.0.
- ⚠Org-wide Sharing Rules + Profiles + Permission Sets + Permission Set Groups don't translate 1:1 to a sensible RBAC model. Plan the explicit role mapping in Phase 2, not at cutover.
- ⚠Salesforce session-scoped governor limits will throttle the extract — keep concurrent Bulk API jobs at or below your org's limit (default 100 active, but tier-dependent).
What stays where it is
Migrations work because they're scoped honestly. Here's what we don't move — by design.
- ·Apex triggers and classes — we rewrite the logic in TypeScript with tests
- ·Visualforce pages and Lightning Web Components — UI gets rebuilt in React
- ·Salesforce Flow — automation becomes durable jobs (Inngest / BullMQ) with retries and a real audit trail
- ·AppExchange managed packages — either replaced by native code or dropped if shelfware
- ·Reports & dashboards inside Salesforce — rebuilt in Metabase on the new Postgres warehouse
- ·Salesforce CPQ / Billing / Field Service — these are large enough products to merit their own scoping conversations
Get a Salesforce-shaped migration plan.
Tell us your seat count, contract end date, and the integrations you can't lose. We'll come back within a business day with a fixed-price scope and a timeline tuned to your renewal.
Scope a Salesforce migration
One business day. NDA + DPA on request.