A product that started in a bakery
BakePilot is a management system for patisseries and bakeries: orders, recipes with ingredient costing, production, stock, invoices sent to KSeF (the Polish National e-Invoicing System), HACCP food safety records, staff time tracking and finance. It runs in the browser, on a tablet in the production room and as an Android app. We sell it as a subscription at bakepilot.pl.
It was not built for a client. Since May 2018 Kamila and Patryk Kowalski have run PyziPyzi, an artisan patisserie in Kalisz. Over the years they tried notebooks, Excel, sticky notes on the fridge and three off-the-shelf apps. None of them answered the questions a patisserie asks itself every day: what does this cake really cost, what has to be baked for tomorrow, what will run out in the stockroom, and did the invoice reach the tax office.
Patryk also runs KamikStudio. BakePilot is therefore a project in which we are the client, the product owner and the contractor at the same time. That gives us an unusual position: we test every architectural decision on our own business, on real invoices and real orders, before we propose a similar solution to anyone else. Our own product is where we prove the solutions that later go into our clients' systems.
Scale you do not see from the outside
To a pastry chef, BakePilot is a dozen or so menu items. Underneath sits a system whose size and number of dependencies are on a par with custom software for mid-sized manufacturing companies. A few figures counted from the repository:
- Three Next.js applications in one repository (the app for bakeries, an admin panel, and the website with documentation and blog), plus a mobile shell and independent monitoring. Over 270,000 lines of TypeScript in total
- 147 tables in PostgreSQL and several hundred access policies, database functions and triggers, all recorded in 229 migrations
- 75 architecture decisions written up as ADRs, with context, rejected options and consequences
- 23 application views and 175 features described in a single catalogue, which drives both the pricing table on the website and the access gates in the app
- 6 staff roles and 131 permissions in a "module.action" model, which the bakery owner can override
- 3 interface languages (Polish, English, Ukrainian) with 1,087 keys each, and 11 currencies with a currency snapshot stored in every document
- 17 scheduled jobs: from fetching purchase invoices from KSeF and renewing certificates to pickup reminders for orders
- 86 user documentation articles in 23 categories, with screenshots

From sticky note to invoice in one flow
The starting point was very concrete. At PyziPyzi orders went missing on scraps of paper, cake prices were set by gut feeling and admin took more than ten hours a week. Those figures now appear on the product website as the case of our own patisserie.
Orders in a notebook and on sticky notes, 3-5 mistakes a day
Taking an order took around 8 minutes
Cake costs estimated by eye or in Excel
Ingredient prices retyped by hand from wholesaler invoices
Fridge temperatures on paper forms
8-16 hours of admin a week
Orders in the system, a production board for the team, zero mistakes
An order from a phone in 90 seconds
Recipe cost with sub-recipes, energy and overheads calculated live
Purchase invoices fetched from KSeF automatically, ingredient prices update on approval
Temperature log with limits and alarms
About one hour of admin a week
That experience grew into 18 feature groups in the price list. The main areas:
-
Sales
Retail and wholesale orders, standing orders generated on a schedule, price lists for customer groups with effective dates and promotions, a cake configurator priced per kilogram or per portion, quotes and contracts for weddings and catering, deliveries with a route in Google Maps -
Production
Multi-level recipes with sub-recipes (cycles blocked both in the database and in the app), production from a recipe in a weighing mode for touch screens, a production board, printouts scaled to the planned quantity -
Costs and stock
An ingredient database with multiple suppliers and price history, an invoice price scanner, stock of raw materials, finished goods and semi-finished products, stocktaking as a dated document, a purchase planner driven by orders -
Documents and regulations
Sales invoices and credit notes sent to KSeF, delivery notes with consolidated invoicing, daily reports with a VAT breakdown, a VAT estimate and break-even point in the Finance module, HACCP with a temperature log and GHP/GMP hygiene forms -
Team and integrations
Time tracking with a rota, clocking in on a shared tablet with a PIN, leave and payroll hours, a calendar synced to iOS and Android via iCal, a public API documented in OpenAPI for orders coming from the bakery's own website

