The Complete Salesforce to DenchClaw Migration Guide
Complete Salesforce to DenchClaw migration: export via Data Loader, map Leads/Contacts/Opportunities to DenchClaw objects, import to DuckDB, and cut Salesforce costs.
The Complete Salesforce to DenchClaw Migration Guide
Salesforce costs $165-$500/user/month. It requires a certified admin to configure. It takes months to implement properly. And for early-stage startups and small teams, it's almost always overkill.
DenchClaw is local-first, open source, AI-powered, and free. If your team has fewer than 20 people and you're not running a complex multi-region sales operation, migrating from Salesforce to DenchClaw will save you thousands per month and eliminate the admin overhead.
This guide covers every step: exporting from Salesforce, mapping objects to DenchClaw, importing into DuckDB, handling custom fields, and being honest about what Salesforce does that DenchClaw doesn't (yet).
Why Leave Salesforce#
The cost reality:
- Salesforce Essentials: $25/user/month (very limited)
- Salesforce Professional: $165/user/month
- Salesforce Enterprise: $330/user/month
- A 5-person team on Professional: $825/month ($9,900/year)
Beyond licensing: consultant fees, admin salaries, training costs. Salesforce total cost of ownership for a 10-person team easily hits $50,000-$100,000/year.
The complexity problem: Salesforce is built for enterprises with dedicated admins. Customizing it requires Apex code, SOQL queries, and platform knowledge. Every change needs either an admin or a dev.
DenchClaw is configured in minutes. Custom fields via CLI, queries in natural language, no certifications required.
Data ownership: Your Salesforce data lives on Salesforce's infrastructure. Exporting it is possible but not trivial. With DenchClaw, your data is a DuckDB file you can copy, back up, and query directly.
What Salesforce Does Better (Be Honest)#
Before migrating, understand what you're giving up:
- Enterprise-grade reports and dashboards: Salesforce's report builder is powerful. DenchClaw uses DuckDB SQL queries and can push to Google Sheets, but it's not the same visual experience.
- Territory management and complex org hierarchies: Multi-team, multi-region orgs with complex permission structures are Salesforce's strength.
- AppExchange integrations: Salesforce has thousands of certified integrations. DenchClaw's skill system is growing but younger.
- CPQ (Configure, Price, Quote): If you use Salesforce CPQ for complex quoting, DenchClaw doesn't have an equivalent.
- Service Cloud features: Case management, SLA tracking, support queues.
If you're using any of these heavily, migrating to DenchClaw may not be the right move yet. If you're using Salesforce mainly as a contact database with pipeline tracking, you're a perfect candidate.
Step 1: Export from Salesforce#
Salesforce has two main export methods: the built-in Data Export service and the Data Loader application.
Method A: Data Export Service (easiest)#
- In Salesforce, go to Setup → Data → Data Export
- Click Export Now (or schedule weekly)
- Check all the object types you want to export
- Click Start Export
- When the email arrives (up to 48 hours for large orgs), download the zip file
The zip contains CSV files for each exported object: Account.csv, Contact.csv, Lead.csv, Opportunity.csv, Task.csv, Event.csv, etc.
Method B: Salesforce Data Loader (more control)#
Data Loader gives you control over which fields to export and lets you filter records.
- Download Data Loader from Salesforce Setup → Integrations → Data Loader
- Log in with your Salesforce credentials
- Click Export → Choose the object (e.g., Contact)
- Write a SOQL query to select fields:
SELECT Id, FirstName, LastName, Email, Phone, Title, AccountId, LeadSource, Status FROM Contact - Choose output CSV location
- Run the export
Repeat for each object you need.
What to export#
| Salesforce Object | Priority | Notes |
|---|---|---|
| Contact | High | Your main people records |
| Lead | High | Pre-conversion prospects |
| Account | High | Companies |
| Opportunity | High | Deals and pipeline |
| Task | Medium | Activity history |
| Event | Medium | Meetings and calls |
| Note | Medium | Text notes on records |
| CampaignMember | Low | If using campaigns |
| Custom Objects | Varies | Any custom objects you built |
Step 2: Object Mapping — Salesforce to DenchClaw#
| Salesforce Object | DenchClaw Object | Notes |
|---|---|---|
| Contact | people | Primary contact records |
| Lead | people (with status=Lead) | Or separate object if preferred |
| Account | companies | Companies/organizations |
| Opportunity | deals | Pipeline and deal tracking |
| Task | activities | To-dos and follow-ups |
| Event | activities | Meetings, calls |
| Note | documents | Free-text notes |
On Leads vs Contacts: Salesforce's Lead-to-Contact conversion model is one of its most confusing features. In DenchClaw, it's simpler: use the status field to distinguish leads from contacts. Import Leads with status = 'Lead' and Contacts with appropriate statuses.
Step 3: Field Mapping#
Contact/Lead → People#
| Salesforce Field | DenchClaw Field | Notes |
|---|---|---|
| Id | sf_id | Keep for reference |
| FirstName + LastName | name | Combine on import |
| Dedup key | ||
| Phone | phone | |
| Title | job_title | |
| AccountId | company (relation) | Link post-import |
| LeadSource | lead_source | |
| Status (Lead) | status | Map values |
| OwnerId | owner | Import as text (Owner.Name) |
| CreatedDate | created_at | |
| LastModifiedDate | updated_at | |
| Description | notes | |
| MailingCity | city | |
| MailingState | state | |
| MailingCountry | country |
Account → Companies#
| Salesforce Field | DenchClaw Field |
|---|---|
| Id | sf_id |
| Name | name |
| Website | website |
| Phone | phone |
| BillingCity | city |
| BillingState | state |
| BillingCountry | country |
| Industry | industry |
| AnnualRevenue | annual_revenue |
| NumberOfEmployees | employees |
| Type | company_type |
| Description | notes |
Opportunity → Deals#
| Salesforce Field | DenchClaw Field |
|---|---|
| Id | sf_id |
| Name | name |
| Amount | value |
| StageName | status |
| CloseDate | close_date |
| Probability | probability |
| OwnerId | owner |
| AccountId | company (relation) |
| Type | deal_type |
| LeadSource | lead_source |
| Description | notes |
| ForecastCategoryName | forecast_category |
Opportunity Stage Mapping#
| Salesforce Stage | DenchClaw Status |
|---|---|
| Prospecting | Lead |
| Qualification | Qualified |
| Needs Analysis | Qualified |
| Value Proposition | Proposal |
| Id. Decision Makers | Proposal |
| Perception Analysis | Proposal |
| Proposal/Price Quote | Proposal |
| Negotiation/Review | Negotiation |
| Closed Won | Closed Won |
| Closed Lost | Closed Lost |
Step 4: Prepare and Clean Exports#
import pandas as pd
import numpy as np
# ---- Prepare Contacts ----
contacts = pd.read_csv('salesforce-export/Contact.csv')
leads = pd.read_csv('salesforce-export/Lead.csv')
# Combine name fields
contacts['name'] = (contacts['FirstName'].fillna('') + ' ' + contacts['LastName'].fillna('')).str.strip()
leads['name'] = (leads['FirstName'].fillna('') + ' ' + leads['LastName'].fillna('')).str.strip()
# Add status
contacts['import_status'] = contacts.get('Status', 'Contact')
leads['import_status'] = leads['Status'].fillna('Lead')
# Select common fields
common_fields = ['name', 'Email', 'Phone', 'Title', 'LeadSource', 'import_status', 'CreatedDate', 'Id']
contacts_out = contacts[[f for f in common_fields if f in contacts.columns]].rename(columns={
'Email': 'email',
'Phone': 'phone',
'Title': 'job_title',
'LeadSource': 'lead_source',
'import_status': 'status',
'CreatedDate': 'created_at',
'Id': 'sf_id'
})
leads_out = leads[[f for f in common_fields if f in leads.columns]].rename(columns={
'Email': 'email',
'Phone': 'phone',
'Title': 'job_title',
'LeadSource': 'lead_source',
'import_status': 'status',
'CreatedDate': 'created_at',
'Id': 'sf_id'
})
# Merge contacts and leads (dedup by email)
all_people = pd.concat([contacts_out, leads_out]).drop_duplicates(subset='email')
all_people.to_csv('people_ready.csv', index=False)
print(f"Prepared {len(all_people)} people records")
# ---- Prepare Opportunities ----
opps = pd.read_csv('salesforce-export/Opportunity.csv')
# Map stage names
stage_map = {
'Prospecting': 'Lead',
'Qualification': 'Qualified',
'Needs Analysis': 'Qualified',
'Value Proposition': 'Proposal',
'Id. Decision Makers': 'Proposal',
'Proposal/Price Quote': 'Proposal',
'Negotiation/Review': 'Negotiation',
'Closed Won': 'Closed Won',
'Closed Lost': 'Closed Lost'
}
opps['status'] = opps['StageName'].map(stage_map).fillna(opps['StageName'])
deals_out = opps[['Name', 'Amount', 'status', 'CloseDate', 'Probability', 'AccountId', 'CreatedDate', 'Id']].rename(columns={
'Name': 'name',
'Amount': 'value',
'CloseDate': 'close_date',
'Probability': 'probability',
'CreatedDate': 'created_at',
'Id': 'sf_id'
})
deals_out.to_csv('deals_ready.csv', index=False)
print(f"Prepared {len(deals_out)} deal records")Step 5: Import Into DenchClaw#
Create custom fields first#
# Add Salesforce ID field to track original records
npx denchclaw field create --object people --name "sf_id" --type text
npx denchclaw field create --object companies --name "sf_id" --type text
npx denchclaw field create --object deals --name "sf_id" --type text
# Other common Salesforce fields
npx denchclaw field create --object people --name "lead_source" --type select \
--options "Web,Phone,Email,Referral,Paid Search,Social Media,Other"
npx denchclaw field create --object deals --name "probability" --type number
npx denchclaw field create --object deals --name "close_date" --type date
npx denchclaw field create --object deals --name "forecast_category" --type select \
--options "Pipeline,Best Case,Commit,Closed"Import in order: companies first, then people, then deals#
# 1. Import companies
npx denchclaw import csv \
--file salesforce-export/Account.csv \
--object companies \
--field-map "Name:name,Website:website,Phone:phone,Industry:industry,AnnualRevenue:annual_revenue,NumberOfEmployees:employees,Id:sf_id" \
--dedupe name
# 2. Import people
npx denchclaw import csv \
--file people_ready.csv \
--object people \
--dedupe email
# 3. Import deals
npx denchclaw import csv \
--file deals_ready.csv \
--object deals \
--dedupe nameVerify with DuckDB#
-- Overall import summary
SELECT 'people' as object, COUNT(*) FROM v_people
UNION ALL SELECT 'companies', COUNT(*) FROM v_companies
UNION ALL SELECT 'deals', COUNT(*) FROM v_deals;
-- Pipeline value check
SELECT status, COUNT(*) as count, SUM(value::DECIMAL) as total
FROM v_deals
GROUP BY status
ORDER BY total DESC NULLS LAST;
-- Find records missing email (data quality)
SELECT COUNT(*) FROM v_people WHERE email IS NULL;Step 6: Migrate Custom Fields and Objects#
Salesforce custom fields (anything with __c suffix) need to be created in DenchClaw.
List your Salesforce custom fields#
In Salesforce: Setup → Object Manager → Contact → Fields & Relationships → filter by Custom
Script to create all custom fields#
#!/bin/bash
# Create Salesforce custom fields in DenchClaw
# Contact custom fields
npx denchclaw field create --object people --name "account_tier" --type select \
--options "Enterprise,Mid-Market,SMB,Startup"
npx denchclaw field create --object people --name "nps_score" --type number
npx denchclaw field create --object people --name "renewal_date" --type date
npx denchclaw field create --object people --name "product_version" --type text
# Opportunity custom fields
npx denchclaw field create --object deals --name "competitor_names" --type text
npx denchclaw field create --object deals --name "reason_lost" --type select \
--options "Price,Features,Timing,Incumbent,No Decision,Unknown"
npx denchclaw field create --object deals --name "discount_pct" --type numberStep 7: Reports and Dashboards Migration#
Salesforce reports don't export directly. Recreate the most important ones as DuckDB queries.
Common Salesforce reports as DuckDB SQL#
Pipeline by Stage:
SELECT
status as stage,
COUNT(*) as opportunities,
SUM(value::DECIMAL) as total_value,
AVG(value::DECIMAL) as avg_deal_size
FROM v_deals
WHERE status NOT IN ('Closed Won', 'Closed Lost')
GROUP BY status
ORDER BY total_value DESC;Win Rate:
SELECT
ROUND(
COUNT(*) FILTER(WHERE status = 'Closed Won')::DECIMAL /
NULLIF(COUNT(*) FILTER(WHERE status IN ('Closed Won', 'Closed Lost')), 0) * 100, 1
) as win_rate_pct,
COUNT(*) FILTER(WHERE status = 'Closed Won') as won,
COUNT(*) FILTER(WHERE status = 'Closed Lost') as lost
FROM v_deals
WHERE created_at >= NOW() - INTERVAL 90 DAYS;Activities by Rep:
SELECT owner, COUNT(*) as activities, MAX(last_activity) as last_active
FROM v_people
WHERE owner IS NOT NULL
GROUP BY owner
ORDER BY activities DESC;These queries run directly in DenchClaw natural language or via the DuckDB CLI. Push them to Google Sheets for visual dashboards.
Migration Timeline and Risk Mitigation#
Realistic timeline#
| Phase | Duration |
|---|---|
| Export from Salesforce | 1-24 hours (depends on org size) |
| Data cleaning and prep | 2-4 hours |
| Import into DenchClaw | 1-2 hours |
| Verify and fix data quality | 2-4 hours |
| Recreate views and pipeline | 2-3 hours |
| Set up custom fields | 1-2 hours |
| Recreate key reports | 2-4 hours |
| Team training | 1-2 hours |
| Total | 1-3 days |
Risk mitigation#
- Keep Salesforce active for 30 days after going live on DenchClaw. Don't cancel until you're confident.
- Export a second time 2 weeks in to capture any data entered between your first export and go-live.
- Identify your power users (the people who live in Salesforce) and get them involved in testing early.
- Document your key workflows before migrating so you can verify they work in DenchClaw.
FAQ#
Can I export Salesforce sandbox data? Yes. Use Data Loader with sandbox credentials, or enable Data Export in the sandbox. Note that sandbox data may not reflect your actual production records.
What happens to Salesforce relationships (lookup/master-detail)? They export as ID references. You'll need to post-process them to link entries in DenchClaw. The sf_id field you created helps with this — use it to join records after import.
Does DenchClaw support Salesforce-style record types?
Not directly. Use a custom record_type select field to differentiate types, and create separate DenchClaw views filtered by type.
How do I handle Salesforce sharing rules and permissions? DenchClaw is currently single-user or shared-access (same credentials). Team permission models are on the roadmap. If you need row-level security, DenchClaw isn't ready for that use case yet.
What about Salesforce Flows and Process Builder automations? Replace them with DenchClaw webhooks, shell scripts, or skills. Document each automation before migration, then rebuild the most important ones.
Ready to try DenchClaw? Install in one command: npx denchclaw. Full setup guide →
