> ## Documentation Index
> Fetch the complete documentation index at: https://docs.base44.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Internal links on these pages omit the .md extension. Append .md to a docs page URL, or send an Accept: text/markdown header, to get that page as markdown.

# Work with data

> Create, read, update, and delete data in your Base44 app

The [`entities`](/developers/references/sdk/docs/type-aliases/entities) module lets you work with your app's data. Each entity type in your app has methods for creating, reading, updating, and deleting records.

You access entity methods through your entity name: `base44.entities.YourEntityName.methodName()`. For example, if you have a `Task` entity, you use [`base44.entities.Task.list()`](/developers/references/sdk/docs/type-aliases/entities#list) to get a list of tasks.

<Tip>
  Generate TypeScript types from your entities to get autocomplete and type
  safety. Run [`base44 types
      generate`](/developers/references/cli/commands/types-generate) in your
  project, and all entity fields, methods, and return types will be fully typed.
  Learn more about [dynamic
  types](/developers/references/sdk/getting-started/dynamic-types).
</Tip>

## Permissions

Data access is controlled by the client's authentication mode and your app's permission rules. You can configure the permissions for each entity in your app's security settings. Learn more about [changing data permissions](https://docs.base44.com/Building-your-app/Managing-your-app-data#changing-data-permissions).

* **Anonymous users**: Can only access entities marked as public.
* **Authenticated users**: Can access entities and records they have permission to view or modify based on your app's configured access rules.
* **Service role**: Bypasses entity access rules and field-level security entirely. Has full read and write access to every entity and record in the app.

In backend code, you can use the [service role to access data without access-rule restrictions](#service-role-data-access).

## Create records

Use [`create()`](/developers/references/sdk/docs/type-aliases/entities#create) to add new records to an entity:

<CodeGroup>
  ```typescript Create a record theme={null}
  const newTask = await base44.entities.Task.create({
    title: "Complete documentation",
    status: "pending",
    dueDate: "2024-12-31",
  });
  ```

  ```typescript With generated types theme={null}
  // After running: base44 types generate
  const newTask = await base44.entities.Task.create({
    title: "Complete documentation",
    status: "pending",
    // TypeScript knows which fields exist
    // and validates their types
  });

  // Autocomplete works for fields
  console.log(newTask.id);
  console.log(newTask.created_date);
  ```
</CodeGroup>

## Read records

Retrieve data using [`get()`](/developers/references/sdk/docs/type-aliases/entities#get) for a single record, [`list()`](/developers/references/sdk/docs/type-aliases/entities#list) for all records, or [`filter()`](/developers/references/sdk/docs/type-aliases/entities#filter) for records matching specific criteria.

Use [`get()`](/developers/references/sdk/docs/type-aliases/entities#get) with a record ID to retrieve a specific record:

<CodeGroup>
  ```typescript Get by ID theme={null}
  const task = await base44.entities.Task.get(taskId);
  ```
</CodeGroup>

Use [`list()`](/developers/references/sdk/docs/type-aliases/entities#list) to retrieve all records. The method supports sorting, pagination, and field selection:

<CodeGroup>
  ```typescript List all theme={null}
  const tasks = await base44.entities.Task.list();
  ```

  ```typescript Sort results theme={null}
  const tasks = await base44.entities.Task.list("-created_date");
  ```

  ```typescript Paginate results theme={null}
  const tasks = await base44.entities.Task.list(
    "-created_date",
    10, // limit
    20, // skip
  );
  ```

  ```typescript Select specific fields theme={null}
  const tasks = await base44.entities.Task.list(
    "-created_date",
    10,
    0,
    ["title", "status"], // only return these fields
  );
  ```
</CodeGroup>

<Tip>
  For loops that read through many pages, pass an options object with a `cursor` instead of `skip`. See [paging with a cursor](#paging-with-a-cursor) below. Records added or deleted between pages never shift the boundary.
</Tip>

Use [`filter()`](/developers/references/sdk/docs/type-aliases/entities#filter) to find records that match specific criteria:

<CodeGroup>
  ```typescript Filter by single field theme={null}
  const pendingTasks = await base44.entities.Task.filter({
    status: "pending",
  });
  ```

  ```typescript Filter by multiple fields theme={null}
  const urgentTasks = await base44.entities.Task.filter({
    status: "pending",
    priority: "high",
  });
  ```

  ```typescript Filter with sorting theme={null}
  const recentTasks = await base44.entities.Task.filter(
    { status: "pending" },
    "-created_date", // Sort by created date, descending
  );
  ```

  ```typescript Filter with pagination theme={null}
  const tasks = await base44.entities.Task.filter(
    { status: "pending" },
    "-created_date",
    10, // limit
    20, // skip
  );
  ```
</CodeGroup>

### Paging with a cursor

Pass an options object instead of positional arguments to [`list()`](/developers/references/sdk/docs/type-aliases/entities#list) or [`filter()`](/developers/references/sdk/docs/type-aliases/entities#filter) to read one page by cursor. Each call returns a page with `items`, `next_cursor`, and `has_more`:

<CodeGroup>
  ```typescript Walk every record theme={null}
  const allTasks = [];
  let page = await base44.entities.Task.list({ sort: "-created_date", limit: 1000 });
  allTasks.push(...page.items);

  while (page.has_more) {
    page = await base44.entities.Task.list({ cursor: page.next_cursor, limit: 1000 });
    allTasks.push(...page.items);
  }
  ```

  ```typescript Cursor with filter() theme={null}
  const allOrders = [];
  let page = await base44.entities.Order.filter(
    { status: "open" },
    { sort: "-created_date", limit: 1000 },
  );
  allOrders.push(...page.items);

  while (page.has_more) {
    page = await base44.entities.Order.filter(
      { status: "open" },
      { cursor: page.next_cursor, limit: 1000 },
    );
    allOrders.push(...page.items);
  }
  ```

  ```typescript Distinct values of a field theme={null}
  const { items: categories } = await base44.entities.Product.list({
    distinct: "category",
  });
  ```
</CodeGroup>

<Note>
  A cursor carries this walk's parameters, so a later page needs only `cursor` and `limit`. Passing different parameters with a cursor fails. Existing code using `skip` still works, but prefer a cursor for pagination. Records added or deleted between pages never shift the boundary.
</Note>

### Count records

Use [`count()`](/developers/references/sdk/docs/type-aliases/entities#count) to get the number of matching records without fetching them, for totals, badges, and "page N of M":

<CodeGroup>
  ```typescript Count matching records theme={null}
  const open = await base44.entities.Task.count({ status: "open" });
  ```

  ```typescript Count all records theme={null}
  const total = await base44.entities.Task.count();
  ```
</CodeGroup>

### Aggregate records

Use [`aggregate()`](/developers/references/sdk/docs/type-aliases/entities#aggregate) to group records on the server and compute totals, sums, averages, or distinct counts per group, up to 1,000 rows. This is faster than loading every record and adding up the results yourself:

<CodeGroup>
  ```typescript Sum grouped by field theme={null}
  const { rows } = await base44.entities.Sale.aggregate({
    query: { sale_date: { $gte: "2026-09-01" } },
    groupBy: "agent_id",
    sum: "amount",
    sort: "-sum_amount",
  });
  // rows: [
  //   { agent_id: "a1", count: 42, sum_amount: 18250 },
  //   { agent_id: "a2", count: 37, sum_amount: 15400 },
  //   ...
  // ]
  ```

  ```typescript Group by date bucket theme={null}
  const { rows } = await base44.entities.Task.aggregate({
    dateBucket: { field: "created_date", unit: "day" },
  });
  // rows: [
  //   { created_date: "2026-09-01", count: 8 },
  //   { created_date: "2026-09-02", count: 11 },
  //   ...
  // ]
  ```

  ```typescript Find duplicates theme={null}
  const { rows } = await base44.entities.Order.aggregate({
    groupBy: "external_id",
    having: { count: { $gt: 1 } },
  });
  // rows: [
  //   { external_id: "ext-42", count: 3 },
  //   { external_id: "ext-77", count: 2 }
  // ]
  ```
</CodeGroup>

<Note>
  You can group by up to four fields. Every date field supports `"day"`, `"month"`, and `"year"` for `dateBucket.unit`. The `created_date` and `updated_date` fields also support `"week"`. A `having` filter applies to the computed rows after grouping, keyed by computed field names like `count` and `sum_<field>`, not by raw or `groupBy` fields. See the [`aggregate()` reference](/developers/references/sdk/docs/type-aliases/entities#aggregate) for `avg`, `min`, `max`, and `countDistinct`.
</Note>

## Update records

Use [`update()`](/developers/references/sdk/docs/type-aliases/entities#update) to modify an existing record:

<CodeGroup>
  ```typescript Update a record theme={null}
  await base44.entities.Task.update(taskId, {
    status: "completed",
  });
  ```
</CodeGroup>

## Delete records

Use [`delete()`](/developers/references/sdk/docs/type-aliases/entities#delete) to remove a single record:

<CodeGroup>
  ```typescript Delete a record theme={null}
  await base44.entities.Task.delete(taskId);
  ```
</CodeGroup>

Use [`deleteMany()`](/developers/references/sdk/docs/type-aliases/entities#deletemany) to remove multiple records matching specific criteria:

<CodeGroup>
  ```typescript Delete multiple records theme={null}
  const result = await base44.entities.Task.deleteMany({
    status: "completed",
    priority: "low",
  });
  ```
</CodeGroup>

## Bulk operations

Use [`bulkCreate()`](/developers/references/sdk/docs/type-aliases/entities#bulkcreate) to create multiple records in a single request:

<CodeGroup>
  ```typescript Create multiple records theme={null}
  const tasks = await base44.entities.Task.bulkCreate([
    { title: "Task 1", status: "pending" },
    { title: "Task 2", status: "pending" },
    { title: "Task 3", status: "in-progress" },
  ]);
  ```
</CodeGroup>

Use [`importEntities()`](/developers/references/sdk/docs/type-aliases/entities#importentities) to import records from a CSV file. This is useful for migrating data or bulk uploads from user interfaces:

<CodeGroup>
  ```typescript Import from file theme={null}
  const handleFileUpload = async (event) => {
    const file = event.target.files?.[0];
    if (file) {
      const result = await base44.entities.Task.importEntities(file);
      console.log(`Imported ${result.output.length} records`);
    }
  };
  ```
</CodeGroup>

<Note>
  Using `importEntities()` requires a browser environment. It cannot be used in
  backend code.
</Note>

### Upsert by a key

Use [`upsert()`](/developers/references/sdk/docs/type-aliases/entities#upsert) to create or update records by a key you define, instead of by `id`. This is useful whenever records have a natural key, such as an ID from another system or a one-per-user record keyed by `user_id`, so you don't have to look up each record first to decide between create and update. Name the field, or fields, that identify a record, and matching records are updated while the rest are created, in one call:

<CodeGroup>
  ```typescript Sync by sku theme={null}
  const result = await base44.entities.Product.upsert(
    supplierCatalog.map((p) => ({ sku: p.sku, name: p.name, price: p.price })),
    { key: "sku" },
  );
  console.log(`${result.created} new, ${result.updated} updated`);
  ```

  ```typescript Compound key theme={null}
  const result = await base44.entities.Inventory.upsert(
    warehouseFeed.map((row) => ({ sku: row.sku, warehouse: row.warehouseCode, quantity: row.qty })),
    { key: ["sku", "warehouse"] },
  );
  console.log(`${result.created} new, ${result.updated} updated`);
  ```
</CodeGroup>

<Note>
  Every record needs a value, not an object or array, for the field, or fields, named in `key`, so the server can find or create by it. Other fields are optional, and only the ones you include are merged into a matched record, like [`update()`](#update-records). You can upsert up to 500 records per request. When two records in one call share a key, the last one wins.
</Note>

## Realtime subscriptions

Use [`subscribe()`](/developers/references/sdk/docs/type-aliases/entities#subscribe) to receive realtime updates when records are created, updated, or deleted:

<CodeGroup>
  ```typescript Subscribe to changes theme={null}
  const unsubscribe = base44.entities.Task.subscribe((event) => {
    console.log(`Task ${event.id} was ${event.type}d`);
    console.log("Updated data:", event.data);
  });

  // Later, clean up the subscription
  unsubscribe();
  ```
</CodeGroup>

<Note>
  Realtime events follow your entity's security rules. When an entity has row-level or field-level `read` rules, each subscriber only receives events for the records their rules allow, and the event data only includes the fields they can read.
</Note>

## Service role data access

By default, data access is scoped to the current user's permissions. With service role authentication, you can read and write any data in your app. Entity access rules and field-level security are bypassed entirely, regardless of how the entity is configured.

Use `base44.asServiceRole.entities` to access data without access-rule restrictions:

<CodeGroup>
  ```typescript Service role access theme={null}
  // User-level access (limited to current user's permissions)
  const myTasks = await base44.entities.Task.list();

  // Service role access (bypasses entity access rules)
  const allTasks = await base44.asServiceRole.entities.Task.list();
  ```
</CodeGroup>

<Warning>
  Service role access skips every access rule on the entity. Use it only for trusted backend logic. If your function needs access checks, you're responsible for enforcing them yourself.
</Warning>

## User entity

Every Base44 app includes a built-in `User` entity that stores user account information. This entity has special security rules:

* Regular users can only read and update their own user record
* With service role authentication, you can read, update, and delete any user
* You cannot create users through the entities module - use the `auth` module instead

You can add fields to the User entity to store additional user data, then use those fields in security rules to control data access or in your app to personalize the experience. See [User Schema](/developers/backend/resources/entities/user-schema) for details.

<CodeGroup>
  ```typescript List all users using the service role theme={null}
  const allUsers = await base44.asServiceRole.entities.User.list();
  ```
</CodeGroup>

## See more

<CardGroup cols={2}>
  <Card title="entities module" icon="database" href="/developers/references/sdk/docs/type-aliases/entities">
    Complete API reference
  </Card>

  <Card title="Base44 client" icon="code" href="/developers/references/sdk/getting-started/client">
    Learn about the client and service role
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.