Web Access Builder programmer's guide
<img src="../public/img/logo.svg" alt="Web Access Builder" width="276">Web Access Builder runs VBA-style code in the browser, on an interpreter that follows VBA's rules: the same statements, the same error numbers and messages, and the Access objects (Me, Forms, DoCmd, CurrentDb) that form code relies on. Code written for Access usually runs as it is, as long as it stays inside the database and the browser. This guide covers what is there, what behaves differently and what is not. (The repo and code name is webaccess; some messages in the app still use it.)
For the user-facing features see the user guide. For a side-by-side list of differences see Access and Web Access Builder compared.
The code window
Only admins write code. Open the code window with Alt+F11, Database Tools > Visual Basic, Create > Module or Class Module, View Code on the Form Design tab, or the … button on a property sheet's Events tab. It opens as one tab called Visual Basic, modelled on Access's Visual Basic Editor.
- Project pane: Form modules (one per form,
Form_<name>; dimmed until they have code), Modules, Class modules and JavaScript modules. A dot marks a module with unsaved changes. The pane is hidden on a phone. - Object and Procedure drop-downs: (General) with (Declarations) and the module's procedures; in a form module, Form and each control that has events; in a class module, Class (Initialize, Terminate). Choosing an event without code writes its first and last lines. Events that have code are marked ●.
- Bottom panes: Immediate, and tabs for Locals, Watches, Call Stack and Compile errors. There is no Properties window and no Object Browser.
- Code tab on the ribbon: Insert Module, Class Module, JavaScript Module, Import File…, Export File…, Rename, Remove; Indent, Outdent, Comment Block, Uncomment Block, List Members, Complete Word; Run / Continue, Break, Reset; Step Into, Step Over, Step Out, Run to Cursor, Toggle Breakpoint, Clear Breakpoints, Compile; References…; Immediate, Locals, Watches.
Editing
- VBA colours, line numbers, a line between procedures. Tab and Shift+Tab indent and outdent (4 spaces for VBA, 2 for JavaScript).
- Enter keeps the indent and indents after
If…Then,For,Do,WithandSelect Case. A newSub,FunctionorPropertygets its End line. - Tidying on leaving a line, as VBA does: keyword and name case (
dim→Dim,me.txtname→Me.txtName), spaces around operators, a missing closing quote or(), and block closers lined up with their openers.endifbecomesEnd If.? xbecomesDebug.Print x, at the start of a line or afterThenorElsein a single-line If (VBA would writePrint x, which fails in code), so the line still runs in Access. - Syntax errors turn the line red when you leave it, with the message above the code (
Compile error: Expected: Then or GoTo). - IntelliSense: after
Asthe types, afterNewthe classes, after an object and a dot its members (DoCmd.,Me.,Me.txtName.,rs.,rs.Fields(0)., a class variable, an Enum, a With block's.), afterMe!the controls and fields, afterForms!the forms, and in arguments and after=the constants that fit (MsgBox "Hi",offers vbOKOnly, vbYesNo…). Tab inserts, Enter inserts and starts a new line, a space, bracket, comma or dot inserts and is typed, Esc closes. Ctrl+Space completes the word, Ctrl+J lists members, Ctrl+I shows Parameter Info. - Parameter Info: typing a call shows its arguments under the line, the current one in bold and optional ones in [brackets], for built-ins, object methods, JavaScript functions and your own procedures.
- Saving: Ctrl+S or Save saves every changed module. A module with errors still saves, and the toast says how many errors it has.
Compile
Compile checks the whole module, as Debug > Compile does: unclosed and mismatched blocks, declarations in the wrong place, labels, duplicate and ambiguous names, Exit in the wrong kind of block, undeclared variables under Option Explicit, calls to procedures that don't exist, Me. names the form doesn't have, DoCmd methods that don't exist, and things a web page cannot do (see What a web page cannot do). After a compile the error list keeps up as you type.
When a form opens, its module is checked. A module that does not compile does not run: admins see a Compile error dialog with a Debug button; users see a note asking them to tell an admin. The runtime runs the saved code with any unsaved edits from the code window on top, as Access runs the code you are editing.
Import and export
Export File… writes a standard module as .bas (with Attribute VB_Name), a class or form module as .cls (with the VERSION 1.0 CLASS header) and a JavaScript module as .js, with CRLF line ends, so the files open in Access's editor. Import File… reads .bas, .cls, .txt, .js and .mjs up to 512 KB. The module name comes from Attribute VB_Name; a Form_<name> file goes behind that form, which must exist. If the name is taken you can Replace it or Import as a copy (Module1 → Module11, as VBA does).
Modules
| Kind | Made with | Notes |
|---|---|---|
| Form module | the form's View Code, or an event's … | Named Form_<form>; renamed and deleted with its form. |
| Standard module | Create > Module, Insert Module | Procedures every form can use. |
| Class module | Create > Class Module | A type of object, made with New. |
| JavaScript module | Code tab > JavaScript Module | Functions VBA calls, in place of DLLs. |
Only admins can open the code window, but every module's code is sent to each user's browser to run (and JavaScript modules run there too), so never put passwords or API keys in code.
Module names start with a letter and use letters, digits and _ (no spaces), up to 64 characters, and cannot start with Form_ or Report_. New VBA modules start with Option Compare Database and Option Explicit. A module holds up to 512 KB of code. Attribute lines are accepted and ignored; Option Private Module is accepted.
Class modules
' class module Customer
Private mName As String
Public Town As String
Private Sub Class_Initialize()
Debug.Print "made"
End Sub
Public Property Get Name() As String
Name = mName
End Property
Public Property Let Name(ByVal value As String)
If Len(value) = 0 Then Err.Raise 5, "Customer", "A name is needed"
mName = value
End Property
Public Function Greeting() As String
Greeting = "Hello, " & mName & " of " & Town
End Function
Dim c As Customer
Set c = New Customer
c.Name = "Ann": c.Town = "Leeds"
MsgBox c.Greeting
Set c = Nothing ' Class_Terminate runs here
- Each object has its own copy of the module's variables. Its members are the Public variables, Subs, Functions and Property Get/Let/Set; Private ones raise error 438 from outside. Inside the class,
Meis the object. Dim x As New Customermakes the object on first use, and again if used afterSet x = Nothing.Class_Initializeruns when an object is made.Class_Terminateruns when the last reference lets go (Set c = Nothing, the end of the procedure holding it, removal from a Collection).Endand Reset stop without running it, as in VBA.TypeName(c),TypeOf c Is Customer,Is Nothing,a Is b, passing ByRef or ByVal, returning from Functions and keeping in Collections all work.- Not supported:
Implements,Event,RaiseEvent,WithEvents,Friendoutside Sub/Function/Property, default members andFor Eachover your own class.
The language
Declarations: Dim, Static, Const, ReDim [Preserve], Erase, Public/Private/Global module variables, Type … End Type, Enum … End Enum, arrays with any bounds (Dim a(1 To 10, 0 To 3)), Option Explicit, Option Compare Binary|Text|Database, Option Base 0|1.
Types after As: Boolean, Byte, Integer, Long, LongLong, LongPtr, Single, Double, Currency, Decimal, Date, String, Variant, Object, Collection; Access's Form, Report, Control, Controls, TextBox, ComboBox, ListBox, CheckBox, OptionButton, OptionGroup, ToggleButton, CommandButton, Label, Image, SubForm, TabControl, Page, Rectangle, Line, Section; DAO's Database, Recordset, Recordset2, Field, Fields, QueryDef, QueryDefs, TableDef, TableDefs, Parameter, Parameters, Index, Indexes, Property, Properties, Workspace; ErrObject, TempVar, JSON; your Types, Enums and class modules; and Dictionary and RegExp when their references are ticked. DAO., Access. and VBA. prefixes are accepted.
Procedures: Sub, Function, Property Get/Let/Set, Static procedures, ByRef (the default) and ByVal, Optional with defaults and IsMissing, ParamArray, named arguments (Cancel:=True), Call.
Flow: If … Then … ElseIf … Else … End If and single-line If; Select Case with lists, To ranges and Is >; For … Next with Step; For Each; Do … Loop with While or Until at either end; While … Wend; With; GoTo label; Exit For/Do/Sub/Function/Property; End; Stop.
Errors: On Error GoTo label, On Error Resume Next, On Error GoTo 0, On Error GoTo -1, Resume, Resume Next, Resume label, Err (see Errors).
Other: Debug.Print (with ; and ,), Debug.Assert (accepted, does nothing yet), Mid(s, start, n) = x, LSet and RSet (as plain assignment), date literals #…#, vbObjectError.
Rules worth knowing
- Date literals in code are read as VBA reads them:
#month/day/year#, or ISO#2026-11-08#. A first number over 12 is taken as the day. (Access SQL and criteria strings read#dd/mm/yyyy#day first; see The SQL dialect.) Option Compare Database(the default) compares text ignoring case;Option Compare Binarymakes=,<and the other comparisons case-sensitive.InStr,InStrRev,StrComp,ReplaceandFilterignore case unless you passvbBinaryCompare, whatever the Option Compare.- Integer, Long and Byte overflow raises error 6; division by zero error 11; Null into a typed variable error 94.
Roundrounds half to even, as VBA does.- Objects are reference-counted;
Class_Terminateruns at the end of the statement that drops the last reference. - There is no recursion depth limit and no runaway-loop timeout: an infinite loop keeps the tab busy (it yields every 5,000 statements, so Break works from the code window). Error 28 (out of stack space) is never raised.
Not in the language: GoSub … Return, On … GoTo and On … GoSub, Declare, AddressOf, file statements (Open, Close #, Print #, Input #, Line Input, Get, Put, Write #, Lock, Seek), a bare Print, Implements, Event, RaiseEvent, WithEvents. Each is a compile error with a message saying what to do instead.
Built-in functions and constants
| Group | Functions |
|---|---|
| Text | Len, Left, Right, Mid, InStr, InStrRev, UCase, LCase, Trim, LTrim, RTrim, Replace, Space, String, StrReverse, StrComp, StrConv (modes 1–3), Chr, ChrW, Asc, AscW, Split, Join, Filter, Format, FormatNumber, FormatCurrency, FormatPercent, FormatDateTime, Hex, Oct, Val, Str |
| Conversion | CStr, CInt, CLng, CByte, CDbl, CSng, CDec (as Double), CCur, CDate, CBool, CVar, Nz |
| Maths | Abs, Int, Fix, Round, Sgn, Sqr, Exp, Log, Sin, Cos, Tan, Atn, Rnd, Randomize (accepted, no effect), RGB |
| Dates | Date, Now, Time, Timer, Year, Month, Day, Hour, Minute, Second, Weekday, DateSerial, TimeSerial, DateValue, TimeValue, DateAdd, DateDiff, DatePart, MonthName, WeekdayName |
| Information | IsNull, IsEmpty, IsNumeric, IsDate, IsArray, IsObject, IsMissing, IsError (always False), TypeName, VarType, LBound, UBound |
| Arrays | Array, Split, Join, Filter, LBound, UBound, SortArray (an addition) |
| Choice | IIf, Choose, Switch |
| Interaction | MsgBox, InputBox, Beep, DoEvents, Eval |
| Domain | DLookup, DCount, DSum, DAvg, DMin, DMax, DFirst, DLast |
| Objects | CreateObject (Scripting.Dictionary and VBScript.RegExp only) |
| Other | CVErr, CurrentUser (always "Admin", as in an unsecured Access database; use Application.CurrentUser for the signed-in name), SysCmd (returns 0) |
Notes:
Formattakes the expression and the format; the first-day-of-week and first-week arguments are ignored, as they are inDateDiffandDatePart(Weekdayhonours its first-day argument). Named formats (General Date, Long Date, Medium Date, Short Date, Long Time, Medium Time, Short Time, General Number, Currency, Fixed, Standard, Percent, Yes/No, True/False, On/Off) and custom formats (dd/mm/yyyy,hh:nn,#,##0.00,0.0%, two;sections) work; scientific (E+) and text (@ & < >) formats do not.- Dates are UK by default:
Format(d, "Short Date")gives08/11/2026;FormatCurrencyuses £. MsgBoxsupports every button set (vbOKOnly to vbRetryCancel) and the default-button flags, and returns vbYes, vbNo and so on. The title defaults to "Web Access Builder".InputBoxreturns""if cancelled; position and help arguments are ignored.Eval("[Forms]![Orders]![Qty] * 2")works out an Access expression, readingForms!andTempVars!references from what is open.
Constants: VBA's (vbCrLf, vbTab, vbNullString, the MsgBox, VarType, compare, day-of-week and colour constants, vbKey… for the common keys) and Access's (acForm, acNormal, acDialog, acFormAdd, acSaveNo, acNext, acNewRec, the acCmd… constants below, acToolbarYes/No, acTextBox and the other control types, acDetail, acHeader, acFooter) and DAO's (dbOpenDynaset, dbOpenSnapshot, dbFailOnError, dbSeeChanges, dbText, dbLong and the other data types).
Events
An event runs when the form's module has a procedure called <control>_<Event> or Form_<Event>, as when Access shows [Event Procedure]. The property sheet's Events tab shows which ones have code. Code runs for everyone who opens the form, not just admins.
Form events fired
| Event | Arguments | Notes |
|---|---|---|
| Open | Cancel | Cancel stops the form opening |
| Load | ||
| Current | Each time a record becomes current | |
| BeforeInsert | Cancel | First change to a new record |
| Dirty | Cancel | First change to a record; Cancel undoes it |
| BeforeUpdate | Cancel | Before saving; Cancel keeps the record unsaved |
| AfterInsert, AfterUpdate | After saving | |
| Delete | Cancel | |
| Timer | Every Timer Interval milliseconds | |
| Unload | Cancel | Cancel keeps the form open |
| Close |
Control events fired: Click, DblClick, Change, BeforeUpdate (Cancel), AfterUpdate, Enter, Exit (Cancel keeps the focus), GotFocus, LostFocus, a tab control's Change, and Render (not in Access, below).
Listed but not fired yet: the editor offers Activate, Deactivate, Resize, Error, KeyDown, KeyUp, KeyPress, NotInList, the form's own Click and DblClick, and a text box's or combo box's Dirty. Code for them compiles but is not run yet. MouseDown, MouseUp, MouseMove, BeforeDelConfirm and AfterDelConfirm are not offered.
Datasheet view runs no code. A form shown in Datasheet view (its own Datasheet view, a datasheet subform, or a form whose Default View is Datasheet) is a plain grid with conditional formatting: none of its event procedures, including On Render, run there. Use Continuous Forms when the form needs code.
DoCmd.CancelEvent cancels as setting Cancel = True does. While code is paused in the debugger, forms run no other events, as in Access.
On Render
Private Sub txtItem_Render() runs each time a record's copy of the control is drawn, in Single Form and Continuous Forms, with Me pointing at that record. It runs after conditional formatting, so code has the last word.
Private Sub txtDue_Render()
If Me.Due < Date And Not Me.Paid Then
Me.txtDue.ForeColor = vbRed
Me.txtDue.FontBold = True
Me.Detail.BackColor = RGB(255, 235, 235)
End If
End Sub
It can read the record (Me.Qty, Me!Status) and set that copy's BackColor, ForeColor, FontBold, FontItalic, FontUnderline, FontSize, Visible, Enabled, Caption and ControlTipText, and Me.Detail.BackColor for the row. It cannot change values (error 2448) or move the focus. If it raises an error it is reported once and not run again until the form reopens. Access has no equivalent.
Forms and controls from code
The form (Me, Forms!Name, Screen.ActiveForm)
| Read and write | Read only | Methods |
|---|---|---|
| Caption, Dirty (False saves), AllowEdits, AllowAdditions, AllowDeletions, DataEntry, TimerInterval, Tag, Filter, FilterOn, OrderBy, OrderByOn, Bookmark | Name, NewRecord, CurrentRecord, RecordCount, OpenArgs, RecordSource, DefaultView, PopUp, Modal, Visible, Width, InsideWidth, InsideHeight, Controls, Count, Detail, FormHeader, FormFooter, ActiveControl, Form, Parent, RecordsetClone, Recordset, HasModule | Requery, Refresh, Recalc, Repaint, Undo, SetFocus, Move, Controls(i), Section(n) |
Me.txtNameandMe!txtNamereach controls, then fields; a field with no control is reached asMe!Field.Me("txtName")andMe(0)reach controls only.- Setting
Me.RecordSourceraises 2448 ("Changing the Record Source from code comes in a later update"); settingMe.Dirty = Trueraises 2448 as in Access. Me.Filter/Me.FilterOn,Me.OrderBy/Me.OrderByOn,DoCmd.ApplyFilterandDoCmd.OpenForm's WhereCondition take Access criteria and sort lists:"Town = 'Leeds' And Score > 5","Surname Like 'B*' Or Joined >= #01/01/2026#","Score DESC, Surname". The form's totals follow the filter. A field the form doesn't have is error 2471.Me.RecordsetCloneandMe.Bookmarkfind a record:Set rs = Me.RecordsetClone: rs.FindFirst "Surname = 'Clark'": If Not rs.NoMatch Then Me.Bookmark = rs.Bookmark.Me.Recordsetmoves the form as it moves.- Setting a control's property shows on every row of a continuous form, as in Access.
- Not implemented although they compile:
Me.Cycle,Hwnd,Painting,Properties,WindowLeft/Top/Width/Height,AutoCenter,Height,BackColorandAllowFilters(errors 2465 or 438 when run).
Controls
| Read and write | Read only | Methods |
|---|---|---|
| Value (the default), Text, Visible, Enabled, Locked, Caption, BackColor, ForeColor, FontBold, FontItalic, FontUnderline, FontSize, Left, Top, Width, Height, Tag, ControlTipText, DefaultValue, Format, TextAlign, HyperlinkAddress, RowSource, ControlSource, SourceObject | OldValue, Name, ControlType, Parent, Form (subform), ListCount, ListIndex, Section, Picture | SetFocus, Requery, Undo, Dropdown, Column(i, [row]), ItemData(i) |
- Colours are Access colour numbers (
vbRed,RGB(…)); positions and sizes are in twips (15 per pixel). Textis for text boxes and combo boxes.Valueis for data controls (text box, combo, list, check box, option button, option group, toggle button, tab control); on a subform control the default member is its form, soMe.sfrmLines!QtyandMe.sfrmLines.Form.Requerywork.TypeNameandControlTypegive TextBox 109, ComboBox 111, ListBox 110, CheckBox 106, OptionButton 105, OptionGroup 107, ToggleButton 122, CommandButton 104, Label 100, Image 103, SubForm 112, TabControl 123, Line 102, Rectangle 101.- Sections (
Me.Detail,Me.FormHeader,Me.FormFooter) have BackColor and Visible (read and write), Height and Name. For Each ctl In Me.ControlsandFor Each frm In Formswork.Formsholds open top-level forms, not subforms.
DoCmd
| Method | Notes |
|---|---|
| OpenForm FormName, [View], , [WhereCondition], [DataMode], [WindowMode], [OpenArgs] | Views acNormal, acDesign, acFormDS; data modes acFormAdd, acFormEdit, acFormReadOnly; acDialog waits until the form closes. FilterName is ignored. With acFormDS the WhereCondition, OpenArgs and the form's code don't apply and the form isn't in Forms; for users acFormDS is ignored and the form opens in its Default View. |
| Close [ObjectType], [ObjectName], [Save] | Saves the record first, unless Save is acSaveNo, which discards it. With no name, closes the form the code belongs to. |
| GoToRecord , , acFirst | acPrevious | acNext | acLast | acGoTo | acNewRec, [Offset] | Error 2105 past the ends |
| GoToControl Name | Error 2109 if not found |
| Requery [ControlName], RefreshRecord, ShowAllRecords | |
| ApplyFilter , WhereCondition | FilterName is ignored |
| RunCommand | acCmdSaveRecord, acCmdUndo, acCmdDeleteRecord, acCmdRecordsGoToNew, acCmdRefresh, acCmdRemoveFilterSort, acCmdSelectRecord (no effect); others raise 2046 |
| RunSQL SQL | Asks before changing records unless SetWarnings False (see below) |
| SetWarnings, Hourglass, Beep, CancelEvent | |
| ShowToolbar "Ribbon" | "Navigation Pane", acToolbarYes | acToolbarNo | Other toolbar names are ignored |
| MoveSize [Right], [Down], [Width], [Height] | Pop-up forms; twips |
| OpenTable, OpenQuery | View and data mode are ignored. OpenQuery on a saved action query runs it, with the same warnings as RunSQL unless SetWarnings False (No is error 2501) |
| Save, Maximize, Minimize, Restore | Accepted, no effect (designs are saved in Design view) |
| OpenReport, OutputTo, FindRecord, FindNext, SearchForRecord | Compile, but raise 2501 "… comes in a later update". Reports open from the Navigation Pane; opening them from code is still to come |
Not available on the web (compile error): TransferDatabase, TransferSpreadsheet, TransferText, TransferSharePointList, PrintOut, RunMacro, SendObject, OpenModule, CopyObject, DeleteObject, Rename, SetMenuItem, Echo, LockNavigationPane, NavigateTo, SelectObject, Quit, RunDataMacro, SetProperty, SetParameter, OpenFunction, BrowseTo, CloseDatabase, CopyDatabaseFile, OpenDiagram, OpenStoredProcedure, OpenView, RepaintObject, RunSavedImportExport, SetDisplayedCategories, SingleStep, AddMenu.
Other Access objects
- Forms:
Forms!Orders!txtName,Forms("Orders"),Forms(0),Forms.Count. - Screen:
ActiveForm(2475 if none),ActiveControl(2474 if none),MousePointer = 11for the wait cursor. - TempVars:
TempVars!Name = …,TempVars("Name"),.Add,.Remove,.RemoveAll,.Count,For Each. They last for the browser session and are shared by every form and module. An unset TempVar is Null. - Application:
Forms,Screen,DoCmd,TempVars,CurrentUser(the signed-in user name),Name("Web Access Builder"),Version.Application.Quitraises 2501 ("Close the browser tab instead"). - Debug:
Print,Assert,Clear(an addition). - Not available, although the names compile:
CodeDb,CurrentProject,DBEngine,Reportsand theErrorfunction.
Data from code: DAO and domain functions
Code reads and changes data through the same Access SQL rules as queries, and only in this database's own tables and queries. Every data call goes to the server, which checks the user is signed in; users can read and change data from code just as they can on forms. Only admins can make new tables.
Domain functions
DLookup, DCount, DSum, DAvg, DMin, DMax, DFirst, DLast (expression, domain, [criteria]), e.g. DLookup("Surname", "Customers", "ID = " & Me.CustomerID). The domain is a table or saved query; criteria are Access criteria. * is allowed only in DCount. They work in calculated controls too. DStDev, DStDevP, DVar and DVarP are not available.
Recordsets
Dim rs As DAO.Recordset
Set rs = CurrentDb.OpenRecordset("SELECT * FROM Customers WHERE Town = 'Leeds' ORDER BY Surname")
Do Until rs.EOF
Debug.Print rs!Surname, rs("Town")
rs.MoveNext
Loop
rs.Close
CurrentDb.OpenRecordsettakes a table name, a saved query's name or SQL;dbOpenSnapshotopens it read-only. A QueryDef'sOpenRecordsetworks too (below).- Properties: EOF, BOF, RecordCount (all records, at once), AbsolutePosition, Bookmark, LastModified, NoMatch, EditMode, Fields, Name, Updatable, Type.
- Methods: MoveFirst, MoveLast, MoveNext, MovePrevious, Move n, FindFirst/FindNext/FindPrevious/FindLast with NoMatch, Clone, Requery, GetRows, Close; and for updatable recordsets Edit, AddNew, Update, CancelUpdate, Delete. Each Update and Delete is saved straight away, with the same checks as a form.
- Fields:
rs!Name,rs("Name"),rs(0),rs.Fields(0).Name,For Each fld In rs.Fields. A Field has Value, Name, Type (dbText 10, dbLong 4 and so on), Size, OriginalValue, Required, SourceTable and SourceField. - A recordset is updatable when its rows are one table's records, whether it was opened from the table, from SQL or from a saved query that reads one table. Joins, totals, DISTINCT, queries on other queries and snapshots are read-only: Edit, AddNew and Delete raise 3027. (Forms on queries stay read-only.)
- A recordset reads every record at once, up to 50,000; more raises an error asking for criteria.
- Moving away from an unsaved Edit or AddNew drops it, as DAO does.
Action queries
CurrentDb.Execute "UPDATE Orders SET Status = 'Late' WHERE Due < Date() AND Status = 'Open'", dbFailOnError
Debug.Print CurrentDb.RecordsAffected
DoCmd.SetWarnings False
DoCmd.RunSQL "DELETE * FROM Orders WHERE Status = 'Cancelled'"
DoCmd.SetWarnings True
INSERT INTO … VALUES (…),INSERT INTO … SELECT …,UPDATE … SET … WHERE,DELETE … WHERE, and (admins only)SELECT … INTO NewTable, which replaces a table of that name. UPDATE and DELETE take joins (UPDATE Items INNER JOIN NewPrices ON … SET Items.Price = NewPrices.Price,DELETE Items.* FROM Items LEFT JOIN … WHERE … Is Null); they change one table.CurrentDb.Execute "Reprice"runs a saved action query without asking;DoCmd.OpenQuery "Reprice"runs it with RunSQL's warnings.- Values are checked against their fields (error 3464 for text in a number field). Each statement runs as one transaction: with
dbFailOnErrora bad value raises its error; without it, the statement changes nothing and raises no error, andRecordsAffectedis 0.RecordsAffectedsays how many records changed. DoCmd.RunSQLfirst works out how many rows will change and asks "You are about to update N row(s)…", unless warnings are off. No raises 2501.- Parameters: RunSQL and OpenQuery fill
[Forms]![Orders]![ID]and[TempVars]![Name]references from open forms and TempVars, and ask for any others.CurrentDb.ExecuteandCurrentDb.OpenRecordsetfill Forms and TempVars references too (Access doesn't), but any other parameter raises 3061, as in Access: put the value into the SQL, or use a QueryDef. - Not yet: transactions (
BeginTrans,CommitTrans,Rollback): each statement is saved as soon as it runs.
QueryDefs and TableDefs
Dim qdf As DAO.QueryDef, prm As DAO.Parameter
Set qdf = CurrentDb.QueryDefs("Orders by town")
qdf![Which town?] = "Leeds" ' or qdf.Parameters("Which town?") = "Leeds"
Set rs = qdf.OpenRecordset
Set qdf = CurrentDb.QueryDefs("Close old orders")
For Each prm In qdf.Parameters: prm.Value = Eval(prm.Name): Next ' fills [Forms]!… as in Access
qdf.Execute dbFailOnError
Debug.Print qdf.RecordsAffected
CurrentDb.QueryDefs("Name"),QueryDefs(0)andFor Eachgive each saved query:Name,SQL(setting it saves the query, as SQL view does),Type(dbQSelect,dbQMakeTable,dbQAppend,dbQUpdate,dbQDelete),Fields,Parameters(each with Name, Type and Value),OpenRecordset,Execute [dbFailOnError]andRecordsAffected. As in Access, a QueryDef doesn't fill[Forms]!…parameters itself: set them, or useEval(prm.Name).CurrentDb.CreateQueryDef("Name", sql)saves a new query;CreateQueryDef("", sql)makes a temporary one that isn't saved.CurrentDb.QueryDefs.Delete "Name";QueryDefs.Append.CurrentDb.TableDefs("Name")givesName,RecordCount,Fields(Name, Type, Size, Required, DefaultValue, Attributes, OrdinalPosition, andProperties("Caption")andProperties("Description")),Indexes(the primary key and indexed fields) andOpenRecordset.- Make a table in code with
CurrentDb.CreateTableDef("Roads"),tdf.CreateField("Road", dbText, 40)(fld.Attributes = dbAutoIncrFieldfor an AutoNumber),tdf.Fields.Append fldandCurrentDb.TableDefs.Append tdf. On a saved table,tdf.Fields.Appendadds a field,tdf.Fields.Delete "Name"deletes one and setting a field's or table'sNamerenames it.CurrentDb.TableDefs.Delete "Name"deletes a table. - Saving or changing a query or table design from code needs an admin; for users it fails with an error.
- Not on the web: linked tables (
Connect,RefreshLink),CreateIndex(set a field's Indexed property in Design view),Relations,ContainersandWorkspaces.
The SQL dialect
Queries, criteria strings, filters, domain functions and recordsets all use one Access SQL dialect, which the server translates to SQLite.
Accepted
[PARAMETERS name type, …;] SELECT [DISTINCT] [TOP n] … [INTO table] FROM … [WHERE] [GROUP BY] [HAVING] [ORDER BY].DISTINCTROWandALLare accepted and ignored.INNER JOIN,LEFT [OUTER] JOIN,RIGHT [OUTER] JOIN, nested in parentheses as Access writes them; comma lists for cross joins; subqueries in FROM (with an alias), scalar subqueries,In (SELECT …)andExists (SELECT …), correlated or not. Saved queries can be used as sources (they become WITH clauses).- Operators:
And,Or,Xor,Not,= <> < > <= >=(and!=),Like,Between … And …,In (…),Is [Not] Null,&,+ - * /,\,Mod,^. - Literals: numbers,
"text"or'text',True/False/Yes/No,Null, and dates as#dd/mm/yyyy#,#yyyy-mm-dd#,#8 Nov 2026#, with an optional time (14:30,2:30 pm). Likeuses Access's wildcards:*,?,#(a digit),[a-z],[!abc].%and_are ordinary characters.- Functions: Len, Left, Right, Mid, InStr, UCase, LCase, Trim, LTrim, RTrim, Replace, Space, Format, StrComp, Nz, IIf, Switch, Choose, IsNull, IsNumeric, IsDate, Val, CStr, CInt, CLng, CDbl, CSng, CCur, CDate, DateValue, CBool, Abs, Int, Fix, Round, Sgn, Sqr, Date, Now, Time, Year, Month, Day, Hour, Minute, Second, Weekday, DateSerial, DateAdd, DateDiff, DatePart, MonthName, WeekdayName; aggregates Sum, Avg, Count, Min, Max, First, Last, StDev, StDevP, Var, VarP.
- Unknown names in brackets become parameters, typed from what they are compared with or declared in
PARAMETERS.
Different from Access
- Dates in
#…#are read day first (#08/11/2026#is 8 November). Access SQL reads them month first. Use#yyyy-mm-dd#in code you share. - Text comparisons ignore case (ASCII letters).
ReplaceandInlists are case-sensitive;InStrandStrCompalways ignore case (StrComp's compare argument is ignored, and InStr takes none: a fourth argument is an error). Trueis 1 in SQL, not −1;Notis logical, not bitwise.TOP ndoes not include ties. Division by zero gives Null rather than an error.- HAVING and ORDER BY may use a column's alias.
Action queries (saved, or run from code): INSERT INTO … VALUES, INSERT INTO … SELECT, UPDATE … SET … WHERE, DELETE [Table.*] FROM … WHERE and SELECT … INTO, with joins in UPDATE and DELETE (they change one table).
Not accepted: UNION, TRANSFORM … PIVOT (crosstabs), TOP n PERCENT, DDL (CREATE, ALTER, DROP), IN 'other.accdb', pass-through SQL, domain functions inside SQL, and these functions in SQL: Chr, Asc, StrConv, InStrRev, StrReverse, Hex, Oct, TimeSerial, TimeValue, Log, Exp and the trigonometric functions. Calculated controls have a slightly different set (they have Chr and Asc but not StrComp).
Errors
Errors carry VBA's and Access's numbers and messages, and the line they happened on.
On Error Resume NextfillsErrand carries on.On Error GoTo labeljumps to the handler;Resumeruns the failing statement again,Resume Nextthe one after,Resume labelgoes to the label; a handler that runs to its end leaves the procedure. An error inside a handler goes to the caller.Resumeoutside a handler is error 20.Err.Number(the default),Err.Description,Err.Source,Err.Raise Number, [Source], [Description],Err.Clear.- An unhandled error shows Run-time error 'n' with End, and for admins Debug, which pauses on the failing line.
| Number | Meaning |
|---|---|
| 5 | Invalid procedure call or argument |
| 6 | Overflow |
| 9 | Subscript out of range |
| 11 | Division by zero |
| 13 | Type mismatch |
| 20 | Resume without error |
| 35 | Sub or Function not defined |
| 91 | Object variable or With block variable not set |
| 94 | Invalid use of Null |
| 424 | Object required |
| 429 | ActiveX component can't create object |
| 438 | Object doesn't support this property or method |
| 440 | JavaScript error in Module.Fn: … |
| 449 | Argument not optional |
| 450 | Wrong number of arguments or invalid property assignment |
| 457 | This key is already associated with an element of this collection |
| 2046 | The command or action isn't available now |
| 2105 | You can't go to the specified record |
| 2109 | There is no field named … in the current record |
| 2448 | You can't assign a value to this object |
| 2450 | Access can't find the form … |
| 2465 | Access can't find the field … |
| 2471 | No such field (in criteria or a filter) |
| 2501 | The action was cancelled |
| 3020 | Update or CancelUpdate without AddNew or Edit |
| 3021 | No current record |
| 3022 | Duplicate value |
| 3027 | Cannot update. Database or object is read-only |
| 3061 | Too few parameters |
| 3065 | Cannot execute a select query |
| 3078 | Cannot find the input table or query |
| 3167 | Record is deleted |
| 3219 | This is an action query: run it with CurrentDb.Execute |
| 3464 | Type conversion failure |
| 5017 | Syntax error in regular expression |
| 32811 | Method 'Remove' of object 'IDictionary' failed |
Debugging
Debugging is for admins; for users, breakpoints are ignored.
| Key | Command |
|---|---|
| F5 | Run the Sub the cursor is in (one with no arguments), or Continue |
| F8 | Step Into |
| Shift+F8 | Step Over |
| Ctrl+Shift+F8 | Step Out |
| Ctrl+F8 | Run to Cursor |
| F9, or click the margin | Toggle Breakpoint |
| Ctrl+Shift+F9 | Clear All Breakpoints |
| Ctrl+Break | Break |
| Reset | Stop the code |
| Ctrl+G | Immediate window |
- Paused code shows the next line in yellow with an arrow; breakpoints are red. Stepping stops on the Sub line, Next, Loop and Wend lines and End Sub, as VBA does.
Stoppauses too. The debug keys work from anywhere in the app while code is paused. - Locals shows the paused procedure's variables with values and types (
Variant/Long). Watches evaluate expressions each time the code pauses (<Out of context>when a name isn't in scope). Call Stack lists the callers. - A run-time error's Debug button pauses on the failing line. Change a variable in the Immediate window, then F5 or F8 runs the line again. Edits to the code take effect the next time the procedure runs, not in the paused one (VBA's Edit and Continue applies them at once).
- Breakpoints move with the code as you type, last for the session and pause form events as well as code you run.
AI help with errors
When the server has an Anthropic API key (the ANTHROPIC_API_KEY environment variable; AI_MODEL picks the model, claude-sonnet-5-5 unless set), admins get two commands for errors. They are on the Code tab (Explain Error, Fix Error), on the code window's toolbar and in the compile-error bar. The run-time and compile error messages also offer Explain (AI). Without a key the buttons don't appear.
- Explain Error: Claude says what the error means, why it happened on that line, and what to change. It is sent the module's code, the error, the database's table and query names (with their fields), the form's controls for a form module and, for a run-time error the code is paused on, the Locals and the Call Stack.
- Fix Error: Claude rewrites the procedure with the error (or the declarations, or a short module). The change shows line by line, and Apply fix puts it in the code window: Ctrl+Z undoes it, and it isn't saved until you save. A paused run is reset first, as the code it was running has changed. When the fix isn't in the code (a missing table or control), it says what to do instead.
- AI only explains and fixes errors; it doesn't write code otherwise. The key stays on the server, and each admin can make 60 requests an hour.
Access has nothing like this. Records are not sent, but the module's code, the table, query, field and control names, and (for a paused run) the values of up to 40 variables are.
The Immediate window
One editable text, as in the VBA editor. Enter runs the line the cursor is on and writes the result underneath; Ctrl+Enter starts a new line without running. ? expression prints a value; anything else runs as statements, several per line with : (For i = 1 To 3: Debug.Print i;: Next). Variables you make there last until the code changes. It can call public procedures and reach open forms (? Forms!Orders!txtTotal). While code is paused it works in the paused procedure (? total, total = 0). It has the IntelliSense lists (not Parameter Info), and Debug.Print and console.log output lands there. It keeps the last 400 lines (300 of what is printed while the code window is closed); Clear, Cls or Debug.Clear empties it.
Web Access Builder additions
These do more than Access allows, so code that uses them won't run in Access. For SortArray, Cls, Debug.Clear, the Collection extras and ReDim Preserve beyond VBA's rules, the first time each line runs the Immediate window prints a note such as Note (Module1, line 12): SortArray is a webaccess addition; Access's VBA doesn't have it. JSON, JavaScript modules and libraries and On Render print no note, since their names make them plain.
| Addition | What it does |
|---|---|
| On Render event | Per-record formatting from code (see On Render) |
| ReDim Preserve on any bound | Change any dimension's bounds, keeping the elements still in range. Changing the number of dimensions is still error 9. |
| Collection extras | c("Leeds") = 42 or c(2) = "x" changes an item in place (a missing key is added); c.Exists(key), c.Key(i), c.Keys, c.Items, c.RemoveAll, c.Sort [Descending], [ByKey]. Keys ignore case. |
| SortArray arr, [Descending], [Column] | Sorts a 1-D array in place, or a 2-D array's rows by a column. Stable; Empty and Null first, numbers as numbers, dates as dates, text ignoring case. A procedure of your own called SortArray is used instead. |
| Debug.Clear, Cls | Clear the Immediate window |
| JSON type | See below |
| JavaScript modules and libraries | See below |
JSON, Dictionary and RegExp
JSON (not in VBA)
Dim j As JSON
Set j = JSON.Parse("{""name"":""Ann"",""items"":[1,2,3]}")
Debug.Print j("name"), j("items")(0), j("items").Count
j("town") = "Leeds"
j("items").Add 4
Debug.Print j.Stringify ' or JSON.Stringify(j, 2) for indented text
JSON.Parse(text),JSON.Stringify(value, [indent]),JSON.NewObject,JSON.NewArray,JSON.FromArray(arr),JSON.FromRecordset(rs)(the rows as objects).- A value:
j("key")(exact, or else ignoring case; a missing key is Empty),j(0)(arrays count from 0),j!key,j("key") = v(adds or changes),Count,Keys,Items,IsArray,IsObject,Exists,Add value(array) orAdd key, value(object; 457 if present),Remove,RemoveAll,Stringify,Clone.For Eachgives an array's values or an object's keys. Nested values are shared, soj("a")("b") = 1changesj. Dim j As JSONandSetare needed, as for any object.
Dictionary
As the Scripting Runtime's. CreateObject("Scripting.Dictionary") always works; Dim d As New Dictionary needs the Microsoft Scripting Runtime reference. Add (457 on a duplicate), Exists, Item (reading a missing key adds it, as in VBA), Keys, Items, Remove (32811 for a missing key), RemoveAll, Count, CompareMode (binary unless changed while empty). For Each gives the keys. Objects cannot be keys.
RegExp
As VBScript's. CreateObject("VBScript.RegExp"), or Dim re As New RegExp with the Microsoft VBScript Regular Expressions 5.5 reference. Pattern, Global, IgnoreCase, MultiLine, Test, Replace ($1 for groups) and Execute, giving Matches with Count, Item, FirstIndex, Length, Value and SubMatches. Patterns use JavaScript's regular-expression syntax, which is close to VBScript's.
CreateObject makes only these two. Anything else (Excel.Application, ADODB.Connection, Scripting.FileSystemObject…) is error 429, and a fixed name is a compile error.
References and JavaScript libraries
Tools > References… lists what code can use. Visual Basic For Applications, Microsoft Access Object Library, Microsoft Office Access database engine Object Library and webaccess JSON are always on. Admins tick the rest; the choice is saved with the database.
| Reference | Name in code | What it gives |
|---|---|---|
| Microsoft Scripting Runtime | Dictionary | The Dictionary type by name |
| Microsoft VBScript Regular Expressions 5.5 | RegExp | RegExp, MatchCollection, Match, SubMatches by name |
| CSV (Papa Parse) | Csv |
Parse(text, [hasHeader], [delimiter], [convertTypes]) → a 2-D array, or JSON rows by heading; Headings(text); Stringify(data, [delimiter], [quoteAll], [newline]) |
| Statistics (simple-statistics) | Stats |
Count, Sum, Min, Max, Mean, Median, Mode, GeometricMean, HarmonicMean, StdDev, StdDevP, Variance, VarianceP, Skewness, Kurtosis, MedianAbsoluteDeviation, InterquartileRange, Percentile (0–1, as Excel's PERCENTILE.INC), Quartile, ZScore, Correlation, Covariance, LinearRegression (JSON: Slope, Intercept, RSquared), Forecast, NaturalBreaks, Clusters, Frequency, and Call(name, …) for the rest of the library |
| Coordinates (proj4) | Geo |
ToLatLong, ToGrid (British National Grid ↔ WGS84, to a few metres; OSTN15 not included), GridRef, FromGridRef, Distance, Bearing, Destination, GridDistance, GridBearing, LineLength, Chainage (JSON: Chainage, Offset, Easting, Northing), PointAt, Transform(x, y, from, to) between BNG, WGS84, ETRS89, Web Mercator, Irish Grid, ITM and UTM 29–31, and Define |
| Dates (date-fns) | Dates |
BankHolidays(year, [region]) and IsBankHoliday (England and Wales, Scotland, NorthernIreland), Easter, IsWorkday, WorkDay, NetWorkDays, start and end of week, month, quarter and year, ISOWeek, ISOWeekYear, ISOWeekStart, FinancialYear ("2026/27"), FinancialYearStart, Age, FormatDate, ParseDate |
| Fuzzy search (Fuse.js) | Fuzzy |
Search, BestMatch, Matches (JSON: Item, Index, Similarity), Levenshtein, Similarity, Soundex |
| Crypto | Crypto |
SHA1, SHA256, SHA384, SHA512, HMACSHA1, HMACSHA256, HMACSHA512 (lowercase hex), UUID, Base64Encode, Base64Decode, RandomInt, RandomString |
' With Coordinates and Statistics ticked
Debug.Print Geo.GridRef(530000, 180000, 6) ' TQ 300 800
Debug.Print Stats.Percentile(speeds, 0.85)
Set r = Stats.LinearRegression(years, counts): Debug.Print r("Slope")
Lists can be a VBA array, a Collection or a JSON array; Stats leaves Nulls out. The libraries' licences are in public/vendor/LICENSES.txt.
JavaScript modules
Access code calls Windows DLLs with Declare, which a browser can't. Web Access Builder has JavaScript modules instead: Insert > JavaScript Module, write functions and export them.
// JavaScript module Tools
export function twice(x) { return x * 2; }
export function initials(name = '') {
return name.split(/\s+/).map((w) => w[0] || '').join('').toUpperCase();
}
export async function slow(ms) { await new Promise((r) => setTimeout(r, ms)); return 'done'; }
Debug.Print Tools.Twice(21) ' 42
Debug.Print Initials("ann b clark") ' ABC: no VBA procedure has that name
- VBA calls them as
Module.Fn(...), orFn(...)when no VBA procedure has that name; names match without regard to case. - Going in: numbers, text, True/False and Null as they are; dates as JavaScript
Dates; a VBA array as an array (a 2-D array as rows of arrays); a Dictionary as an object; a Collection as an object if every item has a key, otherwise an array; JSON as itself. Missing Optional arguments arrive asundefined, so JavaScript defaults apply. Other objects raise error 13. - Coming back: numbers, text, Booleans and dates as VBA values;
undefinedas Empty,nullas Null; an array of plain values as a VBA array (from 0); rows of equal length as a 2-D array; anything else as JSON (useSet).asyncfunctions are waited for. - A JavaScript error stops the VBA with error 440 ("JavaScript error in Tools.Twice: …"), which
On Errorcan handle. A call that runs longer than 10 seconds is stopped. console.logprints in the Immediate window. Module-level variables keep their values between calls until the code changes or Reset.- Compile loads the module and shows a syntax or loading error on its line. Export and Import File take
.jsfiles. There are no breakpoints in JavaScript. - The sandbox: modules and libraries run in a hidden frame with no origin of its own, in a Web Worker, under a policy that allows no network. They see only the values passed to them, not the page, the session, the database or the internet. A module can't
importanother, but can use the ticked libraries through their globals (Papa,ss,proj4,dateFns,Fuse).
What a web page cannot do
These compile to an error naming the reason, so code ported from Access shows at once what needs changing.
| In Access | In Web Access Builder |
|---|---|
Shell, SendKeys, AppActivate |
Not available: they drive Windows |
Kill, MkDir, RmDir, ChDir, ChDrive, FileCopy, Dir, CurDir, FileLen, FileDateTime, GetAttr, SetAttr, Open … For, Print #, Input #, Line Input, FreeFile, EOF(n), LOF |
No files on the computer. Import and export through External Data; build text with Csv.Stringify or JSON |
Environ, GetSetting, SaveSetting, DeleteSetting, GetAllSettings |
No Windows settings or registry. Keep settings in a table, or in TempVars for the session |
Declare, AddressOf |
No DLLs. Write a JavaScript module |
GetObject, CreateObject("Excel.Application"), ADO, FileSystemObject |
Only Scripting.Dictionary and VBScript.RegExp |
CallByName |
Not supported |
GoSub, On … GoTo |
Use a Sub, or Select Case |
DoCmd.TransferSpreadsheet, TransferText, OutputTo, PrintOut, SendObject, Quit… |
See DoCmd |
Limits
| Thing | Limit |
|---|---|
| Module size | 512 KB |
| Records in a recordset or a form's RecordsetClone | 50,000 |
| A JavaScript call | 10 seconds |
| Immediate window | 400 lines (300 while the code window is closed) |
| Combo and list box rows | 1,000 |
| Rows per API request | 1,000 (datasheets page through all; exports take all) |
| SQL text | 100,000 characters |
| Criteria in a data call | 10,000 characters |
| Controls on a form | 1,000 |
| Fields in a table | 255 |
| Subform nesting | 3 deep |
| Import file | 50 MB |
| Image on a form | 400 KB |
REST API
The browser talks to the server through a JSON API under /api. It is described here for anyone extending the app; it is not a stable public interface.
- Sign in with
POST /api/auth/login{username, password}; the session is thewa_sessioncookie (httpOnly, SameSite=Lax, 14 days by default). - Every non-GET request needs the header
X-Requested-With: webaccess, or it is refused with 403. - Errors come back as
{ error, … }; 401 "Please sign in", 403 "Only admins can do that".
| Method | Path | Role | Purpose |
|---|---|---|---|
| POST | /api/auth/login, /api/auth/logout | — | Sign in and out |
| GET | /api/auth/me | — | The signed-in user |
| POST | /api/auth/password | user | {current, next} |
| GET, POST | /api/users | admin | List, add {username, displayName, password, role} |
| PATCH, DELETE | /api/users/:id | admin | Change {displayName, role, active, password}, delete |
| GET | /api/objects | user | Tables, queries, forms (and modules for admins) |
| GET, POST | /api/tables | user / admin | List; create {table} |
| GET, PUT, DELETE | /api/tables/:name | user / admin | Design; change {table, force} (409 with warnings when data would change); delete |
| GET, POST | /api/tables/:name/rows | user | Read (offset, limit ≤ 1000, sort, dir, filter, where, orderBy); add {values} |
| GET, PATCH, DELETE | /api/tables/:name/rows/:rowid | user | One record |
| GET, POST | /api/queries | user / admin | List; save {query} |
| POST | /api/queries/generate, /api/queries/preview | admin | Design → SQL; run an unsaved design |
| POST | /api/queries/execute | admin | Run an unsaved action query {design | sql, params, dryRun} |
| POST | /api/queries/:name/execute | user | Run a saved action query {params, dryRun} (make-table: admin) |
| GET, PUT, DELETE | /api/queries/:name | user / admin | |
| POST | /api/queries/:name/run | user | {params, offset, limit, sort, dir, filter, where, orderBy}; 400 with needsParameters when values are missing |
| GET, POST | /api/forms | user / admin | |
| GET, PUT, DELETE | /api/forms/:name | user / admin | PUT and DELETE also rename or delete the form's module |
| GET, POST | /api/reports | user / admin | List; create {report} |
| GET, PUT, DELETE | /api/reports/:name | user / admin | |
| GET | /api/ai/status | admin | Whether AI help is available |
| POST | /api/ai/explain, /api/ai/fix | admin | AI help with an error (60 an hour per admin; 429 after that) |
| GET | /api/modules, /api/modules/:name | admin | |
| PUT, DELETE | /api/modules/:name | admin | {code, kind, form, name} |
| GET | /api/runtime/code | user | Every module's code and the references, for the runtime |
| GET, PUT | /api/references | user / admin | |
| POST | /api/vba/domain | user | Domain functions {fn, expr, domain, criteria} |
| POST | /api/vba/recordset | user | {source, params} |
| POST | /api/vba/form-records | user | A form's records |
| POST | /api/vba/describe | user | A QueryDef's type, parameters and columns {sql} |
| POST | /api/vba/execute | user | Action SQL {sql, params, dryRun} (make-table: admin) |
| POST | /api/aggregate | user | Form totals |
| POST | /api/pivot, /api/pivot/values | user | PivotTable data |
| POST | /api/data/import/upload, …/describe, …/run; DELETE …/:token | admin | The import wizard |
| GET | /api/data/export/table/:name | user | ?format=xlsx|csv |
| POST | /api/data/export/query/:name | user | {format, params} |
| GET | /api/data/backup | admin | SQLite download |
| POST | /api/data/compact | admin | Compact and Repair |
| GET | /healthz | — | Health check |
How it is built
- Server (
server/): Express on Node 22, with better-sqlite3.lib/ai.jscalls the Claude API for AI help;lib/accessimport.jsrebuilds Access queries on import.system.dbholds users and sessions;app.dbholds your data and, in themsys_objectstable, each object's design as JSON, much like Access's hidden system tables. Changing a table's design rebuilds the SQLite table and copies the rows across. - Access SQL (
server/lib/access/): lexer, parser, printer, compiler and the design-grid translator; the VBA functions are registered inside SQLite. - VBA (
server/lib/vba/): lexer, parser, compile checks and the interpreter, served to the browser from/shared/vba/, so the editor and the runtime share one implementation. - Browser (
public/js/): plain JavaScript modules, no build step.vbahost.jsandvbaobjects.jsgive code its dialogs and the Access objects;dao.jsthe DAO objects;jssandbox.jsruns JavaScript modules in the sandbox frame (server/static/sandbox.html);aihelp.jsthe AI dialogs;reportengine.jslays reports out into pages (reportdoc.jsshows them,formdesigner.jsdesigns them);jslib/holds the libraries' VBA-facing functions andvendor/the third-party code. The logo and mark are inpublic/img/. - Tests:
npm test(node:test).
Microsoft Access is a trademark of Microsoft Corporation. Web Access Builder is an independent product and isn't affiliated with or endorsed by Microsoft. © 2026 Simon Wilson.