Web Access Builder Request an instance

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, With and Select Case. A new Sub, Function or Property gets 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. endif becomes End If. ? x becomes Debug.Print x, at the start of a line or after Then or Else in a single-line If (VBA would write Print 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 As the types, after New the 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 .), after Me! the controls and fields, after Forms! 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, Me is the object.
  • Dim x As New Customer makes the object on first use, and again if used after Set x = Nothing.
  • Class_Initialize runs when an object is made. Class_Terminate runs when the last reference lets go (Set c = Nothing, the end of the procedure holding it, removal from a Collection). End and 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, Friend outside Sub/Function/Property, default members and For Each over 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 Binary makes =, < and the other comparisons case-sensitive. InStr, InStrRev, StrComp, Replace and Filter ignore case unless you pass vbBinaryCompare, whatever the Option Compare.
  • Integer, Long and Byte overflow raises error 6; division by zero error 11; Null into a typed variable error 94.
  • Round rounds half to even, as VBA does.
  • Objects are reference-counted; Class_Terminate runs 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:

  • Format takes the expression and the format; the first-day-of-week and first-week arguments are ignored, as they are in DateDiff and DatePart (Weekday honours 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") gives 08/11/2026; FormatCurrency uses £.
  • MsgBox supports 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". InputBox returns "" if cancelled; position and help arguments are ignored.
  • Eval("[Forms]![Orders]![Qty] * 2") works out an Access expression, reading Forms! and TempVars! 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.txtName and Me!txtName reach controls, then fields; a field with no control is reached as Me!Field. Me("txtName") and Me(0) reach controls only.
  • Setting Me.RecordSource raises 2448 ("Changing the Record Source from code comes in a later update"); setting Me.Dirty = True raises 2448 as in Access.
  • Me.Filter/Me.FilterOn, Me.OrderBy/Me.OrderByOn, DoCmd.ApplyFilter and DoCmd.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.RecordsetClone and Me.Bookmark find a record: Set rs = Me.RecordsetClone: rs.FindFirst "Surname = 'Clark'": If Not rs.NoMatch Then Me.Bookmark = rs.Bookmark. Me.Recordset moves 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, BackColor and AllowFilters (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).
  • Text is for text boxes and combo boxes. Value is 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, so Me.sfrmLines!Qty and Me.sfrmLines.Form.Requery work.
  • TypeName and ControlType give 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.Controls and For Each frm In Forms work. Forms holds 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 = 11 for 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.Quit raises 2501 ("Close the browser tab instead").
  • Debug: Print, Assert, Clear (an addition).
  • Not available, although the names compile: CodeDb, CurrentProject, DBEngine, Reports and the Error function.

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.OpenRecordset takes a table name, a saved query's name or SQL; dbOpenSnapshot opens it read-only. A QueryDef's OpenRecordset works 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 dbFailOnError a bad value raises its error; without it, the statement changes nothing and raises no error, and RecordsAffected is 0. RecordsAffected says how many records changed.
  • DoCmd.RunSQL first 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.Execute and CurrentDb.OpenRecordset fill 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) and For Each give 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] and RecordsAffected. As in Access, a QueryDef doesn't fill [Forms]!… parameters itself: set them, or use Eval(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") gives Name, RecordCount, Fields (Name, Type, Size, Required, DefaultValue, Attributes, OrdinalPosition, and Properties("Caption") and Properties("Description")), Indexes (the primary key and indexed fields) and OpenRecordset.
  • Make a table in code with CurrentDb.CreateTableDef("Roads"), tdf.CreateField("Road", dbText, 40) (fld.Attributes = dbAutoIncrField for an AutoNumber), tdf.Fields.Append fld and CurrentDb.TableDefs.Append tdf. On a saved table, tdf.Fields.Append adds a field, tdf.Fields.Delete "Name" deletes one and setting a field's or table's Name renames 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, Containers and Workspaces.

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]. DISTINCTROW and ALL are 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 …) and Exists (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).
  • Like uses 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). Replace and In lists are case-sensitive; InStr and StrComp always ignore case (StrComp's compare argument is ignored, and InStr takes none: a fourth argument is an error).
  • True is 1 in SQL, not −1; Not is logical, not bitwise.
  • TOP n does 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 Next fills Err and carries on. On Error GoTo label jumps to the handler; Resume runs the failing statement again, Resume Next the one after, Resume label goes to the label; a handler that runs to its end leaves the procedure. An error inside a handler goes to the caller. Resume outside 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. Stop pauses 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) or Add key, value (object; 457 if present), Remove, RemoveAll, Stringify, Clone. For Each gives an array's values or an object's keys. Nested values are shared, so j("a")("b") = 1 changes j.
  • Dim j As JSON and Set are 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(...), or Fn(...) 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 as undefined, so JavaScript defaults apply. Other objects raise error 13.
  • Coming back: numbers, text, Booleans and dates as VBA values; undefined as Empty, null as 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 (use Set). async functions are waited for.
  • A JavaScript error stops the VBA with error 440 ("JavaScript error in Tools.Twice: …"), which On Error can handle. A call that runs longer than 10 seconds is stopped.
  • console.log prints 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 .js files. 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 import another, 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 the wa_session cookie (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.js calls the Claude API for AI help; lib/accessimport.js rebuilds Access queries on import. system.db holds users and sessions; app.db holds your data and, in the msys_objects table, 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.js and vbaobjects.js give code its dialogs and the Access objects; dao.js the DAO objects; jssandbox.js runs JavaScript modules in the sandbox frame (server/static/sandbox.html); aihelp.js the AI dialogs; reportengine.js lays reports out into pages (reportdoc.js shows them, formdesigner.js designs them); jslib/ holds the libraries' VBA-facing functions and vendor/ the third-party code. The logo and mark are in public/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.