
Programming
Intranet: Directus today, NocoBase tomorrow
A shared institutional database: 136 collections, from HR and working hours to PSZOK (waste collection point) and contract registers. Why the Directus license forces a switch, how I chose the successor using a prototype, and what the new intranet will look like.
18/10/2026
The intranet I describe here is a system that residents will never see, yet it underpins many services they use. It powers the boards in the hall, PSZOK registration, time tracking, signature generation, and protocol printouts.
It runs on Directus, which is a data management panel built on a standard SQL database. Over several years, it has grown to several modules. Now I am preparing its successor and explaining why.
(enlarge)What's Inside
In practice, the intranet is a shared database for the office with a panel to manage it. It has 136 collections of data, over 1500 fields, nearly 400 relationships, and several thousand files. Key modules include:
- Managing the institution: employees, department and office structure, positions, job scopes, access cards and IDs,
- Work time tracking: card reader check-ins, daily attendance records, corrections and HR requests,
- Protection of personal data: register of processing activities, systems, authorizations, risk analysis,
- PSZOK and waste management system: visits, types of waste, limits, declarations, barcodes,
- IT: computers, software and licenses, accesses, mobile devices and SIM cards,
- Finance and assets: budget, expenditure, public procurement, inventory,
- Shared registers: Central Contract Register with files, contractors, resident card,
- The investment database published on the municipal portal, serving the Municipal Council and Volunteer Fire Brigades,
- Screens and kiosks: slideshows, room bookings, and announcements for the lobby boards,
- Helpdesk, FAQ and SMS gateway settings.
(enlarge)Around the Intranet
Most of my systems for the office use the intranet via API. Each with their own account and their own scope of permissions:
- the RCP importer records swipes from the card reader, and the HR application displays attendance based on them,
- the PSZOK system reads declarations and records visits,
- the signature generator takes the employee data and office settings
- the boards in the hall retrieve a list of rooms,
- my custom extension generates PDFs from templates, such as PSZOK protocols, using Gotenburg.
What needed to be organized
The intranet grew over years, piece by piece, as needed by each department. Analyzing the structure showed typical results of such growth:
- the folders in the panel did not correspond to domains, and several collections had no folder at all
- three different styles of collection names, fields numbered by a generator, and typos fixed in the schema,
- content statuses in the CMS like 'published' in registers where nothing is published,
- user account and employee as two separate entities, so the scope of 'my department' couldn't be expressed simply,
- permissions created on an ad-hoc basis in the panel, without a role template.
I also conducted a security review of the API: who has access to what, which service accounts have overly broad permissions, and what is available without logging in. Permissions were tightened up, and each service now gets its own account with minimal scope.
(enlarge)Licensing Issue
Directus was open-source for years. Since version 10, it operates under the BSL 1.1 license. It is free only for organizations with a budget of less than $5 million per year. The municipality exceeds this threshold several times over, so formally it needs a commercial license.
The panel reminds you of this itself. After the update, every login is preceded by a window asking for the project owner and license acceptance. Version 12 goes further: in the free Core edition, it allows for 3 users and 25 collections. The intranet has 136 collections and several dozen users.
(enlarge)For the office, this isn't just a minor formality. A paid license for a system used by several other applications ties up the budget for years, and the supplier may change the terms again. This is exactly the kind of dependency I wanted to free the office from.
Choosing a successor
Instead of choosing from descriptions, I built a prototype. Five tools received the same fictional data: 100 employees, 44 departments, 3,000 residents, and 1,500 PSZOK visits. Each also received the same four roles: HR, manager, registry, and regular clerk.
- Mathesar, with permissions enforced in PostgreSQL by RLS,
- Teable and Grist are spreadsheet databases
- NocoBase is a no-code platform with roles, record ranges, and field lists
- Frappe, the framework on which ERPNext is built.
I tested through the API to ensure each role sees exactly what it should. The manager only sees their own department, the registry has full data on residents, and everyone else only sees a masked PESEL number view. NocoBase won out. The Apache-2.0 license allows for internal use, and the free version includes roles with record ranges, workflow, import/export, and API.
The test migration of the Central Contract Register, which included 2711 contracts, 137 contractors, 300 PDF files and permissions, took around 10 seconds using a script. The values matched exactly to the penny, and the relationships pointed to the same records.
(enlarge)How will the new intranet look?
The new intranet is not just a copy of the old one on a different engine. A few principles I established before writing the first line of code:
- core always enabled (organization, dashboard, dictionaries, data protection, settings) and modules enabled as needed,
- the schema, roles and personal data classes as code in the repository, with the panel serving only for work and viewing purposes,
- each field has a data class defined in the module's definition, which determines masking, export, and retention,
- employee linked 1:1 with an account, so “my department” is a simple filter,
- the intranet does not store PESEL numbers. A resident is searched for through a link to the domain system, and only what is needed remains in the intranet,
- everything that is used by someone outside the office, such as the Volunteer Fire Department or resident voting, is a separate application with its own database.
The officials won't be working in the admin panel. They will get their own front end in Astro, compliant with WCAG 2.2, with an accessibility bar and the same look as the gmina portal. The front end communicates with NocoBase using a token of the logged-in user, so the backend, not the browser, enforces the data range and hidden fields.
(enlarge)Migrating module by module
The new intranet launches alongside the old one. Modules migrate one by one, with one-way data synchronization before switching over. Directus will be turned off once there are no active modules left in it.
- dictionaries and organizational structure,
- Central Register of Contracts,
- PSZOK and waste management system,
- staff and working hours,
- other modules,
- shutting down Directus.
The files go to their own S3 storage on RustFS, thumbnails are generated by imgproxy, and PDF, XLSX, and CSV documents are handled by a separate printing service. Logging will eventually switch to Authentik, which is common across all of the institution's systems.
The most expensive system is not the one you pay for a license of. It's the one that can't be changed because everything else is welded to it.
- Aleksander Kowal
Stack
- today: Directus 11, MySQL 8, Redis, GraphQL, custom PDF extension,
- tomorrow: NocoBase 2 on PostgreSQL 16, RustFS (S3), imgproxy,
- front-end for officials: Astro 7 SSR, TailwindCSS v4, DaisyUI v5, WCAG 2.2 AA,
- login: Authentik (OIDC),
- Docker, OneDev CI, deployment from version tag.