Architecture: one database, many companies
The first serious decision was the multi-tenancy model. We considered three options: a separate database per bakery, separate schemas in one database, and one shared database with row-level isolation. The first fell on infrastructure cost, which with a few hundred customers would have eaten most of the margin. The second fell on migration complexity, since every schema change would have to run through hundreds of copies.
We chose PostgreSQL on Supabase with Row Level Security. Every row of bakery data carries an organisation identifier, and the database itself refuses to return another company's data, even if a bug appears in the application code. This is complemented by column-level grants, and server operations running under a technical account use allowlists of tables and columns instead of full access.
The same "never trust a single layer" principle returns in the order flow. Since version 3.5.0 an order with its line items is saved in a single database transaction, with a request identifier issued by the form. When the internet drops for a moment in the production room, the form retries the save and the database recognises the repeat instead of creating a duplicate. Every change to an order goes into an append-only history.
A connection drop in the middle of a save is everyday reality on a factory floor, in a warehouse or for a sales rep in the field. An atomic save with a request identifier is a pattern we use wherever a duplicate order or half a saved document costs real money.
Plans and permissions from a single source
BakePilot has three subscription plans: Basic, Professional and Master. Until September 2026 the plan worked at the level of a whole module, so everything inside inherited one plan. An audit comparing the price list with the code found 9 mismatches: features listed in a more expensive plan available in a cheaper one, and the other way round.
Instead of fixing nine places, we changed the model. We built a feature catalogue: one file describing 175 features at the level of view and tab, each with a minimum plan, a role permission, a route and a statement of what enforces it in the code. Three things are built from this catalogue at once: the pricing table on the website (76 rows in 18 groups), the access gates in the app and the preview in the admin panel. Moving a feature to another plan is a single change that updates the price list and actual access at the same time.
Catalogue entry
A new feature gets a minimum plan, a role permission, a route and a declaration of how it is enforced
Permission and label
A key in the "module.action" model and a readable name in the permission matrix the bakery owner sees
Gate in the code
On the page, in the server action, in the API route or in the interface, depending on where the feature lives
Copy synchronisation
A script distributes the catalogue to the app and the admin panel, which are separate deployment projects. Copies are never edited by hand
Article and screenshot
A feature without a user documentation article does not count as finished
All of this is enforced by guard tests. The first checks that the catalogue copies match the source, that every permission has a catalogue entry, every menu item has a feature, and that the number of features with no gate at all is zero. The second reads the source code and checks that the catalogue does not lie: if a feature claims to have its own gate, a call to that gate must exist in the code. Today 105 features have their own gate, 19 are enforced at module level, 40 by role only, and 11 describe things outside the code (priority support, for example), each with a mandatory note explaining why.

Per-organisation role overrides are built on the same foundation. Six roles (administrator, sales, pastry chef, stock, purchasing, accounting) come with sensible defaults, and the owner can change them in the matrix. Eight base permissions are locked, and sensitive modules such as Finance and Time tracking can additionally be closed behind a second administrator password. That matters in small businesses where several people share one tablet.
KSeF without tokens and without the tax office
KSeF, the Polish National e-Invoicing System, was from the start one of the main reasons bakeries looked for new software. The integration in BakePilot covers both directions: sending sales invoices and credit notes in the FA(3) format, and fetching purchase invoices automatically.
Authentication turned out to be the hardest part. Redirecting users to log in through login.gov.pl is not available to commercial applications, and KSeF tokens stop working on 1 January 2027. So we built a one-click wizard: the user downloads an authorisation file, signs it with Profil Zaufany (the free Polish government e-ID) or a qualified signature, and the system generates the key, submits the request and enrols an internal KSeF certificate on its own. The certificate is free, valid for up to two years and renewed by a scheduled job without user involvement. Stored certificates are encrypted, and requests are signed with XAdES.

Purchase invoices are fetched by a scheduled job that remembers where it stopped and catches up on history. The Ministry of Finance API has hard limits: 8 queries per second, 16 per minute and 20 per hour for searches, and 64 per hour for downloading invoice content. The code respects them: it waits 3.9 seconds between downloads, and when it receives a rate-limit response it stops gracefully and resumes in the next cycle.
In production we found that pagination parameters sent in the request body are silently ignored by KSeF, which then returns the default ten records. They only work in the query string. Pitfalls like this are not in the documentation, and in a client's system they would mean incomplete bookkeeping with no error message at all. We keep every confirmed case on record together with the correct way to make the call.
A fetched invoice does not just end up in an archive. The parser reads the FA(3) structure and passes the line items to the price scanner, which matches them to ingredients and shows what has gone up and which recipes need recalculating. This exposed a quirk of the industry: the largest cash-and-carry wholesaler does not send the number of units per carton to KSeF, so the same unit sometimes means a block of butter and sometimes a carton of eight. The fix is a wholesale packaging catalogue, starting with 205 products, which divides the carton price by the number of units based on the EAN code. The overriding rule: a line that cannot be converted with certainty is never guessed; the system skips it and records the reason.

