How to migrate from Base44 to your own hosting

Three migration depths: move only the frontend, eject to a new Base44 backend, or fully replace with Supabase. Why partial migration is usually the right starting point.

11 min read·Updated May 27, 2026

Base44 is the tightest-coupled of the major AI builders. Exporting a Base44 project to GitHub gets you a real Vite + React frontend codebase, but the application is still calling Base44's backend for data, auth, functions and integrations. The frontend is portable; the backend is not — at least not without rebuilding it.

Base44's own local-development documentation makes this explicit: the environment variables you set up for local development include VITE_BASE44_APP_ID and VITE_BASE44_APP_BASE_URL, where the second value points at a *.base44.app URL (source). The exported frontend talks to Base44 over that URL. Moving the frontend to Vercel does not change that.

Plan prerequisite. GitHub 2-way sync is gated behind the Builder plan ($40/mo or higher) (source). On the Starter ($16/mo) or free tier, you cannot connect the app to GitHub at all — meaning the first step of any migration requires upgrading. Worth knowing before you commit to building anything serious on Base44.

This guide covers three migration depths, ordered by realism:

  1. Move only the frontend. Backend stays on Base44.
  2. Eject to a new Base44 backend you own. Schemas copy across; data does not.
  3. Full migration off Base44. Replace the backend with Supabase or your own Postgres + functions.

Most teams should run path 1 first, treat it as the stable state for a while, and only commit to path 3 when there is a concrete reason to leave Base44 entirely.

What a Base44 project actually contains

The repository structure, based on Base44's own example apps (base44/apps-examples):

your-app/
├── base44/
│   ├── entities/          # JSONC entity schemas (board.jsonc, task.jsonc, ...)
│   └── agents/            # AI agent instructions
├── public/                # static assets
├── src/
│   ├── sdk-client/        # Base44 SDK client configuration
│   ├── components/        # UI components
│   └── pages/             # route pages (auth.tsx, dashboard.tsx, ...)
├── index.html
├── package.json
├── vite.config.ts
├── tsconfig.*.json
└── CLAUDE.md / agents.md  # agent instructions for the editor

What syncs to GitHub, what stays in Base44

This is the asymmetry that surprises people when they start a migration:

ComponentIn the GitHub-synced repo?Where it actually runs
src/ frontend code✅ yesIn the browser (after Vite build)
src/sdk-client/ Base44 SDK setup✅ yesn/a — config only
base44/entities/ schemas⚠️ Not via GitHub 2-way sync (Base44 docs: "entities are managed in Base44 and are not included in your local repository"). Visible in the repo when ejected via CLI.On Base44
Backend functions⚠️ Code may or may not be synced — Base44's example apps do not include a functions/ folder in the GitHub repo. The runtime is always on Base44's Deno environment.On Base44
Actual database data (rows)❌ neverOn Base44
Auth users + password hashes❌ neverOn Base44
Secret values (Stripe keys, OpenAI keys)❌ neverOn Base44, managed via the base44 secrets CLI
package.json, vite.config.ts, agent instruction files✅ yesn/a — config

Bottom line: the repo is a readable copy of the frontend plus partial backend metadata, not a deployable backend. To get a complete codebase including entity schemas, you have to run base44 eject (a separate CLI operation that creates a new Base44 backend project — covered in Path 2).

How to spot the SDK call sites

The SDK call pattern is the give-away. Anywhere the code reads like this:

import { base44 } from "@/sdk-client/base44Client";

const tasks = await base44.entities.Task.list();
await base44.auth.me();
await base44.functions.invoke("syncOrders", payload);

…the frontend is depending on Base44's backend. Counting how many files contain base44.entities, base44.auth and base44.functions is the fastest way to gauge migration scope.

Path 1 — Move only the frontend (recommended starting point)

This path treats Base44 like a backend-as-a-service. You self-host the React app, keep using Base44 for data and auth, and remove the dependency only when there is a real reason to.

Step 1 — Connect Base44 to GitHub

GitHub 2-way sync requires the Builder plan or higher, and only the app owner can perform the initial connection (source). In the Base44 dashboard, click the GitHub icon, authorize Base44 Builder, choose the GitHub account or organization, and create a repository for the app. Once connected, edits in Base44 push commits to the repo, and commits to the active branch sync back into Base44.

