BASE44DEVS

FIX · PLATFORM · HIGH

Break Base44 Vendor Lock-In Caused by SDK Dependency

Base44 vendor lock-in is real because exported backend code is tightly coupled to the @base44/sdk for database, auth, and storage calls. The SDK is not portable. Decouple before migrating by introducing a thin data-access layer that wraps every SDK call, replacing the SDK import with a generic interface, and porting the implementation to Supabase, Firebase, or a custom backend once the layer is stable.

Last verified
2026-10-20
Category
PLATFORM
Difficulty
HARD
DIY possible
YES

What's happening

Base44 vendor lock-in is real because every exported backend file imports @base44/sdk and calls it for database reads, auth checks, file storage, and async messaging — and the SDK is a thin client that calls hosted Base44 endpoints, not a portable library. Without Base44's backend the SDK has nothing to call. Three structural decisions deepen the lock-in: there is no database connection string that bypasses Base44's SDK and API, auth is platform-specific because every code path calls base44.auth.me(), and backend functions assume the Base44 Deno runtime for any data work. GitHub sync runs both ways on the Builder plan or higher, but with it your entities and their access rules stay managed in Base44 rather than in the repository, and no data comes with the code. Decouple while still on Base44 by introducing a thin DataLayer interface in src/lib/data.ts that mentions no platform, implementing a base44 adapter that satisfies it via the SDK, refactoring every business-logic file to import from the wrapper instead of @base44/sdk, capturing schema and RLS as code in migrations/, and only then building a Supabase or custom implementation of the same interface and swapping it in on cutover day.

You decided you want to leave Base44, or at least keep the option open. You exported your project to GitHub. You cloned it locally. You ran npm install. Then you looked at what the code calls: every file that touches data imports @base44/sdk, and every one of those calls goes to Base44's hosted backend. The code runs — locally through Base44's CLI, or on another host — only as long as that backend is Base44's.

A user on shipper.now wrote: "The backend code is strongly dependent on Base44-sdk database calls; exported code won't function elsewhere without significant redevelopment." Nocode.mba's review of Base44 noted the same: "It's unclear how you'd migrate the database if you later move off the platform."

The marketing claim is "you own your code." The technical reality is that you own a codebase that runs only against Base44's backend. The SDK is a platform-specific client, not a portable library, and the export does not include the server-side machinery the SDK depends on. You did not get a portable application. You got source code that talks to a backend only Base44 hosts.

Why this happens

Base44 generates code that is convenient to write inside the platform and inconvenient to run anywhere else. The convenience comes from the SDK collapsing database, auth, file storage, and async messaging into a single import surface. You write base44.entities.Order.create({...}) and it works.

The inconvenience comes from what that line of code actually does. It calls a hosted Base44 endpoint that knows your project's schema, RLS policies, auth context, and storage layout. The SDK is a thin client; the heavy lifting happens on the platform. Without Base44's backend, the SDK has nothing to call.

Three structural decisions deepen the lock-in.

Base44's SDK and API are the only data-access paths. Base44 describes its developer surface as "the real API, SDK and CLI" (base44.com/developers), and all three reach a backend Base44 hosts. It does not hand you a database connection string, so you cannot point your app at the database directly and bypass the platform. Even if you could, there is no migration history to replay: in an app built in the editor, entities and their access rules are managed in Base44 and are not included in a GitHub-synced repository, and in a CLI project they are JSON files in base44/entities/, versioned with your code but with no migration log.

Auth is platform-specific. User sessions, permission checks, and SSO are all SDK-mediated. Your code calls base44.auth.me() everywhere. There is no portable equivalent that maps cleanly to Supabase Auth, Auth0, or Clerk without an adapter layer.

Backend functions assume Base44 runtime. Functions are Deno code that imports the SDK for any data work. Outside Base44, the Deno runtime alone is portable but the SDK calls inside those functions are not.

Base44's GitHub sync has run both ways since December 2025, and the exported frontend can run on another host, but only against Base44's backend: nothing in the repo runs in an environment without Base44. Earlier independent reviews called the export a beta ("GitHub export remains in beta...feature shipping too fast, suggesting stability concerns", Nocode.mba); Base44's current docs describe two-way sync on the Builder plan or higher. Leaving the backend no longer means leaving Base44's AI editor, though: Base Code works on "an existing GitHub codebase" through branches and pull requests, with GitHub as the source of truth, and leaves production hosting to you.

