Skip to content

SQL API and metadata reference ​

Functions for application SQL ​

These are available to every application's SQL (region sources, processes, RLS policies, triggers, your own functions).

FunctionReturnsDescription
meta.app_user()textThe signed-in application user, or nobody. In PostgREST requests: the token's user (chapter 13)
meta.app_id()intThe current application's id (in PostgREST: the app named in the token)
meta.has_role(role)booleanWhether the current user has the role in this application (roles are resolved at sign-in; in PostgREST: the token's roles claim or the account's roles in the token's app)
meta.jwt_claims()jsonbThe verified JWT claims of a PostgREST request, or NULL
meta.app_language()textThe language of the request (nl, en-GB, …)
meta.message(name, variadic params)textA text message in the current language, %0…%9 replaced; falls back to the base and primary language
meta.password_days_left(username)intDays until the password expires (0: must change now, NULL: never)
meta.v(name)textThe session-state value of an item (use it inside functions and DO blocks)
meta.set_tenant(tenant)voidSets (or with NULL or '' clears) the current session's tenant (APEX: apex_session.set_tenant_id): at once and for the session's next requests. Workflows and tasks then carry it and show only to that tenant (chapter 6)
meta.tenant_id()textThe current session's tenant, or NULL (APEX: sys_context('APEX$SESSION', 'APP_TENANT_ID')); in a workflow step, the workflow's tenant
meta.v_boolean(name)booleanAn item's value as a boolean (APEX 26.1: BOOLEAN session state): true, t, yes, y, 1, on → true; false, f, no, n, 0, off → false; empty or anything else → NULL. Switches and checkboxes store true / false, so :P3_ACTIVE::boolean works in SQL too
meta.page_url(page, items jsonb default '{}', clear boolean default true)textA URL to a page of the current app, with a valid checksum for the items: meta.page_url(3, jsonb_build_object('P3_EMPNO', empno))
meta.html_escape(text)textEscapes & < > " ' for HTML (use it in dynamic content regions)
meta.url_encode(text)textPercent-encodes a URL component
meta.temp_files (view)rowsThe current session's uploaded files: id, item_name, filename, mime_type, size, content, created_at (like APEX_APPLICATION_TEMP_FILES; chapter 16)
meta.delete_temp_file(id)voidRemoves one of the current session's uploaded files
meta.debug(level, text), meta.debug(text)voidA debug message (APEX: apex_debug.message): recorded with the request when the app's debug level is at least level (1 error, 2 warning, 4 information (the default), 6 trace, 9 everything); otherwise it returns at once. Texts are cut at 4000 characters
meta.debug_enabled(level default 4)booleanWhether a message of this level would be recorded (skip building expensive texts)
meta.debug_level()intThe request's debug level (0: off, and outside a request)
meta.web_request(url, method default 'GET', body default null, headers jsonb default null, credential default null, timeout_s default 10)bigintQueue an HTTP request (APEX: apex_web_service.make_rest_request); the server makes it, see web requests from SQL. Returns the request's id
meta.web_request_source(source, params jsonb default null, timeout_s default null)bigintQueue a call of a REST data source of the app (its URL, method, headers and web credential; parameter values as text)
meta.web_response(id)jsonbThe request's state and response: status (queued, running, ok, error), status_code, headers, content_type, body (text), json (the body parsed when it is JSON), size, url, message, times. NULL for a request of another application
meta.web_response_blob(id)byteaThe response body as bytes (files, images)
meta.parse_data(content bytea, file_name, format, headers, delimiter, row_selector, skip_rows, max_rows)tableThe rows of a CSV/TSV, JSON, XML or Excel file (APEX: apex_data_parser.parse): line_number, cols (text[] by position), data (jsonb by column name). See parsing files
meta.parse_data_columns(…the same arguments)tableThe file's columns (APEX: apex_data_parser.get_columns): column_position, column_name, heading, data_type
meta.zip_add(zip bytea, name, content bytea), meta.zip_finish(zip)byteaBuild a zip (APEX: apex_zip.add_file, apex_zip.finish); see zip files
meta.zip_agg(name, content order by …)byteaAggregate: one zip of the rows' files
meta.zip_entries(zip), meta.zip_entry(zip, name)table / byteaThe files in a zip and one file's content (APEX: apex_zip.get_files, apex_zip.get_file_content)

The runtime sets these settings in each request's transaction (don't set them yourself): pgkiln.app_user, pgkiln.app_id, pgkiln.session_id, pgkiln.tenant_id, pgkiln.debug_level, pgkiln.public_url (the server's PUBLIC_URL), pgkiln.web_pending (set by meta.web_request).

Web requests from SQL ​

PostgreSQL can't make an HTTP request without an extension (see chapter 15 for http and pg_net), so meta.web_request() doesn't wait for the answer: it queues the request in meta.web_request_log and returns its id. The pgkiln server makes the request and stores the response, which meta.web_response(id) returns. When that happens depends on where the SQL runs:

Queued inMadeRead the response
A page process of type sql (also inside a chain or a background chain)Right after that process, in the same transaction, before the next process runs (at most 5 per process; more wait for the scheduler)In the next process of the same submit
Anything else: automations, workflow steps, computations, validations, region sources, triggers, PostgREST requests, psqlAfter the transaction commits, by the next pass of the scheduler (SCHEDULER_INTERVAL_S, default 30 seconds; up to 50 per pass, 5 at a time)Later: from another page view, an automation, a workflow step

A rolled-back transaction takes its queued requests with it (one made in a page process has been sent anyway, as with the invoke_api process). With AUTOMATIONS=off on every server, requests outside page processes are never made (they end as error after 24 hours).

sql
-- process 1 (button FETCH): queue the request, keep its id in an item
select meta.web_request('https://api.example.com/rates?base=' || meta.url_encode(:P5_CURRENCY),
                        p_credential => 'RATES_API') as p5_request;

-- process 2: the server has made it by now
select r -> 'json' ->> 'rate' as p5_rate,
       case r ->> 'status' when 'ok' then null else r ->> 'message' end as p5_error
  from meta.web_response(:P5_REQUEST::bigint) r;

-- POST with a JSON body and headers; a REST data source with parameters
select meta.web_request('https://api.example.com/orders', 'POST', json_build_object('id', 42)::text,
                        '{"Content-Type": "application/json"}');
select meta.web_request_source('EXCHANGE', '{"currency": "EUR"}');
  • The same protections as REST data sources (chapter 19): the host must be on PGKILN_REST_ALLOWED_HOSTS (unset: no calls), private and loopback addresses are refused when the connection is made unless the host is in PGKILN_REST_PRIVATE_HOSTS, redirects (at most 3) go through the same checks, the response is cut off at PGKILN_REST_MAX_BYTES (default 5 MB) and the time limit is 1–60 seconds.
  • Web credentials are named, never passed: p_credential => 'NAME' signs the request with a credential of the current application (basic, bearer, header, OAuth2), and only for the URLs it is valid for. The secret is decrypted by the server for the request only and is never stored with it or visible to SQL.
  • status is ok when an answer came back, whatever its status code (check status_code, like apex_web_service.g_status_code); error means no answer (message says why: the host is not allowed, a time-out, the size limit, the credential).
  • Limits per call: URL ≤ 4000 characters, body ≤ 1 MB (text), ≤ 30 headers (no Host, Content-Length, Connection and the like, no line breaks), methods GET, HEAD, POST, PUT, PATCH, DELETE; at most 100 requests of an application waiting at a time.
  • Requests and responses are kept 24 hours (at most 500 finished ones per application), then removed by the scheduler. They belong to the application, not to a user: any code of the app that knows an id can read the response, so don't let users choose the id when responses differ per user.
  • Application roles can't read meta.web_request_log; the owner can, e.g. in the SQL Workshop: select id, status, status_code, url, message from meta.web_request_log order by id desc.

Push notifications from SQL ​

APEX_PWA push notifications (chapter 17); the application needs push notifications on (Settings → Progressive Web App).

FunctionReturns
meta.send_push(p_user, p_title, p_body, p_page, p_items, p_tag, p_urgency, p_ttl_s)Queues a notification for every device of p_user that turned notifications on, and returns its id. p_title (required, at most 200 characters), p_body (at most 1000), p_page + p_items (a page of the application and item values: the link, signed for the recipient), p_tag (1–32 letters, digits, -, _: replaces an older notification with the same tag), p_urgency (very-low, low, normal, high), p_ttl_s (how long the push service keeps it for an offline device, default 86400, at most 28 days)
meta.has_push_subscription(p_user)Whether the user (default: the current one) has a device with notifications on (APEX_PWA.HAS_PUSH_SUBSCRIPTION)

The message is sent by the pgkiln server after the transaction commits (a rolled-back transaction sends nothing), at once when the server is notified, otherwise on the scheduler's next pass. At most 1000 messages per application wait at a time. Application roles can't read the queue; the owner can: select id, username, status, devices, delivered, message from meta.push_message order by id desc (sent: at least one device received it; no_device; error). Messages are kept 7 days.

AI requests from SQL ​

meta.ai_generate(service, prompt, system, schema) queues a request to an AI service the current application may use and returns its id; meta.ai_result(id) returns {"id", "status", "service", "text", "json", "message", times} (status: queued, running, ok, refused, error; json is the parsed answer when a schema was given). Like web requests, the server makes the call right after the sql page process that queued it (at most 3 per process), in the same transaction, so the next process can read the answer; anything else is made by the scheduler after the commit.

sql
-- process 1 (button SUMMARISE)
select meta.ai_generate('CLAUDE', 'Summarise this complaint in one sentence: ' || :P5_TEXT,
                        'You help a customer service team.') as p5_request;
-- process 2
select meta.ai_result(:P5_REQUEST::bigint) ->> 'text' as p5_summary;

-- structured: a JSON schema of an object
select meta.ai_generate('CLAUDE', 'Classify: ' || :P5_TEXT, null,
  '{"type": "object", "properties": {"category": {"type": "string", "enum": ["billing", "delivery", "other"]}},
    "required": ["category"], "additionalProperties": false}');
  • The prompt is sent as your SQL built it (no &ITEM. substitutions and no data wrapping): when it contains user input, say in the system prompt that it is data, and treat the answer as untrusted.
  • Limits: the prompt 1–200,000 characters, the system prompt 50,000, the schema 50 KB; at most 20 requests of an application waiting. The service's daily limits apply and every call is logged in meta.ai_usage (without prompt or answer).
  • Requests and answers are kept 24 hours. Application roles can't read meta.ai_request, only their own application's requests through meta.ai_result.
  • meta.ai_available(service): true when the application may use the service (it exists, is enabled and the application is allowed); whether the provider accepts the key shows only when a request is made.

Parsing files in SQL ​

meta.parse_data() reads CSV/TSV and JSON in a bytea (an uploaded file in meta.temp_files, a file column, a web response) with the same rules as the data loader (chapter 16):

sql
-- the columns: names (SQL names, the keys of "data"), headings and inferred types
select * from meta.parse_data_columns((select content from meta.temp_files where item_name = 'P8_FILE'));

-- the rows, typed through a table's row type
insert into app.employee (name, hire_date, salary)
select e.name, e.hire_date, e.salary
  from meta.parse_data((select content from meta.temp_files where item_name = 'P8_FILE')) p,
       jsonb_populate_record(null::app.employee, p.data) e;

-- or by position
select p.cols[1] as name, p.cols[3]::numeric as salary from meta.parse_data(:file) p;
ArgumentDefaultMeaning
p_contentThe file
p_file_namenullHelps detecting the format (.csv, .tsv, .json, .jsonl, .ndjson)
p_formatautocsv, tsv, json; auto looks at the name and the first bytes
p_headerstrueCSV: the first row holds the headings (else column_1, column_2, …)
p_delimiterdetectedCSV: one character; detected from the first line (, ; tab |)
p_row_selectornullJSON: the path to the array of records (data, result.items); without it an array, an object holding one array, or JSON Lines
p_skip_rows0Rows to skip before the headings
p_max_rows100000More rows is an error (at most 1,000,000)
  • Values are text (blank → NULL), as written in the file; nested JSON values are JSON text. Column names are lower-case SQL names made from the headings (Hire Date → hire_date, made unique); types are inferred over all rows: integer, bigint, numeric, boolean, date (ISO), timestamp, else text.
  • Text is read as UTF-8 (with or without a byte order mark), else as Windows-1252.
  • XML (.xml, or text starting with <): the rows of a repeating element, as the data loader reads them. p_row_selector names the row element (employee, or a path ending in it: employees/employee); without it the element path that occurs most often among elements with children or attributes. Columns are the row's attributes (@id → column id), its child elements (name) and deeper elements by path (address/city → address_city); namespace prefixes are ignored. Documents with a DTD or entity declarations are refused.
  • Excel (.xlsx): an .xlsx file is a zip archive of compressed parts, and PostgreSQL can't decompress. pgkiln reads the sheets when it receives the file: a file item's upload (so meta.temp_files content works) or a meta.web_request() response, and keeps them for 24 hours, found by the file's content. p_row_selector names the sheet (default: the first); cells are text as the data loader writes them (dates YYYY-MM-DD). Other .xlsx content (e.g. a file column of a table) gives an error that says so: load it with a data_load process.
  • It runs as the caller (no special rights) and within the statement time limit (a 4 MB CSV file of 100,000 rows takes about a second).

Zip files in SQL ​

APEX_ZIP in SQL. Build a zip from files, for a download process or a web request body:

sql
-- one row per file
select meta.zip_agg(d.filename, d.content order by d.filename) as content, 'documents.zip' as file_name, 'application/zip' as mime_type
  from hr.emp_document d where d.empno = :P3_EMPNO;

-- or step by step, like apex_zip.add_file / finish
declare z bytea;
begin
  z := meta.zip_add(z, 'report.csv', convert_to(csv_text, 'UTF8'));
  z := meta.zip_add(z, 'images/logo.png', logo);
  z := meta.zip_finish(z);
end;

Entries are stored uncompressed (PostgreSQL has no compression in SQL); names are relative paths in UTF-8 (.. and leading / are refused). For big downloads a download process with several rows makes a compressed zip in the server instead (chapter 6).

Read one:

sql
select name, size from meta.zip_entries((select content from meta.temp_files where item_name = 'P8_ZIP'));
select meta.zip_entry(:zip, 'data/employees.csv');   -- bytea, or null when there's no such file
select * from meta.parse_data(meta.zip_entry(:zip, 'data/employees.csv'), 'employees.csv');

meta.zip_entries and meta.zip_entry read zips built in SQL directly. Compressed zips (nearly all others) are unpacked by pgkiln when it receives them, like Excel files above (a file item's upload or a web response; at most 2,000 files and 200 MB unpacked), and read from there for 24 hours; for any other compressed zip they say so.

From APEX_JSON ​

PostgreSQL's JSON functions do what APEX_JSON does, in SQL:

APEX_JSONPostgreSQL
apex_json.open_object … write('name', value) … close_objectjsonb_build_object('name', value, …)
open_array … close_array over a cursorjsonb_agg(jsonb_build_object(…) order by …) from a query
write(p_cursor)select jsonb_agg(to_jsonb(t)) from (…) t (column names become keys)
get_clob_outputthe value itself (::text for text)
parse(text) then get_varchar2('a.b'), get_number, get_countdoc #>> '{a,b}', (doc ->> 'n')::numeric, jsonb_array_length(doc -> 'items')
apex_json.find_paths_likejsonb_path_query(doc, '$.**.name')
apex_json.to_xmltype / JSON_TABLEjson_table(doc, '$.items[*]' columns (…)) (PostgreSQL 17) or jsonb_to_recordset(doc -> 'items')

Web services return JSON through meta.web_response(id) -> 'json', and meta.parse_data turns a JSON file into rows.

Functions for developers and scripts ​

Run these as the owner (in the SQL Workshop, psql or migrations):

FunctionDescription
meta.generate_crud(app, table, report_page, form_page, label default null, icon default 'table')Report page + modal form page + menu entry for a table
meta.generate_grid(app, table, page, label default null, icon default 'grid')Interactive grid page + menu entry
meta.generate_page(app, kind, table, page, options default '{}')The create page wizards: kind is form, cards, calendar, chart, map, facets, master_detail (or report_form, grid). Options left out take the defaults of meta.wizard_defaults; JSON null means none. Returns the new page's id (below)
meta.wizard_defaults(kind, table)The options a wizard proposes for a table, from the catalog
meta.wizard_catalog(table)The columns as the wizards see them: kind (text, number, date, timestamp, boolean, point, geometry, binary, other), key, unique, foreign key and the parent's display column
meta.export_app(alias)The application as JSON (pgkiln/2 format)
meta.import_app(json, alias default null)Import an export, optionally under a new alias; returns the new app id
meta.hash_password(text)A bcrypt hash for meta.app_user.password_hash / meta.developer.password_hash
meta.authenticate(app_id, username, password)Username on success, NULL otherwise, including when the account has no access to the app (used by the login page)
meta.account_roles(app_id, username)The account's roles in an application
meta.set_password(username, password, change_on_first_use default true)Set a password (ends the account's sessions)
meta.expire_password(username), meta.unexpire_password(username)Require (or no longer require) a new password at the next sign-in
meta.change_password(app_id, username, old, new, keep_session)Change a password knowing the current one (used by My account; runtime only)
meta.api_check()PostgREST's pre-request function (db-pre-request): rejects tokens whose app doesn't use the current role as its API role, or whose account is inactive or has no access

Options of meta.generate_page (all optional; column names must be columns of the table):

KindOptions
alllabel (page name and menu entry), icon, nav (add a navigation entry; default true, false for form)
formcolumns (array), mode (normal or modal), return_page
cardstitle, subtitle, body, badge, form_page
calendarstart, end, title, drag (boolean), form_page
chartchart (bar, column, line, area, donut, pie, funnel), label_column, function (count, sum, avg, min, max), value_column
maplocation (geometry, point or lat,lng text) or lat + lng, title, body, report (boolean), form_page
facetscolumns (report columns, array), facets (array), search (boolean), form_page
master_detaildetail (a table), detail_column (its column referring to this table)
report_formform_page (required)
sql
select meta.generate_page('shop', 'calendar', 'shop.meeting', 12, '{"form_page": 13, "drag": true}');
select meta.generate_page('shop', 'facets', 'shop.product', 14, '{"facets": ["category_id", "price"]}');
select meta.generate_page('shop', 'master_detail', 'shop.orders', 15);

Metadata tables ​

All in schema meta. id columns are generated; seq orders siblings (default 10).

Application level ​

app: an application.

ColumnTypeDescription
aliastextURL name (^[a-z][a-z0-9_-]*$), unique
nametextDisplay name
home_pageintPage opened by /a/<alias>
authenticationtextapp_users, header, database, custom or none
custom_auth_function, custom_auth_code, custom_auth_post_codetextCustom authentication: a function (p_username text, p_password text) returns boolean, or a PL/pgSQL body, and post-authentication code (chapter 8)
nav_list, navbar_listtextA list shown as the navigation menu (instead of nav_entry) and as the navigation bar
access_controltextassigned (only accounts with access) or any_user
local_loginbooleanOffer username and password sign-in
sso_providerstext[]Names of identity providers (OpenID Connect or SAML) offered on the login page
ldap_directoriestext[]LDAP directories the password form checks, after local accounts
remember_me_daysintDays a "Keep me signed in" sign-in lasts; NULL = not offered
pwa, pwa_short_name, pwa_icon, pwa_offline_pages, pwa_offline_submitboolean, text, bytea, boolean, booleanProgressive Web App (chapter 17)
db_roletextDatabase role every request runs as
api_roletextDatabase role of REST API tokens for this app (PostgREST switches to it)
language, languages, language_fromtext, text[], textPrimary language, translated languages, browser / user / primary
date_format, timestamp_formattextDisplay masks (e.g. DD-MM-YYYY); NULL: the language's default
debugbooleanShow database error details to users
debug_levelsmallintDebug messages: 0 off, 1, 2, 4, 6 or 9 (APEX levels)
debug_retention_daysintDays debug messages are kept (1–90, default 7)
themejsonb{"accent": "#0b63c5", "header": "#13294b", "nav": "side" | "top", "mode": "auto", "user_choice": true, "styles": [{"name": "Ocean", "accent": "#0b7285", "font": "serif", "font_size": "large", "radius": "small"}], "style": "Ocean", "style_choice": true} (style variants)

account (the user directory): username (unique, case-insensitive), display_name, email, password_hash (bcrypt; NULL = no password), active, created_at, last_login_at.

app_access: app_id, account_id, roles (text[]); who may use which application.

account_style: account_id, app_id, style ('' = Standard); the style variant a user chose in an application (installation data: not exported).

app_user: a view over account + app_access (id, app_id, username, password_hash, roles, active, last_login_at), kept for compatibility. Inserting creates the account if needed and grants access; deleting revokes access.

app_group_role: app_id, group_name, role; identity-provider group → application role.

authz_scheme: app_id, name (uppercase), type (role / sql), value, error_message.

nav_entry: app_id, parent_id (sub-menu), seq, label, icon, target_page, authz.

lov: app_id, name (uppercase; used as LOV:NAME), query, rest_source (the query then reads the source's rows from rest).

list: app_id, name (uppercase), type (static / sql), query, description (chapter 4).

list_entry: app_id, list_name, parent_id (an entry of the same list), seq, label, icon, target_page, target_items (jsonb), target_url (a path inside the app or an http(s) address), badge, description, condition, authz, build_option.

supporting_script: app_id, name, kind (install / upgrade / deinstall), seq, script (chapter 3).

web_credential: app_id, name (uppercase), description, type (basic / header / bearer / oauth2), username (or client id), header_name, token_url, scope, valid_for (text[] of URL prefixes), secret_enc (encrypted by the server; not readable by the runtime role, never exported) (chapter 19).

rest_source: app_id, name (uppercase), description, url (with {param}), method, credential, headers (jsonb), params (jsonb), body, row_selector, columns (jsonb), cache_seconds, timeout_s, max_rows (chapter 19).

app_item: app_id, name, description.

app_process: app_id, seq, name, point (after_login / before_page), code, authz.

build_option: app_id, name (uppercase, unique per app), status (include / exclude), description (chapter 6). meta.build_option_on(app_id, ref) tells whether a component with that build_option value is part of the application.

Page level ​

page

ColumnDescription
app_id, page_noUnique together
name, titleTitle supports &ITEM.
requires_authboolean
parent_pageBreadcrumb parent
modenormal / modal
protectionchecksum / unrestricted
authzAuthorization scheme
build_optionNAME / !NAME: only while the build option is included / excluded

Regions, items, buttons, dynamic actions, validations, processes, computations, branches, navigation entries and application processes have the same build_option column.

region

ColumnDescription
page_id, seq, title
typereport, grid, form, chart, cards, calendar, facets, smart_filters, display_selector, map, tree, template_component, list, data_reporter, ai_assistant, tasks, workflows, static, dynamic
sourceSELECT (or HTML for static)
table_name, pk_columnFor form and grid
pk_itemFor form: the item holding the key
columns1–12
templatestandard, plain, collapsible
condition, authzVisibility
configAttributes per type (chapter 4)
rest_sourceA REST data source the region reads; source is then SQL over rest (chapter 19)
template_optionstext[]: CSS classes from a fixed list (template options)

item: page_id, region_id, seq, name, label, type, lov, source_column, default_value, required, help, readonly_condition, authz, config (chapter 5).

button: page_id, region_id, seq, name, label, action (submit / redirect / da / document / menu), target_page, target_items (jsonb), condition, authz, hot, confirm, menu (jsonb, for menu), badge, badge_query (chapter 6), template_options (text[], template options).

dynamic_action: page_id, seq, name, event, trigger_element, condition_type, condition_value, action, affected_items, affected_region_id, code, items_to_submit, message, css_classes, authz (chapter 7).

validation: page_id, seq, name, item_name, type (not_null / sql / regex), expression, message, when_button.

process: page_id, seq, name, type (form_dml / grid_dml / sql / data_load / invoke_api), region_id, code, config (jsonb, for data_load and invoke_api), point (submit / load), when_button, authz, success_message.

computation: page_id, seq, item_name, point (before_header / after_submit), type (static / item / sql_query / sql_expression / function_body), expression, condition_type, condition_expr, condition_value, authz (chapter 6).

branch: page_id, seq, name, point (after_processing / before_header), when_button, condition_type, condition_expr, condition_value, target_type (page / url), target_page, target_items (jsonb), target_url, authz (chapter 6).

Runtime and instance ​

TableContentsReadable by the runtime role
sessionSessions: token_hash (SHA-256 of the cookie), app_id (NULL = builder), username, roles (resolved at sign-in), csrf_token, state (jsonb session state), created_at, last_seenyes
activity_logat, app_id, page_no, username, event (page_view, login, login_failed, login_locked, login_unlocked, logout, error, forbidden, api_token, password_expired, password_changed; builder: lock_broken, supporting_objects, sample_data, …), ip, elapsed_ms, detailyes (insert/select)
debug_viewDebug messages: one row per recorded request: app_id, page_no, username, session_id, method, path (without the query string), status, level, started_at, elapsed_ms, entries. Written through meta.debug_save() (runtime role only), not exportedno
debug_messageThe entries of a recorded request: view_id, seq, elapsed_ms (since the start), duration_ms (timed steps), level, component, messageno
web_request_logWeb requests from SQL: app_id, status, the request (url or source + params, method, headers, body, credential name, timeout_s), requested_by, times, the response (status_code, response_url, response_headers, response_body), message. Kept 24 hours, not exportedno (through meta.web_response)
push_keyThe VAPID key pair of an application with push notifications: public_key, private_key (encrypted with PGKILN_SECRET_KEY). Not exportedno
push_subscriptionDevices with notifications on: app_id, username, endpoint (the push service URL), the device's keys p256dh and auth, user_agent, created_at, last_sent_at, failures. Not exportedno (through meta.has_push_subscription)
push_messagePush notifications from SQL: app_id, username, title, body, url, tag, urgency, ttl_s, status, attempts, devices, delivered, message, requested_by, times. Kept 7 days, not exportedno
ai_serviceAI services of the installation: name, provider (anthropic, openai), model, effort, refusal_fallback, max_tokens, timeout_s, base_url, api_key_enc (encrypted with PGKILN_SECRET_KEY, write-only), enabled. Not exportedno
app_ai_serviceWhich applications may use which AI service, with daily limits max_requests and max_tokens (null: no limit). Not exportedno (through meta.ai_available)
ai_usageOne row per AI request: at, app_id, page_no, username, service, provider, model, source, input_tokens, output_tokens, duration_ms, status, message (an error class, never prompt or answer text). Not exportedno
ai_conversationAI assistant conversations: one per session and region (messages: the provider's history, turns: what the page shows); deleted with the session. Not exportedno
ai_table_noteDescriptions of tables and columns for models (App Builder AI): schema_name, table_name, column_name ('' for the table), note. Installation data, not exportedno
blueprintSaved blueprints: name, spec (the JSON), app_id (the application last created from it), created_by. Builder data, not exportedno
builder_aiThe App Builder's AI service (one row, service_id; administrators). Not exportedno
ai_requestAI requests from SQL and their answers, kept 24 hours. Not exportedno (through meta.ai_result)
developerBuilder accounts (is_admin: manages developers, breaks locks)no
builder_lockPage (page_no) and application (page_no 0) locks: locked_by, locked_at, noteno
dev_commentDeveloper comments on an application (page_no 0) or page: author, body, created_atno
auth_providerOpenID Connect and SAML providers (protocol; SAML: idp_sso_url, idp_cert): name, display_name, issuer, client_id, client_secret, scopes, username_claim, groups_claim, auto_create, enabledno
account_identityLinks an account to a provider's subject (provider_id, subject, account_id)no
sso_pendingSign-ins in progress (state, PKCE verifier, nonce or SAML request ID; kept for 10 minutes)no
saml_requestSAML AuthnRequest IDs awaiting their response (used once)no
ldap_directoryLDAP directories: URL, service account (write-only password), user and group searchno
task_definitionApproval and action task definitions per appyes (read)
rest_moduleREST modules per app: name, title, handlers (JSON)yes (read)
workflow_definitionWorkflow definitions per app: the active version's steps as JSON, a development version and the inactive onesyes (read)
workflow, workflow_eventWorkflow instances (state, current step, variables, their version and a copy of its steps) and their history; reached through meta.workflows / meta.workflow_events and the functionsno
workflow_branchThe parallel branches of workflow instances (step, wait, task and state of each)no
task, task_eventTasks and their history; reached only through meta.tasks, meta.task_events and the meta.*_task functionsno
list, list_entryLists and their entries per app (see above)yes (read)
supporting_scriptSupporting objects per app (see above)no
document_templateDocument templates per app: name, description, query, template, layout, filename, authzyes (read)
ldap_identityLinks an account to a directory entry (directory_id, subject = entryUUID or DN)no
persistent_login"Keep me signed in" tokens: token_hash, account_id, app_id, groups, method, expires_atno
instance_settingSecrets, e.g. the URL checksum keyno
settingAccount settings: password_min_length, password_require_mixed, password_lifetime_daysyes (read)
text_messagePer app: name, language, textyes
translationPer app and language: source (primary-language text) → targetyes
temp_fileUploaded files per session (deleted with the session; at most 20 per session). Read through the view meta.temp_filesno (through the view)

Outside meta: public.pgkiln_migration (applied migrations), public.pgkiln_seed (applied example scripts) and public.pgkiln_install_log (each migration run that applied or failed a file: started_at, finished_at, version, kind install/upgrade, applied files, status, error, db_user), shown under Workspace utilities → Installation.

Retention: expired sessions are purged automatically; debug messages after the application's debug_retention_days (and at most 5000 requests per application), and web requests after 24 hours (at most 500 per application), and AI requests from SQL after 24 hours, by the scheduler. The AI usage log (meta.ai_usage) is kept until you delete from it. The activity log is kept until you delete from it, for example with a scheduled delete from meta.activity_log where at < now() - interval '90 days'.

URL parameters ​

ParameterUsed byMeaning
<ITEM>=valuepagesSet an item (the page's items are cleared first)
cspagesChecksum of the item values
clear=1pagesClear the page's items
dialog=1pagesRender for display inside a modal dialog
r<id>_qreport, gridSearch text
r<id>_s, r<id>_d=descreport, gridSort by column position, descending
r<id>_preport, gridPage number
r<id>_nreportRows per page
r<id>_a=fn|columnreport, gridAn aggregate (repeatable); fn is sum, avg, count, min, max
r<id>_sel=value, r<id>_selcsmaster gridSelect a master row: the value goes into the grid's select_row item; selcs is its signature (made by the runtime)
r<id>_dup=keygridShow the row with this key again as a new, unsaved row (Duplicate without JavaScript)
r<id>_f=column|op|valuereportColumn filter (repeatable); op is eq, ne, contains, not_contains, gt, ge, lt, le, null, not_null
r<id>_x_<column>=valuereport + facets, smart filtersFacet selection (repeatable)
r<id>_xn_<column>=1report + facetsExclude the selected values (facet with exclude)
r<id>_rg_<column>=from|toreport + facets, smart filtersOne of a range or star facet's ranges (~: the custom range)
r<id>_rf_<column>, r<id>_rt_<column>report + facetsCustom range from / to, inclusive (facet with custom)
r<id>_csv=1reportDownload CSV
r<id>_xlsx=1reportDownload Excel
r<id>_load=1lazy regionShow the region in the page (the link of its placeholder without JavaScript)
r<id>_pdf=1reportDownload PDF
r<id>_m=YYYY-MMcalendarMonth shown

Icons ​

136 line icons of pgkiln's own (public/icons.svg, listed below), and (0.31) the Lucide set of about 1,600 more in the same 24×24 line style (lucide.dev/icons, ISC licence, shipped with pgkiln; APEX: Font APEX), usable in navigation entries, list entries, cards (icon column) and template components. An icon value is a name followed by optional modifiers:

text
users                  pgkiln's own icon
car-front              a Lucide icon (pgkiln's own wins where both have the name)
fa-car-front fa-lg     Font APEX style: the fa- prefix is dropped, so Font APEX names work where Lucide has the icon
refresh spin           modifiers: xs sm lg 2x 3x 4x · spin pulse · rotate-90 rotate-180 rotate-270 · flip-h flip-v
truck flip-h success   · colours success warning danger info muted

Unknown names show no icon; unknown modifiers are ignored. spin and pulse stand still for users who ask for reduced motion. Each Lucide icon is its own small file (/static/icon/<name>.svg, cached for good, versioned with the package), so a page loads only the icons it shows. In the builder the icon picker shows pgkiln's icons; its filter box also searches the Lucide icons by name and search word (vehicle finds car, bus, …), and Or any icon, with modifiers takes any value (checked on save).

home users user building chart table list calendar shield history settings org gridfile check menu logout plus download filter database code activity inbox closechevron edit layers bolt key play upload printer clock search alert map scancloud-off arrow-up arrow-down arrow-left arrow-right chevron-down chevron-up chevron-leftexternal refresh undo redo compass info help check-circle x-circle plus-circle minusban power trash copy save share send link paperclip lock unlock eye eye-off bellstar heart bookmark tag flag pin globe hash at chat comments phone megaphonefolder archive image camera video book clipboard note qr barcode briefcase cartcredit-card wallet coins percent calculator receipt truck box store factory targettrophy gift id-card user-plus graduation lightbulb pie-chart line-chart trend-uptrend-down kanban dashboard sliders server cloud wifi cpu tool car plane routehome-heart hospital heart-pulse leaf sun moon droplet thermometer

HTTP endpoints ​

Method and pathPurpose
GET /a/:alias/:pageShow a page (and CSV downloads)
POST /a/:alias/:pageSubmit a page
POST /a/:alias/:page/da/:idRun a server-side dynamic action (JSON)
POST /a/:alias/:page/lov/:itemRe-render a cascading list (JSON)
GET/POST /a/:alias/login, POST /a/:alias/logoutSign in and out
GET /a/:alias/sso/:providerStart single sign-on with a provider
GET /sso/callback/:providerOpenID Connect redirect URI
POST /sso/saml/:provider, POST /sso/saml/:provider/finishSAML assertion consumer service (and the same-site step after it)
GET /sso/saml/:provider/metadataSAML service provider metadata
GET/POST/PUT/PATCH/DELETE /a/:alias/rest/:module/…, GET …/openapi.jsonREST module endpoints (bearer token) and their OpenAPI description
POST /a/:alias/workflows/:idWorkflow console action (terminate, retry)
POST /a/:alias/tasks/:idA task list action (claim, release, approve, reject, complete, delegate, cancel, comment)
GET /a/:alias/manifest.webmanifest, /sw.js, /icon-192.png, /icon-512.png, /offlineProgressive Web App files (only when the app has PWA on)
any page ?doc=NAMEDownload a document template filled with the page's values
POST /a/:alias/account/devicesSign out on all devices ("Keep me signed in")
POST /a/:alias/passwordChange an expired password while signing in
GET/POST /a/:alias/account, POST /a/:alias/account/password, POST /a/:alias/account/theme, POST /a/:alias/account/styleMy account, own password, the light/dark switch, the style variant switch
POST /a/:alias/tzThe browser's time zone for the session (automatic time zone; sent by app.js, CSRF token required)
any page ?lang=xxSwitch the language for the session
/builder/...Builder
PostgREST (separate service, API_URL)REST API of each app's api schema, see chapter 13
/static/...CSS, JavaScript, icons

Released under the Apache-2.0 license. Not affiliated with Oracle; Oracle and APEX are trademarks of Oracle.