Clone the repo locally:

git clone <your-github-repo-url>
cd <repo-name>
npm install
git tag pre-migration && git push --tags

Create .env.local with the two Base44-specific variables:

VITE_BASE44_APP_ID=your_app_id
VITE_BASE44_APP_BASE_URL=https://your-app.base44.app

Both values come from the Base44 dashboard. The base URL is the same one Base44 uses to serve your app.

Where Base44 stores secrets

The two VITE_BASE44_* variables above are the only frontend env vars Path 1 needs. Anything else — Stripe keys, OpenAI keys, custom integration secrets — was set inside Base44 and is consumed by Base44's backend functions via Deno.env.get(). Since Path 1 keeps the Base44 backend, those secrets stay where they are; you do not migrate them.

The Base44 CLI manages secrets:

base44 secrets list                  # enumerate every secret on the app
base44 secrets set KEY=VALUE         # add or overwrite one
base44 secrets set --env-file .env   # bulk import from a file
base44 secrets delete KEY            # remove one

Setting a secret auto-redeploys any backend functions that reference it (source). Run base44 secrets list before you migrate so you have a complete inventory — useful if you ever go to Path 2 or 3, where the backend leaves Base44 and the secrets need to land somewhere else (Supabase Edge Functions, Vercel env vars, Railway variables). See the hub guide's secrets-moving principles for what to rotate vs migrate.

Step 2 — Run locally

npm run dev

The frontend should boot on localhost:5173 and load data exactly as it did inside Base44 — because it is still calling Base44's backend over the network. Then:

npm run build
npm run preview

If npm run build fails, fix it before deploying. Vite outputs to dist/, which is what every static host expects.

Step 3 — Deploy to Vercel

  1. Import the GitHub repo at vercel.com/new.
  2. Framework preset: Vite (auto-detected).
  3. Build command: npm run build. Output directory: dist.
  4. Add the same two environment variables under "Environment Variables":
    • VITE_BASE44_APP_ID
    • VITE_BASE44_APP_BASE_URL
  5. Deploy.

Base44 apps use React Router, so direct navigation to /settings will 404 unless the host falls back to index.html. Add vercel.json at the repo root:

{
  "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }]
}

Railway, Netlify and Cloudflare Pages all work too. The Vite build settings are identical; the SPA-fallback config is host-specific (_redirects on Netlify and Cloudflare Pages).

Step 4 — Update CORS on Base44

Base44 needs to accept requests from the new frontend domain. The app's CORS / allowed-origins configuration lives in the Base44 dashboard. Add the production domain (and any preview domains you care about) before users hit the deployed app.

Path 2 — Eject to a new Base44 backend you own

Base44's CLI includes an eject command that creates a new Base44 backend project with a local codebase (source). The original app stays untouched. The eject produces a new backend with its own app ID, frontend code locally, and entity schemas / functions / config in a base44/ folder.

npm install -g base44@latest
base44 eject

The CLI requires Node.js 20.19.0 or newer. It prompts for login, the source app and the destination folder.

The critical limitation: entity schemas copy across, but the actual data does not. The new backend's database starts empty. If the app has production users and records, this path requires a separate data migration plan — typically a script using the Base44 SDK that reads from the old app and writes to the new one.

When path 2 is the right choice: you want the app under your own Base44 account (separate billing, separate access), but you are not ready to rebuild the backend in Supabase. It is a halfway step, not a final destination.

Path 3 — Full migration off Base44

Working with a coding agent. Path 3 is mostly mechanical translation work — SDK calls rewritten, schemas defined, RLS policies added, data scripted from one backend to another. Claude Code, Codex, or another agent with repo access can do the bulk of this. Open the cloned repo in the agent, point it at this section, and let it inventory the base44.entities.* / base44.auth.* / base44.functions.* call sites, propose the Supabase schema, and rewrite the SDK calls. CLI work (base44 secrets list, supabase link, running migration scripts) happens in your local terminal inside the agent session with your approval.

