You vibe-coded a thing. It works. People are using it. Now it has problems your prototype shape can't really solve: real authentication, real permissions, real admin dashboards, real audit logs. You're staring at the choice between "keep duct-taping" and "rewrite on something serious."

This page is the honest version of what that migration looks like onto UserSpice — when it's worth doing, when it isn't, and what the four phases of the work actually involve. The estimates are from real engagements, not what we wish were true.

When migration makes sense

A few signals that "yes, it's time":

  • Your auth model is getting complicated. Multiple user roles, group-based access, password resets, 2FA, email verification — and you're hand-rolling each of these in your prototype.
  • You have an admin user. Right now they're "the email I'm checking against in code." Soon you'll want a real admin dashboard, real permissions, real ability to manage users without editing the DB.
  • You're storing sensitive data. Encryption, secure cookies, session hygiene, audit logs — your prototype doesn't do these and is starting to need them.
  • The codebase is getting hard to extend. Adding a new feature requires touching five files in three patterns, none of them documented, all of them slightly different.
  • You have actual users. Real downtime hurts. Real bugs lose trust. The "I'll fix it on a Saturday" model is wearing out.

When it doesn't

Migration isn't free. Reasons to stay put a little longer:

  • You're still iterating on what the product is. Don't migrate a prototype that might pivot next month. Migrate after the shape is settled.
  • Auth and permissions are the whole feature. Sometimes a prototype is literally a one-trick demo. Adding a real auth framework is overkill if there's no future product.
  • The prototype is already on a different sensible framework. If you're on Laravel or Symfony and they work for you, don't migrate to UserSpice for migration's sake. Migrate when the framework is the problem.
  • You can't take the downtime. If "the site cannot be offline for an hour" is a hard constraint, migration is a multi-environment dance, not a weekend rewrite. Plan accordingly.

The four phases

Every migration we've run shakes out into roughly the same four phases, in this order. The order matters — skipping ahead always costs more than doing them in sequence.

Phase 1 — Extract the data model

Before any code moves, the database does. Take the schema of the prototype, normalize it to UserSpice's user / group / permission conventions, and write the migration SQL.

The usual surprises here: vibe-coded prototypes tend to flatten things AI couldn't model well in conversation. You often find users tables with is_admin, is_premium, is_beta_tester as boolean columns. UserSpice uses group membership instead, so those become rows in user_groups_matches pointing at named groups in groups.

Time: half a day to two days, depending on schema complexity. Most of it is rewriting the migration SQL until the data round-trips correctly.

Phase 2 — Port the auth layer

Throw away the prototype's auth and replace it with UserSpice's. password_verify against the same column. Login flows go to users/login.php. Registration goes through UserSpice's join.php (configurable to your fields). Password reset, magic links, email verification — all switch to the framework's flows.

The surprise here is usually the password storage. AI prototypes sometimes use md5() or sha1() for password hashing. UserSpice uses password_hash(PASSWORD_DEFAULT). You can't migrate the old hashes — they're not crackable in either direction. The clean solution is "first login after migration triggers a password reset." Awkward, but it's the only path that doesn't break the cryptographic model.

Time: one to two days for a clean prototype. Add a day if there's 2FA or social-login to port over.

Phase 3 — Replace ad-hoc admin pages

Almost every prototype has an "admin" section that's a single PHP file with hardcoded "if email == mine, show everything" logic. UserSpice ships an admin dashboard, a real permission system, and pre-built pages for user / group / permission / settings management.

The phase is "delete the prototype's admin section, configure UserSpice's." The deletion side is fast; the configuration side is where the surprises live. AI prototypes often embed business logic in the admin section ("when an admin toggles this checkbox, also do these three other things"). That logic has to go somewhere — usually a UserSpice hook or a plugin in usersc/plugins/.

Time: one to four days. The wider variance is from the embedded business-logic count. A prototype where "admin" was literally just viewing rows: half a day. A prototype where "admin" did 14 different mutations: most of a week.

Phase 4 — Port the user-facing UI

