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 chardet before 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’s ignorecase and 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.

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.

Estimate tokens → Calculate your usage with our Token Estimator.

Try it: Estimate your monthly spend with our Cost Calculator.