
Programming
Stary Sącz Municipality Consultation and Survey System
A new social consultation system for the Town of Stary Sącz: anonymous surveys compliant with WCAG 2.2 AA, risk assessment instead of image captcha, kiosk mode and invitation-based surveys. A successor to a closed system that could no longer be maintained.
16/10/2026
The municipality is required to consult with residents before making important decisions. Previously, this was done using an old, closed system that no one in the office could maintain anymore. I built a new one, using the same tech stack as the municipal portal.
The consultation and survey system is available at konsultacje.starysacz.um.gov.pl. Responses are anonymous, the form meets WCAG 2.2 AA standards, and it works even without JavaScript enabled.

Starting Point: An Old System That Could Not Be Fixed
Before I wrote the first line of code, I checked the old server. It was running on CentOS 7 with PHP 7.2 and MySQL 5.6 - software that had been unsupported by the manufacturer for years.
The application itself had several serious flaws:
- the controllers' code was encrypted with ionCube, so the logic couldn't be checked or improved
- the result was recorded as a separate row for each question, without linking to the respondent,
- which meant it was impossible to create any cross-tables, for example responses by age group,
- there was no protection against multiple voting,
- there were no open questions, ranking or conditional logic available.
The conclusion was straightforward: the new system needed to save the entire survey as one unit, and the archive of three old consultations could simply be moved using a one-time script.
A survey that can be completed without frustration
The first task for the system was an online survey about the indoor swimming pool in Stary Sącz. It defined the scope of the initial version, and the rest had to wait for subsequent stages.
The form guides residents step by step:
- one section per screen and clear information about which step you're on,
- questions appear based on previous answers, and hidden ones are not required,
- question types: single and multiple choice, scale, ranking, text field,
- the 'other, specify' option and exclusion responses such as 'I don't use it',
- the sketch is saved in the browser, so refreshing the page doesn't erase the answers.

Accessibility from the first field
The municipal survey must be fillable with a screen reader, using just the keyboard, and at a magnification of 400 percent. Therefore:
- each question with options is a fieldset with a caption,
- errors appear in the summary at the top with links to fields and right next to the question itself,
- the ranking works by selecting from a list, without dragging the mouse (WCAG 2.5.7),
- checkboxes have a clickable area of at least 24x24 pixels,
- validation and conditional logic run on the server side, so the form works even without JavaScript enabled.
Each change undergoes automatic audit with the axe tool.

Anonymously, but without overwhelming with results
An anonymous survey is tempting to fill out a hundred times. On the other hand, I didn't want to collect IP addresses or force residents to create accounts.
The solution is risk assessment. No submission is rejected outright; instead, it receives points and a list of reasons. In the results panel, editors filter suspicious responses, and in exports, there are separate columns for assessments.
Instead of reCAPTCHA, ALTCHA is used - an open-source proof-of-work library. It runs in the background, doesn't require clicking on images, and doesn't send anything to Google.

The device fingerprint is not an IP address but a HMAC hash of the secret key and survey ID. Once the survey is closed, a nightly task in Directus resets all fingerprints. Only timestamps remain, which can no longer be linked to any specific device.
Kiosk modes and channels
The same survey can have multiple channels: Facebook, schools, the PSZOK point, a village meeting. The channel link is signed with HMAC, so manually changing the parameter won't work. The panel generates a QR code in SVG and PNG for each channel to be printed.
The controlled channel enables kiosk mode. After submitting the survey, the tablet displays 'Thank you, please pass the device to the next person' and a clean form. This allows collecting responses at meetings without marking them as duplicates from the same device.
Editor's Dashboard
The creator of the surveys is the Directusa panel: sections, questions, options and display conditions. Results, exporting, channels and QR codes are in a separate panel in Astro. Logging in is done with a Directusa account, and permissions are managed by Directus.
- number of responses by day and channel, chart for each closed question,
- filters: channel, residents and non-residents of the municipality, no duplicates, only suspicious ones,
- export to CSV that Excel opens with Polish characters and to XLSX with a code dictionary,
- one row per respondent, so the results are immediately ready for analysis.
A separate hook prevents the deletion of surveys, questions, or options that already have responses assigned to them. The editor receives a clear message instead of silently losing data.
Invitation-based Surveys
It quickly became clear that the same mechanism would be useful within the municipality itself. The IT exploratory survey was sent to subordinate units: schools, kindergartens, and centers. Each unit received its own link, and the results were kept confidential.
- the invitation-based survey does not exist publicly; without a valid link, the address returns a 404 error
- the link is not stored in the database; the panel calculates it from a secret, so the backup does not contain working links,
- responses are saved after each step and you can return to the survey using the same link
- invitations and reminders are sent via email directly from the dashboard.
Gmina's Archive and Portal
Three consultations from the old system, dating back to 2021 and 2024, were migrated to the new system along with their descriptions and documents. Along the way, a script repaired broken Polish characters and cleaned up the HTML.

The system provides a public API with a list of ongoing consultations and a ready-to-use widget. The municipal portal fetches data on the server side and displays the banner ‘Public consultations are ongoing’ only when there is actually something happening. The same widget can be embedded by schools and associations.
Stack
On purpose the same as in the municipal portal, so one person can maintain both systems:
- Directus 11 with its own PostgreSQL 16 database and Redis, separately from the portal's CMS,
- Astro 7 in SSR mode, TailwindCSS v4 and DaisyUI v5 with a custom theme,
- ALTCHA instead of reCAPTCHA,
- together with MinIO, Matomo and Kuma Uptime portal,
- OneDev's implementation of CI with database backups and health checks.
A separate database was a deliberate choice. Residents' responses have different retention periods and permissions compared to portal content, so they shouldn't be in the same database.
The hardest part of a municipal survey is not collecting the responses, but collecting them in such a way that you can trust them and know nothing about the person who provided them.
- Aleksander Kowal
What's next
- SMS verification through a custom GSM gateway at the office: one number, one vote, no storage of numbers,
- IT maturity scoring for units and heatmap in the exploratory survey,
- register of legal consultations with documents, comments, and the final report.
