BASE44DEVS

ARTICLE · 10 MIN READ

Base44 SDK Reference: Entities, Auth, Functions, and Integrations

The Base44 SDK is the only supported way to talk to your data, auth, files, and backend functions from the frontend. This reference covers the public surface — Entity CRUD, the User module, file uploads, integration helpers, and backend function invocation — plus the undocumented behavior that catches teams off guard. It is opinionated about what to use, what to avoid, and where the SDK's design forces unsafe defaults you must override server-side.

Last verified
2026-05-01
Published
2026-05-01
Read time
10 min
Words
1,877
  • SDK
  • REFERENCE
  • ENTITIES
  • AUTH
  • FUNCTIONS

Why this reference exists

Base44's documentation covers the SDK's happy paths but leaves out the failure modes, the undocumented behavior, and the design choices you must work around in production. This reference fills the gap. It is written for engineers who already know JavaScript and need to ship a real app, not for users learning the platform through the AI agent.

The opinions in this document come from auditing dozens of Base44 apps. Where the SDK is well-designed, we say so. Where its defaults are dangerous, we name the danger and the workaround. We will keep this updated; check the Last updated stamp at the top of the page.

SDK overview

The SDK is auto-injected in the Base44 IDE. In code, it is imported as:

import { base44 } from "@base44/sdk";

The base44 object is the root of the public API. Its main namespaces:

  • base44.entities.<EntityName> — CRUD for any entity defined in your schema.
  • base44.auth and base44.User — authentication and current-user helpers.
  • base44.functions.<functionName> — invocation of backend Deno functions.
  • base44.integrations.<integrationName> — managed third-party calls (LLM, image gen, email).
  • base44.files — upload and download.

We will walk each.

Entities

entities.<EntityName>.list(filter?, sort?, limit?)

Returns an array of records.

Critical default: with no filter argument, this returns every record in the entity, scoped only by the entity name. There is no implicit currentUser scoping.

// Returns ALL todos in the entire app, across every user.
const allTodos = await base44.entities.Todo.list();

// Returns only the current user's todos.
const myTodos = await base44.entities.Todo.list({
  created_by: (await base44.User.me()).email,
});

This is the single most consequential design decision in the SDK and the source of most data-leak incidents we audit. Treat list() as fundamentally unsafe without a filter. Add ESLint rules in your repo to flag any call site that is missing one.

Filter syntax supports equality, $in, $ne, $gt, $lt, $gte, $lte, and limited string operators. Logical AND is implicit between fields; logical OR requires $or at the top level.

const recentHighValue = await base44.entities.Order.list({
  $and: [
    { user_id: currentUserId },
    { total: { $gte: 100 } },
    { created_date: { $gte: "2026-04-01" } },
  ],
});

Sort: pass a string with a leading - for descending: "-created_date". Multi-field sort is not officially supported; some apps have made it work with comma-separated fields, but the behavior is undocumented.

Limit: caps at 5,000 per call as of November 2025. There is no offset parameter; for paginated lists, use a sortable field as a cursor.

entities.<EntityName>.filter(query) (deprecated alias)

Older code uses .filter(); new code should use .list(). They are aliases as of the current SDK, but the platform has signaled filter will be removed in a future release.

entities.<EntityName>.get(id)

Returns a single record by ID. Returns null if not found. Does not enforce ownership. A user who knows another user's record ID can fetch it. Wrap in a backend function if the data is sensitive.

entities.<EntityName>.create(data)

Creates a record. The created_by field is auto-populated with the current user's email. The created_date is set server-side.

const todo = await base44.entities.Todo.create({
  title: "Ship the audit",
  done: false,
});
// todo.id, todo.created_by, todo.created_date populated by server.

Trap: if your entity schema has a field the user should not control (e.g., role, subscription_tier, verified), the client can pass it and the server will accept it. The SDK does not strip unknown or dangerous fields. Strip them server-side in a backend function before allowing creates of sensitive entities.

entities.<EntityName>.update(id, data)

Updates a record by ID. Does not check ownership server-side by default. Any user with the SDK can update any record by ID. This is the second-most-dangerous default after list().

The pattern that works:

// Frontend - never use directly for sensitive entities.
// Instead, call a backend function:
await base44.functions.updateTodoSafely({ id: todoId, patch });

Inside updateTodoSafely, fetch the record, verify record.created_by === currentUser.email, then call update. Without this wrap, the entity is globally writable.

entities.<EntityName>.delete(id)

