# How to Create Sandboxed Alpine.js Extensions in Agent-Native: A Complete Guide

> Learn to create sandboxed Alpine.js extensions in Agent-Native. Build self-contained mini-apps that securely communicate with your host application using injected helpers. Get the complete guide today.

- Repository: [Builder.io/agent-native](https://github.com/BuilderIO/agent-native)
- Tags: how-to-guide
- Published: 2026-06-28

---

**Agent-Native extensions are self-contained Alpine.js mini-apps that run inside sandboxed iframes and communicate with the host application through injected helpers like `appAction`, `extensionFetch`, and `extensionData`.**

Agent-Native provides a robust sandboxing primitive that allows developers to build interactive widgets without compromising application security. These extensions are stored as HTML snippets in the SQL `tools` table—exposed via the `extensions` Drizzle export—and execute within isolated iframes where they cannot access the host filesystem or surrounding DOM. Understanding how to create sandboxed Alpine.js extensions in Agent-Native enables you to add custom functionality while maintaining strict security boundaries.

## The Extension Architecture

### Sandboxed Environment

Extensions in Agent-Native are rendered inside sandboxed iframes that prevent code from affecting the surrounding page or reading sensitive host data. According to the source code in [`.agents/skills/extensions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/.agents/skills/extensions/SKILL.md), this isolation ensures that the only communication channel between the extension and the host is through a tightly controlled set of injected helper functions.

### Injected Helper Functions

The host application injects several helpers into the extension's global scope that enable controlled interaction:

| Helper | Purpose | Use Case |
|--------|---------|----------|
| `appAction(name, params)` | Invokes server-side actions defined in `actions/` | Triggering background jobs or fetching email lists |
| `appFetch(path, options)` | Calls internal endpoints under `/_agent-native/*` | Retrieving JSON previews of resources |
| `dbQuery(sql, args)` / `dbExec(sql, args)` | Direct SQL access with table-level permissions | Custom reporting and ad-hoc queries |
| `extensionFetch(url, options)` (alias `toolFetch`) | Proxies external HTTP requests with secret injection | Calling third-party APIs like GitHub or OpenAI |
| `extensionData` (alias `toolData`) | Scoped key-value storage (`user`, `org`, `all`) | Persisting UI state and small datasets |

The complete API signatures for these helpers are documented in [`references/api.md`](https://github.com/BuilderIO/agent-native/blob/main/references/api.md).

## Creating Extensions

You can create extensions using either the CLI or the HTTP API, both of which store the component in the database and make it available for rendering.

### CLI Method

Use the `create-extension` action from your terminal:

```bash
pnpm action create-extension \
  --name "GitHub PR Dashboard" \
  --description "Shows open PRs for the repo" \
  --content '<div x-data="...">...</div>'

```

### HTTP API Method

Send a POST request to the extensions endpoint:

```http
POST /_agent-native/extensions
Content-Type: application/json

{
  "name": "GitHub PR Dashboard",
  "description": "Shows open PRs",
  "content": "<div x-data=\"...\">...</div>"
}

```

For large HTML payloads, use `contentFromAttachment` to reference files instead of embedding content directly, as detailed in the "Hosting a pasted file (by reference)" section of [`.agents/skills/extensions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/.agents/skills/extensions/SKILL.md).

## Building Alpine.js Components

Extensions use standard Alpine.js directives including `x-data`, `x-init`, `x-show`, and `x-for`. The patterns for organizing your component code depend on complexity.

### Simple Inline State

For trivial extensions, define state directly in `x-data` attributes:

```html
<div x-data="{ count: 0 }" class="p-4">
  <button x-on:click="count++" class="btn">Add</button>
  <span x-text="count"></span>
</div>

```

### Complex Components with Alpine.data()

For non-trivial logic, move the component definition into a `<script>` block and register it with `Alpine.data()`:

```html
<div x-data="noteApp" class="p-4">
  <input x-model="title" placeholder="Title" class="border"/>
  <textarea x-model="body" placeholder="Body" class="border mt-2"></textarea>
  <button x-on:click="save" class="mt-2 bg-primary text-white px-3 py-1">
    Save
  </button>
</div>

<script>
document.addEventListener('alpine:init', () => {
  Alpine.data('noteApp', () => ({
    title: '',
    body: '',
    async save() {
      await extensionData.set('notes', this.title, {
        title: this.title,
        body: this.body
      });
    }
  }));
});
</script>

```

This pattern prevents the fragility of large inline JavaScript objects and keeps your HTML clean, as recommended in the "Component shape" section of the extensions skill file.

## Persisting Data with extensionData

Instead of writing raw SQL, extensions should use `extensionData` (the modern alias for legacy `toolData`). This helper automatically creates necessary tables, handles scoping by user or organization, and provides an upsert API:

```js
// Store data with automatic user scoping
await extensionData.set('notes', 'note-1', { title: 'My Note', body: 'Hello' });

// Retrieve scoped data
const myNotes = await extensionData.list('notes'); // user-scoped by default

```

The `extensionData` API supports three scope levels—`user`, `org`, and `all`—allowing you to persist private notes, shared organization data, or global configuration without schema changes. See the data persistence examples in [`.agents/skills/extensions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/.agents/skills/extensions/SKILL.md).

## Calling External APIs Securely

The `extensionFetch` helper proxies requests through the server, allowing extensions to call third-party APIs without exposing secrets in client-side code. It injects secret values via `${keys.NAME}` placeholders:

```js
const res = await extensionFetch('https://api.github.com/repos/${keys.GITHUB_OWNER}/${keys.GITHUB_REPO}/pulls', {
  headers: { Authorization: 'Bearer ${keys.GITHUB_TOKEN}' }
});
const prs = await res.json();

```

**Critical security note:** Placeholders must be inside **single quotes** to prevent browser-side evaluation. The helper enforces this pattern to ensure API keys never appear in the extension's source code, as documented in the "Secrets and sensitive data in extensions" section of the skill file.

## Updating Existing Extensions

Agent-Native provides the `update-extension` action for modifying existing extensions without full replacements.

### JSON Patch Updates

For small changes, send an array of patch operations:

```http
PATCH /_agent-native/extensions/12345
Content-Type: application/json

{
  "patches": [
    { "find": "bg-primary", "replace": "bg-accent" }
  ]
}

```

### Structured Edits

For more complex modifications, use the `edits` operation targeting named comment sections:

```http
PATCH /_agent-native/extensions/12345
Content-Type: application/json

{
  "edits": [
    {
      "section": "my-chart",
      "content": "<!-- new chart HTML -->"
    }
  ]
}

```

The extensions skill file details each supported edit type and provides examples for both approaches.

## Security Best Practices

When developing sandboxed Alpine.js extensions in Agent-Native, follow these guidelines from [`.agents/skills/extensions/SKILL.md`](https://github.com/BuilderIO/agent-native/blob/main/.agents/skills/extensions/SKILL.md):

- **Never embed secrets** directly in HTML or script blocks. Always use `${keys.*}` placeholders with `extensionFetch`.
- **Use default canvas padding** and avoid custom outer padding unless necessary. For full-bleed layouts, use `data-tool-layout` and `data-tool-padding` attributes.
- **Prefer Tailwind color tokens** like `bg-background` and `text-foreground` to ensure automatic theme inheritance.
- **Implement loading and error states** in your UI to prevent blank or broken widgets when async operations fail.

The server-side implementation of these security checks resides in [`actions/create-extension.ts`](https://github.com/BuilderIO/agent-native/blob/main/actions/create-extension.ts) and [`actions/update-extension.ts`](https://github.com/BuilderIO/agent-native/blob/main/actions/update-extension.ts).

## Summary

- **Extensions** are sandboxed Alpine.js components stored in the `tools` table and rendered in isolated iframes within Agent-Native applications.
- **Communication** with the host occurs only through injected helpers: `appAction`, `appFetch`, `dbQuery`, `extensionFetch`, and `extensionData`.
- **Creation** happens via CLI (`pnpm action create-extension`) or HTTP POST to `/_agent-native/extensions`.
- **Complex state** should use `Alpine.data()` registration rather than inline `x-data` attributes.
- **Data persistence** uses `extensionData.set()` and `.list()` with automatic scoping (`user`, `org`, `all`).
- **External APIs** are accessed through `extensionFetch` with `${keys.NAME}` secret injection to prevent credential exposure.
- **Updates** support both JSON Patch operations and structured edits targeting named HTML comment sections.

## Frequently Asked Questions

### What is the difference between extensionData and toolData?

`extensionData` is the modern alias for `toolData`, which is the legacy name maintained for backward compatibility. Both refer to the same key-value storage API that provides automatic table creation, scoping by user or organization, and upsert functionality. New code should use `extensionData` as it aligns with current Agent-Native terminology found in [`references/api.md`](https://github.com/BuilderIO/agent-native/blob/main/references/api.md).

### How do I prevent extensions from accessing sensitive host resources?

Agent-Native enforces security through iframe sandboxing. Extensions cannot access the host DOM, localStorage, cookies, or filesystem. All interactions with the server must go through the injected helper functions, which enforce permissions at the action and database level. Additionally, the `extensionFetch` proxy prevents extensions from making direct external HTTP requests that could leak host IP addresses or headers.

### Can I use npm packages in Agent-Native extensions?

No, extensions cannot import npm packages directly because they run in sandboxed iframes without access to a module bundler. You must either use vanilla JavaScript with Alpine.js or leverage the injected helpers for functionality. For complex dependencies, build the logic into server-side actions accessible via `appAction`, or use `extensionFetch` to call external services that host the required functionality.

### How do I debug an extension that isn't rendering correctly?

First, check the browser console for JavaScript errors inside the extension's iframe context. Verify that Alpine.js directives are properly formed and that `Alpine.data()` components are registered before the DOM initializes. For server-side issues, inspect the network requests made by `extensionFetch` or `appAction` to ensure proper authentication and payload formatting. The [`references/examples.md`](https://github.com/BuilderIO/agent-native/blob/main/references/examples.md) file contains five working examples you can compare against your implementation.