# Async API + React HTML Components

## What's this?

The iframe API gives users access to pre-existing sandbox API endpoints **without** the need for message handling. We've exposed a set of asynchronous functions that can be awaited, this ensures that HTML components can be more deterministic as a result is guaranteed. Buzzy handles the message handling, making your HTML code snippets simpler!

This feature is current **opt-in** and you must enable it through the HTML field settings under "**Enable Async API**".

This page focuses on Async API behavior that is specific to Code Widgets. For shared row semantics such as metadata fields, linked-table structures, and `embeddingRowID`, use the REST API as the canonical source:

* [Row Metadata and Relationships](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/row-metadata-and-relationships.md)
* [microappdata](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/microappdata.md)
* [microappdata/row](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/microappdata-row.md)

Async API reads also follow Buzzy server-side row access, field access, and [Private Data](/the-building-blocks/datatables-fields-and-data/private-data.md) rules. If a Code Widget fetches rows or child file/image records, the returned values are shaped for the current user before the widget receives them.

## Script Placement

{% hint style="warning" %}
**Warning**

You must place the `<script> inside the <body> of your custom HTML code.`

`This is important as the Buzzy Frame API is loaded in the head and may not be available due to loading order.`
{% endhint %}

## Table of Contents

* [Lifecycle and Context](#lifecycle-and-context)
  * [initialise()](#initialise)
  * [destroy()](#destroy)
  * [getResourceJSON()](#getresourcejson)
  * [getRowJSON()](#getrowjson)
* [Private Data and Secure Workflows](#private-data-and-secure-workflows)
* [Data Management](#data-management)
  * [Insert a Row](#insert-a-row)
  * [Update a Row](#update-a-row)
  * [Remove a Row](#remove-a-row)
  * [Fetch Embedded Row Data](#fetch-embedded-row-data)
  * [Fetch All Embedded Data](#fetch-all-embedded-data)
  * [Fetch Data Table Rows](#fetch-data-table-rows)
  * [Fetch Datatable Metadata](#fetch-datatable-metadata)
  * [filterMicroappView](#filtermicroappview)
  * [getMicroappViewFilter](#getmicroappviewfilter)
* [Shared State](#shared-state)
  * [setSharedState](#setsharedstate)
  * [getSharedState](#getsharedstate)
* [Send Notification](#send-notification)
* [MicroAppChild Management](#microappchild-management)
  * [createMicroappChild](#createmicroappchild)
  * [getChildItemsByField](#getchilditemsbyfield)
  * [readMicroappChild](#readmicroappchild)
  * [updateMicroappChild](#updatemicroappchild)
  * [removeMicroappChild](#removemicroappchild)
  * [copyS3File](#copys3file)
  * [uploadMicroAppChildToS3FromUrl](#uploadmicroappchildtos3fromurl)
* [Functions and Constants](#functions-and-constants)
  * [runAppFunction](#runappfunction)
  * [getConstant](#getconstant)
* [Navigation](#navigation)
  * [navigate](#navigate)
  * [getScreenID](#getscreenid)
  * [getScreenURL](#getscreenurl)
  * [openURL](#openurl)
  * [close](#close)
  * [closeAll](#closeall)
* [Organization and Team Management](#organization-and-team-management)
  * [createOrganization](#createorganization)
  * [readOrganization](#readorganization)
  * [updateOrganization](#updateorganization)
  * [deleteOrganization](#deleteorganization)
  * [createTeam](#createteam)
  * [readTeam](#readteam)
  * [updateTeam](#updateteam)
  * [deleteTeam](#deleteteam)
  * [createTeamMember](#createteammember)
  * [readTeamMember](#readteammember)
  * [updateTeamMember](#updateteammember)
  * [deleteTeamMember](#deleteteammember)
* [Listen for Updates](#listen-for-updates)

## Private Data and Secure Workflows

Code Widgets and embedded React HTML components should treat the Async API as a secure data access surface, not as a bypass around app permissions.

When a widget reads rows, field values, or MicroAppChild image/file records:

* row Viewers and Team Viewers are evaluated by the server
* Field view controls whether a field value is returned
* Field edit controls whether update operations are allowed
* Basic Private Data is masked or redacted when the current user should not receive the raw value
* Sensitive Private Data is hidden unless the current user has access through the supported reveal/read path
* denied Private Data image/file fields do not expose signed URLs, file names, storage keys, or unsafe metadata

Use the same design pattern for Code Widgets as for runtime screens: keep safe workflow metadata in non-private fields for filtering and display, and keep sensitive raw values in Private Data fields.

See [Secure and Compliant Workflow App](/the-ultimate-guide-for-vibe-coding-an-application-with-ai/building-examples/secure-compliance-workflow.md) for a complete workflow pattern, and [Private Data](/the-building-blocks/datatables-fields-and-data/private-data.md) for field configuration.

## Available Asynchronous Functions

### Lifecycle and Context

#### `initialise()`

Initialises the code widget bridge and loads the current frame context. You should call this once before using other `BuzzyFrameAPI` methods.

```javascript
const initData = await buzzyFrameAPI.initialise();
```

Typical resolved value includes:

```javascript
{
  appID,
  resourceJSON,
  rowJSON,
  frameToken
}
```

#### `destroy()`

Removes the message listener created by `BuzzyFrameAPI`. Use this when you need to explicitly clean up a widget instance.

```javascript
buzzyFrameAPI.destroy();
```

**Return**

None. This method performs cleanup and returns `undefined`.

#### `getResourceJSON()`

Returns the most recently loaded resource context after `initialise()`.

```javascript
const resourceJSON = buzzyFrameAPI.getResourceJSON();
```

**Return**

The current resource context object loaded during `initialise()`.

Typical shape:

```javascript
{
  topResource: {
    _id: "app-or-resource-id",
    title: "App Title"
  },
  ...otherResourceContext
}
```

#### `getRowJSON()`

Returns the current row context after `initialise()`.

```javascript
const rowJSON = buzzyFrameAPI.getRowJSON();
```

**Return**

The current row object loaded during `initialise()`. This follows the normal Buzzy row shape used elsewhere in the app and REST API.

Typical shape:

```javascript
{
  _id: "row-id",
  submitted: 1710000000000,
  clientSubmitted: 1710000000000,
  ...fieldValues
}
```

### Data Management

{% hint style="info" %}
**Working with Linked Table Fields**: When inserting or updating rows that contain linked table (cross app) fields, you need to provide both a `crossAppRowID` and a `value` object. See the examples below and the [REST API documentation](/rest-api/buzzy-rest-api/rest-api.md) for detailed patterns.

**Canonical Row Semantics**: Metadata fields such as `_id`, `submitted`, `clientSubmitted`, and `embeddingRowID` are defined in [Row Metadata and Relationships](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/row-metadata-and-relationships.md).

**Common Examples**: For comprehensive examples showing the same operations across BuzzyFrameAPI, REST API, and Node.js client, see [Common API Examples](/rest-api/buzzy-rest-api/rest-api/common-api-examples.md).
{% endhint %}

#### **Insert a Row**

**Usage**

```javascript
insertMicroappRow({ body })
```

**Return**

```javascript
{ rowID }
```

**Linked Table Field Example**

When inserting a row with linked table (cross app) fields using the async API, provide both the `crossAppRowID` and `value` object in the `rowData`:

```javascript
const result = await buzzyFrameAPI.insertMicroappRow({
  body: {
    microAppID: "your-microapp-id",
    rowData: {
      organizationName: "Acme Corp",
      marketplaceListing: {
        crossAppRowID: "marketplace-listing-row-id",
        value: {
          label: "listingName",
          value: "AI Customer Support Solution"
        }
      },
      assignedUser: {
        crossAppRowID: "user-row-id",
        value: {
          label: "name", 
          value: "John Smith"
        }
      }
    }
  }
});

console.log("Created row with ID:", result.rowID);
```

**Key Points:**

* `crossAppRowID`: The `_id` of the row in the linked table
* `value.label`: The field name from the linked table to display
* `value.value`: The actual display value from that field

**See Also:**

* [REST API insertmicroapprow](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/insertmicroapprow.md#linked-table-field-example) - REST API equivalent
* [microappdata/row](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/microappdata-row.md#linkedtable-crossapp-row-field-example) - Understanding linked table field structure

### **Update a Row**

#### **Usage**

```javascript
updateMicroappRow({ body })
```

#### **Return**

<pre class="language-javascript"><code class="lang-javascript">{
<strong>    body: { status: 'success', message: 'MicroApp Record Updated' },
</strong>}
</code></pre>

#### **Linked Table Field Update Example**

When updating linked table (cross app) fields using the async API, provide the complete linked field structure:

```javascript
const result = await buzzyFrameAPI.updateMicroappRow({
  body: {
    rowID: "existing-row-id",
    rowData: {
      assignedUser: {
        crossAppRowID: "new-user-row-id",
        value: {
          label: "name",
          value: "Jane Doe"
        }
      },
      marketplaceListing: {
        crossAppRowID: "listing-row-id", 
        value: {
          label: "listingName",
          value: "Advanced AI Analytics Platform"
        }
      }
    }
  }
});

console.log("Update result:", result.body);
```

**Important Notes:**

* To update a linked table field, provide the new `crossAppRowID` and corresponding `value`
* To clear a linked table field, set the field to `null` in `rowData`
* The `value.label` should match a field name in the linked table
* The `value.value` should be the display value from that field

**See Also:**

* [REST API updatemicroapprow](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/updatemicroapprow.md#linked-table-field-update-example) - REST API equivalent
* [microappdata/row](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/microappdata-row.md#linkedtable-crossapp-row-field-example) - Understanding linked table field structure

### **Remove a Row**

#### Usage

```javascript
removeMicroappRow({ rowID })
```

#### Return

None.

### Fetch Embedded Row Data

Use `embeddingRowID` for parent-child sub-table relationships. The canonical definition lives in [Row Metadata and Relationships](/rest-api/buzzy-rest-api/rest-api/microapp-data-operations/row-metadata-and-relationships.md#parent-child-relationships-with-embeddingrowid).

#### Usage

```javascript
fetchEmbeddedRowData({
    microAppID,
    embeddingRowID,
    subLimit,
    viewFilters,
    viewSort,
  })
```

#### Return

Resource JSON for the current app context with the requested embedded rows resolved inside that resource structure.

This is not just a plain array. It returns the broader resource JSON object used by the runtime, with child row data included for the requested embedded table.

The exact structure depends on your app schema, but embedded row objects follow the standard Buzzy row shape.

### Fetch All Embedded Data

#### Usage

```javascript
fetchAllEmbeddedData(microAppID)
```

#### Return

Nested row and sub-table data resolved recursively to **N levels**.

The exact structure depends on your schema. Expect a nested object or array structure containing the requested table or row plus all resolved child sub-table rows.

### Fetch Data Table Rows

This method shares the same underlying row semantics as the REST `microappdata` endpoint. Use the REST docs for canonical filtering, metadata, and child-row query behavior.

#### Usage

```javascript
fetchDataTableRows({
    microAppID,
    embeddingRowID,
    subLimit,
    viewFilters,
    queryOptions,
    viewSort,
    optSearchFilters,
  })
```

#### Return

Array of row objects.

Typical item shape:

```javascript
[
  {
    _id: "row-id",
    submitted: 1710000000000,
    clientSubmitted: 1710000000000,
    ...fieldValues
  }
]
```

This method also supports built-in current-user filter descriptors such as:

```javascript
{ filterField: 'userID', operator: 'me' }
{ filterField: 'userID', operator: 'nme' }
{ filterField: 'viewers', operator: 'me' }
{ filterField: 'viewers', operator: 'nme' }
```

### Fetch Datatable Metadata

Fetches metadata for the current app context. This method uses the app context established during `initialise()` and does not require a datatable ID argument.

#### Usage

```javascript
fetchDatatableMetadata()
```

#### Return

Array of datatable metadata objects for the current app.

Typical item shape:

```javascript
[
  {
    id: "datatable-id",
    dataTableName: "Bookings",
    fields: [
      {
        _id: "field-id",
        fieldName: "Check In"
      }
    ]
  }
]
```

### Filter Data View

#### **filterMicroappView**

Apply filters to a microapp view to show only specific data rows. This method allows you to create search and filtering functionality within code widgets.

**Usage**

```javascript
filterMicroappView({
  microAppID,
  embeddingRowID,
  viewFilters,
  viewFilterIsMongoQuery,
  filterContext
})
```

**Parameters**

* `microAppID` (string): ID of the data-table (microapp) to filter
* `embeddingRowID` (string, optional): ID of the embedding row (used for sub-table filtering)
* `viewFilters` (array): Array of filter objects using MongoDB query syntax
* `viewFilterIsMongoQuery` (boolean): Whether to use MongoDB query language for complex filtering
* `filterContext` (string): String identifier for the filter context to distinguish between different filters

**Return**

```javascript
Promise<void>
```

**Example - Complete Contact Age Filter Implementation**

```javascript
// Always instantiate Buzzy Frame API outside the component
const buzzyFrameAPI = new BuzzyFrameAPI();

function ContactAgeFilter() {
  const { useEffect, useState, useRef } = React;

  // UI state
  const [minAge, setMinAge] = useState('');
  const [maxAge, setMaxAge] = useState('');
  const [contacts, setContacts] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [debug, setDebug] = useState('');

  // Table ID and filter context
  const CONTACTS_TABLE_ID = "your-contacts-table-id";
  const FILTER_CONTEXT = "AgeRangeFilter";

  // Track last filter for listener refresh
  const lastFilterRef = useRef({min: '', max: ''});

  // Initialize and set up listener
  useEffect(() => {
    async function initialiseAll() {
      try {
        setDebug(d => d + '\n[init] Calling buzzyFrameAPI.initialise()...');
        const init = await buzzyFrameAPI.initialise();
        
        // Set up microapp listener for reactive updates
        buzzyFrameAPI.addMicroappListener({
          microAppID: CONTACTS_TABLE_ID,
          listener: async () => {
            setDebug(d => d + '\n[listener] Data changed, refreshing...');
            await fetchContacts(lastFilterRef.current.min, lastFilterRef.current.max);
          }
        });

        // Initial fetch (no filter)
        await fetchContacts('', '');

      } catch (err) {
        setError('Error during initialisation: ' + (err.message || err));
        setDebug(d => d + `\n[error] ${err.stack || err}`);
      }
    }

    initialiseAll();
  }, []);

  // Filtering function
  async function fetchContacts(min, max) {
    setLoading(true);
    setError('');
    setDebug(d => d + `\n[fetchContacts] min=${min}, max=${max}`);

    lastFilterRef.current = {min, max};

    try {
      // Build MongoDB filters
      let filters = [];
      if (min !== '' && max !== '') {
        filters.push({ age: { $gte: Number(min) } });
        filters.push({ age: { $lte: Number(max) } });
      } else if (min !== '') {
        filters.push({ age: { $gte: Number(min) } });
      } else if (max !== '') {
        filters.push({ age: { $lte: Number(max) } });
      }

      setDebug(d => d + `\n[fetchContacts] Filters: ${JSON.stringify(filters)}`);

      // Apply filter to main view (reactively updates main app)
      if (filters.length > 0) {
        await buzzyFrameAPI.filterMicroappView({
          microAppID: CONTACTS_TABLE_ID,
          viewFilters: filters,
          viewFilterIsMongoQuery: true,
          filterContext: FILTER_CONTEXT
        });
        setDebug(d => d + `\n[fetchContacts] Called filterMicroappView()`);
      }

      // Fetch filtered rows
      const rows = await buzzyFrameAPI.fetchDataTableRows({
        microAppID: CONTACTS_TABLE_ID
      });
      setDebug(d => d + `\n[fetchContacts] Got ${rows.length} rows`);
      setContacts(rows);

    } catch (err) {
      setError('Error fetching contacts: ' + (err.message || err));
      setDebug(d => d + `\n[error] ${err.stack || err}`);
    } finally {
      setLoading(false);
    }
  }

  // Handle filter submit
  function handleFilter(e) {
    e.preventDefault();
    setDebug(d => d + `\n[UI] Filter submit: minAge=${minAge}, maxAge=${maxAge}`);
    fetchContacts(minAge, maxAge);
  }

  return (
    <div>
      <h2>Filter Contacts by Age</h2>
      <form onSubmit={handleFilter}>
        <input
          type="number"
          placeholder="Min Age"
          value={minAge}
          onChange={e => setMinAge(e.target.value)}
        />
        <input
          type="number"
          placeholder="Max Age"
          value={maxAge}
          onChange={e => setMaxAge(e.target.value)}
        />
        <button type="submit" disabled={loading}>Filter</button>
      </form>
      {error && <div style={{color: 'red'}}>{error}</div>}
      {loading && <div>Loading...</div>}
      <div>
        {contacts.map(contact => (
          <div key={contact._id}>
            <strong>{contact.Name}</strong> - Age: {contact.Age}
          </div>
        ))}
      </div>
      <pre style={{fontSize: '12px', background: '#f0f0f0', padding: '10px'}}>
        Debug: {debug}
      </pre>
    </div>
  );
}
```

**Advanced Usage Patterns**

When implementing `filterMicroappView` in production applications, follow these patterns demonstrated in the example above:

**1. BuzzyFrameAPI Lifecycle Management**

* Always instantiate `BuzzyFrameAPI` outside React components to prevent re-initialization
* Call `buzzyFrameAPI.initialise()` once during component mount
* Set up microapp listeners immediately after initialization

**2. Reactive Data Updates**

* Use `addMicroappListener()` to automatically refresh data when the underlying microapp changes
* Track filter state with `useRef` to ensure listeners can access current filter parameters
* Implement proper cleanup in `useEffect` return functions if needed

**3. Error Handling and User Experience**

* Wrap all async operations in try/catch blocks
* Provide meaningful error messages to users
* Implement loading states for better user feedback
* Include debug logging for development and troubleshooting

**4. State Management**

* Use React state for UI-related data (loading, error, results)
* Use `useRef` for values that need to persist across renders but don't trigger re-renders
* Separate filter application from data fetching for better code organization

**5. Performance Considerations**

* Debounce filter inputs to avoid excessive API calls
* Cache filter results when appropriate
* Use `filterContext` parameter to distinguish between different filter instances

This method applies the filters to the view outside the code widget, allowing users to create custom search interfaces that filter the main application data reactively.

#### `getMicroappViewFilter`

Gets the currently applied filter payload for a given filter context.

```javascript
const currentFilter = await buzzyFrameAPI.getMicroappViewFilter({
  filterContext: "AgeRangeFilter"
});
```

**Return**

The currently stored filter payload for that `filterContext`, or `null` if no filter has been stored yet.

Typical shape:

```javascript
{
  filterContext: "AgeRangeFilter",
  microAppID: "contacts-table-id",
  embeddingRowID: null,
  viewFilters: [{ age: { $gte: 18 } }],
  viewFilterIsMongoQuery: true,
  updatedAt: 1710000000000
}
```

### Shared State

Use shared state to store lightweight values for the current user session across cooperating widgets and widget interactions within the current app context.

Shared state is session-based only. It is not persisted as row data or app data, so it should be treated as temporary UI/runtime state rather than durable storage.

This is useful when you have multiple widgets across different screens that need to work from the same temporary state model. For example, in an accommodation booking app you might have a Discover widget, Search widget, Availability Calendar widget, Booking Summary widget, and Booking Request widget all sharing session state for the user's selected booking dates, guest counts, and other in-progress booking preferences.

#### `setSharedState`

```javascript
await buzzyFrameAPI.setSharedState({
  key: "selectedCategory",
  value: "fruit"
});
```

**Return**

`null`. This method writes session state and resolves once the write has completed.

#### `getSharedState`

```javascript
const selectedCategory = await buzzyFrameAPI.getSharedState({
  key: "selectedCategory"
});
```

**Return**

The stored shared-state record for that key, or `null` if nothing has been stored yet.

Typical shape:

```javascript
{
  key: "selectedCategory",
  value: "fruit",
  updatedAt: 1710000000000
}
```

### Send Notification

#### Usage

```javascript
sendNotification({
  email,
  message,
  badgeCount,
  channel,
})
```

#### Parameters

* `email` (string): Recipient email address
* `message` (string, optional): Visible notification body
* `badgeCount` (number, optional): Absolute badge number to send
* `channel` (string, optional): `push` for push-only, `inApp` for unread notification only, or omit for default behavior

Code widgets automatically send the current `appID` behind the scenes. The request only succeeds if the logged-in user can edit that app.

#### Return

```javascript
Promise<Object>
```

Typical resolved value:

```javascript
{
  ok: true,
  appID: 'your-app-id',
  email: 'user@example.com',
  channel: 'default',
  hasMessage: true,
  badgeCount: null,
  notificationCount: 1,
  pushCount: 1
}
```

#### Examples

**Visible notification with default delivery**

```javascript
const result = await buzzyFrameAPI.sendNotification({
  email: 'user@example.com',
  message: 'Hello from a code widget'
});

console.log(result);
```

**Badge-only push update (app only)**

Use this when you want to programmatically change the badge number on a user's Apple device from a code widget. When you send only `badgeCount` with `channel: 'push'`, Buzzy does not show a visible notification message and only updates the app badge number.

```javascript
const result = await buzzyFrameAPI.sendNotification({
  email: 'user@example.com',
  badgeCount: 7,
  channel: 'push'
});
```

**In-app only notification**

```javascript
const result = await buzzyFrameAPI.sendNotification({
  email: 'user@example.com',
  message: 'Check the app inbox',
  channel: 'inApp'
});
```

Code widgets automatically use the logged-in user's auth token through the parent app, so you do not need to pass auth headers manually for this method.

#### Query Options (Pagination) and Filtering

When fetching embedded row data, you might to do pagination, filtering, and/or limit the number of results that you get. Buzzy uses MongoDB under the hood, as such, view filters follow the rules for MongoDB queries. This includes support for geo/spatial queries using `sortValGeometry` - see [geo/spatial query examples](https://docs.buzzy.buzz/rest-api/buzzy-rest-api/rest-api/microappdata#geo-spatial-query-examples) for more details.

**Query Options (Pagination and Data Limits)**

Query options allow you to control the number of results you get back from the query, as well as allowing you to do pagination using the `skip` parameter.

For paging, always pass both `queryOptions.limit` and `queryOptions.skip`. The `subLimit` parameter is only a fallback subscription limit for older or non-paged calls. If `queryOptions.limit` is missing, Buzzy uses `subLimit` as the limit. If both are missing, the code-widget bridge defaults `subLimit` to `50`.

This means a paging loop should use:

```javascript
const rows = await buzzyFrameAPI.fetchDataTableRows({
  microAppID: LOOKUP_TABLE_ID,
  viewSort: { sortVal: 1 },
  queryOptions: [{
    resourceID: LOOKUP_TABLE_ID,
    limit: pageSize,
    skip
  }]
});
```

Do not rely on `subLimit` as the page size for paged reads. Use `queryOptions.limit` for the page size and `queryOptions.skip` for the offset.

Common supported MongoDB aggregation parameters:

* [`$limit`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/limit/)
* [`$skip`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/skip/)
* [`$sort`](https://www.mongodb.com/docs/manual/reference/operator/aggregation/sort/)

**More info on Sorting:**\
The data fetched from the Microapp can be sorted. The `order` and `field` parameters are used in the searchFilter to specify which Microapp sort field and if it is ascending or descending. The sort fields are configured in the Microapp Results Tab. Sort field 1 corresponds to the `field` value 1 and so on. Where `order` is either `1` = ascending or `-1` = descending.

Example fetching microapp rows using Sort Field 1.

```
queryOptions: [
	{
		"resourceID": "<microapp ID goes here>",
		"order": 1,
		"field": "1",
		"skip": "5",
		"limit": 100
	}
]
```

**View Filters (Filtering)**

In cases where you might want to filter the results based on the value of a field, you can query based on the value of a field of the data table row. `viewFilters` is an array of view filters to be used, as such you may filter many views as they are scoped to the data table ID (resourceID).

**Combining the Above Concepts, see the following example with filtering and pagination included:**

```
const recipeData = await buzzyFrameAPI.fetchDataTableRows(
  {
    microAppID: recipeMetadata.id, 
    viewFilters:[ {sortVal: {$regex: 'Lasagna'}} ],
    queryOptions:[{resourceID: recipeMetadata.id, limit: 1, skip: 0}]
  }
);
```

In the example above, the query will fetch rows where the `sortVal` contains the word "Lasagna", in this case, the `sortVal` is referencing the data table row title (the recipe title). The query options them limit the rows to only 1 row, while skipping 0. If we were doing some pagination, when a user clicked a "Next Page" button, we could change the skip to 1 so it would get the next recipe that satisfies the filters.

### MicroAppChild Management

#### createMicroappChild

Creates a new MicroAppChild entry.

```javascript
await buzzyFrameAPI.createMicroappChild({
  microAppID,        // Preferred: ID of the MicroApp resource
  rowID,             // Preferred: ID of the parent row
  fieldID,           // ID of the parent field the child is associated with
  content            // Content data for the child (e.g., file metadata, chart config)
});
```

For backward compatibility, `microAppResourceID` and `appID` are also accepted as aliases for `microAppID` and `rowID`.

Example usage for file upload:

```javascript
await buzzyFrameAPI.createMicroappChild({
  microAppID: rowJSON.parentResourceID,
  rowID: rowJSON._id,
  fieldID: rowJSON.fieldID,
  content: {
    url: presignedReadUrl,
    filename: file.name,
    size: file.size,
    type: file.type,
    expiredAt: Date.now() + (7 * 24 * 60 * 60 * 1000) // 7 days
  }
});
```

**Return**

The created MicroAppChild record.

Typical shape:

```javascript
{
  _id: "child-id",
  parentAppItemID: "row-id",
  parentAppFieldID: "field-id",
  content: {
    url: "https://...",
    filename: "example.png"
  }
}
```

#### getChildItemsByField

Retrieves all MicroAppChild entries for a specific parent app item and field.

```javascript
const children = await buzzyFrameAPI.getChildItemsByField({
  rowID,    // Preferred: ID of the parent row
  fieldID   // ID of the parent field to get children for
});
```

For backward compatibility, `appID` is also accepted as an alias for `rowID`.

Example usage for fetching images:

```javascript
const images = await buzzyFrameAPI.getChildItemsByField({
  rowID: rowJSON._id,
  fieldID: rowJSON.fieldID
});
```

**Return**

Array of MicroAppChild records for that parent row and field.

Typical shape:

```javascript
[
  {
    _id: "child-id",
    parentAppItemID: "row-id",
    parentAppFieldID: "field-id",
    content: {
      url: "https://...",
      filename: "image.png"
    }
  }
]
```

#### readMicroappChild

Retrieves a single MicroAppChild entry by ID.

```javascript
const child = await buzzyFrameAPI.readMicroappChild({
  childID
});
```

**Return**

A single MicroAppChild record.

#### updateMicroappChild

Updates the content of a MicroAppChild entry.

```javascript
await buzzyFrameAPI.updateMicroappChild({
  childID,  // ID of the child to update
  content   // New content data
});
```

Example usage for updating file URL:

```javascript
await buzzyFrameAPI.updateMicroappChild({
  childID: file._id,
  content: {
    ...file.content,
    url: presignedReadUrl,
    expiredAt: Date.now() + (7 * 24 * 60 * 60 * 1000)
  }
});
```

**Return**

The updated MicroAppChild record.

#### removeMicroappChild

Deletes a MicroAppChild entry.

```javascript
await buzzyFrameAPI.removeMicroappChild({
  childID  // ID of the child to delete
});
```

**Return**

The removed MicroAppChild record.

#### copyS3File

Copies an existing file between two S3-backed resources and returns the new file URL or key data from the server.

```javascript
const copiedFile = await buzzyFrameAPI.copyS3File({
  sourceResourceID,
  destinationResourceID,
  fileKey,
  newFileKey
});
```

**Return**

Signed read URL string for the copied file.

#### uploadMicroAppChildToS3FromUrl

Uploads a file to a MicroAppChild field from a URL or data URL.

```javascript
const uploadResult = await buzzyFrameAPI.uploadMicroAppChildToS3FromUrl({
  microAppID,
  fieldID,
  url,
  filename
});
```

If `url` is a browser `blob:` URL, BuzzyFrameAPI automatically converts it to a data URL before upload so it can be transferred safely across the iframe boundary.

**Return**

Object containing the signed read URL for the uploaded file.

Typical shape:

```javascript
{
  url: "https://..."
}
```

### Functions and Constants

#### `runAppFunction`

Runs an app function with optional payload and row context.

```javascript
const result = await buzzyFrameAPI.runAppFunction({
  functionId,
  payload,
  rowId
});
```

**Return**

The response returned by the underlying Buzzy function. The exact shape depends on that function's implementation.

If your function follows a structured API-style response, expect something like:

```javascript
{
  success: true,
  data: { ... },
  error: null
}
```

#### `getConstant`

Retrieves a non-secret app constant by name.

```javascript
const apiBaseUrl = await buzzyFrameAPI.getConstant({
  name: "API_BASE_URL"
});
```

**Return**

Lookup result object for the requested constant.

Typical shape:

```javascript
{
  found: true,
  value: "https://api.example.com",
  error: null
}
```

### Navigation

#### navigate

Navigates to a different screen within the same app.

```javascript
await buzzyFrameAPI.navigate({
  screenID,  // ID of the screen to navigate to (use getScreenID to obtain)
  rowID,     // Optional: ID of the row to view on the screen
  baseScreenID, // Optional: parent/base route-backed screen context
  baseRowID     // Optional: parent/base row context
});
```

Example usage:

```javascript
const screenID = await buzzyFrameAPI.getScreenID({ screenName: 'Product Details' });
await buzzyFrameAPI.navigate({
  screenID,
  rowID: product._id
});
```

**Return**

None. This method triggers navigation and does not resolve a payload.

#### getScreenID

Helper method to get the screenID for navigation.

```javascript
const screenID = await buzzyFrameAPI.getScreenID({
  screenName  // Name of the screen to get ID for
});
```

**Return**

Screen ID string when found, otherwise `null`.

#### getScreenURL

Builds a route-backed screen URL without navigating.

```javascript
const url = await buzzyFrameAPI.getScreenURL({
  screenID,
  rowID,
  queryParams: { mode: "preview" }
});
```

**Return**

Absolute URL string for the requested route-backed screen, or `null` if no route-backed URL can be built.

#### openURL

Opens a URL using Buzzy's runtime link semantics.

```javascript
await buzzyFrameAPI.openURL({
  url: "https://example.com",
  newTab: true
});
```

`newTab` defaults to `false`.

**Return**

Result object describing whether the URL was accepted and how it was normalized.

Typical shape:

```javascript
{
  success: true,
  url: "https://example.com/",
  newTab: true,
  error: null
}
```

#### close

Closes the current overlay or screen context.

```javascript
buzzyFrameAPI.close();
```

**Return**

None. This method closes the current context and does not resolve a payload.

#### closeAll

Closes all open overlay or child screen contexts in the current stack.

```javascript
buzzyFrameAPI.closeAll();
```

**Return**

None. This method closes open child contexts and does not resolve a payload.

### Organization and Team Management

#### createOrganization

Creates a new organization.

```javascript
await buzzyFrameAPI.createOrganization({
  organizationInfo  // Organization data object
});
```

**Return**

Parsed JSON response from the matching REST endpoint. Typical shape mirrors the REST `insertorganization` response:

```javascript
{
  status: "success",
  body: {
    _id: "organization-id"
  }
}
```

#### readOrganization

Reads an organization by ID.

```javascript
const organization = await buzzyFrameAPI.readOrganization({
  organizationID  // ID of the organization to read
});
```

**Return**

Parsed JSON response from the matching REST endpoint.

Typical shape:

```javascript
{
  status: "success",
  body: {
    _id: "organization-id",
    name: "Organization Name",
    description: "Organization description"
  }
}
```

#### updateOrganization

Updates an organization's information.

```javascript
await buzzyFrameAPI.updateOrganization({
  organizationID,    // ID of the organization to update
  organizationInfo   // Updated organization data
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object with the updated organization in `body`.

#### deleteOrganization

Deletes an organization.

```javascript
await buzzyFrameAPI.deleteOrganization({
  organizationID  // ID of the organization to delete
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object confirming deletion.

#### createTeam

Creates a new team within an organization.

```javascript
await buzzyFrameAPI.createTeam({
  teamInfo  // Team data object including organizationID
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically containing the created team ID in `body`.

#### readTeam

Reads a team by ID.

```javascript
const team = await buzzyFrameAPI.readTeam({
  teamID  // ID of the team to read
});
```

**Return**

Parsed JSON response from the matching REST endpoint containing the team record in `body`.

#### updateTeam

Updates a team's information.

```javascript
await buzzyFrameAPI.updateTeam({
  teamID,    // ID of the team to update
  teamInfo   // Updated team data
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object with the updated team in `body`.

#### deleteTeam

Deletes a team.

```javascript
await buzzyFrameAPI.deleteTeam({
  teamID  // ID of the team to delete
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object confirming deletion.

#### createTeamMember

Adds a member to a team.

```javascript
await buzzyFrameAPI.createTeamMember({
  memberInfo  // Team member data including teamID and userID
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object for the created membership record.

#### readTeamMember

Reads a team member by team ID and user ID.

```javascript
const member = await buzzyFrameAPI.readTeamMember({
  teamID,  // ID of the team
  userID   // ID of the user
});
```

**Return**

Parsed JSON response from the matching REST endpoint containing the membership record in `body`.

#### updateTeamMember

Updates a team member's role.

```javascript
await buzzyFrameAPI.updateTeamMember({
  teamID,  // ID of the team
  userID,  // ID of the user
  role     // New role ('admin' or 'member')
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object with the updated membership record in `body`.

#### deleteTeamMember

Removes a member from a team.

```javascript
await buzzyFrameAPI.deleteTeamMember({
  teamID,  // ID of the team
  userID   // ID of the user to remove
});
```

**Return**

Parsed JSON response from the matching REST endpoint, typically a success object confirming removal.

### Listen for Updates

#### Usage

{% code overflow="wrap" %}

```javascript
addMicroappListener({ 
    microAppID, 
    listener 
})
```

{% endcode %}

Register a listener function for a given microapp (aka data table).

This method registers the listener and does not return a data payload.

Whenever data is added or updated that fits the query you have running on the given microapp, the supplied listener function will be executed. In the native app version, this function will also be run if the user initiates a refresh by dragging down on the screen.

Typically this would be used to re-run your query and set the results to a state variable, and thus trigger re-rendering of your fetched data whenever it is updated.

The listener will be passed on object with these properties:

* microAppID - the id that passed into the listener
* isUserRefresh - true if this is a user initiated refresh
* timestamp - timestamp of the update that triggered the listener

## Code Example

### Preamble

The API works best within the context of a sandbox iframe (see the [Code Widget documentation](/the-building-blocks/code-widget-custom-code.md#advanced-configuration) for security considerations). In our examples and testing, we utilise the [React](https://react.dev/learn) front-end library to construct HTML snippets. We've found that the behaviour of UI elements using React for HTML components allows for more granular control over rendering and state. As such, in the examples shown on this page, we will use React with [functional components](https://react.dev/learn/your-first-component#defining-a-component).

### Buzzy HTML Field Settings

Due to the usage of React, we want to **disable handlebars** and opt-in to the new **Async API**. This is easily accomplished in the settings panel of the HTML field definition:

<figure><img src="/files/not8vSqnndeiKRDdjp4k" alt="" width="375"><figcaption></figcaption></figure>

### Our Example Data table Layout

As a reference for the code snippet, the fields and set up of the data tables referenced in the code example are provided below:

**Fruit Reports (Data table)**

* Fruit Report (Text)
* Date (Datetime)
* Fruit Counters (Sub table)
* Graphic-HTML **(HTML Field)**

**Fruit Counters (Sub table)**

* Fruit Type (Select list)
* Number (Count)
* Date Counted (Datetime)

### Code

```javascript
<!DOCTYPE html>
<html>
  <head>
    <title>HTML Example</title>
    <script
      src="https://unpkg.com/react@18/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.3.3/dist/chart.umd.min.js"></script>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel" data-presets="env,react">
      // Initializing the global BuzzyFrameAPI class
      // This gives us access to API methods to do CRUD operations
      const buzzyFrameAPI = new BuzzyFrameAPI();

      // React component
      function App() {
        const { useEffect, useState } = React;

        const [frameToken, setFrameToken] = useState(null);
        const [fruitData, setFruitData] = useState(null);

        useEffect(() => {
          let isUnmounted = false;

          async function initData() {
            try {
              const initData = await buzzyFrameAPI.initialise();
              const {
                rowJSON = {},
                frameToken,
                id,
                resolveData,
              } = initData || {};

              if (rowJSON?._id && rowJSON["Fruit Counters"]) {
                const fruitTableRowData =
                  await buzzyFrameAPI.fetchDataTableRows({
                    microAppID: rowJSON["Fruit Counters"],
                    embeddingRowID: rowJSON?._id,
                  });

                if (!isUnmounted) {
                  setFruitData(fruitTableRowData);
                }
              }
            } catch (error) {
              console.log("[IFRAME] Error occured in initData().", error);
            }
          }

          initData();

          return () => {
            isUnmounted = true;
          };
        }, []);

        const PieChart = ({ data }) => {
          const labels = data.map((fruit) => fruit["Fruit Type"]);
          const counts = data.map((fruit) => fruit["Count"]);

          const chartData = {
            labels,
            datasets: [
              {
                data: counts,
                backgroundColor: [
                  "#FF6384",
                  "#36A2EB",
                  "#FFCE56",
                  "#4CAF50",
                  // Add more colors if needed
                ],
              },
            ],
          };

          const canvas = document.createElement("canvas");
          document.getElementById("root").appendChild(canvas);

          new Chart(canvas.getContext("2d"), {
            type: "doughnut",
            data: chartData,
          });
        };

        return (
          <div>
            {fruitData ? <PieChart data={fruitData} /> : <p>Loading...</p>}
          </div>
        );
      }

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
```

### Result

With some data table rows, the above example provides the following output in Buzzy:\\

<figure><img src="/files/qhHXscfMowqq2Pi9kdPf" alt=""><figcaption></figcaption></figure>

## Advanced Examples

### Example 1: Course Enrollment with File/Image Copying

This example demonstrates how to create a course enrollment system that:

1. Creates a copy of a course template for a user
2. Copies all associated questionnaires, questions, and options
3. Handles file and image copying between templates and user instances
4. Uses navigation to guide the user through the process

```javascript
<!DOCTYPE html>
<html>
  <head>
    <title>Do Course with Images and Files</title>
    <script
      src="https://unpkg.com/react@18/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@500&display=swap" rel="stylesheet" />
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
        -ms-overflow-style: none;  /* IE and Edge */
        scrollbar-width: none;  /* Firefox */
      }
      body::-webkit-scrollbar {
        display: none;
      }
      button {
        display: block;
        width: 100%;
        height: 40px;
        font-family: "Poppins", serif;
        font-weight: 500;
        font-size: 16px;
        line-height: 24px;
        cursor: pointer;
        background: transparent;
        color: white;
        border: none;
        outline: none;
      }
      .spinner {
        display: inline-block;
        width: 1em;
        height: 1em;
        border: 3px solid rgba(255, 255, 255, 0.3);
        border-radius: 50%;
        border-top-color: #fff;
        animation: spin 1s ease-in-out infinite;
      }
      @keyframes spin {
        to {
          transform: rotate(360deg);
        }
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel" data-presets="env,react">
      const buzzyFrameAPI = new BuzzyFrameAPI();
      const IMAGE_EXPIRY_TIME = 604800; // 7 days seconds
      const NUM_ITEMS = 100;
      function App() {
        const { useEffect, useState, useRef } = React;
        const [loading, setLoading] = useState(false);
        const [
          studentQuitCourseProcessingScreenID,
          setStudentQuitCourseProcessingScreenID,
        ] = useState(null);
        const [studentCourseDetailsScreenID, setStudentCourseDetailsScreenID] =
          useState(null);
        const metadata = useRef([]);
        const TEMPLATE_COURSE_MICROAPPID = useRef(null);
        const TEMPLATE_QUESTIONNAIRE_MICROAPPID = useRef(null);
        const TEMPLATE_QUESTION_MICROAPPID = useRef(null);
        const TEMPLATE_QUESTION_OPTIONS_MICROAPPID = useRef(null);
        const USER_COURSE_MICROAPPID = useRef(null);
        const USER_QUESTIONNAIRE_MICROAPPID = useRef(null);
        const USER_QUESTION_MICROAPPID = useRef(null);
        const USER_QUESTION_OPTIONS_MICROAPPID = useRef(null);
        const USER_MICROAPPID = useRef(null);

        useEffect(() => {
          async function initData() {
            try {
              const { rowJSON } = await buzzyFrameAPI.initialise();
              metadata.current = await buzzyFrameAPI.fetchDatatableMetadata();

              TEMPLATE_COURSE_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "CourseTemplate"
              )?.id;
              TEMPLATE_QUESTIONNAIRE_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "QuestionnaireTemplate"
              )?.id;

              TEMPLATE_QUESTION_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "QuestionTemplate"
              )?.id;
              TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current =
                metadata.current.find(
                  (table) => table.dataTableName === "Option"
                )?.id;

              USER_COURSE_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "UserCourse"
              )?.id;
              USER_QUESTIONNAIRE_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "Questionnaire"
              )?.id;
              USER_QUESTION_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "Question"
              )?.id;
              USER_QUESTION_OPTIONS_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "OptionUser"
              )?.id;
              USER_MICROAPPID.current = metadata.current.find(
                (table) => table.dataTableName === "User"
              )?.id;

              console.log("Metadata initialized:", {
                TEMPLATE_COURSE_MICROAPPID: TEMPLATE_COURSE_MICROAPPID.current,
                TEMPLATE_QUESTIONNAIRE_MICROAPPID:
                  TEMPLATE_QUESTIONNAIRE_MICROAPPID.current,
                TEMPLATE_QUESTION_MICROAPPID:
                  TEMPLATE_QUESTION_MICROAPPID.current,
                TEMPLATE_QUESTION_OPTIONS_MICROAPPID:
                  TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
                USER_COURSE_MICROAPPID: USER_COURSE_MICROAPPID.current,
                USER_QUESTIONNAIRE_MICROAPPID:
                  USER_QUESTIONNAIRE_MICROAPPID.current,
                USER_QUESTION_MICROAPPID: USER_QUESTION_MICROAPPID.current,
                USER_QUESTION_OPTIONS_MICROAPPID:
                  USER_QUESTION_OPTIONS_MICROAPPID.current,
                USER_MICROAPPID: USER_MICROAPPID.current,
              });
            } catch (error) {
              console.error("Error initializing metadata:", error);
            }
          }
          initData();
        }, []);

        async function handleDoCourse() {
          setLoading(true);

          try {
            const initData = await buzzyFrameAPI.initialise();
            const { rowJSON } = initData;

            function generateRandomString(length) {
              const characters =
                "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
              let result = "";
              for (let i = 0; i < length; i++) {
                result += characters.charAt(
                  Math.floor(Math.random() * characters.length)
                );
              }
              return result;
            }

            const extractS3Keys = (url) => {
              try {
                // Extract the pathname from the URL
                const pathname = new URL(url).pathname;

                // Split the pathname into parts
                const parts = pathname.split("/");

                if (parts.length >= 3) {
                  const resourceID = parts[1]; // First part after the domain
                  const fileKey = parts[2]; // Second part after the domain
                  return { resourceID, fileKey };
                } else {
                  throw new Error("URL does not have the expected structure.");
                }
              } catch (error) {
                console.error("Error parsing URL:", error.message);
                return null;
              }
            };

            // Copy MicroAppChild data helper
            async function copyMicroAppChildDataForRow({
              sourceRowID,
              targetRowID,
              sourceMicroAppID,
              targetMicroAppID,
            }) {
              const fieldsToCopy = metadata.current
                .find((table) => table.id === sourceMicroAppID)
                ?.fields.filter(
                  (field) =>
                    field.fieldType === "images" || field.fieldType === "files"
                );

              console.log("Fields to copy:", fieldsToCopy);

              if (!fieldsToCopy || fieldsToCopy.length === 0) {
                console.log(
                  "No fields to copy for MicroApp:",
                  sourceMicroAppID
                );
                return;
              }

              for (const field of fieldsToCopy) {
                const items = await buzzyFrameAPI.getChildItemsByField({
                  appID: sourceRowID,
                  fieldID: field.id,
                });
                console.log("Items to copy:", items);

                for (const item of items) {
                  const targetFieldID = metadata.current
                    .find((table) => table.id === targetMicroAppID)
                    ?.fields.find((f) => f.fieldName === field.fieldName)?.id;
                  console.log("Target field ID:", targetFieldID);
                  const { url } = item.content || {};
                  const extractedKeys = extractS3Keys(url);
                  const newFileKey = generateRandomString(10);

                  console.log("About to copy s3", {
                    url,
                    extractedKeys,
                    newFileKey,
                  });
                  const expiredAt =
                    new Date().getTime() + IMAGE_EXPIRY_TIME * 1000;
                  const newURL = await buzzyFrameAPI.copyS3File({
                    sourceResourceID: extractedKeys.resourceID,
                    destinationResourceID: extractedKeys.resourceID,
                    fileKey: `${extractedKeys.resourceID}/${extractedKeys.fileKey}`,
                    newFileKey: `${extractedKeys.resourceID}/${newFileKey}`,
                  });

                  console.log("COPIED New URL:", {
                    oldUrl: item.content.url,
                    newURL,
                    targetFieldID,
                  });

                  if (targetFieldID) {
                    const newContent = {
                      ...item.content,
                      url: newURL,
                      expiredAt,
                    };

                    console.log("Copying item:", newContent);

                    await buzzyFrameAPI.createMicroappChild({
                      microAppResourceID: targetMicroAppID,
                      appID: targetRowID,
                      fieldID: targetFieldID,
                      content: newContent,
                    });
                  }
                }
              }
            }

            const courseTemplate = rowJSON;
            console.log("About to nav Student Add Course Processing");

            buzzyFrameAPI.navigate({
              screenID: await buzzyFrameAPI.getScreenID({
                screenName: "Student Add Course Processing",
              }),
            });
            console.log("about to fetch user template:", {
              courseTemplate,
              USER_MICROAPPID: USER_MICROAPPID.current,
            });
            // Fetch the current user based on logged-in user ID
            const currentUser = await buzzyFrameAPI.fetchDataTableRows({
              microAppID: USER_MICROAPPID.current,
              sortVal: courseTemplate.currentLoggedInUser,
              subLimit: 1,
            });

            if (!currentUser || currentUser.length === 0) {
              throw new Error("Current user not found.");
            }

            console.log("About to create UserCourse for user", currentUser);

            // Create UserCourse and copy children
            const userCourse = await buzzyFrameAPI.insertMicroappRow({
              body: {
                microAppID: USER_COURSE_MICROAPPID.current,
                rowData: {
                  title: courseTemplate.name,
                  description: courseTemplate.description,
                  status: "Not Started",
                },
                embeddingRowID: currentUser[0]._id,
                ignoreActionRules: true,
              },
            });

            console.log("UserCourse created:", userCourse);

            await copyMicroAppChildDataForRow({
              sourceRowID: courseTemplate._id,
              targetRowID: userCourse.rowID,
              sourceMicroAppID: TEMPLATE_COURSE_MICROAPPID.current,
              targetMicroAppID: USER_COURSE_MICROAPPID.current,
            });

            const questionnaireTemplates =
              await buzzyFrameAPI.fetchDataTableRows({
                microAppID: TEMPLATE_QUESTIONNAIRE_MICROAPPID.current,
                embeddingRowID: courseTemplate._id,
                subLimit: NUM_ITEMS,
              });

            console.log("Questionnaire templates:", questionnaireTemplates);

            for (const questionnaireTemplate of questionnaireTemplates) {
              const userQuestionnaire = await buzzyFrameAPI.insertMicroappRow({
                body: {
                  microAppID: USER_QUESTIONNAIRE_MICROAPPID.current,
                  rowData: { title: questionnaireTemplate.name },
                  embeddingRowID: userCourse.rowID,
                  ignoreActionRules: true,
                },
              });

              await copyMicroAppChildDataForRow({
                sourceRowID: questionnaireTemplate._id,
                targetRowID: userQuestionnaire.rowID,
                sourceMicroAppID: TEMPLATE_QUESTION_MICROAPPID.current,
                targetMicroAppID: USER_QUESTIONNAIRE_MICROAPPID.current,
              });

              const questionTemplates = await buzzyFrameAPI.fetchDataTableRows({
                microAppID: TEMPLATE_QUESTION_MICROAPPID.current,
                embeddingRowID: questionnaireTemplate._id,
                subLimit: NUM_ITEMS,
              });

              for (const questionTemplate of questionTemplates) {
                const userQuestion = await buzzyFrameAPI.insertMicroappRow({
                  body: {
                    microAppID: USER_QUESTION_MICROAPPID.current,
                    rowData: { ...questionTemplate, answer: "" },
                    embeddingRowID: userQuestionnaire.rowID,
                    ignoreActionRules: true,
                  },
                });

                await copyMicroAppChildDataForRow({
                  sourceRowID: questionTemplate._id,
                  targetRowID: userQuestion.rowID,
                  sourceMicroAppID: TEMPLATE_QUESTION_MICROAPPID.current,
                  targetMicroAppID: USER_QUESTION_MICROAPPID.current,
                });

                const options = await buzzyFrameAPI.fetchDataTableRows({
                  microAppID: TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
                  embeddingRowID: questionTemplate._id,
                  subLimit: NUM_ITEMS,
                });

                for (const option of options) {
                  const userOption = await buzzyFrameAPI.insertMicroappRow({
                    body: {
                      microAppID: USER_QUESTION_OPTIONS_MICROAPPID.current,
                      rowData: { optionText: option.optionText },
                      embeddingRowID: userQuestion.rowID,
                      ignoreActionRules: true,
                    },
                  });

                  await copyMicroAppChildDataForRow({
                    sourceRowID: option._id,
                    targetRowID: userOption.rowID,
                    sourceMicroAppID:
                      TEMPLATE_QUESTION_OPTIONS_MICROAPPID.current,
                    targetMicroAppID: USER_QUESTION_OPTIONS_MICROAPPID.current,
                  });
                }
              }
            }

            console.log("Course data copied successfully.");
            buzzyFrameAPI.navigate({
              screenID: await buzzyFrameAPI.getScreenID({
                screenName: "Student Add Course Success",
              }),
              rowID: userCourse.rowID,
            });
          } catch (error) {
            console.error("Error during course copy:", error);
          } finally {
            setLoading(false);
          }
        }

        return (
          <div>
            <button onClick={handleDoCourse} disabled={loading}>
              {loading ? <span className="spinner"></span> : "Take this course"}
            </button>
          </div>
        );
      }

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
```

This example demonstrates several key features:

* Using `getChildItemsByField` to fetch file and image attachments
* Using `createMicroappChild` to copy attachments to new rows
* Using `navigate` to guide users through the enrollment process
* Handling complex data relationships and maintaining referential integrity

### Example 2: Course Unenrollment with Cleanup

This example shows how to properly remove a course and all its associated data, maintaining referential integrity:

```javascript
<!DOCTYPE html>
<html>
  <head>
    <title>Manage Courses</title>
    <script
      src="https://unpkg.com/react@18/umd/react.production.min.js"
      crossorigin
    ></script>
    <script
      src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"
      crossorigin
    ></script>
    <script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
    <link rel="preconnect" href="https://fonts.googleapis.com" />
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
    <link
      href="https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,200;0,300;0,400;0,500&display=swap"
      rel="stylesheet"
    />
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        overflow: hidden;
       -ms-overflow-style: none;  /* IE and Edge */
        scrollbar-width: none;  /* Firefox */
      }
      body::-webkit-scrollbar {
        display: none;
      }
      button {
        display: block;
        width: 140px;
        height: 40px;
        padding: 0px;
        font-family: "Poppins", serif;
        font-weight: 500;
        font-size: 16px;
        line-height: 40px;
        text-align: center;
        background: transparent;
        cursor: pointer;
        color: white;
        border: none;
        outline: none;
      }
    </style>
  </head>
  <body>
    <div id="root"></div>
    <script type="text/babel" data-presets="env,react">
      const DEFAULT_NUM_ROWS = 100;

      let TEMPLATE_QUESTION_MICROAPPID,
        TEMPLATE_QUESTION_OPTIONS_MICROAPPID,
        USER_COURSE_MICROAPPID,
        USER_QUESTIONNAIRE_MICROAPPID,
        USER_QUESTION_MICROAPPID,
        USER_QUESTION_OPTIONS_MICROAPPID,
        USER_MICROAPPID;

      let metadata = [];

      const buzzyFrameAPI = new BuzzyFrameAPI();

      function App() {
        const { useEffect, useState } = React;
        const [courseID, setCourseID] = useState(null);

        useEffect(() => {
          async function initData() {
            try {
              const { rowJSON } = await buzzyFrameAPI.initialise();
              metadata = await buzzyFrameAPI.fetchDatatableMetadata();
              setCourseID(rowJSON._id);

              USER_COURSE_MICROAPPID = metadata.find(
                (table) => table.dataTableName === "UserCourse"
              )?.id;
              USER_QUESTIONNAIRE_MICROAPPID = metadata.find(
                (table) => table.dataTableName === "Questionnaire"
              )?.id;
              USER_QUESTION_MICROAPPID = metadata.find(
                (table) => table.dataTableName === "Question"
              )?.id;
              USER_QUESTION_OPTIONS_MICROAPPID = metadata.find(
                (table) => table.dataTableName === "OptionUser"
              )?.id;
              USER_MICROAPPID = metadata.find(
                (table) => table.dataTableName === "User"
              )?.id;
            } catch (error) {
              console.error("[ERROR] Error during initialization:", error);
            }
          }

          initData();
        }, []);

        const handleDeleteCourse = async () => {
          if (!courseID) return;

          try {
            buzzyFrameAPI.navigate({
              screenID: await buzzyFrameAPI.getScreenID({
                screenName: "Student Quit Course Processing",
              }),
            });
            // Fetch questionnaires linked to the course
            const questionnaires = await buzzyFrameAPI.fetchDataTableRows({
              microAppID: USER_QUESTIONNAIRE_MICROAPPID,
              subLimit: DEFAULT_NUM_ROWS,
              embeddingRowID: courseID,
            });

            console.log("QUESTIONNAIRES TO DELETE:", questionnaires);

            for (const questionnaire of questionnaires) {
              // Fetch questions linked to each questionnaire
              const questions = await buzzyFrameAPI.fetchDataTableRows({
                microAppID: USER_QUESTION_MICROAPPID,
                subLimit: DEFAULT_NUM_ROWS,
                embeddingRowID: questionnaire._id,
              });

              for (const question of questions) {
                // Fetch options linked to each question
                const options = await buzzyFrameAPI.fetchDataTableRows({
                  microAppID: USER_QUESTION_OPTIONS_MICROAPPID,
                  subLimit: DEFAULT_NUM_ROWS,
                  embeddingRowID: question._id,
                });

                // Delete each option
                for (const option of options) {
                  await buzzyFrameAPI.removeMicroappRow({
                    body: { rowID: option._id, ignoreActionRules: true },
                  });
                }

                // Delete the question
                await buzzyFrameAPI.removeMicroappRow({
                  body: { rowID: question._id, ignoreActionRules: true },
                });
              }

              // Delete the questionnaire
              await buzzyFrameAPI.removeMicroappRow({
                body: { rowID: questionnaire._id, ignoreActionRules: true },
              });
            }

            // Delete the course
            await buzzyFrameAPI.removeMicroappRow({
              body: { rowID: courseID, ignoreActionRules: true },
            });

            console.log(
              "[DEBUG] Course and all its children deleted successfully."
            );

            buzzyFrameAPI.navigate({
              screenID: await buzzyFrameAPI.getScreenID({
                screenName: "Student Course List",
                rowID: null,
              }),
              rowID: null,
            });
          } catch (error) {
            console.error("[ERROR] Error during course deletion:", error);
          }
        };

        return (
          <div>
            <button onClick={handleDeleteCourse}>Quit course</button>
          </div>
        );
      }

      ReactDOM.render(<App />, document.getElementById("root"));
    </script>
  </body>
</html>
```

This example demonstrates:

* Proper cleanup of nested data structures
* Using `navigate` to show processing and completion states
* Maintaining referential integrity during deletion
* Error handling and user feedback

## Limitations

The current iteration of the new API is still under development. There are a number of key issue yet to be resolved:

* [ ] Extensive error handling
* [ ] Asynchronous call timeouts


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.buzzy.buzz/the-building-blocks/code-widget-custom-code/new-async-api-+-react-html-components.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
