A practical reference for extending QuickerSite — an all-in-one CMS written in
classic ASP / VBScript that runs on IIS (5+) and IIS Express. This guide focuses on the parts you
actually touch when writing custom code: the object model, the helper library, and the
constant engine that lets you embed VBScript anywhere in a page.
Who this is for. Site developers and AI coding agents writing custom scripts inside QuickerSite.
Every signature and rule below was extracted directly from the source in asp/includes/.
File and line references point at the real implementation so you can verify anything.
The two ways to add custom logic
Constants (recommended). Author a VBScript function in the backsite
(bs_constantEdit.asp), stored as a constant of type QS_VBScript. Invoke it
anywhere in page content with a shortcode like [MYCODE] or [MYCODE(arg)].
This is the safe, supported path — see chapter 3.
Module / application scripts. Stand-alone .asp files (see modules/)
run via the page’s Application path or run() / Server.Execute.
Useful for larger features and AJAX endpoints.
Ground rules (read before writing anything)
Option Explicit is enforced site-wide (asp/begin.asp:3) —
declare every variable with Dim.
The page is UTF-8, Response.Buffer = true, no-cache.
In production, runtime errors in constants are swallowed — a broken script silently
outputs nothing. Always code defensively and test with the Test! button first.
Never trust Request(...). Sanitize for output, escape for SQL.
asp/begin.asp is the master include. It pulls in ~140 include files (all the classes and
helpers) and creates the global objects you will use. By the time your code runs, these already exist:
The current site/tenant and all its settings. customer.pick(cId) already called.
selectedPage
cls_page
The page being requested (picked by iId or sCode).
logon
cls_logonEdit
Authentication / logged-in user state.
message
cls_Messages
Status / error message collector.
cPopup
cls_popup
Popup / lightbox handler.
cId
function → Integer
Current customer id. The multi-tenant scoping key for every query.
Rendering pipeline
process.asp decides what the request is (page view, login, form post, search, AJAX…).
The chosen cls_page is rendered via selectedPage.buildTemplate
(asp/includes/page.asp:1158), which substitutes [PAGEBODY], [PAGEMENU],
meta tags, etc. into the template HTML.
While building the body, treatConstants() (asp/includes/insertConstants.asp)
scans the text for [SHORTCODE] tokens and replaces them — including running your
VBScript constants. This is where your custom code executes.
Multi-tenant. One QuickerSite install can host many sites, each identified by cId.
Almost every table has an iCustomerID column. Always scope your queries with
iCustomerID = cId or you will leak/clash data across sites.
This is the most important chapter for custom development. Understand it and everything else follows.
How a constant is stored
A custom script is a row in tblConstant of type QS_VBScript (value 2),
edited in bs_constantEdit.asp. It has three fields:
Field
DB column
Purpose
Parameters
sParameters
The VBScript argument list, e.g. iCount, sCategory.
Function body
sValue
The code that produces output.
Global Code
sGlobal
Optional helper Functions/Classes, loaded via ExecuteGlobal before the body runs.
What QuickerSite does with it
At render time, executeConstant() (asp/includes/insertConstants.asp:246) wraps your
body like this and runs it:
function CustomFunction(<your Parameters>)
<your Function body>
end function
executeConstant = CustomFunction(<actual arguments from the shortcode>)
The Response.Write trick. Before running, the engine text-replaces every
response.write in your body with CustomFunction = CustomFunction &.
So both of these append to the returned output:
' Style A - looks like normal ASP
Response.Write "<p>Hello</p>"
' Style B - assign directly (identical effect)
CustomFunction = CustomFunction & "<p>Hello</p>"
The hard rules
Return a string fragment only. Do not emit <html>/<body>,
do not set headers, never call Response.End — your output is spliced into an
existing page where the shortcode appears.
Errors are silent in production.executeConstant runs under
On Error Resume Next; if your script errors, it returns "" and the visitor
sees nothing. Errors are only shown in Test! mode.
Declare everything (Option Explicit).
Nesting is limited. Constants can contain other shortcodes, resolved recursively up to a small depth.
Invoking your constant
[MYCODE] ' no parameters
[MYCODE(5)] ' one parameter
[MYCODE(5, "news")] ' multiple parameters - strings in double quotes
Put the shortcode in any page body, list item, template, header/footer, or even another constant.
The matching regex is asp/includes/insertConstants.asp:49.
The author/test loop
Backsite → ASP/VBScripts (bs_scriptList.asp) → new item.
Fill Parameters, Function body, optional Global Code.
Click Test!. The test harness (bs_constantTest.asp) runs the script and shows
TEST OK! with the output, or TEST FAILED! with the error. Supply sample parameter
values (in double quotes) when prompted.
Because constants run inside the fully-loaded page context, the entire object model and helper library
is in scope. The objects you will reach for most:
db — database
Dim rs
Set rs = db.Execute("select sTitle from tblPage where iCustomerID=" & cId & " and bOnline=" & getSQLBoolean(true))
Do While not rs.EOF
Response.Write "<li>" & sanitize(rs("sTitle")) & "</li>"
rs.MoveNext
Loop
rs.Close : Set rs = Nothing
customer — current site settings
Common reads: customer.siteName, customer.sDatumFormat,
customer.language, customer.sUrl, customer.sQSUrl,
customer.bUserFriendlyURL. It also exposes collections like
customer.forms, customer.feeds, customer.galleries,
customer.constants (each returns a Scripting.Dictionary).
selectedPage — the current page
Common reads: selectedPage.iId, selectedPage.sTitle,
selectedPage.sValue, selectedPage.iParentID. Useful methods:
selectedPage.subPages(true), selectedPage.listitems(true),
selectedPage.parentPage. See cls_page.
cId & localization
cId returns the current customer id (always use it to scope queries).
l("key") returns a localized label string.
Convention: the codebase prefixes variables by type —
i integer, s string, b boolean, d date.
Class fields and DB columns follow the same scheme (e.g. iId, sTitle, bOnline, dCreatedTS).
Ranked by how often they are instantiated across the codebase (new cls_* count). These are
the classes worth knowing. All follow the same lifecycle pattern:
Dim obj
Set obj = new cls_page ' 1. create
obj.Pick(123) ' 2. load a row by id (customer-scoped where relevant)
Response.Write obj.sTitle ' 3. read properties
' obj.getRequestValues() ' 4. (back-office) fill from Request.Form
' obj.Save ' 5. validate + upsert -> returns True/False
' obj.remove ' 6. delete (cls_contact calls it .delete)
Set obj = Nothing ' 7. release
Collections (e.g. customer.forms, page.listitems) return a
Scripting.Dictionary keyed by id. Iterate with For Each key In dict ... dict(key) ....
Class
Uses
Role
cls_page
49
Pages, list pages, list items, menu nodes, rendering
asp/includes/page.asp — one class to rule everything content: normal pages,
container/menu pages, list pages and their list items, external links and the homepage.
Key properties
iIdPrimary key (Null for a new, unsaved page).
iParentID, iListPageIDParent in the menu tree / owning list page.
iCustomerIDOwning tenant (defaults to cId).
sTitle, sValue, sValueTextOnlyTitle, HTML body, plain-text body.
sCodeApplication code (uppercase) used by pickByCode and the sCode form field.
sUserFriendlyURLSEO slug.
bOnline, bDeleted, bContainerPagePublished / soft-deleted / is a menu container.
iRangSort order among siblings.
dOnlineFrom, dOnlineUntill, dPagePublish window and page date.
iHits, iVisitorsCounters.
sOrderBYPresence marks the page as a list page; defines item sort.
SQL portability. Because the DB engine varies, use the helpers
getSQLBoolean(b), getSQLDate(d), getSQLDateFunction() and
getTOPSyntax(n, sql) instead of hard-coding True/#dates#/TOP.
build(action, align, buttonType, itemID)htmlRender the full form HTML; on postback it validates, uploads, stores a submission, mails, runs the script, then shows feedback or redirects.
submissions / removeAllSubmissionsdb
To drop a managed form into a page you usually just reference it via the page’s
Form setting; in custom code you can render one with
Set f = new cls_form : f.Pick(id) : Response.Write f.build("default.asp","","submit",0).
Three “widget” classes with the same shape: Pick(id), Save,
remove, a sCode shortcode, and a build that returns ready HTML.
They are usually invoked through their dedicated shortcodes
([qs_feed:CODE], [qs_gallery:CODE], [QS_POLL:CODE]) rather than from VBScript.
asp/includes/constant.asp — the storage object behind your custom scripts and text/HTML
snippets. You rarely instantiate it directly, but it is good to know the shape.
There are also many btn_* back-office button ids and c* action keys
(e.g. cLogOff = "logoff", cRegister = "register") used by process.asp
dispatch — relevant only if you build back-office or login flows.
QuickerSite ships with 54 tables (627 columns). The default install uses an Access
(Jet) database; the same schema runs on SQL Server and MySQL via cls_database.
The full schema is created by the bootstrap DDL (CREATE TABLE / ALTER TABLE ... ADD COLUMN).
This chapter is the map you need when writing custom db.Execute queries.
Reading this with an AI agent? The live, fetchable copy of these docs (including this
data model) is at pietercooreman.github.io/QuickerSite/.
The prompt builder (chapter 10) embeds that URL so an agent can pull the exact column list on demand.
Conventions
Primary key: every table has an autonumber iId (COUNTER) primary key.
Tenant scoping: most tables carry iCustomerID — always filter by = cId.
Link/child tables (values, subscriptions, page-rights) scope through their parent instead.
Naming: column prefixes encode type — i integer/id, s string,
b boolean, d date. Foreign keys read i<Entity>ID
(e.g. iCatalogID, iFormID, iContactID).
No enforced FKs: relationships are by convention only (Access/Jet) — joins are written in SQL,
integrity is maintained in the VBScript classes.
Access type → meaning
DDL type
Shown as
Meaning
COUNTER NOT NULL
PK
Autonumber primary key (iId).
LONG / INTEGER
Integer
Whole number; ids, counts, flags-as-int.
BIT
Boolean
True/False. Compare with getSQLBoolean(true).
DATETIME
Date/time
Use getSQLDate(d) in queries.
TEXT(255)
Text(255)
Short string (max 255 chars).
MEMO
Long text
Unbounded text (HTML bodies, templates, notes).
Tables by area
Click a table name to jump to its column list below. “Tenant” = has an iCustomerID column.
Watch-outs.tbCatalogItemFields is spelled tb (not tbl).
A few date columns are stored as text (e.g. tblContactRegistration.dCreatedTS is TEXT).
tblPage uses createdTS/updatedTS (no d prefix) alongside dUpdatedOn.
tblPoll stores answers as sA1..sA15 with matching colour columns sA1c..sA15c.
Each example shows exactly what goes in the Function body box of
bs_constantEdit.asp (and the Parameters / Global Code boxes when used).
Remember: write a fragment, never a full page, and never call Response.End.
Parameters:none — invoke with [GREETING]. (Adapted from modules/module1.asp.)
Dim dHour
dHour = Hour(Now)
If dHour < 12 Then
Response.Write "Good morning!"
ElseIf dHour < 17 Then
Response.Write "Good afternoon!"
Else
Response.Write "Good evening!"
End If
Parameters:iCount — invoke with [RECENTPAGES(5)].
Shows the most recent online pages of the current site. Note the defensive coding, tenant scoping,
engine-aware helpers, and cleanup.
Dim n, sql, rs, html
n = convertGetal(iCount)
If n <= 0 Then n = 5
' [TOP] is rewritten by getTOPSyntax to the right syntax per DB engine
sql = "select [TOP] iId, sTitle from tblPage " & _
"where iCustomerID=" & cId & " " & _
"and bOnline=" & getSQLBoolean(true) & " " & _
"and bDeleted=" & getSQLBoolean(false) & " " & _
"order by createdTS desc"
sql = getTOPSyntax(n, sql)
html = ""
Set rs = db.Execute(sql)
Do While not rs.EOF
html = html & "<li><a href=""default.asp?iId=" & EnCrypt(rs("iId")) & """>" & _
sanitize(rs("sTitle")) & "</a></li>"
rs.MoveNext
Loop
rs.Close : Set rs = Nothing
If html <> "" Then Response.Write "<ul>" & html & "</ul>"
SQL injection. If a parameter is a string used in a WHERE clause,
wrap it: "... and sCode='" & cleanUp(convertStr(sCode)) & "'". For numbers, run them
through convertGetal() first. Never paste raw Request/parameter text into SQL.
Parameters:none — invoke with [CHILDLINKS] on any page that has
sub-pages. Builds a menu of the current page’s online children using the in-scope
selectedPage object.
Dim kids, k, html
Set kids = selectedPage.subPages(true) ' true = online only
html = ""
For Each k In kids
html = html & "<li>" & kids(k).getSimpleLink & "</li>"
Next
Set kids = Nothing
If html <> "" Then Response.Write "<ul class=""childnav"">" & html & "</ul>"
The same pattern works for list items via selectedPage.listitems(true), or for any
other page by Set p = new cls_page : p.Pick(id).
Parameters:none — invoke with [ADMINONLY]. Renders content only
for a logged-in backsite (webmaster) user. (Adapted from modules/module2.asp.)
If Session(Application("QS_CMS_iCustomerID") & "isAUTHENTICATED") = true Then
' Webmaster is logged into the backsite
Response.Write "<div class=""adminbox"">Secret editor note here.</div>"
Else
' Not authenticated - output nothing (or a hint)
' Response.Write ""
End If
To also allow the second admin, add
or Session(Application("QS_CMS_iCustomerID") & "isAUTHENTICATEDSecondAdmin") = true.
Put helper Functions/Classes in the Global Code box; they load (via ExecuteGlobal)
before the body. Keep the Function body focused on producing output.
Global Code:
Function Euro(n)
' format a number as a euro amount
Euro = "€ " & FormatNumber(convertGetal(n), 2)
End Function
Parameters:iAmount — Function body — invoke with [PRICE(19.95)]:
Parameters:none — invoke with [NAMEFORM]. A self-posting form;
the sCode hidden field tells QuickerSite which page to reload after submit.
(Adapted from modules/module3.asp.)
Response.Write "<form action=""default.asp"" method=""post"">"
Response.Write "<input type=""hidden"" name=""sCode"" value=""FORM"" />"
Response.Write "Your name: <input type=""text"" name=""field"" value=""" & _
sanitize(Request.Form("field")) & """ /> "
Response.Write "<input type=""submit"" value=""Submit"" />"
Response.Write "</form>"
If Request.Form("field") <> "" Then
Response.Write "<p>Your name is " & sanitize(Request.Form("field")) & ".</p>"
End If
For anything beyond a toy form (validation, email, file upload, stored submissions) use a
managed cls_form instead of rolling your own.
Checklist before you Save
✓ Every variable is Dim’d.
✓ Output is a fragment; no <html>, no Response.End.
✓ Every dynamic value passes through sanitize() / Server.HTMLEncode.
✓ SQL is tenant-scoped (iCustomerID = cId) and inputs are escaped (cleanUp/convertGetal).
Generate a compact, accurate spec for an AI coding agent (or yourself) to produce a QuickerSite-ready
constant. Fill the form, press Build, copy the result. The generated prompt embeds the engine rules
from chapter 3 so the output drops straight into bs_constantEdit.asp.
Browsing vs. offline. Pick the Target AI agent mode below. If your agent can browse
(e.g. a web-enabled assistant), the prompt tells it to fetch the published docs at
pietercooreman.github.io/QuickerSite/ for exact schema
and class details. If it is offline (plain API / local model), the prompt forbids guessing and instead asks you to
paste the relevant schema (copy it from chapter 8) — otherwise the model would
hallucinate the URL’s contents.
Copy real tables from chapter 8 → All tables & columns. With an offline agent and no schema here, the prompt will tell the model to ask instead of guessing.