Getting started
An Olan App is a small React program that runs in a sandbox (a Web Worker on web/desktop, an
isolated JS engine on mobile). It does not draw anything itself: it describes its screen with the
components of @olan-app/sdk, and Olan draws them with its own native controls on every platform.
Data comes from olan.* calls that Olan answers after checking the app's permissions.
Formerly @olan/app-sdk; the old name still builds.
Create an app #
npx olan-app init "Geciken görevler" --template list-detail # or: olan app init …
cd geciken-gorevler
npm install
npx olan-app validate --strict
npx olan-app test
npx olan-app dev # serve it to Olan Studio (Geliştirme sunucuları)
Templates: blank (one screen), list-detail (projects → tasks → task), project-widget (a card on
the project page), project-tab (a project tab and project settings with olan.project.config), project-records
(the app's records on tasks: a task action, a record card, the app's status, calendar items, feed
lines, approvals and a project template; SDK 1.7), external-api (olan.http.fetch with an http:<host> permission), data-list
(olan.db), backend (--backend: UI + backend code that Olan runs, backend.md).
Project layout #
olan-app.json manifest: id, name, version, surfaces, permissions, settings (manifest.md)
src/app.tsx the app; must `export default defineApp({ ... })`
tests/*.test.ts headless tests (node:test + @olan-app/cli/testing) (testing.md)
fixtures/*.json mock data for tests, validate --strict and dev (testing.md#fixtures)
assets/icon.png icon referenced by the manifest
dist/app.js build output (generated, git-ignored)
AGENTS.md, CLAUDE.md instructions for coding agents
The smallest app #
import { useState } from 'react';
import { Button, Screen, Stack, Text, defineApp, olan } from '@olan-app/sdk';
function Home() {
const [count, setCount] = useState(0);
return (
<Screen title="Sayaç">
<Stack gap="md" padding="md">
<Text text={`Düğmeye ${count} kez bastınız.`} />
<Button label="Bas" variant="primary" onPress={() => setCount((value) => value + 1)} />
<Button label="Bildirim" onPress={() => olan.ui.toast({ text: 'Merhaba!', tone: 'success' })} />
</Stack>
</Screen>
);
}
export default defineApp({ app: Home });
{
"schemaVersion": 1,
"id": "com.example.counter",
"name": "Sayaç",
"version": "0.1.0",
"sdk": "^1.8.0",
"entry": "dist/app.js",
"surfaces": { "app": { "title": "Sayaç" } },
"permissions": []
}
Rules (the sandbox enforces them; validate --strict reports them) #
- Surfaces are named components.
defineApp({ app: Home }), never an inline arrow function (the React hook rules only apply to named components). Surface names:app,project.widget,project.tab,project.settings,settings(and the in-context surfaces of surfaces.md) — and each one used must be declared inolan-app.jsonsurfaces. - Only SDK components. No DOM elements (
<div>,<span>), noreact-dom, noreact-native, no Node built-ins (fs,path, …). There is nostyleorclassName: Olan owns colours, fonts and spacing. Usetone,variant,gapandpaddingprops instead. - Text lives in
<Text>. A string directly insideScreen,Stack,CardorFormis an error (tree.text_outside_text). Write<Text text="…" />or<Text>…</Text>. - No browser globals for I/O.
fetch,XMLHttpRequest,WebSocket,localStorage,indexedDB,window,documentdo not exist. Useolan.http.fetch,olan.storage,olan.settings. - Every
olan.*call needs its permission inolan-app.jsonpermissions(permissions.md). Without it the call rejects withOlanErrorcodepermission_denied(http_blockedfor HTTP). - Keys are unique. Every
Listitem,Tablerow,Menuitem andActionhas a uniquekey(string or number); events give it back as{ key }. - Every
Buttonhas alabel. EveryMenuhas alabel(or at least anicon). - Use React normally:
useState,useEffect,useMemo, custom hooks, context. Load data withuseQuery(() => olan.….(), [deps])(hooks.md).
Done means #
npx olan-app validate --strict # manifest, build, TypeScript, every surface rendered, no missing permission
npx olan-app test # all tests pass
Both exit with code 0. Add --json for machine-readable output (cli.md).
Running it in Olan #
During development the app runs unsigned, but only in development mode: Studio, olan-app dev
and "Olan'da dene" (/apps/dev), for people an administrator gave the developer grant, under
a "Geliştirme" label. Studio is in the Olan web app: sign in at your Olan web address (e.g. https://olan.example.com) with an account that has the developer grant (Yönetim → Kullanıcılar) and open <Olan web address>/tr/studio; /tr/studio?section=publish opens Yayınla (marketplace publishing).
Installing it (for yourself or the organization) needs a signed package. An Olan server installs a
package only when its signature verifies and the signing key is on the server's trusted key list.
Every Olan server ships with the Olan Marketplace key built in (olan-marketplace-2026-10-17), and
in production that is the only trusted key, so apps reach real organizations through Olan
Marketplace review. An unsigned package can be uploaded, but it is listed as "İmzasız" and
cannot be installed. For your own local Olan:
npx olan-app keygen --dev # once; prints OLAN_APPS_TRUSTED_KEYS=dev-local=<key> for the local API
npx olan-app validate --strict # first: pack runs only the non-strict checks (and refuses on errors)
npx olan-app pack --dev-sign # dist/<id>-<version>.olanapp, signed with your development key
Then upload it in Olan → Yönetim → Uygulamalar → Katalog (or olan-app publish --org --dev-sign)
and install it there (cli.md#signing-and-publishing). Uploading and, by default, installing are for organization admins: an organization without an install policy is "admin only" until an admin lets members install apps.
For real organizations, submit it to Olan Marketplace (automatic checks, review, signing; marketplace.md):
npx olan-app login --marketplace --server https://api.example.com # once: signs you in in the browser
npx olan-app developer create --marketplace --slug acme --name "Acme Ltd" --accept-agreement # once
npx olan-app publish --marketplace --age-rating 4+ # age rating (4+, 9+, 13+): required before release
npx olan-app release --marketplace <appId> <version> # after approval (`olan-app status` prints the exact command)
With an Olan CLI that has the app command and is signed in, olan app login --marketplace works too. CI does not sign in: it uses the saved session as OLAN_MARKETPLACE_SESSION (marketplace.md#signing-in).
Where to read next #
- components.md — every component and its props
- api.md —
olan.*(projects, chat, storage, settings, http, ui, navigation) - hooks.md, navigation.md —
useQuery,useNavigation, multi-screen apps - permissions.md, manifest.md — what to declare in
olan-app.json - server.md, remote-requests.md, files.md, links.md — the app's own server, requests as the person, file uploads, external sign-in flows and app addresses
- testing.md —
renderApp, fixtures, assertions - cli.md, errors.md — commands and every error code with its fix
- marketplace.md, review-guidelines.md — sign-in, publishing, the automatic checks and the review rules
- examples.md — complete apps to copy from