Photon OS Docs
Creating Apps

Get Started Creating Apps

Build apps for Photon OS using the SDK

Photon OS apps are web applications that run in sandboxed iframes within the OS shell. Apps communicate with the OS through a message-passing API, abstracted by the SDK.

Getting Started

While you can use any web framework, React is recommended as it pairs well with the @photon-os/react package which provides useful hooks.

Create a New Project

npm create vite@latest my-photon-app -- --template react-ts
cd my-photon-app

Install the SDK

npm install @photon-os/sdk @photon-os/react

Using the SDK

Initializing

Create an instance of the OS class to access Photon OS features:

import { OS } from "@photon-os/sdk";

const os = new OS();

The SDK automatically connects to the parent Photon OS shell via PostMessage.

Available Managers

The OS instance provides access to several managers:

ManagerPurpose
os.userGet current user information
os.appsApp lifecycle (get installed, launch, install, uninstall)
os.prefsStore preferences (sandboxed per-app or shared)
os.accountsSecond Life account linking
os.devicesCommunicate with Second Life devices
os.systemSystem info (get/apply scale factor)

Getting the Current User

const user = await os.user.getCurrentUser();
console.log(`Hello, ${user.displayName}!`);

Using Preferences

Store app-specific data that persists across sessions:

// Save a preference (sandboxed to your app)
await os.prefs.set("theme", "dark");

// Read it back
const theme = await os.prefs.get("theme");

// Delete it
await os.prefs.delete("theme");

For data that should be accessible to all apps:

// Shared preferences (all apps can read/write)
await os.prefs.setShared("globalSetting", true);
const value = await os.prefs.getShared("globalSetting");

Send the user back to the OS home screen:

await os.homeButton();

React Integration

The @photon-os/react package provides hooks for common operations.

useInstalledApps

Fetch and monitor installed apps:

import { useInstalledApps } from "@photon-os/react";

function MyComponent() {
  const { installedApps, loading, error, refresh } = useInstalledApps();

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <ul>
      {installedApps.map((app) => (
        <li key={app.bundleId}>{app.name}</li>
      ))}
    </ul>
  );
}

useApplyScale

Photon OS can run at different scale factors (e.g., 2x for high-DPI displays in Second Life). When scaling is enabled, the OS automatically passes the ?scale=X query parameter to your app's iframe URL.

Use useApplyScale to automatically match the OS scale in your app:

import { useApplyScale } from "@photon-os/react";

function App() {
  useApplyScale();

  return <div>My app content</div>;
}

The hook applies the scale factor to your document by:

  • Setting the --os-scale CSS variable
  • Setting zoom on the document element

You also need CSS to properly size your app viewport:

html,
body,
#root {
  width: calc(100dvw / var(--os-scale, 1));
  height: calc(100dvh / var(--os-scale, 1));
  overflow: hidden;
}

The var(--os-scale, 1) provides a fallback of 1 when running outside of Photon OS.

useScale

If you need to read the scale value without automatically applying it:

import { useScale } from "@photon-os/react";

function MyComponent() {
  const { scale, loading, error } = useScale();

  if (loading) return <div>Loading...</div>;

  return <div>Current scale: {scale}x</div>;
}

Scaling Without React

If you're not using React, you can use the SDK directly:

const os = new OS();

// Apply scale to document and get the value
const scale = await os.system.applyScale();

// Or just get the scale value without applying
const scale = await os.system.getScale();

Then manually set the CSS if needed:

document.documentElement.style.setProperty("--os-scale", String(scale));
document.documentElement.style.zoom = String(scale);

Reading Scale from URL

Since the OS passes the scale as a URL query parameter, you can also read it directly without the SDK:

function getScale(): number | null {
  const params = new URLSearchParams(window.location.search);
  const scaleParam = params.get("scale");
  if (scaleParam) {
    const parsed = parseFloat(scaleParam);
    if (!isNaN(parsed) && parsed > 0) return parsed;
  }
  return null;
}

const scale = getScale();
if (scale !== null) {
  document.documentElement.style.setProperty("--os-scale", String(scale));
  document.documentElement.style.zoom = String(scale);
}

This approach works immediately without waiting for the SDK RPC connection.

App Definition

When installing an app, you need to provide:

FieldDescriptionExample
bundleIdUnique identifier for your appcom.mycompany.myapp
nameDisplay name shown to usersMy App
authorDeveloper or company nameMy Company
urlURL where your app is hostedhttps://example.com/app

Installing Your App

To install your app in Photon OS:

  1. Deploy your app to a publicly accessible URL
  2. Open Settings in Photon OS
  3. Go to the Apps section
  4. Click Install App
  5. Fill in the app details:
    • App URL: The URL where your app is hosted
    • App Name: Display name for your app
    • Author: Your name or company
    • Bundle ID: A unique identifier (e.g., com.yourname.appname)
  6. Click Install

Your app will now appear in the Launcher.

Example App

Here's a minimal example that displays the current user and saves a preference:

import { useEffect, useState } from "react";
import { OS, type PhotonUser } from "@photon-os/sdk";

const os = new OS();

function App() {
  const [user, setUser] = useState<PhotonUser | null>(null);
  const [count, setCount] = useState(0);

  useEffect(() => {
    // Load user and saved count on mount
    async function init() {
      const currentUser = await os.user.getCurrentUser();
      setUser(currentUser);

      const savedCount = await os.prefs.get("count");
      if (typeof savedCount === "number") {
        setCount(savedCount);
      }
    }
    init();
  }, []);

  const increment = async () => {
    const newCount = count + 1;
    setCount(newCount);
    await os.prefs.set("count", newCount);
  };

  return (
    <div>
      <h1>Hello, {user?.displayName ?? "Loading..."}!</h1>
      <p>Count: {count}</p>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

export default App;

Type Definitions

The SDK exports TypeScript types for all data structures:

import type {
  PhotonUser,
  AppDefinition,
  SLDevice,
  DeviceMessage,
  PreferenceValue,
} from "@photon-os/sdk";

Next Steps