Paradox is a desktop relational database from the DOS and early Windows era. It was created by Ansa Software in 1985, became one of Borland's flagship products after Borland bought Ansa in 1987, and passed to Corel in the 1990s. It is a legacy product now. Corel still includes it in the Professional edition of WordPerfect Office for Windows, but it has had no significant development since Paradox 11 in the early 2000s.
Most people who search for Paradox today have a folder of .db and .px files from an old business application and need the data out. The short answer: copy the whole folder, then read the tables with the open-source pxlib tools (pxview, or the pypxlib Python package) and load the result into a current database. The details are below.
What Paradox was
Paradox combined a database engine, a visual query tool and an application builder in one desktop program, in the same category as dBASE and, later, Microsoft Access. Its notable features:
Query by Example (QBE). You filled in an on-screen grid with example values instead of writing a query. Paradox popularised this style on PCs.
PAL and ObjectPAL. The DOS versions were scripted with PAL (Paradox Application Language). Paradox for Windows replaced it with ObjectPAL, an event-driven language attached to forms. The two are not compatible.
File-based multi-user access. There is no server. Every user's copy of Paradox opens the same files on a network share and coordinates through lock files.
The Borland Database Engine (BDE). The Windows versions read and wrote tables through the BDE, which Delphi and C++Builder applications also used. A great many custom business applications from the 1990s are Delphi programs storing their data in Paradox tables, with no copy of Paradox itself involved.
The file formats
A Paradox "database" is a directory. Each table is a family of files sharing a base name:
Memo, formatted memo, graphic, OLE and binary (BLOB) field data
.xNN, .yNN (for example .xg0, .yg0)
Secondary indexes
.val
Validity checks, defaults and referential integrity rules
.tv, .fam
Table view settings and the list of family members
.lck, pdoxusrs.net
Lock files and the network control file for multi-user access
.fsl, .rsl, .ssl, .qbe
Forms, reports, ObjectPAL scripts and saved queries (Paradox for Windows)
.sc
PAL scripts (Paradox for DOS)
Points that matter for recovery:
The data is in .db and .mb. You can export a table without its indexes, but not without its .mb file if it has memo or BLOB fields: long text is cut off at the part stored inline in the record.
.db is not unique to Paradox. SQLite databases, Windows Thumbs.db and several other programs use the same extension. dBASE files are .dbf, a different format.
The table format changed between Paradox 3, 4, 5 and 7. Older readers may refuse newer tables.
Text is stored in a DOS or Windows code page, not Unicode. Accented characters come out wrong unless you convert from the right one.
A table can be password-protected, in which case its contents are encrypted and you need the password.
Status today
Paradox for Windows is sold only as part of WordPerfect Office Professional. The 2021 edition includes it. It is a 32-bit Windows application.
The BDE is deprecated and 32-bit only. Embarcadero has recommended against using it for many years.
Microsoft Access used to import and link Paradox tables. Microsoft dropped that support; current versions of Access cannot open them.
Windows still carries the old 32-bit Jet "Microsoft Paradox Driver (*.db)" ODBC driver, visible only in the 32-bit ODBC administrator (C:\Windows\SysWOW64\odbcad32.exe). It handles older table levels, is unreliable with Paradox 7 and later tables unless the BDE is installed, and cannot be used from 64-bit applications.
So there is no supported, modern way to keep using Paradox tables as a live database. The realistic goal is a one-time migration.
Before you start: copy the folder
Work on a copy, never on the originals:
cp -a /mnt/oldserver/accounts /home/me/paradox-copy
Copy the entire directory, not just the .db files, so that .mb and .px files travel with their tables. If the application is still in use, copy while everyone is out of it; a table copied in the middle of a write can be inconsistent.
Option 1: pxview on the command line
pxview is a small open-source program built on the pxlib library. It reads .db, .px and .mb files without Paradox or the BDE. It is plain C and builds on Linux and other Unix-like systems (on Windows, use WSL). Debian and Ubuntu package the pxlib library; pxview itself is usually built from the source on the project site.
--info prints the table header, including the file version, the number of records and the field list; add --verbose for more. If this fails, the file is either not a Paradox table or is damaged.
The SQL output is a CREATE TABLE followed by INSERT statements. Add --use-copy to get PostgreSQL COPY format, or --skip-schema to write only the data. --recode works only if pxlib was compiled with iconv or recode support; pxview --help reports which.
Option 2: Python with pypxlib
pypxlib wraps the same library for Python and is the easier route when you have many tables:
pip install pypxlib
import csv
from pypxlib import Table
with Table("customers.db", encoding="cp1252") as table, \
open("customers.csv", "w", newline="", encoding="utf-8") as out:
names = list(table.fields)
writer = csv.writer(out)
writer.writerow(names)
for row in table:
writer.writerow([row[name] for name in names])
print("done")
Table looks for a matching .mb file next to the .db automatically. The encoding argument is the code page of the stored text; it defaults to cp850 (DOS Western European). Try cp1252 for data entered in Windows applications and cp437 for US DOS data. Wrap the body in a loop over pathlib.Path(".").glob("*.db") to convert a whole directory.
The package was last released in 2020, so test it on your Python version before relying on it. pxview is the fallback.
Option 3: Paradox itself
If you have a licensed copy of Paradox for Windows, open the table and use File > Export to write delimited text. This is the only route that can also open the forms, reports and ObjectPAL code, which you will want to read to understand what the application did, even though none of it can be converted automatically.
Loading into a current database
Create the table with proper types and load the CSV. In PostgreSQL:
CREATE TABLE customers (
customer_id integerPRIMARY KEY,
name varchar(80) NOT NULL,
balance numeric(15,2),
created date,
notes text
);
psql -d legacy -c "\copy customers FROM 'customers.csv' WITH (FORMAT csv, HEADER true)"
How Paradox field types map:
Paradox type
Use in SQL
Note
Alpha
varchar(n)
Up to 255 characters
Number
double precision
Floating point; do not use for money you need exact
Money
numeric(15,2) or wider
Stored as floating point in Paradox, so check the totals after loading
Short, Long Integer
smallint, integer
BCD
numeric
Date, Time, Timestamp
date, time, timestamp
No time zone information
Logical
boolean
Autoincrement
integer identity column
Reset the sequence above the highest loaded value
Memo, Formatted Memo
text
Formatted memos contain formatting codes
Graphic, OLE, Binary, Bytes
bytea (or files on disk)
OLE fields carry a wrapper around the real file
Verify the migration
Compare row counts: the record count from pxview --info against SELECT count(*).
Compare a sum on each numeric column and the minimum and maximum of each date column.
Check that the longest memo values are complete, which confirms the .mb file was read.
Common problems
"Not a Paradox file" or garbage output. The file is another program's .db. Check with pxview --info.
Memo fields truncated or empty. The .mb file is missing, renamed or in a different directory.
Keys and rules did not come across. Primary keys live in .px, and validity checks and referential integrity in .val. Exports carry data only. Recreate keys, constraints and indexes in the new database by hand.
Deleted records reappear. Do not pass --output-deleted to pxview unless you are deliberately recovering deleted rows.
Table reports as corrupt. File-shared Paradox tables were prone to index damage after crashes. The data in .db is often still readable when the .px is not, which is another reason to export from .db directly.
Select-then-insert breaks under concurrency. Use a unique constraint plus one atomic statement: ON CONFLICT, ON DUPLICATE KEY UPDATE, or UPDLOCK/SERIALIZABLE in SQL Server.
The N+1 query problem: each query takes 1 ms, but there are 301 of them. How to spot it, fix it with eager loading and avoid the cartesian-explosion overcorrection.