A website that had to become a system
Stowarzyszenie Rzemieślnik (the Artisan Association) brings together bakeries, patisseries, ice cream makers and meat producers who work the traditional way, without the technological shortcuts of industrial food production. It runs competitions, training and educational projects, and its own mobile app shows the points of sale of verified artisans across Poland.
The previous website was built in the WebWave site builder. It was good enough for publishing news, but the Association grew faster than the site. Membership was handled by e-mail, the list of businesses on the website drifted away from the app, and the President reported that some of the listed businesses "closed long ago". The Association needed a service the editorial team could run without a developer, and which at the same time would handle membership exactly as the statute describes it.
Migrating out of a builder that does not export data
WebWave has no content export. The site existed only as what the browser rendered, and the builder positions every element in fixed pixels, so the order in the code does not match what the reader sees. The footer is sometimes the first element of the document, and images carry a placeholder instead of an address.
So we wrote a tool that reads the site the way a person does. It rebuilds the real order of blocks from element positions stored in the stylesheets, downloads images in the highest available resolution, and filters out repeated elements such as the menu and footer statistically. The same set of scripts pulled business and product data from the Association's app.
The scripts are repeatable: running them again only fetches what is missing. That let us refresh the snapshot right before launch, after the editors had published new posts on the old site. Old addresses contained exclamation marks and unusual characters, so the redirect map compares them after decoding and only steps in where the new site would return a 404.
One business, many points of sale
The Rzemieślnik app stores points of sale: a company shop, a market stall, a bakery. The first approach, one entry per point, produced several profiles of the same company on the website. We changed the model: one profile per business, with its points as a list of addresses, coordinates and opening hours. The map shows every point, and every marker leads to the same profile.
The data is not read from the app on every page view. It is copied to the website once a day, at night. The website keeps working when the app server does not respond, search and map do not wait for external requests, and the editors can add a history, gallery and motto that the app does not store.
An automation that hides businesses on its own is dangerous the first time something breaks on the other side. So the sync stops when the app returns fewer than 60% of known businesses, will not hide more than 10% of profiles in one run, will not overwrite points if all coordinates fall outside Poland, and leaves alone entries that the editors have deliberately put on hold. Fields filled in manually by the editors or the member are never overwritten.
The first run fixed what the President had complained about: eight businesses missing from the app disappeared from the website and two new ones appeared. Since then the number of profiles on the website equals the number of businesses in the app. In the admin panel every profile field shows where it comes from: "from the app, read only", "from the app on creation only" or "website only".
Membership run by the statute, not by an inbox
The most important part of the service is invisible from outside. The Association's statute defines four member categories, an admission procedure with an audit of production standards, refusal with the right to reapply after six months, and removal. We turned those paragraphs into a data model.
Account and declaration
Registration with a tax ID checksum, choice of membership category, acceptance of the statute. After logging in, candidates see which stage their application is at
Entry fee and audit
The candidate reports a payment in the panel and the Board books it. Amounts are stored in grosze, with no rounding errors. Ordinary members go through an audit of production standards
Board resolution
Admission creates the business profile from the declaration and sends a welcome e-mail. Refusal blocks a new application for six months from the date of the resolution
Fees and reminders
Fee status is calculated automatically, with reminders for candidates after 7, 14 and 30 days of inactivity and for members before the due date. After six months of arrears the e-mails stop, because removal is the Board's decision
Every stage change, payment, export and e-mail sent is written to a log: who, what, when. Stage history is never overwritten. The Board has its own panel with a dashboard (stuck candidates, payments to book, overdue fees), a candidate funnel, a member list with export and e-mail templates it edits itself.
- Member panel: membership status, payments, business profile edited by the member, account security
- Members-only area: announcements, resolutions, meetings with a calendar file, documents available only to authorised categories
- Member ideas with support votes and comments, moderated by the Board
- Partner benefits with discount codes that never appear in the page source, only for the logged-in member
- Recipes submitted by members together with an ad for their business, approved by the editors with one button
- Forum with trade sections on the same account and the same login as the panel
An admin panel for someone who is not technical
The President set one condition: running the site must be as simple as it gets. The standard WordPress admin shows dozens of options the editors never use, and each of them is a chance to make a mistake. So we built a simplified panel for the Board.
English labels, "Dashboard", "Posts", "Featured image"
Block editor with dozens of block types
Login and panel pages can be moved to the bin
No way to tell which data the app will overwrite
New messages get lost among posts
Polish labels and an instruction next to every field
A simple text editor for every content type
Technical pages protected from deletion
Data source shown next to every profile field
Dashboard with things to handle and counters
On top of that, a 106-page user manual, screen by screen, with screenshots from the real panel, and technical documentation for whoever takes over maintenance one day. Every non-obvious mechanism has its place in the code and the reason it was built that way.
Security that matches members' data
The Board panel processes personal data, tax IDs and payments, so security could not be a plugin bolted on. We wrote two-factor authentication into the theme, following the one-time code standard, so that it covers login through the website, the WordPress admin and the forum at the same time.
- Two-factor authentication mandatory for the Board and administrator, optional for members; backup codes stored only as hashes
- Second-factor secret encrypted, QR code generated in the browser, no secret sent to external services
- Login attempt limits per account and per address, the same error message whether or not the account exists
- WordPress admin under its own address, standard login addresses return a regular 404 page
- Forms with a bot trap, submission timing checks and limits, without a CAPTCHA that annoys people
- Members-area files out of reach of direct links, served only after a permission check
- CSV export protected against formula injection, IP addresses in the log only as a hash
- Old data deleted automatically according to the retention periods in the privacy policy
The Digital Services Act ahead of the deadline
A service with a forum, business profiles and member recipes publishes user content, so the Digital Services Act applies to it. The amendment to Polish law gave until November 2026 to comply. We reviewed the site's documents, published new terms of service, forum rules, privacy and cookie policies, and adapted the code to what those documents promise.
Every forum post, business profile and the footer now carry a "Report content" link. Each report gets a number, the Board decides with a justification, chooses the measure against the author and its legal basis, and the system sends the decision to both parties with information about the right to appeal. Maps and videos connect to Google only after consent, and fonts and map libraries are served from the Association's own server rather than external providers.
Visible in Google and to AI assistants
The requirement was ambitious: full indexing, news articles in Google News, and presence not only in search but also in the answers of AI assistants. We did it without SEO plugins, because none of them knew the Association's content types.
- Structured data that fits the content: a business as a local business, bakery or ice cream shop with points and hours, a recipe with the member business as author, competitions and training as events
- A Google News sitemap with articles from the last 48 hours
- An
llms.txtfile describing the Association, businesses, recipes and latest news for language models - 18 search and AI assistant crawlers explicitly invited, harmful bots and mass-download tools blocked at server level
- Private pages, filtered lists and search results excluded from the index so they do not dilute the site
Measurement that never goes beyond consent
The Association plans Google Ads campaigns and wanted measurement "from A to Z": clicks on a business's phone and e-mail, navigation to a point of sale, event sign-ups, registrations and membership declarations. We deployed the Tag Manager container and the Google Analytics 4 configuration with scripts through the API, so they can be rebuilt: key events, attribution model, and ten audiences for remarketing and for excluding members from recruitment campaigns.
Consent Mode runs in basic mode: until a visitor agrees to statistics, Google tags do not fire. A click on a business's e-mail or phone is counted without the address or number, page addresses reach Analytics stripped of form parameters, and there is no measurement at all on panels, login and forum profiles. A form conversion is counted only after a real success, through a one-time token, so reloading the page does not duplicate it.
Basic mode has a cost: Analytics only sees people who consented. So we added our own server-side visit counter, with no cookies and no personal data, aggregated by day. The Board sees full traffic in the panel, visits from search engines, social media and AI assistants, and for every new account also where the person who created it came from.
Moving house in one evening
We built the service on a staging server, prepared production under a temporary address and checked it with an automated audit of 400 pages before the domain changed servers. The move covered the Association's website, domain and e-mail at once.
Copy on the target server
Database and files moved from staging, new keys, production mode without displayed errors, audit under a temporary address: 0 issues
E-mail before the domain
Mailboxes created at the new provider and moved with full folder consistency before mail records changed direction. Nobody lost a single message
Switch in twelve minutes
After the domain transfer completed: name servers, website and mail records, address replacement in the database with nothing left behind, certificate and canonical address
The same evening
Audit of 400 pages on the live domain, business sync with the app, code review and fixes, removal of demo accounts and content
Content consistency like in a large organisation
The President noticed that in many places the website said something different from the page next to it. The content had accumulated over four years, written by different people. Instead of fixing single sentences, we went through the whole site and listed 62 inconsistencies: amounts, dates, names, membership descriptions. The obvious ones were fixed straight away, in one operation with a copy of every changed value, and the ones that needed the Association's decision went to the President as a list of specific questions.
We treated trust-building details the same way. When a yeast cake recipe turned out to show a photo of sourdough rye bread, we checked every recipe and found two more similar cases. The new photos come from open repositories, credited with author and licence, because without the credit the licence does not allow their use.
Two code reviews after launch
After launch the service kept growing: videos from the YouTube channel on the home page, patron and partner sections, registration sources, full measurement. Every batch of changes ended with a code review. The first one found and removed 10 bugs, the second, covering measurement and consent, 13. Every fix was checked in the browser, with data transfer to Google blocked, before it reached production.
During one deployment the partners page returned a server error for about two minutes, because a file with a syntax error reached production. Since then the theme is deployed only by a script that checks the syntax of every file on the server before swapping it, keeps a copy of the previous version and queries key pages after deployment. Copying files to production by hand is gone.
Technologies
Custom theme written from scratch
PHP 8.5
MariaDB
Advanced Custom Fields PRO
wpForo
Tailwind CSS
Alpine.js
Google Maps API
Leaflet and OpenStreetMap
Mobile app REST API
TOTP (RFC 6238)
Google Tag Manager
GA4 with Consent Mode v2
Schema.org JSON-LD
Google News
llms.txt
YouTube RSS
Python
Playwright
Results
What comes next
The next stage is already planned: one account for the website, the Rzemieślnik app and the Virtual Baker app. Around 1,500 app users will log in to the website with the same account, without creating a new one, and a membership status change in the President's panel will show up everywhere at once. Online payment of entry and membership fees and messaging between the website and the app come on top.
What this project teaches
From the outside, a membership organisation looks like a website with news. From the inside it is a system: it has a statute that says who can become a member and when, money that has to be booked, personal data that has to be protected, and legal obligations towards everyone who publishes content. A site builder only handles the first part.
That is why we started with the statute and with the data that already existed in the app, and only then with the look. And because the service will be run by a non-technical person, what that person sees after logging in turned out to be as important as the code: plain labels, an explanation next to every field and a manual to come back to months later.