Claude Code for FoxPro Database (2026)
Why Claude Code for FoxPro Modernization
Visual FoxPro reached end of life in 2015, yet thousands of business applications still depend on DBF file-based databases, PRG procedure files, and SCX/SCT screen files. These applications often run inventory management, point-of-sale, and small business ERP systems where the original developers are long gone. The Xbase dialect, cursor-based data manipulation, and tightly coupled UI-to-data architecture make migration particularly challenging.
Claude Code can parse FoxPro’s PRG syntax, understand SCAN/ENDSCAN loops, REPLACE commands, and SQL-SELECT hybrids that mix Xbase DML with SQL queries. It maps DBF table structures (including memo fields, index tags, and relations) to proper relational schemas.
The Workflow
Step 1: Inventory the FoxPro Project
# Catalog FoxPro project files
mkdir -p ~/foxpro-migration/{source,output,schemas}
find /path/to/vfp-app -type f \( \
-name "*.prg" -o -name "*.dbf" -o -name "*.cdx" \
-o -name "*.scx" -o -name "*.frx" -o -name "*.vcx" \
-o -name "*.mnx" -o -name "*.dbc" \) | sort > ~/foxpro-migration/file-inventory.txt
# Count by type
for ext in prg dbf cdx scx frx vcx mnx dbc; do
echo "$ext: $(grep -c "\.$ext$" ~/foxpro-migration/file-inventory.txt)"
done
# Extract DBF schema using Python dbfread
pip install dbfread
python3 -c "
from dbfread import DBF
import json, glob
for dbf_file in glob.glob('/path/to/vfp-app/*.dbf'):
table = DBF(dbf_file, load=True)
schema = {'name': table.name, 'fields': [
{'name': f.name, 'type': f.type, 'length': f.length, 'decimal': f.decimal_count}
for f in table.fields
], 'record_count': len(table)}
print(json.dumps(schema, indent=2))
"
Step 2: Convert DBF Schemas to PostgreSQL
-- FoxPro DBF field type mapping generated by Claude Code
-- Source: customers.dbf
-- C(50) -> VARCHAR(50), N(10,2) -> NUMERIC(10,2), D -> DATE,
-- L -> BOOLEAN, M -> TEXT (memo), T -> TIMESTAMP, I -> INTEGER
CREATE TABLE customers (
id SERIAL PRIMARY KEY, -- Added: FoxPro uses RECNO()
cust_code VARCHAR(10) NOT NULL, -- Was: C(10), index tag CUSTCODE
cust_name VARCHAR(50) NOT NULL, -- Was: C(50)
address1 VARCHAR(40), -- Was: C(40)
address2 VARCHAR(40), -- Was: C(40)
city VARCHAR(30), -- Was: C(30)
state VARCHAR(2), -- Was: C(2)
zip VARCHAR(10), -- Was: C(10)
balance NUMERIC(12,2) DEFAULT 0,-- Was: N(12,2)
credit_limit NUMERIC(10,2), -- Was: N(10,2)
last_order DATE, -- Was: D
is_active BOOLEAN DEFAULT TRUE, -- Was: L
notes TEXT, -- Was: M (memo field)
created_at TIMESTAMP DEFAULT NOW(),-- Added for audit
updated_at TIMESTAMP DEFAULT NOW() -- Added for audit
);
CREATE UNIQUE INDEX idx_customers_code ON customers(cust_code);
-- FoxPro SET RELATION equivalent becomes foreign keys
ALTER TABLE orders ADD CONSTRAINT fk_orders_customer
FOREIGN KEY (cust_code) REFERENCES customers(cust_code);
Step 3: Convert FoxPro PRG Procedures
Original FoxPro procedure:
* CalcInvoice.prg - Calculate invoice totals
PROCEDURE CalcInvoice
LPARAMETERS tcInvoiceNo
LOCAL lnSubtotal, lnTax, lnDiscount
SELECT InvDetail
SET ORDER TO TAG InvNo
SEEK tcInvoiceNo
lnSubtotal = 0
SCAN WHILE InvDetail.InvNo == tcInvoiceNo
lnLineTotal = InvDetail.Qty * InvDetail.UnitPrice
IF InvDetail.DiscPct > 0
lnLineTotal = lnLineTotal * (1 - InvDetail.DiscPct / 100)
ENDIF
REPLACE InvDetail.LineTotal WITH lnLineTotal
lnSubtotal = lnSubtotal + lnLineTotal
ENDSCAN
* Get tax rate from customer's state
SELECT Invoices
SEEK tcInvoiceNo
lcState = Invoices.ShipState
SELECT TaxRates
SEEK lcState
lnTax = IIF(FOUND(), lnSubtotal * TaxRates.Rate / 100, 0)
SELECT Invoices
REPLACE SubTotal WITH lnSubtotal, ;
TaxAmount WITH lnTax, ;
TotalDue WITH lnSubtotal + lnTax
ENDPROC
Claude Code generates:
# services/invoice_service.py
from decimal import Decimal
from sqlalchemy import select
from models import InvoiceDetail, Invoice, TaxRate
from database import async_session
async def calculate_invoice(invoice_no: str) -> Invoice:
async with async_session() as session:
# Fetch line items (replaces SCAN/ENDSCAN)
details = await session.execute(
select(InvoiceDetail)
.where(InvoiceDetail.inv_no == invoice_no)
.order_by(InvoiceDetail.line_no)
)
details = details.scalars().all()
subtotal = Decimal('0.00')
for detail in details:
line_total = detail.qty * detail.unit_price
if detail.disc_pct and detail.disc_pct > 0:
line_total *= (1 - detail.disc_pct / 100)
detail.line_total = line_total.quantize(Decimal('0.01'))
subtotal += detail.line_total
# Get tax rate (replaces SEEK on TaxRates)
invoice = await session.get(Invoice, invoice_no)
tax_rate_row = await session.execute(
select(TaxRate).where(TaxRate.state == invoice.ship_state)
)
tax_rate = tax_rate_row.scalar()
tax_amount = (subtotal * tax_rate.rate / 100) if tax_rate else Decimal('0.00')
invoice.subtotal = subtotal
invoice.tax_amount = tax_amount.quantize(Decimal('0.01'))
invoice.total_due = subtotal + invoice.tax_amount
await session.commit()
return invoice
Step 4: Verify
# Compare record counts
python3 -c "
from dbfread import DBF
for t in ['customers', 'orders', 'inv_detail']:
print(f'{t}: {len(DBF(f\"/path/to/vfp-app/{t}.dbf\"))} records')
"
# Compare against PostgreSQL
psql -d migrated_db -c "
SELECT 'customers' as tbl, count(*) FROM customers
UNION ALL SELECT 'orders', count(*) FROM orders
UNION ALL SELECT 'inv_detail', count(*) FROM invoice_details;"
# Run data integrity checks
python3 -m pytest tests/test_migration_parity.py -v
CLAUDE.md for FoxPro Migration
# FoxPro to Python/PostgreSQL Migration Standards
## Domain Rules
- DBF tables map to SQLAlchemy models with explicit column types
- RECNO() references must be replaced with SERIAL primary keys
- SCAN/ENDSCAN loops map to SELECT queries with iteration
- SET RELATION maps to SQLAlchemy relationships with foreign keys
- REPLACE command maps to attribute assignment + session.commit()
- FoxPro's loose typing requires explicit Decimal handling in Python
## File Patterns
- Source: *.prg, *.dbf, *.cdx, *.scx, *.frx, *.vcx, *.dbc
- Target: Python (FastAPI + SQLAlchemy + PostgreSQL)
- PRG procedures: src/services/
- DBF tables: src/models/ (SQLAlchemy)
- SCX forms: src/routes/ (FastAPI endpoints)
- FRX reports: src/reports/ (WeasyPrint or ReportLab)
## Common Commands
- python3 -c "from dbfread import DBF; ..."
- alembic revision --autogenerate -m "initial migration"
- alembic upgrade head
- pytest tests/test_migration_parity.py
- uvicorn main:app --reload
Common Pitfalls in FoxPro Migration
-
Memo field encoding issues: FoxPro memo fields (.fpt files) may use code page 1252 or other encodings. Claude Code adds explicit encoding detection using
chardetbefore inserting into PostgreSQL UTF-8 columns. -
Deleted records still in DBF: FoxPro’s DELETE marks records but does not remove them until PACK. Claude Code filters these with
dbfread’signorecaseand deleted-record handling during migration. -
Index expression complexity: FoxPro CDX indexes can contain expressions like
UPPER(LastName)+STR(ZipCode,5). Claude Code converts these to PostgreSQL functional indexes with equivalent expressions.
Related
- Claude Code for Clipper to PostgreSQL Migration
- Claude Code for Delphi to C# Conversion
- Claude Code for VB6 to .NET Migration
- Claude Code for PowerBuilder Modernization (2026)
- Claude Code for SCADA Modernization (2026)
- Claude Code for RPG/AS400 to Modern API (2026)
Frequently Asked Questions
Do I need a paid Anthropic plan to use this?
Claude Code works with any Anthropic API plan, including the free tier. However, the free tier has lower rate limits (requests per minute and tokens per minute) that may slow down multi-step workflows. For professional use, the Build or Scale plan provides higher limits and priority access during peak hours.
How does this affect token usage and cost?
The token cost depends on the size of your prompts and Claude’s responses. Typical development tasks consume 10K-50K tokens per interaction. Using a CLAUDE.md file and skills reduces exploration tokens by 50-80%, which directly lowers costs. Monitor your usage at console.anthropic.com/settings/billing.
Can I customize this for my specific project?
Yes. All Claude Code behavior can be customized through CLAUDE.md (project rules), .claude/settings.json (permissions), and .claude/skills/ (domain knowledge). The most impactful customization is adding your project’s specific patterns, conventions, and common commands to CLAUDE.md so Claude Code follows your standards from the start.
What happens when Claude Code makes a mistake?
Claude Code creates files and edits through standard filesystem operations, so all changes are visible in git diff. If a change is wrong, revert it with git checkout -- <file> for a single file or git stash for all changes. Claude Code does not make irreversible changes unless you explicitly allow destructive commands in settings.json.
Practical Details
When working with Claude Code on this topic, keep these implementation details in mind:
Project Configuration. Your CLAUDE.md should include specific references to how your project handles this area. Include file paths, naming conventions, and any project-specific patterns that differ from defaults. Claude Code reads this file at session start and uses it to guide all operations.
Integration with Existing Tools. Claude Code works alongside your existing development tools rather than replacing them. It respects .gitignore for file visibility, uses your project’s installed dependencies, and follows the build/test scripts defined in package.json (or equivalent). Ensure your toolchain is working correctly before involving Claude Code.
Performance Considerations. For large codebases (10,000+ files), Claude Code’s file scanning can be slow if not properly scoped. Use .claudeignore to exclude generated directories (dist, build, .next, coverage) and dependency directories (node_modules, vendor). This typically reduces scan time by 80-90%.
Version Control Integration. All changes Claude Code makes are regular filesystem operations visible to git. Use git diff after each significant change to review what was modified. For experimental changes, create a branch first with git checkout -b experiment/topic so you can easily discard or keep the results.
Build yours → Create a custom CLAUDE.md with our Generator Tool.
Related Guides
Estimate tokens → Calculate your usage with our Token Estimator.
Try it: Estimate your monthly spend with our Cost Calculator.