This is the path most articles describe and the fewest teams actually need. Base44's backend gives you NoSQL entities, auth, access rules, Deno functions, realtime and integrations as a bundled offering. Replacing all of it means rebuilding against Supabase or your own stack — that is not a migration, it is a re-implementation of the backend.

What to replace

Base44 featureSupabase equivalent
Entities (NoSQL collections)Postgres tables
AuthSupabase Auth
Access rulesRow Level Security (RLS) policies
Deno functionsSupabase Edge Functions
RealtimeSupabase Realtime
Integrations / connectorsDirect API calls in your backend

Migration approach

There are two realistic paths:

  1. Rewrite SDK calls manually. Replace every base44.entities.X.list() with the corresponding Supabase query (supabase.from("x").select("*")). Slow, but every developer on the team ends up understanding the data model.
// Before
const tasks = await base44.entities.Task.list();

// After
const { data: tasks, error } = await supabase
  .from("tasks")
  .select("*");
if (error) throw error;
  1. Use a community drop-in SDK. A project called base44-to-supabase-sdk exists on GitHub as a community-maintained drop-in replacement that mimics the Base44 SDK shape against Supabase. Useful as a starting point. Not a substitute for owning the data model — the schema, RLS policies and edge functions still need design decisions.

Schema design

Base44 entities are loose by design — the data model can change without explicit migrations. Postgres is strict, which means the migration is also a forcing function for data modeling. For each Base44 entity:

create table public.tasks (
  id uuid primary key default gen_random_uuid(),
  title text not null,
  status text,
  due_date date,
  user_id uuid references auth.users(id),
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

Then RLS policies for access control:

alter table public.tasks enable row level security;

create policy "Users can read their own tasks"
  on public.tasks for select
  using (auth.uid() = user_id);

create policy "Users can write their own tasks"
  on public.tasks for insert
  with check (auth.uid() = user_id);

The Base44 access rules need to translate to these policies one by one. Get this wrong and the new backend either leaks data or denies legitimate requests.

Auth migration

Base44 auth users do not export with a pg_dump. The two realistic options:

  • Force re-authentication. Users sign up again on Supabase Auth. Acceptable if the user base is small or the friction is acceptable.
  • Programmatic migration. Use the Base44 SDK to fetch all users, then call Supabase Auth's admin API to create matching accounts. Requires generating new passwords or sending password-reset emails.

Data migration

For each entity:

// Fetch from Base44
const users = await base44.entities.User.list();

// Insert into Supabase
const { error } = await supabase.from("users").insert(
  users.map(u => ({
    email: u.email,
    name: u.name,
    created_at: u.createdAt,
  }))
);

Run this as a one-off script. Add idempotency (skip if the row already exists by email or by ID) so the script can be re-run safely if something fails halfway.

Common gotchas

404 on every API call after deploy. VITE_BASE44_APP_BASE_URL is missing or wrong on the host. The browser is hitting undefined/... and getting nothing.

CORS blocks the production app. Base44 needs the new domain whitelisted in the app's allowed origins. Local development worked because localhost was already in the list.

Direct navigation 404s on /dashboard. SPA fallback is not configured. Add vercel.json rewrites or the platform equivalent.

Eject worked, but the new app has no data. Expected — eject copies schemas, not data. Write a migration script using the Base44 SDK against both apps.

Functions migrated to Supabase Edge Functions return 500. Base44 Deno functions and Supabase Edge Functions are both Deno-based but expose different APIs (auth context, environment, request object). Each function needs the same logic but a different entry point.

Realistic recommendation

For most teams: run path 1, leave the backend on Base44, ship the frontend on Vercel. Revisit path 3 only when one of the original five graduation signals applies — the cost cliff hits, the customization wall blocks a real feature, lock-in becomes a board-level concern. Until then, Base44's bundled backend is a feature, not a problem.

Where to go next

The next guide in this pillar, "Your first real deploy: Supabase + Vercel from scratch," is the cleanest reference for path 3 — it walks through provisioning a fresh Supabase project, setting up a schema, and deploying a Vite frontend that talks to it. If the project might end up on Postgres anyway, that guide is the destination shape.

Get the next guide when it lands

One email on Sunday with new /learn guides, tool updates, and a couple of links worth reading.