All postsPublished

How I moved orders out of Notion and replaced Filament with a custom back office

360Películas ran on Notion as its order database and Filament as its admin for a year. What went wrong, and how I migrated in phases without stopping the store.

For thirteen months, every order at 360Películas, my store for pre-cut car window tint, lived in a Notion database. The admin panel was Filament. In the week of 6 October 2026 I changed both: orders moved into the application's own database, and Filament gave way to a custom back office.

It is worth being exact about where this stands. Today the store reads and writes its own database, and that database has the final say. Notion still receives a copy of every change, so the team can look at the views they are used to, and it will be switched off soon. Filament is gone completely. This post covers why I started that way, what went wrong and how I made the move without stopping the store.

Why it started in Notion

The store is a Laravel application, born in September 2025. On the second day of code, an order was already being created as a page in a Notion database. A week later Filament came in to manage the rest: the vehicle catalogue, discount codes, users.

Notion gave me, without building anything, what a small store needs to work its orders: a table, views by status, filters and editing from a phone. The team worked there. In March 2026 the supplier got a portal of its own inside the store, which read and wrote that same database, and the prepaid balance with them was a second Notion database, filled in by hand.

For getting to the first sales quickly, it worked. The trouble is that a shared sheet slowly became the system that payments, emails, the supplier portal and the WhatsApp agent all rested on.

Data without rules

Notion stores whatever it is sent. It has no transactions, no unique keys, and it knows nothing about the business. Each of those gaps showed up in its own time.

  • Duplicate orders. The Notion API has no idempotency key: a repeated request creates a second page. In June I had to guarantee on the store's side that one form submission could never create two orders.
  • A formula as the price of record. The amount customers and resellers saw came from a formula inside Notion, which redid the sum the server had already done. Two implementations of one calculation, in different places, with nothing keeping them in step. In August the application became the authority on the total, and a test now restates the formula to warn me if the two drift apart.
  • Stuck payments. When looking an order up in Notion failed at the moment Stripe reported a payment, the event was marked as handled and never retried. An order paid by MB WAY could stay pending. The fix was to fail properly, so Stripe tries again.
  • One order book, no rehearsal copy. The Notion workspace is the real book. A local copy of the store with the real credentials read and wrote real orders. Now every call to Notion outside production is refused in the HTTP client itself.
  • Rules that do not fit in a view. An order may carry only one production debit against the supplier balance. In Notion nothing stops two, and nothing stops someone deleting a row to "undo" a mistake.

The clearest sign was a round trip in July. Because the team managed everything in Notion, I moved abandoned checkouts there too. Nine days later they were back in the database, because I needed reminders, opt-outs and automatic clean-up, and none of that works well on top of pages in a document.

Where Filament stopped helping

Filament is very good at what it was built for: lists and forms over the application's models. But the most important thing in the store, the orders, was not an application model. It was in Notion. So everything that touched orders was a hand-made page inside the panel: mark as paid, duplicate, handle reseller orders.

There were also two technologies side by side. The storefront, the customer area and the supplier portal are React with Inertia. The panel was Livewire. Any screen with some substance meant choosing between building it twice or fighting the panel. The reseller pages had already left Filament in December 2025, and the WhatsApp conversations took more than one round of work to display properly.

And the team needed more than lists: an order board by stage, an order page with history, costs and comments, a supplier balance with rules. That is no longer an admin panel. It is the tool people work in every day.

Moving without stopping the store

There was no migration day. There was a switch with three positions, and every step can be undone by changing an environment variable.

A single door for orders

The first step was to route everything that reads or writes orders through one point in the code, with three implementations: notion, dual and database. The first is the old behaviour, unchanged, and it is the default. A misspelt value counts as notion, so a typo can never stop an order being recorded.

The phases

  • Mirror. Every write goes to Notion first and, if Notion accepts it, to the database as well. Every five minutes, whatever the team edited in Notion is pulled over. Nobody notices anything.
  • History. A command copies every old order and the supplier balance. It only reads from Notion, can be interrupted and resumed, and never creates duplicates.
  • Parity. Another command compares each order on both sides, field by field, and ends with either "all equal" or the list of what differs. It is also the only way to find what was deleted in Notion, because Notion does not report deletions.
  • Database first. This is where the store is today. The write lands in the database, and that is the result the customer and the team see. Only then does it go on to Notion.
  • Database only. The next step: Notion is no longer called. After that, the code that talks to it comes out.

When Notion fails

Before, Notion being down meant orders failing. Now, if Notion is slow or refuses a write, the order is not affected: the write waits in an outbox and is retried every minute, in the original order, until it lands. Since Notion has no idempotency key, each retry looks the order number up first before creating anything.

There is only one conflict rule: Notion wins, unless the order still has a local write waiting in the outbox. That way an outage can never let an older state in Notion overwrite a newer change.

The supplier balance

The balance went from a hand-filled table to a ledger in the back office. Nothing is deleted or edited: to void a line, a new one is added with the opposite amount, and the original stays as it was, with the reason and who voided it. One debit per order became a database rule, and the migration listed the orders that had been debited more than once in the past.

From Filament to a custom back office

The new back office was born next to the old one, at its own address, with Filament still running until the two matched. It uses the same React with Inertia as the rest of the store, so tables, forms and dialogs are the same components everywhere.

  • One module per old resource. Close to forty sections, each with its own controller, validation, pages and tests: customers, catalogue, partners, marketing, settings, support tickets and WhatsApp conversations.
  • Orders as a workspace. A list with tabs by stage, saved views, search, bulk changes and CSV export. A board for dragging orders between stages. An order page with a timeline, tracking, costs and margin, and comments.
  • The tests moved first. Before deleting the panel, everything its tests guarded was carried over to the back office. When Filament left, there were more than six hundred tests on the new side.
  • Then, delete. 278 files and about 22,000 lines fewer, and 37 packages out of the dependencies, Livewire among them.

Much of this work was done with AI agents coding in parallel, each on its own branch. What made that safe was not the model. It was the tests and the parity check.

What only the database makes possible

Having the orders at home opened up things that were not worth attempting before. Orders now get consecutive numbers, in the form 360-26-00001, instead of a code made of a date and random letters, with the guarantee that a number is never issued twice. And the team can edit an order, from the contact to the price, knowing that only the field they touched changes.

What I would do differently

  • The database as the record from day one. If the team likes Notion, Notion can be a mirror. That is what it is today, and it could always have been.
  • A single door, sooner. The service that talked to Notion was called from everywhere. Half the migration work was gathering those calls into one place.
  • No money sums outside the code. A formula in a sheet has no tests and no change history.
  • Protect the real data before you need to. Refusing calls outside production is an afternoon of work. It should have been the first one.
  • Filament for what is CRUD, and no more. It is still the fastest way to get a panel. Once the panel becomes the team's daily tool, it is time to build it to measure.

If your business runs on a spreadsheet or a Notion that can no longer cope, and you want to move to a system of your own without stopping sales, you can get in touch.

Need a site your team can update without calling a developer?

Start a project