The fun part, in our experience. UserSpice's template system, the framework's idiomatic page recipe (init.php + prep.php + securePage), the parsers/ folders for AJAX. Existing pages get rewritten one by one to follow the pattern.

Two non-obvious things here:

  • This is when you do the security retrofit, not before. Trying to fix SQL injection / CSRF / XSS in the prototype's existing structure is wasted work — you're going to rewrite the pages anyway. Wait until phase 4 and the security gets fixed as a side effect of using the framework's helpers.
  • This is where the AI Prompts plugin and Claude skills earn their keep. Each rewritten page can be scaffolded with /userspice-page-scaffold and audited with /userspice-audit. The pages come out the other side framework-correct on day one rather than getting retrofitted iteratively.

Time: roughly one day per existing page, very rough. A 20-page prototype ends up as a 3-4 week migration once you include reviews, deploy work, and the weird one-off cases.

Honest time estimates

Pulling the four phases together:

  • Small prototype (one auth flow, one dashboard, <10 pages, simple schema): 3–5 days of focused work. One developer.
  • Medium prototype (multi-role auth, real admin section, ~20 pages, embedded business logic): 2–4 weeks. Better with two developers.
  • Large prototype (plugins, custom workflows, custom auth, dozens of admin actions): 1–3 months. Real project planning required.

These are end-to-end including testing and a couple of cutover rehearsals against a copy of production. They are not "running the migration end-to-end on a quiet Tuesday" — those numbers are smaller and unrealistic.

What you keep, what you discard

You keep:

  • The data model (normalized to UserSpice conventions, but the entities survive).
  • Any custom feature code that's actually business logic, not framework plumbing.
  • The front-end design / Bootstrap theming, if you like it. UserSpice's templates are customizable; you don't have to use the default theme.
  • Email templates, content pages, static assets.

You discard:

  • The prototype's auth code (login, registration, password reset, session handling). All replaced.
  • The prototype's admin pages. Replaced by UserSpice's admin dashboard plus your custom pages for the business logic.
  • Hand-rolled CSRF, rate-limiting, encryption helpers. Framework provides them.
  • Hardcoded "if user.email == admin" permission checks. Replaced by hasPerm() calls against the framework's permission system.

Common surprises

Things that come up on real engagements more often than people expect:

  • Password hash format mismatch. Old hashes can't be re-hashed without the plaintext. First-login-resets-password is the workaround.
  • Session-attached state. Prototypes often stuff important data into the session ("the user's current shopping cart"). UserSpice's session lifecycle is different; that state often needs to move to the database.
  • "It worked locally but broke in production." The prototype often had relaxed file paths, no real HTTPS, no real reverse proxy. UserSpice doesn't fix these by itself — you fix them during the cutover.
  • Email deliverability. Prototype was using a hardcoded SMTP relay; UserSpice has its own configuration; production has its own DNS and SPF/DKIM/DMARC. The migration is often the moment to fix the whole pipeline.

What "done" looks like

At the end of a clean migration, you have:

  • A UserSpice install with your users in it, your data in it, your business logic in it.
  • An admin dashboard that does most of what your custom one did, plus user management, group management, security dashboard, plugin manager.
  • Your custom pages rebuilt on the framework's secure-page pattern — passing /userspice-audit cleanly.
  • A documented permission model (groups, permissions, what each user role can do) instead of "I remember which emails are admins."
  • The Security Scanner wired into CI, the AI Prompts plugin installed, the Claude skills on your developers' machines.

The next feature, then, gets built on top of all of that — instead of starting from a flat prototype every time. Compounding starts working in your favor.

Get a migration estimate

Migration scoping needs eyes on the codebase — there's no honest answer without seeing what's there. Send the repo URL (or a description of the stack, if it's private) and a one-paragraph "what's the prototype, what does the end state need to be." We'll reply with a scoping question or two and a rough number, free.

Want a real migration estimate for your project? Send the repo URL and a one-paragraph description of where you want to end up. We will reply with a scoping question or two and a rough number.

We reply within 1–2 business days.