Deletes a record by ID. Same ownership default as update: not enforced. Same wrap pattern required.

entities.<EntityName>.bulkCreate(records)

Creates multiple records. Batch limit is 1,000 per call.

Bulk delete is missing

There is no bulkDelete method. This has been documented as a production blocker for any app that needs to clean up large volumes. Workaround: a backend function that loops delete() calls, paginated by ID. Each delete is a separate API call, so this is slow and credit-expensive at scale. We track this in our database best practices article.

User and auth

User.me()

Returns the current user object: { id, email, full_name, role, ... }. Returns null if not authenticated.

const me = await base44.User.me();
if (!me) {
  // Redirect to login.
  base44.auth.signInWithGoogle();
}

Trap: the role field is whatever you stored on the user entity. The SDK does not have a built-in role concept beyond Base44's coarse "admin/user" platform roles. Build your own role model on the user entity if you need granular permissions.

auth.signInWithGoogle() / signInWithEmail({ email, password })

Initiate sign-in flows. Both redirect to a hosted page; you cannot fully customize the UI without forking.

auth.signOut()

Signs the current user out. Clears the JWT from local storage.

User.update(patch)

Updates the current user's record. Note: this can update fields like role if the schema allows it. Strip dangerous fields in a backend function on critical paths.

Backend functions

functions.<functionName>(payload)

Invokes a backend Deno function. Returns the function's response.

const result = await base44.functions.processPayment({
  amount: 4900,
  currency: "USD",
  customerId: "cus_abc123",
});

Behavior notes:

  • Functions execute on Base44's Deno runtime.
  • Cold-start latency is 200–800ms typical.
  • The function inherits the calling user's identity via the JWT.
  • Functions cannot share state without going through entities or external storage.
  • The ISOLATE_INTERNAL_FAILURE error indicates Deno couldn't load the function; usually a syntax error or unsupported import.
  • Function URLs are routed via /functions/<name>. There is a documented routing bug where POST requests can return 405 Method Not Allowed; see the function routing fix.

Defining a function

In the Base44 IDE, create a file under backend/functions/<name>.ts:

export default async function handler(req: Request): Promise<Response> {
  if (req.method !== "POST") {
    return new Response("Method Not Allowed", { status: 405 });
  }

  const body = await req.json();
  // ... validate, do work, return ...

  return new Response(JSON.stringify({ ok: true }), {
    status: 200,
    headers: { "content-type": "application/json" },
  });
}

Imports: the Deno runtime supports a curated subset of npm and JSR. Some packages will fail with Unsupported dependency. Test imports in the IDE before assuming they work.

Environment variables: set in the IDE under the function settings. Read with Deno.env.get("KEY"). Do not put secrets in code; the AI agent has read access to your code.

Integrations

integrations.invokeLLM({ prompt, model? })

Calls a managed LLM. Costs platform credits. Latency 1–8 seconds depending on model and length.

const response = await base44.integrations.invokeLLM({
  prompt: "Summarize this in three bullets: " + text,
  model: "gpt-4o-mini",
});

Cost trap: every call burns credits regardless of cache hits. Add your own cache layer (entity-backed or external) for any prompt that repeats.

integrations.generateImage({ prompt, size? })

Generates an image via the platform's image model. High credit cost; cap usage per user.

integrations.sendEmail({ to, subject, body })

Sends transactional email through the platform's managed sender. Trap: deliverability depends on Base44's reputation, which is shared across all platform tenants. A spammy neighbor can land your transactional emails in spam. For anything mission-critical (password reset, payment receipt), use a dedicated provider (Resend, Postmark) via your own API key in a backend function.

Custom integrations are deprecated

Base44 has signaled that adding new custom integrations after March 1, 2026 is no longer supported. Existing custom integrations continue to work, but new ones must go through backend functions. Plan accordingly.

Files

files.upload(file, { entityId?, fieldName? })

Uploads a file to Base44's managed storage. Returns a file record with url, mime_type, size.

Limits:

  • 50MB per file.
  • MIME type is checked but not magic-byte verified; do not rely on it for security.
  • Total storage limits are per-tier and not always published; you can hit an invisible cap.

For files that need access control, store them in your own bucket (S3 with signed URLs) via a backend function and store only the URL in the entity.

files.delete(fileId)

Deletes a file. Does not cascade-delete from entities — if an entity references the file by URL, the URL becomes a 404. Clean up references explicitly.

Error handling

