dBASE III is back. In your browser. USE customers like it's 1984.
Remember the dot prompt? Before SQL won, before ORMs, before anyone said "full-stack" — there was dBASE III. You typed USE customers, then LIST, and your data was just there. WebBase-III brings that whole world back: the terminal, the language, BROWSE, @ SAY GET forms, .prg programs, indexes, reports — rebuilt from scratch as a modern web app with its own interpreter in TypeScript, backed by Node.js, WebSockets, and SQLite.
Try it in one click — no install:
The Codespace installs dependencies and starts the dev server automatically. Open the forwarded port 5173 and you're at the dot prompt.
The command interface — type W3Script and see results instantly.
LIST prints all records in active index order. The status bar shows the active database and table.
INDEX ON name TO BYNAME creates a SQLite index and activates it — subsequent LIST output is sorted alphabetically. SEEK "Delta NV" jumps the record pointer to the first match in O(log n).
BROWSE opens a spreadsheet-style grid. Records are shown in active index order. Tab/Enter to edit a cell, Ctrl+N for a new row, Delete to remove a row, Esc to return to the terminal.
EDIT <name> opens the built-in .prg source editor. Programs support the full W3Script language: DO CASE/ENDCASE, DO WHILE/ENDDO, IF/ENDIF, form layouts, and all data commands. Ctrl+S saves, Esc cancels.
@ row,col SAY "label" GET variable lays out character-cell form fields. READ renders them as a live form and waits for the user to fill in values and submit.
The Assistant — sidebar
The permanent left sidebar with category pickers and action buttons.
Wizards open in the main area with a live W3Script preview.
| Feature | Details |
|---|---|
| W3Script interpreter | dBASE III command dialect: navigation, filters, variables, loops, conditionals, forms, programs |
| BROWSE grid | Inline cell editing, keyboard nav, index-ordered display |
| Form engine | @ ROW,COL SAY … GET character-cell layout with READ |
| Indexing | INDEX ON, SEEK, FIND — active index controls all record order |
| DO CASE | Multi-branch conditional, OTHERWISE fallback |
| Built-in functions | EOF(), BOF(), FOUND(), RECNO(), SUBSTR(), STR(), AT(), CTOD(), DTOC() and more |
| Program files | Save, edit, and run .prg scripts with DO / EDIT |
| The Assistant | Permanent left sidebar — open databases/tables, browse, filter, index, search, design reports, run programs without typing |
| Multi-user | Each WebSocket connection gets its own isolated interpreter session |
| Persistent storage | better-sqlite3 with WAL mode — databases survive server restart |
The sidebar on the left drives everything without typing: open or create databases and tables, browse and filter data, build indexes, search, design and run reports, run programs, and modify table structure. Every click generates a real W3Script command that echoes into the terminal — watch it to learn the language. Wizards (New table, Filter, Modify structure, report designer, …) open in the main area and show a live preview of the command they will run.
npm install
npm run dev # http://localhost:5173Production:
npm run serve # builds, then serves everything on http://localhost:3000LAN / Tailscale: the server binds to 0.0.0.0, so http://<tailscale-ip>:3000 works out of the box.
USE DATABASE mydb
CREATE TABLE customers (name CHAR(40), phone CHAR(20), country CHAR(30))
USE customers
APPEND RECORD
REPLACE name WITH "Acme Corp", phone WITH "555-1234", country WITH "BE"
APPEND RECORD
REPLACE name WITH "Zeta Ltd", phone WITH "555-5678", country WITH "NL"
INDEX ON name TO BYNAME
LIST * sorted A→Z
SEEK "Zeta Ltd" * jump to record instantly
BROWSE * open editable grid
SET FILTER TO country == "BE"
LIST * filtered view
SET FILTER TO * clear filter
WebBase-III supports unlimited work areas — each independently holding a table, record pointer, filter, and index. Link areas by key field using SET RELATION TO for relational data access. Cross-area field access uses alias.field dot notation.
Note: dBASE III supported a maximum of 10 work areas (DOS file handle limit). WebBase-III has no such limit. dBASE III used
alias->fieldarrow syntax; WebBase-III uses modernalias.fielddot notation.
| Command | What it does |
|---|---|
SELECT <alias> |
Activate (or create) a work area by name |
USE <table> [ALIAS <name>] |
Open table in active area; optional alias override |
SET RELATION TO <expr> INTO <alias> |
Link active area to another; auto-seeks on every navigation |
SET RELATION TO |
Clear relation on active area |
LIST [col, alias.col, ...] |
List records; optional column list with cross-area fields |
LIST AREAS |
Show all open work areas, pointers, indexes, and relations |
CLOSE |
Close active area's table |
CLOSE ALL |
Close all work areas, reset to single empty area 1 |
Cross-area field access: use alias.field dot notation anywhere an expression is accepted — SET FILTER TO, IF, REPLACE, LIST, INDEX ON.
| Command | What it does |
|---|---|
USE <table> |
Select a table; restores any saved active index |
USE DATABASE <name> |
Open a named SQLite database |
LIST |
Print records in active index order (up to 500) |
LIST STRUCTURE |
Show column schema |
LIST TABLES |
Show all tables with record counts |
LIST DATABASES |
Show all databases on disk (alias: LIST DBS) |
BROWSE |
Open the editable grid |
CLEAR |
Clear terminal output |
CREATE TABLE <n> (col TYPE, ...) |
Create a table |
DROP TABLE <name> |
Delete a table |
APPEND RECORD |
Insert a blank row |
DELETE / DELETE ALL |
Delete current or all records |
PACK |
VACUUM the SQLite file |
GO TOP / GO BOTTOM / GO <n> |
Move record pointer |
SKIP <n> |
Move pointer forward/back |
REPLACE <field> WITH <val>, ... |
Update field(s) on current row |
REPLACE ALL <field> WITH <val>, ... |
Update all (filtered) rows |
SET FILTER TO <expr> |
Set a WHERE clause; empty clears it |
MODIFY STRUCTURE |
Open the Modify-structure wizard for the active table |
ALTER TABLE <t> ADD <col> <type> |
Add a column to a table |
ALTER TABLE <t> DROP <col> |
Remove a column from a table |
ALTER TABLE <t> RENAME <col> TO <new> |
Rename a column |
ALTER TABLE <t> ALTER <col> <type> |
Change a column's type (copy-table dance; data preserved) |
Column ops that can invalidate an index (DROP, RENAME, ALTER type) drop all of the table's indexes and warn you to rebuild with
INDEX ON.
| Command | What it does |
|---|---|
INDEX ON <expr> TO <tag> |
Create index on expression; sets it active immediately |
SET INDEX TO <tag> |
Activate a previously created index |
SET INDEX TO |
Clear active index — restores natural insert order |
REINDEX |
Rebuild SQLite indexes for current table |
LIST INDEXES |
Print all indexes for current table with * active marker |
SEEK <expr> |
Position record pointer at first index match |
FIND <string> |
Alias for SEEK (unquoted string — dBASE III legacy form) |
SORT ON <field>[/D] TO <newtable> |
Write a sorted copy of the table to a new table; /D = descending; honours the active filter |
| Command | What it does |
|---|---|
CREATE REPORT <name> |
Create a new report definition (opens JSON editor) |
MODIFY REPORT <name> |
Edit an existing report definition |
REPORT FORM <name> |
Run report — ASCII to terminal + HTML preview panel |
LIST REPORTS |
List all saved report definitions |
DELETE REPORT <name> |
Delete a report definition |
| Command | What it does |
|---|---|
DO <name> |
Run a saved .prg program |
EDIT <name> |
Open .prg source editor |
LIST PROGRAMS |
Show all saved programs |
Demo programs live in
demos/*.prgand are the single source of truth: they are seeded into the program store on every server start, overwriting any store copy. TryDO inventoryfor a full interactive showcase (work areas, relations, indexes, forms).
| Command | What it does |
|---|---|
STORE <val> TO <var> |
Assign a variable |
INPUT "prompt" TO <var> |
Collect keyboard input |
@ r,c SAY "text" GET <var> |
Define a form field |
READ |
Display the form and wait for submit |
| Command | What it does |
|---|---|
IF <cond> … ENDIF |
Conditional block |
DO WHILE <cond> … ENDDO |
Loop |
DO CASE … ENDCASE |
Multi-branch conditional (CASE, OTHERWISE) |
HELP |
Print command reference |
QUIT |
Exit |
Functions work anywhere an expression is accepted — IF, DO WHILE, STORE, REPLACE, INDEX ON, SET FILTER TO, etc.
| Function | Returns |
|---|---|
EOF() |
True if record pointer is past last record |
BOF() |
True if record pointer is before first record |
FOUND() |
True if last SEEK / FIND matched |
RECNO() |
Current record number |
RECCOUNT() |
Total records in current table |
UPPER(str) |
Uppercase |
LOWER(str) |
Lowercase |
TRIM(str) |
Strip leading and trailing spaces |
LTRIM(str) |
Strip leading spaces only |
SUBSTR(str, start, len) |
Substring — 1-based; len optional (to end) |
LEN(str) |
String length |
AT(needle, haystack) |
1-based position; 0 if not found (case-sensitive) |
STR(num, len, dec) |
Number to right-justified string; default len=10, dec=0 |
VAL(str) |
String to number; non-numeric → 0 |
INT(n) |
Truncate toward zero |
ABS(n) |
Absolute value |
SPACE(n) |
String of n spaces |
REPLICATE(str, n) |
Repeat string n times |
DATE() |
Today as MM/DD/YY |
DTOC(date) |
Date to display string MM/DD/YY |
CTOD(str) |
Display string MM/DD/YY to ISO date |
W3Script supports both styles:
| Syntax | Value |
|---|---|
TRUE / FALSE |
Boolean true/false |
.T. / .TRUE. |
Boolean true (dBASE III style) |
.F. / .FALSE. |
Boolean false (dBASE III style) |
Boolean values display as .T. / .F. in output to match dBASE conventions.
Logical operators are accepted in both styles too: NOT / .NOT., AND / .AND., OR / .OR. (e.g. DO WHILE .NOT. EOF()).
| Key | Action |
|---|---|
| Arrow keys | Navigate cells |
| Enter / F2 | Edit selected cell |
| Tab / Shift+Tab | Move right / left |
| Ctrl+N | New row |
| Delete | Delete current row |
| F5 | Refresh from DB |
| Esc | Exit grid, return to terminal |
server/
index.ts Node.js HTTP + WebSocket server (port 3000)
Session.ts Per-connection session: parses commands, drives Executor
SessionManager.ts Tracks all active sessions
ServerDatabaseBridge.ts IDatabaseBridge impl wrapping better-sqlite3
ProgramStore.ts .prg program storage in data/system.sqlite3
IndexStore.ts Index metadata + active index in data/system.sqlite3
src/
interpreter/
Lexer.ts Tokenises W3Script input (case-insensitive)
Parser.ts Recursive-descent AST builder
Executor.ts Async AST runner; manages db/table/filter/vars/rowPtr/activeIndex
Builtins.ts Stateless built-in function implementations
terminal/
Terminal.ts REPL UI — command history, multi-line block accumulation
ui/
Grid.ts BROWSE spreadsheet — inline cell editing, keyboard nav
FormLayout.ts @ SAY GET form engine — character-cell coordinates
ProgramEditor.ts .prg source editor UI
ws/
WsClient.ts Browser WebSocket client
npm test # unit + integration tests (Vitest)
npx playwright test # end-to-end browser tests (requires dev server running)The Playwright suite (tests/integration.spec.ts, tests/crm.spec.ts) drives a real browser against the running app and covers navigation, filters, indexing, programs, forms, and BROWSE.
AGPL-3.0 — see LICENSE.md.
Why AGPL? WebBase-III is a toy, and the license keeps it that way: anyone can use it, fork it, and learn from it, but nobody can take it closed and sell it as a hosted service without giving their changes back. If you want to run it, hack it, or ship features from your dBASE memories — that's exactly what it's for.