Sources: shipper.now/export-code-base44/, nocode.mba/articles/base44-review, feedback.base44.com posts on export and migration limitations.

How to reproduce

  1. Open your Base44 project in the editor.
  2. Connect it to GitHub (requires the Builder plan or higher).
  3. Clone the resulting repo locally.
  4. Run grep -r "@base44" src/ --include="*.ts" --include="*.tsx" --include="*.js" | wc -l to count SDK call sites.
  5. Run npm install.
  6. Start it the way Base44's docs describe: install the Base44 CLI, run base44 login and base44 link, then base44 dev, which starts a local Base44 backend and your frontend together.
  7. Note what that needed: Base44's CLI, your Base44 account and a link to your Base44 app. Every SDK call in the running app is served by a Base44 backend, local or hosted. Take Base44 away and there is nothing for those calls to reach.
  8. Pick any backend function file. Read it. Note that nearly every line either calls the SDK directly or references types the SDK provides. There is no portable layer between your business logic and the platform.

Step-by-step fix

The fix is to introduce a data-access layer in your code that wraps every SDK call. Once every business-logic file imports your wrapper instead of the SDK, you can swap the wrapper's implementation when you migrate. Do this work while still on Base44.

1. Audit your SDK usage

Run a grep and categorize call sites by capability.

grep -rn "@base44" src/ \
  --include="*.ts" --include="*.tsx" \
  | tee sdk-usage.txt

Categorize each line as one of: data (collection reads/writes), auth (user/session/permission), storage (files), realtime (subscriptions), function (function invocations). The tally shows where the refactor effort sits and which part of the wrapper interface to design first.

2. Define a portable data interface

Create src/lib/data.ts with a generic interface that does not mention Base44.

// src/lib/data.ts
export interface DataLayer {
  list<T>(collection: string, filter?: Record<string, unknown>): Promise<T[]>;
  get<T>(collection: string, id: string): Promise<T | null>;
  create<T>(collection: string, payload: Partial<T>): Promise<T>;
  update<T>(collection: string, id: string, patch: Partial<T>): Promise<T>;
  remove(collection: string, id: string): Promise<void>;
}

export interface AuthLayer {
  currentUserId(): Promise<string | null>;
  currentUserRoles(): Promise<string[]>;
}

This is the contract your business logic will depend on. It mentions no platform.

3. Implement the Base44 adapter

Create src/lib/data.base44.ts that satisfies the interface using the SDK.

// src/lib/data.base44.ts
import { base44 } from "@/api/base44Client";
import type { DataLayer, AuthLayer } from "./data";

// `collection` is the Base44 entity name, e.g. "Order".
export const base44Data: DataLayer = {
  list: async (collection, filter) => {
    return base44.entities[collection].filter(filter ?? {});
  },
  get: async (collection, id) => {
    return base44.entities[collection].get(id);
  },
  create: async (collection, payload) => {
    return base44.entities[collection].create(payload);
  },
  update: async (collection, id, patch) => {
    return base44.entities[collection].update(id, patch);
  },
  remove: async (collection, id) => {
    await base44.entities[collection].delete(id);
  },
};

export const base44Auth: AuthLayer = {
  currentUserId: async () =>
    (await base44.auth.isAuthenticated()) ? (await base44.auth.me()).id : null,
  currentUserRoles: async () =>
    (await base44.auth.isAuthenticated()) ? [(await base44.auth.me()).role] : [],
};

4. Wire the adapter through a single entry point

// src/lib/data.index.ts
import { base44Data, base44Auth } from "./data.base44";

export const data = base44Data;
export const auth = base44Auth;

Now every business-logic file imports from data.index.ts instead of @/api/base44Client. When you migrate, you swap the contents of data.index.ts to point at the Supabase or custom adapter instead.

5. Refactor incrementally, file by file

Pick one component or function at a time. Replace import { base44 } from "@/api/base44Client", the client import Base44's client guide shows for generated apps (read 17 Oct 2026), with import { data, auth } from "@/lib/data.index". Replace SDK calls with their wrapper equivalents. Test in Base44 (the wrapper still calls the SDK underneath, so behavior is unchanged). Commit. Move to the next file.