The SDK throws on network failure and on 4xx/5xx responses. Errors include status, message, and sometimes details. Common error codes:

ErrorMeaningFix
401Token expired or invalidRe-auth
403Permission deniedCheck entity permissions and RLS
404Record or function not foundVerify ID and function name
405Method not allowedFunction routing bug; see fix article
409Conflict (concurrent update)Retry with backoff
429Rate limitedExponential backoff
500Server errorRetry; if persistent, check status page

Wrap every SDK call in a try/catch and surface user-friendly messages. The default error messages leak implementation details.

Common SDK mistakes

Calling Entity.list() without a filter. The default returns global data. Every production audit we run flags at least one of these.

Trusting the client to set ownership fields. Always re-validate created_by server-side in a backend function for sensitive entities.

Forgetting that backend functions inherit user identity. There is no service-account mode. If you need cross-user reads, you build the authorization logic yourself.

Using integrations.sendEmail for password resets. Shared deliverability reputation will burn you. Use a dedicated provider.

Ignoring 429 rate limits. The SDK does not retry by default. Add backoff or accept user-visible failures during traffic spikes.

Storing sensitive files in Base44 storage. No fine-grained access control. Use your own bucket for anything sensitive.

Assuming the SDK is portable. It binds to base44.com. Exported code still calls into the platform. Migration requires replacing every SDK call with your stack's equivalent.

Reference summary

  • Use Entity.list({ created_by: currentUser.email }) always; list() with no filter is a bug.
  • Wrap update/delete in backend functions for sensitive entities.
  • Build your own role model on the user entity.
  • Cache LLM calls; they are slow and expensive.
  • Use external email for anything mission-critical.
  • Wrap the SDK in a thin project module for one-place swap during migration.
  • Add ESLint rules to enforce the patterns above.

Want us to audit your SDK usage?

Our $497 production audit grep-walks every SDK call site in your repo, flags missing ownership filters, identifies privilege-escalation paths, and delivers a prioritized fix list. Most apps have 5–20 unsafe call sites. Order an audit or book a free 15-minute call.

QUERIES

Frequently asked questions

Q.01What does the Base44 SDK actually do, and why can't I just call REST endpoints?
A.01

The SDK is a JavaScript client that wraps Base44's internal API. It handles authentication, request signing, error normalization, and entity schema validation. Base44 does not publish the underlying REST API as a stable contract, so calling it directly is unsupported and the platform may change shapes without notice. Use the SDK for anything that needs to keep working through platform updates.

Q.02Where is the SDK installed by default in a Base44 app?
A.02

The SDK ships pre-installed in every Base44 project at @base44/sdk and is auto-imported in the platform's code editor. You do not need to npm install it inside the platform. If you export your code and run it externally, the SDK still requires base44.com as its API origin, which is the core lock-in concern documented in our [vendor lock-in deep dive](/blog/base44-vendor-lock-in-deep-dive).

Q.03Can I use the SDK to call entities from a backend function?
A.03

Yes, but with important caveats. The SDK in a backend function operates as the calling user, not as a service account, which means it inherits the user's permissions. There is no documented service-role mode. For privileged operations like cross-user data access, you must build your own authorization logic and run it in the backend function before issuing the SDK call.

Q.04Why does Entity.list() return data for users who shouldn't see it?
A.04

Because list() defaults to returning every record in the entity. Base44's entity layer does not enforce per-user scoping unless you have explicitly configured Row-Level Security rules or added a filter parameter. The default is global readability for any authenticated user. Every production app must add an ownership filter, typically on a created_by field, on every list call. We cover this in detail in our [security hardening checklist](/blog/base44-security-hardening-checklist).

Q.05What is the rate limit on SDK calls?
A.05

Base44 enforces a soft rate limit that triggers 429 responses on burst traffic but does not publish exact thresholds. Observed behavior: roughly 60 requests per minute per user on entity endpoints, lower on AI integration endpoints. The platform does not surface limits in headers. Production apps should implement client-side throttling and exponential backoff on 429s, plus an external monitor that catches sustained throttling before users notice.

Q.06Can I extend the SDK with custom methods?
A.06

Not directly. The SDK is closed-source and does not expose a plugin API. The pattern that works is to write a thin wrapper module in your project that adds your conventions — ownership filters, error handling, telemetry — and use the wrapper everywhere instead of the raw SDK. This also gives you a single seam to swap during migration.

NEXT STEP

Need engineers who actually know base44?

Book a free 15-minute call or order a $497 audit.