Advanced Plugins
Plugin Tabs (Full HTML UI)
Section titled “Plugin Tabs (Full HTML UI)”The plugins covered in Building Plugins run as background scripts — they add toolbar buttons, status bar items, and modals, but don’t have their own dedicated UI panel. Tab plugins go further: they get their own tab in Atlas with a full HTML interface rendered in a sandboxed iframe.
Setting up a tab plugin
Section titled “Setting up a tab plugin”Add a ui field to your plugin.json:
{ "id": "dashboard", "name": "Pomodoro Dashboard", "version": "1.0.0", "author": "Your Name", "description": "Visual dashboard for tracking pomodoro sessions", "main": "main.js", "permissions": ["ui_components", "read_vault"], "enabled": true, "ui": { "page": "index.html", "tab_name": "Pomodoros", "icon": "🍅" }}ui fields:
| Field | Type | Description |
|---|---|---|
page | string | Path to the HTML file (relative to plugin folder) |
tab_name | string | Label shown on the tab button |
icon | string | Emoji or short text for the tab icon |
When the plugin is enabled, Atlas creates a tab button in the tab bar. Clicking it shows your HTML page in a sandboxed iframe.
The iframe sandbox
Section titled “The iframe sandbox”Tab plugin HTML runs inside an <iframe sandbox="allow-scripts allow-forms">. This means:
- Your HTML can run JavaScript and submit forms
- Your HTML cannot access the parent Atlas window or DOM
- Your HTML cannot navigate the parent frame
- Your HTML cannot open popups
Atlas injects a bridge script into your iframe that creates the same atlas global object available to script plugins. All atlas.* API calls work from inside the iframe via postMessage IPC — they’re async and behave identically to the script API.
Example: Tab plugin HTML
Section titled “Example: Tab plugin HTML”Create index.html in your plugin folder:
<!DOCTYPE html><html><head> <style> body { font-family: system-ui, sans-serif; padding: 1rem; background: var(--bg-color, #1a1a2e); color: var(--text-color, #e0e0e0); } .stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; margin-top: 1rem; } .stat-card { background: rgba(255, 255, 255, 0.05); border-radius: 8px; padding: 1rem; text-align: center; } .stat-value { font-size: 2rem; font-weight: bold; } </style></head><body> <h1>Pomodoro Dashboard</h1> <div class="stats"> <div class="stat-card"> <div class="stat-value" id="total">0</div> <div>Total Pomodoros</div> </div> <div class="stat-card"> <div class="stat-value" id="today">0</div> <div>Today</div> </div> <div class="stat-card"> <div class="stat-value" id="streak">0</div> <div>Day Streak</div> </div> </div>
<script> // The atlas API is available globally in the iframe async function loadStats() { try { const history = await atlas.data.read('pomodoro-history.json'); const data = JSON.parse(history);
document.getElementById('total').textContent = data.totalPomodoros || 0; document.getElementById('today').textContent = data.todayCount || 0; document.getElementById('streak').textContent = data.streak || 0; } catch (e) { atlas.plugin.log('No history file yet'); } }
loadStats(); </script></body></html>Combining script and tab plugins
Section titled “Combining script and tab plugins”A plugin can have both a main.js (for toolbar buttons, commands, background logic) and a ui tab (for a rich HTML interface). The main.js runs in the main Atlas context, while the tab HTML runs in its iframe. They share the same atlas.data.* storage, so you can pass data between them:
// In main.js — save state when a pomodoro completesawait atlas.data.write('pomodoro-history.json', JSON.stringify(stats));
// In index.html — read the same data for displayconst history = await atlas.data.read('pomodoro-history.json');HTTP Server API
Section titled “HTTP Server API”Atlas runs a local HTTP server (default port 21847) that external tools can use to interact with the vault. This is useful for integrations with scripts, CLI tools, editors, or other applications outside Atlas.
Enabling the server
Section titled “Enabling the server”The plugin server must be started in Atlas Settings > Plugins. Once running, it listens on 127.0.0.1:21847 (localhost only — not accessible from other machines).
Authentication
Section titled “Authentication”All requests require the X-Atlas-Secret header. You can find (or regenerate) the secret in Settings > Plugins.
Endpoints
Section titled “Endpoints”GET /api/health
Section titled “GET /api/health”Check if the server is running.
curl http://localhost:21847/api/health \ -H "X-Atlas-Secret: your-secret"Response:
{ "status": "ok" }GET /api/vault/read
Section titled “GET /api/vault/read”Read a vault file.
| Parameter | Type | Description |
|---|---|---|
path | query string | Path relative to vault root |
curl "http://localhost:21847/api/vault/read?path=daily/2026-02-27.md" \ -H "X-Atlas-Secret: your-secret"Response:
{ "content": "# February 27, 2026\n\n- [ ] Review PRs\n" }POST /api/vault/write
Section titled “POST /api/vault/write”Create or overwrite a vault file.
Request body:
{ "path": "notes/from-script.md", "content": "# Created by external tool\n" }curl -X POST http://localhost:21847/api/vault/write \ -H "X-Atlas-Secret: your-secret" \ -H "Content-Type: application/json" \ -d '{"path": "notes/from-script.md", "content": "# Created by script\n"}'GET /api/vault/list
Section titled “GET /api/vault/list”List files and directories.
| Parameter | Type | Description |
|---|---|---|
path | query string | Directory path relative to vault root |
curl "http://localhost:21847/api/vault/list?path=daily" \ -H "X-Atlas-Secret: your-secret"DELETE /api/vault/delete
Section titled “DELETE /api/vault/delete”Delete a vault file.
| Parameter | Type | Description |
|---|---|---|
path | query string | Path relative to vault root |
curl -X DELETE "http://localhost:21847/api/vault/delete?path=notes/old.md" \ -H "X-Atlas-Secret: your-secret"POST /api/search
Section titled “POST /api/search”Search the vault using RAG.
Request body:
{ "query": "meeting notes from last week", "limit": 10 }curl -X POST http://localhost:21847/api/search \ -H "X-Atlas-Secret: your-secret" \ -H "Content-Type: application/json" \ -d '{"query": "project deadlines"}'GET /api/memory/context
Section titled “GET /api/memory/context”Get the current memory context (useful for RAG-aware integrations).
curl http://localhost:21847/api/memory/context \ -H "X-Atlas-Secret: your-secret"Example: Python integration
Section titled “Example: Python integration”import requests
SECRET = "your-atlas-secret"BASE = "http://localhost:21847"HEADERS = {"X-Atlas-Secret": SECRET}
# Read today's daily noteresp = requests.get( f"{BASE}/api/vault/read", params={"path": "daily/2026-02-27.md"}, headers=HEADERS)content = resp.json()["content"]
# Search the vaultresp = requests.post( f"{BASE}/api/search", json={"query": "project status updates"}, headers=HEADERS)for result in resp.json()["results"]: print(result["path"])
# Write a filerequests.post( f"{BASE}/api/vault/write", json={"path": "notes/automated-report.md", "content": "# Daily Report\n..."}, headers=HEADERS)Per-Plugin Data Storage
Section titled “Per-Plugin Data Storage”Every plugin gets a sandboxed data/ directory inside its plugin folder for persistent storage. This is available to all plugins without any permission.
atlas/plugins/pomodoro/├── plugin.json├── main.js└── data/ # Auto-created on first write ├── history.json # Your plugin's data files └── settings-cache.jsonUse atlas.data.read(filename) and atlas.data.write(filename, content) to access it. See the Plugin API Reference for full details.
When to use atlas.data.* vs atlas.config.*:
| Use | For |
|---|---|
atlas.data.* | Larger data: session history, cached content, logs, exported data |
atlas.config.getPluginSettings() | Small settings: user preferences, toggles, numeric values |
Plugin settings (via atlas.config.*) are stored in Atlas’s main config file. Plugin data files are stored in the plugin’s own folder within the vault.
Plugin Settings
Section titled “Plugin Settings”If your plugin has user-configurable options, use atlas.config.getPluginSettings() and setPluginSettings(). These require no special permission and persist across app restarts.
async function onLoad() { // Load saved settings (or defaults) const settings = await atlas.config.getPluginSettings(); const workMinutes = settings.workMinutes || 25; const breakMinutes = settings.breakMinutes || 5;
atlas.plugin.log(`Work: ${workMinutes}min, Break: ${breakMinutes}min`);}To let users change settings, you could show a modal:
async function openSettings() { const current = await atlas.config.getPluginSettings();
const result = await atlas.ui.showModal({ title: 'Pomodoro Settings', content: ` <div style="padding: 0.5rem;"> <label>Work duration (minutes):</label> <input type="number" id="work-min" value="${current.workMinutes || 25}" /> <br/><br/> <label>Break duration (minutes):</label> <input type="number" id="break-min" value="${current.breakMinutes || 5}" /> </div> `, buttons: [ { label: 'Cancel', value: 'cancel', type: 'secondary' }, { label: 'Save', value: 'save', type: 'primary' } ] });
if (result.value === 'save') { await atlas.config.setPluginSettings({ workMinutes: parseInt(result.formData['work-min']) || 25, breakMinutes: parseInt(result.formData['break-min']) || 5 }); atlas.ui.showNotification('Settings saved!', 'success'); }}Publishing to the Marketplace
Section titled “Publishing to the Marketplace”Once your plugin is ready to share with other Atlas users:
- Create a plugin listing through the Atlas developer portal at atlasnotes.io
- Fill in the name, description, category, screenshots, and repository URL
- Submit for review — the Atlas team reviews all submissions for safety and quality
- Once approved, your plugin is published and visible to all users in the marketplace
Review criteria
Section titled “Review criteria”The review checks for:
- Permissions match what the plugin actually uses (don’t request
write_vaultif you only read) - No malicious behavior or hidden data collection
- A working
plugin.jsonwith accurate metadata - A public repository or download URL
- Proper cleanup in
onDisable()(no orphaned timers or listeners)
Updating your plugin
Section titled “Updating your plugin”Increment the version field in plugin.json and submit an update through the developer portal. Updates go through a lighter review than the initial submission.
Security Model
Section titled “Security Model”Understanding what plugins can and cannot do helps you build safely and helps users trust your plugin.
What plugins CAN do
Section titled “What plugins CAN do”- Read and write vault files (with
read_vault/write_vaultpermission) - Add UI elements: toolbar buttons, status bar items, modals, notifications
- Execute Atlas agent tools (search, create notes, manage tasks)
- Register keyboard shortcuts
- Store persistent data in the plugin’s
data/directory - Read and write plugin-specific settings
- Make HTTP requests (with
networkpermission)
What plugins CANNOT do
Section titled “What plugins CANNOT do”- Access the filesystem outside the vault
- Execute shell commands or spawn processes
- Access the Atlas app’s internal state or DOM directly (tab plugins run in sandboxed iframes)
- Access other plugins’ data directories
- Call APIs that require permissions not listed in their manifest
Sandboxing
Section titled “Sandboxing”Script plugins (main.js) run inside a Function() sandbox in the Atlas webview. They can only access the atlas global — not window, document, or other browser APIs directly.
Tab plugins (index.html) run inside <iframe sandbox="allow-scripts allow-forms">. They cannot access the parent frame’s DOM or JavaScript context. All Atlas API calls go through a secure postMessage bridge.
Path safety
Section titled “Path safety”All vault operations reject path traversal (..). Plugin data filenames reject path separators (/, \) and traversal. These checks are enforced at both the JavaScript API layer and the Rust backend.