--- title: 'Support Diagnostics: browser_info and browser_url_vars' description: Data Controller sends two input tables with the startup service and with every service that runs customer code - the browser context and the page URL parameters. A support ticket can be answered from the job log, and a hook can read them. date: '2026-09-27 09:00:00' author: 'Data Controller' authorLink: https://www.linkedin.com/showcase/data-controller-for-sas tags: - Announcements previewImg: './support-diagnostics.jpeg' --- # Support Diagnostics: browser_info and browser_url_vars A support ticket usually starts with a round of questions. Which browser? Which timezone? Which build? What was in the URL? Data Controller now answers them itself. The frontend sends two input tables - `browser_info` and `browser_url_vars` - with the startup service and with every service that runs customer-provided code, so the context of a session is in the job log before anyone asks for it, and a hook script can read it. This ships with the next Data Controller release. ## The two tables Both are ordinary input tables on the request: the adapter turns each array in the payload into a table in the service's WORK library, so they arrive as `work.browser_info` and `work.browser_url_vars`. ### browser_info A single row, describing the client that made the request: | Column | What it holds | |---|---| | `url` | The URL of the Data Controller page itself (the iframe), not the document embedding it - so an embedded editor is distinguishable from a standalone one. | | `referrer` | The embedding document, from `document.referrer`. For an embedded report this is the report URL. | | `timezone` | The browser's IANA timezone, e.g. `Europe/Berlin`. | | `tz_offset` | The browser's UTC offset in minutes, as `Date.getTimezoneOffset()` returns it - positive west of UTC, so `Europe/Berlin` in summer is `-120`. | | `locale` | The browser locale, e.g. `en-GB`. | | `dc_version` | The Data Controller build, as `appinfo()` reports it. | | `adapter_version` | The `@sasjs/adapter` version the client was built with. | | `browser` | The browser family, parsed from the user agent. | | `browser_version` | The browser version. | | `platform` | The operating system. | | `user_agent` | The raw `navigator.userAgent` string. | ### browser_url_vars One row per URL parameter: | Column | What it holds | |---|---| | `name` | The parameter name. | | `value` | The parameter value. | Parameters are read from both the search string and the hash query string, because Angular routes carry them after the `#`. Where the same name appears in both, the search string wins. The table is sent only when the page URL has at least one parameter. ## Which services receive them | Service | When | |---|---| | `public/startupservice` | Once per session, so every job log carries the session context | | `editors/getdata` | The PRE_EDIT_HOOK | | `editors/stagedata` | The POST_EDIT_HOOK, through the loader | | `editors/restore` | The POST_EDIT_HOOK, through the loader | | `editors/getdynamiccolvals` | The dynamic cell dropdown programs | | `auditors/postdata` | The PRE_APPROVE_HOOK and POST_APPROVE_HOOK | | `editors/loadfile` | The POST_EDIT_HOOK - see the caveat below | Those are the services that run customer code. The viewer's `viewlibs`, `viewtables` and `viewdata`, the metadata services and `usernav` receive neither table, so the high-frequency calls stay lean. ## Reading them in a hook A hook script is `%include`d into the running service, so it shares the service's WORK library and can read the tables directly. A URL parameter by name: ```sas %let labels=N; %if %sysfunc(exist(work.browser_url_vars)) %then %do; proc sql noprint; select value into :labels from work.browser_url_vars where name = 'labels'; quit; %end; ``` Or branch on what the user's browser reports: ```sas data _null_; set work.browser_info; call symputx('dc_timezone', timezone); call symputx('dc_locale', locale); run; ``` Note the `exist()` guard. A service called directly, or by a frontend older than this feature, has no `work.browser_info` at all, so an unguarded `set` or `select` would fail the job. Guard first, then read. ## In the job log Turn debug on and the session initialisation writes both tables to the log, so a ticket can be diagnosed without a second round of questions: ``` NOTE: MPEINIT: work.browser_url_vars: name=embed value=va NOTE: MPEINIT: work.browser_info: url=... referrer=... timezone=Europe/Berlin tz_offset=-120 locale=en-GB dc_version=... adapter_version=... browser=Chrome browser_version=... platform=Linux user_agent=... ``` The debug value depends on the path - `&_debug=131` on the Compute API and SAS 9, `&_debug=128` on the Viya web (JES) path with `runAsTask` enabled, which is what the frontend sends there. Either way, switching debug on in the app is enough. ## Worth knowing - The values are supplied by the client. Treat them as diagnostics hints, never as a security boundary. - `editors/loadfile` is reached through the adapter's multipart file upload, which carries no input tables, so in normal use that service - and the hook it runs - does not receive them. It is on the list for completeness. - Nothing else pays for the tables: they travel only with the services above. Full detail, including the column definitions and the log output, is in the [troubleshooting documentation](https://docs.datacontroller.io/dci-troubleshooting/).