Since 1 April 2026 KSeF applies to everyone, credit notes included. BakePilot issues credit notes in a separate series, works out the correct VAT period, and receives credit notes from suppliers without overwriting ingredient prices, because a silently wrong price is more dangerous than a missing update. It also handles a third party on the invoice, for example when a nursery collects the cakes and the municipality pays.
Our own billing instead of an intermediary
BakePilot subscriptions are charged through Stripe, and VAT invoices are issued automatically by Fakturownia (a Polish invoicing service) and passed on to KSeF. Moving to this model from an external sales platform took a few days, but the rules it runs on are the result of longer work:
- The organisation a payment belongs to comes only from the logged-in administrator's session, never from an email address or tax ID typed into a form
- Subscription state is always read from the API, not from the event payload, because event delivery order is not guaranteed
- Amounts have a single source in grosz (Polish cents), from which the Stripe price catalogue, invoices and discount codes are built
- An invoicing error never reverses a paid plan: the invoice waits in a queue processed every 15 minutes by a scheduled job
- A separate daily job reconciles account status with payments, so a paid account will not expire because of a missed event
Four axes: language, format, currency, country
Since version 3.2.0 the interface has been available in Polish, English and Ukrainian. Many people from Ukraine work in Polish patisseries and bakeries, and the staff screens (production, weighing mode, time tracking, HACCP, production board, stock) have to be clear to them.
The key decision was about what must never be derived from something else. Interface language belongs to the user. Number and date formats and the language of documents belong to the organisation. Currency is a company setting, but it is stored separately in every document. Country determines VAT and KSeF. A Ukrainian baker working in Kalisz has the interface in Ukrainian, yet the customer's invoice goes out in Polish, in zloty, and is sent to KSeF. A notification his app sends to the owner arrives in the owner's language, not the sender's.
BakePilot supports 11 currencies and German VAT rates for bakeries that invoice in euro. Every order, invoice, quote, contract and daily report keeps its own currency snapshot. Changing the organisation's settings never changes old documents retroactively. This rule came out of a bug: at one point historical invoices in zloty could be displayed as euro after the company changed its currency. The fix touched around 30 files and separated two paths: creating new documents and displaying existing ones.
Translations are enforced by tests: the same key must exist in every language, otherwise the build fails. Plurals in Polish and Ukrainian have the full set of forms, and the test environment has a pseudo-language that lengthens texts to check screen layouts for German and Hungarian, which run around 40 per cent longer on average.
Security and compliance
BakePilot holds data the bakery is accountable for to its customers, to Sanepid (the Polish food safety inspectorate) and to the tax office: orders with personal data, invoices, KSeF certificates, HACCP records and working time records. That is why security is written into the code and into documents, not just into statements.
-
Data isolation in the database
Row Level Security on tables holding company data, access policies that distinguish role permissions (payroll, invoices, Finance, KSeF) and column-level grants -
Support access only with consent
Support can view a customer account only after consent is given in the support ticket; access expires after 24 hours and stays in the audit log -
Personal data kept out of logs
Email addresses, tax IDs and phone numbers are masked before an event reaches error monitoring. Rate limits on search and company verification routes -
Information security management
An audit of the database, application and infrastructure in October 2026, and documentation structured on ISO/IEC 27001:2022: a security policy, risk register, statement of applicability covering 93 controls, business continuity plan and incident procedure -
GDPR and the Digital Services Act
Cookie consent with Consent Mode v2, a list of subprocessors in the legal documents, a DSA procedure and a public form for reporting illegal content -
HACCP ready for inspection
A temperature log for equipment with limits and alarms, GHP/GMP hygiene forms, templates with a change history in which every edit requires a reason
Ingredient prices and costs are a compliance area too, a financial one. The Finance module calculates a VAT estimate and the break-even point, and we compared its output with our accountants' figures on our own patisserie's data before releasing it to customers. The calculation engines are pure functions with tests, and the rule is simple: when a result cannot be calculated honestly, the system returns no result rather than zero. Zero can be true; missing data is not.
Process: how we ship 84 versions in six months
BakePilot develops fast, yet the app runs live businesses. Separate environments and a repeatable release process make that possible.
Plan and decision
A larger change starts with an implementation plan, and a decision with architectural consequences goes into the ADR register
Test environment
The working branch deploys automatically to a separate instance with its own database, password-protected and excluded from indexing
Tests and review
Unit tests and architecture guards, code and security review before merging into the production branch
Idempotent migrations
Database changes written so they can safely be run again, deployed to the test database first
Release with notes
Version number in all three apps, a public changelog written in the baker's language and an internal technical log
Documentation and screenshots
The help centre article updated together with the code, with screenshots from the demo account
User documentation has its own rule, written down after an audit: an article describes only features that exist in the code, and button names are quoted from the interface components. The first version of the help centre also described plans, which led to customer questions about buttons that did not exist. Since then a new feature means code, an article and a screenshot, and announcements go only into the "What we are planning" section of the website.
The same discipline applies to bugs. When one of them reveals a class of problem rather than a single case, it gets a guard. For example, adding a second foreign key between two related tables broke queries that had previously joined them unambiguously. After the fix we added a test that scans all such queries in the code and blocks any schema change that would invalidate them.
Monitoring independent of our providers
The app runs on Vercel and Supabase. Sentry collects application errors, but an external watchdog cannot run on the same infrastructure it is watching. So we run it on KamikStudio's own server:
- Every minute it checks the health of the app and of the authentication service
- Every 5 minutes it logs in with a test account and opens the dashboard, like a real user. A redirect to login, an error code or a load time over 4 seconds counts as a failure
- Two failures in a row open an incident and two successes close it, so a single glitch does not wake anyone at night
- A burst of infrastructure errors arrives as one alert and a summary, not as dozens of messages
- A public status page shows 90 days of availability history
On top of that there is a public demo with a sample patisserie: 30 products, 200 orders, 50 customers, 40 invoices and 80 daily reports. The data resets every day at 3:00, so every visitor sees a complete system without creating an account.

