"Ecosystem, Developer Experience (DX) is everything."
This guide will walk you through creating your first Synura extension.
Synura uses a Data-Driven architecture that inverts the traditional web development model:
| Traditional (HTML) | Synura |
|---|---|
| Write HTML structure first | Write JavaScript data first |
| Add JS for interactivity | Views render automatically |
| You control every pixel | Strict, optimized view templates |
| Markup → Logic | Data → Views |
How it works:
- Your extension provides data (JavaScript objects)
- You specify a view type (
list,post,chat, etc.) - Synura renders the UI natively - no HTML, no CSS
// You write data, not markup
synura.open({
view: '/views/list',
models: {
contents: [
{ title: "Article 1", author: "Alice", date: "Today" },
{ title: "Article 2", author: "Bob", date: "Yesterday" }
]
}
});
// Synura renders a native list automaticallyBenefits:
- 🚀 Faster development - No UI code to write
- 📱 Native performance - Views are optimized for each platform
- 🎨 Consistent UX - All extensions look polished
- 🔒 Secure - No DOM manipulation, no XSS risks
We provide a Visual Polyfill that simulates the Synura environment directly in your web browser. This allows you to develop and debug extensions using standard DevTools before testing them on the device. Enable Debug Mode to set breakpoints and step through your handler functions.
- Polyfill Guide: Read this first! It explains how to use the emulator.
- synura_polyfill.js: The emulator script.
- synurart CLI Shell Guide: AI-friendly command reference for local runtime simulation and scripted testing.
- A text editor (VS Code, Sublime Text, etc.).
- The Synura application installed on your device.
A Synura extension is a single JavaScript file that exports a SYNURA object containing metadata and the extension logic.
The SYNURA object serves as the manifest and entry point for your extension.
var SYNURA = {
name: "My First Extension",
api: 0,
version: 0.1,
description: "A simple hello world extension.",
license: "Apache-2.0",
domain: "example.com",
host_permissions: ["https://example.com/*"],
icon: "emoji:📖",
bypass: "firefox",
main: {
home: function() {},
deeplink: function(url) {
return true;
},
resume: function(viewId, context) {}
}
};- domain: The extension identity and default network scope. If
host_permissionsis omitted,fetchcan only usehttp://<domain>/*andhttps://<domain>/*. - host_permissions: Optional explicit network permissions for
fetch. Use this when the site needs sibling subdomains or a narrower path scope, for example["https://*.github.io/*"]. Patterns must stay in the same registrable domain family asdomain; public registry wildcards such as*.io,*.com, and*.co.krare rejected. - icon: The icon must be a URL. It can be an HTTP(S) URL to an image (hosted on the same
domain) or an Emoji URL (e.g.,emoji:📖). If omitted,https://<domain>/favicon.icois used by default.
The main object (inside SYNURA) must define a home function, which is the entry point when the user opens your extension.
main: {
home: function() {},
deeplink: function(url) {
return true;
},
resume: function(viewId, context) {}
}You can enable deep linking to allow your extension to handle links matching your domain.
- Add
deeplink: trueto yourSYNURAobject. - Implement the
deeplink(url)function in yourmainobject.
const SYNURA = {
domain: "example.com",
deeplink: true,
main: {
deeplink: function(url) {
if (url.includes("/post/")) {
synura.open("/views/post", {
models: {
link: url,
}
});
return true;
}
return false;
}
}
};Synura supports bookmarking views. Users can browse their bookmarks, which display the cached state of the view (snapshot).
However, if the user wants to interact with the view again (restore it to a live runtime), they will tap the restore icon in the bookmark list. This action triggers the resume function in your extension instead of home.
Your extension must implement resume to re-attach event listeners to the restored view.
resume: function(viewId, context) {
synura.connect(viewId, context, function(event) {
if (event.eventId === "LOAD") {}
});
}You can allow users to install your extension by simply typing your website domain (e.g., https://example.com) into Synura.
- Host
synura.js: Place your extension file athttps://yourdomain.com/synura.js. - Configure
SYNURA.domain: Ensure thedomainfield in yourSYNURAobject matches your hosting domain.
const SYNURA = {
name: "My Extension",
domain: "yourdomain.com",
};When a user enters `yourdomain.com` or `https://yourdomain.com`, Synura will automatically fetch `https://yourdomain.com/synura.js`, verify the domain match, and install the extension. If no protocol is provided, `https://` is used by default.
The internal DOM engine supports the following CSS selectors for querySelector and querySelectorAll:
- Tag Name: Selects elements by tag name (e.g.,
div,a). - Class: Selects elements by class name (e.g.,
.content,.active). - ID: Selects elements by ID (e.g.,
#main). - Attributes: Selects elements by attribute presence or value (e.g.,
[href],[data-type="post"]). - Compound: Combines tag, class, and ID (e.g.,
div.content,a#link.active). - Descendant: Selects nested elements (e.g.,
div pselects all<p>inside<div>). - Direct Child: Selects direct children (e.g.,
ul > li).
In addition to querySelector and querySelectorAll, the Element object supports:
getAttribute(name): Returns the value of the specified attribute. Returns an empty string if the attribute does not exist.
Let's create a simple extension that displays a list with a single item.
- Create a file named
hello_world.js. - Paste the following code:
var SYNURA = {
name: "Hello World",
api: 0,
version: 0.1,
description: "My first Synura extension.",
license: "Apache-2.0",
main: {
home: function() {
synura.open('/views/list', {
styles: {
title: "Hello World Extension"
},
models: {
contents: [{
title: "Welcome to Synura!",
author: "Me",
date: new Date().toLocaleDateString()
}]
}
});
}
}
};- Save the file.
- Load the extension into Synura (refer to the app's import instructions).
- Open the extension from the main menu. You should see a list with "Welcome to Synura!".
Extensions can listen to events from the UI using the onViewEvent callback.
These events are triggered automatically by the system:
LOAD: Triggered when a view is first rendered or comes into focus. Use this to fetch data or initialize the view.Note:
LOADevents do not reset the "Total Time" (t) metric in the Developer Overlay, ensuring that the initial user action (e.g., clicking a list item) remains the start time for performance measurement.CLOSE: Triggered when a view is popped from the navigation stack.REFRESH: Triggered by pull-to-refresh actions.
These events are triggered by user actions:
CLICK: Tapped on an item (list item, card, etc.).DOUBLE_CLICK: Double-tapped an item.SUBMIT: Form submitted.QUERY: Search query entered.MENU_CLICK: Top-level view menu item selected.ITEM_MENU_CLICK: Item context menu action selected (e.g., on a comment or card).
When Developer Mode is enabled in Settings, a floating overlay displays real-time performance metrics for extension execution.
| Key | Description |
|---|---|
| t | Total time – End-to-end latency from user action to visible UI (includes Flutter render) |
| b | Backend time – Total backend execution time, including extension logic (total minus fetch) |
| f | Fetch time – Total HTTP request duration |
| c | Fetch count – Number of HTTP requests made |
c:/views/...– CreateView (new view opened)u:/views/...– UpdateView (existing view refreshed)
18:30:45 c:/views/list: t=1,245, b=89, f=1,120, c=2
This means:
- Total end-to-end time: 1,245ms
- Backend execution: 89ms
- Network fetches: 1,120ms (2 requests)
When the AI engine is enabled, additional metrics are logged for analysis and translation operations:
| Key | Description |
|---|---|
| t | Total AI processing time |
| f | Fetch time (LLM API call) |
| it | Input tokens sent to the LLM |
| ot | Output tokens received from the LLM |
| ap | AI provider (e.g., gemini, openai) |
| am | AI model name |
a:/views/...– AI Analysis (content summarization, sentiment)t:/views/...– AI Translation
18:30:47 a:/views/post: t=890, f=850, it=1,024, ot=256, ap=gemini
- Explore the API Reference to learn about other view types like
post,chat, andsettings. - Check out Examples for more complex use cases.