This works on Base44 today. The wrapper adds zero functionality and zero overhead. Its only purpose is to give you a single seam to cut on migration day.

6. Capture schema and RLS as code

Base44 manages schema and RLS in the platform (with GitHub sync they are not in your repo; eject downloads the schemas as JSON). Write them up as code for your target.

  • For every collection: write a SQL CREATE TABLE statement that matches the Base44 schema. Save it in migrations/.
  • For every RLS policy: write the equivalent CREATE POLICY statement targeting your migration platform's RLS syntax. Save it next to the migration.
  • For every storage bucket: document its name, ACL, and contents.

Without this, you can export code but cannot rebuild the database it depends on.

7. Plan the cutover

When the wrapper is fully adopted, build a Supabase (or alternative) implementation of the same interface. Run both adapters side by side in a staging environment. Run your test suite against both. When parity is acceptable, swap data.index.ts to point at the new adapter and deploy.

This is when you stop paying Base44.

DIY vs hire decision

DIY this if: Your project has fewer than 30 components, you have an engineer on the team comfortable with refactoring, and you can spread the wrapper migration over 4-8 weeks of part-time work.

Hire help if: Your project is mid-sized or larger, you need to maintain feature velocity during the decoupling, or you are decoupling under deadline pressure (e.g., a Base44 outage or pricing change forced your hand). Decoupling and migration are usually best done as one project. Our migration service includes the wrapper refactor, the schema/RLS code-up, the target-platform implementation, and a verified cutover with no data loss. If you are still scoping the work, the base44 fix catalog lists the symptoms — credit waste, regression loops, support gaps — that can tip the migration math in favor of leaving.

Need to escape the lock-in?

The fastest, lowest-risk path off Base44 is a managed migration: we ship the data-access wrapper, port the schema and RLS, build the target platform, run parity tests, and execute the cutover with verified zero data loss. Small migrations from $6,000.

Start a migration engagement

QUERIES

Frequently asked questions

Q.01Can I just export to GitHub and deploy elsewhere?
A.01

Not without a rewrite, if elsewhere means off Base44. The exported code imports @base44/sdk and calls it for every database read, write, auth check, and storage operation, and the SDK talks to Base44's hosted backend. You can host the frontend somewhere else and keep using that backend — Base44's docs say you can 'use the Base44 backend services with any client type and deploy your frontend to an external hosting provider' — but your data, auth and functions stay on Base44. Export is a starting point for migration, not a complete migration. Plan for at minimum 40-60 hours of refactoring on a small project, more on a complex one.

Q.02How tightly is my code coupled to the SDK?
A.02

Run grep for @base44 in your exported codebase. Count the matches. That is the rough number of call sites you need to refactor. Expect the count to grow with every entity, page and backend function. Each call site binds your code to Base44's data shape, auth model, and SDK error semantics. Frontend code is easier to decouple than backend; backend functions are deeply tied to the SDK for almost every operation.

Q.03Should I decouple while still on the platform or only after exporting?
A.03

Before. Decouple while you are still on Base44, with the SDK still working. This gives you a working baseline at every step. If you wait until after export, you are debugging migration and decoupling simultaneously, which is the worst time to figure out which problem you are looking at. Build the abstraction layer in place, verify it on Base44, then swap implementations when you migrate.

Q.04Is GitHub export enough by itself?
A.04

No. Base44's GitHub integration is two-way sync on the Builder plan or higher, and the repo carries your frontend and backend functions, but Base44's docs say that with GitHub sync your entities are managed in Base44 and are not included in the repository, and no data comes with it. The CLI's eject command goes further: it downloads the frontend code and backend resources, entity schemas included, into a new project with its own app ID and an empty database. Either way, you still need to capture your schema and access rules as code, export the data separately, and rebuild the backend the SDK talks to.

Q.05What does decoupling cost in time and money if I hire it out?
A.05

For a typical mid-sized project (50-100 components, 10-20 backend functions), expect 60-120 engineering hours to fully decouple. At blended rates this is roughly $6,000-$15,000. We package this as a migration engagement — small migration $6,000, medium $12,000 — because in practice the decoupling and the actual move are best done as one project rather than sequentially.

NEXT STEP

Need this fix shipped this week?

Book a free 15-minute call or order a $497 audit. We will respond within one business day.