Mobile app
We design BakePilot for the phone first, from a width of 375 pixels. An order can be taken with one thumb between kneading dough and serving a customer, in about 30 seconds.
We built the mobile app with Capacitor: a native shell for Android and iOS loads the same application that runs in the browser. Every web release reaches phones immediately, without waiting for store review, and native features are added on top. These are push notifications for a new order, a HACCP alarm and a pickup reminder, plus printing through a native plugin. Navigation inside the app is restricted to BakePilot domains, and payment opens in the system browser.
The Android version has been available on Google Play since June 2026. The iPhone app is in preparation; until then the full BakePilot runs on the iPhone in the browser, with nothing to install.

What goes from BakePilot into our clients' systems
A company commissioning custom software rarely sees how a software house works day to day. BakePilot is our answer to that question: a live product in which every pattern has been tested against real data, real authorities and real users. The patterns we carry into client projects:
-
Multi-tenancy with isolation in the database
For corporate groups, branch networks and B2B portals where several companies work on one system -
A feature and permission catalogue with guard tests
A single source of truth for who sees what and what each licence includes, with no drift between the offer and the code -
KSeF in both directions
A certificate instead of tokens, purchase invoices within API limits, credit notes and third parties, proven in production -
Saves that survive connection drops
A transaction and a request identifier wherever a duplicate or half a document means a loss -
Languages and currencies as independent axes
For companies with multilingual teams and international sales -
Documentation and decisions that outlast team changes
ADRs, implementation plans, a changelog and user documentation verified against the code

Technologies
React 19
TypeScript
Supabase
PostgreSQL 17
Row Level Security
Tailwind CSS 4
shadcn/ui
Zod
next-intl
Vitest
Vercel
Capacitor (Android, iOS)
Firebase Cloud Messaging
KSeF API 2.0, FA(3), XAdES
Stripe
Fakturownia API
Resend
Sentry
Polish VAT register API (tax ID lookup)
OpenRouteService
Google Maps
iCal
OpenAPI
GTM with Consent Mode v2
Meta Conversions API
Docker
Results
BakePilot is not a client project, but it shows best how we work on custom systems: from a problem in a real business, through decisions recorded with their reasons, to releases, monitoring and documentation. If your company needs a system for orders, production, stock or e-invoicing integration, these solutions are already proven. We do not start from scratch; we start from what already runs in production.