Skip to main content

Concepts

How Omni works, in 5 minutes.

1. The three "places" a widget can live

iOS has different spots for widgets, and Omni supports all of them:

CategoryTypeWhere it shows up
Home screenSmall / Medium / LargeHome screen
Lock screenCircular / Rectangular / InlineLock screen
Control CenterButton / ToggleControl Center pull-down
Dynamic IslandLive ActivityCapsule area at the top of the screen

Each type has a different size and different capabilities (for example, lock screen widgets can't use color). When you create a new widget, pick the type first.

2. A widget has two parts

1. View (what you see)

The view is built from components. Common ones:

  • text text, image image, SF icon icon, shape shape
  • button button, date/timer date, gauge gauge, spacer spacer
  • Containers: horizontal hstack, vertical vstack, stacked zstack, timed-switch tstack, grid grid
  • Loop loop (repeats a component N times to render array data)

See the Components section for details.

2. Data (the dynamic part)

If you want your widget to show "live data" (weather, unread email, stock prices, etc.), you need to write a snippet of JavaScript.

// Example: fetch data from an API
const req = new Request("https://2f0.cn/api/omni")
const obj = await req.fetchJSON()
this.title = obj.omni // store the result on this.xxx so the components can use it

The result is bound to components via placeholders:

In the text component's "content" field, write: {title}

The widget will then display the value of obj.omni.

Key rule

Only variables written to this.xxx or declared with a top-level let/var/const xxx can be consumed by components. After running the JS, Omni grabs those variables and fills them into the placeholders.

Hiding internal variables

By default, every top-level let / var / const becomes a widget output field. If you don't want a variable exposed to components (internal constants, module-level temporaries), prefix the name with _ and the extractor will skip it. Variables declared inside function bodies are automatically ignored — no need to prefix.

let title = "Hello"            // → output field
const _PAGE_SIZE = 20 // _ prefix, not output
const _WEEK = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]

function load() {
const now = new Date() // function-local, auto-skipped, no _ needed
}

3. Using JS

  • Standard JS syntax, async/await, Promise, Date, JSON all work
  • No window, document, or fetch (use Omni's Request instead)
  • No npm — only Omni's built-in APIs
  • The widget runtime has limited memory (under 30MB) — don't load huge files

4. When does a widget refresh?

iOS decides. Generally:

  • The more often you view it → the system thinks you check it often → it refreshes more frequently
  • Roughly 40-70 times per day (you can't refresh on demand)
  • You can "suggest": setUpdateFreq(30) means "refresh again in 30 minutes" — the system takes it as a hint
warning

Don't expect instant refresh from widgets. For near-real-time scenarios, use Live Activities (Dynamic Island).

5. Settings (so others can use your template)

If you built a widget and want to share it, but others need to fill in their own token, city, etc. Omni offers "settings" — you leave a few slots empty, and after import, others fill them in via the settings panel. See Config read/write.


Next step: Build your first widget →