> ## Documentation Index
> Fetch the complete documentation index at: https://crosslink.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart

> A desktop app with a secure mobile companion, in about five minutes

You will build a desktop host, a mobile page, and nothing else. Crosslink
provides the pairing screen, the QR, the mobile onboarding, the installable PWA,
the offline screen and the reconnection.

## Prerequisites

* Node.js >= 20.19 (22 recommended)
* npm

## 1. Install

```bash theme={null}
mkdir my-crosslink-app && cd my-crosslink-app
npm init -y
npm install @crosslink/sdk-node
```

The browser SDK is served to your pages by the host, so there is no bundler and
no second install.

## 2. Write the host

`host.mjs`:

```js theme={null}
import { createCrosslinkServer } from "@crosslink/sdk-node";

const notes = [{ id: "1", title: "Hello from your computer" }];

const host = createCrosslinkServer({
  application: {
    id: "com.example.notes",
    name: "Example Notes",
    shortName: "Notes",
    accentColor: "#f97316",
    backgroundColor: "#101014",
    appearance: "dark"
  },
  // Your mobile UI. Crosslink serves it, plus the manifest, service worker,
  // icons, browser SDK and all of its own onboarding screens.
  mobile: { entry: "./mobile/index.html" },
  capabilities: [
    { id: "notes.read", title: "Read your notes", risk: "low" },
    { id: "notes.write", title: "Create notes", risk: "medium" }
  ],
  lan: { enabled: true, bind: "all" }
});

host.expose("notes.list", () => notes, { capability: "notes.read" });
host.expose(
  "notes.create",
  ({ title }) => {
    const note = { id: String(notes.length + 1), title };
    notes.push(note);
    return note;
  },
  { capability: "notes.write" }
);

await host.start();
console.log(host.describeMobileDelivery().message);
```

## 3. Write the desktop page

`public/index.html`:

```html theme={null}
<!doctype html>
<html lang="en">
<head><meta charset="utf-8" /><title>Example Notes</title></head>
<body>
  <h1>Example Notes</h1>
  <div id="crosslink"></div>

  <script src="/__crosslink/widget.js"></script>
  <script>
    // The pairing card mints the session, renders the QR and the nine-digit
    // code, refreshes both before they expire, and lists paired devices. It
    // takes the app's name, icon and colours from the host's `application`
    // block, so nothing about the app is restated here.
    CrosslinkSDK.createPairingCard({ target: "#crosslink" });
  </script>
</body>
</html>
```

Serve it from a loopback-bound server with Crosslink's control surface mounted:

```js theme={null}
import http from "node:http";
import { readFile } from "node:fs/promises";

const crosslink = host.createControlHandler({
  fallback: async (req, res) => {
    res.writeHead(200, { "content-type": "text/html" });
    res.end(await readFile("./public/index.html"));
  }
});

http.createServer((req, res) => void crosslink(req, res)).listen(8100, "127.0.0.1");
```

The control surface only answers requests from this machine — it mints pairing
codes and revokes devices, so it refuses anything else.

## 4. Write the mobile page

`mobile/index.html`:

```html theme={null}
<!doctype html>
<html lang="en">
<head><meta charset="utf-8" /><title>Notes</title></head>
<body>
  <ul id="notes" hidden></ul>

  <script>
    crosslink.onConnected(async (rpc) => {
      const list = document.getElementById("notes");
      list.hidden = false;
      list.replaceChildren(
        ...(await rpc.call("notes.list")).map((n) => {
          const li = document.createElement("li");
          li.textContent = n.title;
          return li;
        })
      );
    });

    crosslink.onDisconnected(() => {
      document.getElementById("notes").hidden = true;
    });
  </script>
</body>
</html>
```

There is no manifest link, no service-worker registration, no SDK script tag and
no pairing screen here. Crosslink injects what the page needs and has already
run pairing, install and reconnection by the time `onConnected` fires.

## 5. Run it

```bash theme={null}
node host.mjs
```

Open `http://127.0.0.1:8100`, scan the QR with a phone on the same Wi-Fi, and
Crosslink takes it from there: pairing and then your notes list. The phone loads
the desktop's plain-HTTP LAN address in this development mode, so the browser
does not register a Service Worker or provide the durable installed-PWA
lifecycle. The secure published mode linked under **Before you ship** is the
Add to Home Screen path.

## What you did not write

* a pairing screen, QR generation, or a nine-digit code UI
* a pairing-session fetch, refresh timer or expiry handler
* a manifest, a service worker, or icon generation
* Add to Home Screen or Continue in Browser
* an offline screen, a reconnect loop, or revocation handling
* a bundler configuration

## Before you ship

Read [Durable Origins](/client/durable-origin). Add to Home Screen and the
cached offline screen depend on the origin the phone loaded, and a plain-HTTP
LAN address gives you neither. Your host says which case you are in:

```ts theme={null}
host.describeMobileDelivery().message;
```

## Next steps

<CardGroup>
  <Card title="Pairing Card" icon="qrcode" href="/client/pairing-card">
    The desktop pairing UI and its options
  </Card>

  <Card title="Mobile Bootstrap" icon="mobile" href="/client/mobile-bootstrap">
    What happens between the scan and your app
  </Card>

  <Card title="Durable Origins" icon="lock" href="/client/durable-origin">
    Making an installed app survive the desktop going offline
  </Card>

  <Card title="Capabilities and RPC" icon="key" href="/build/capabilities-and-rpc">
    Capability-gated methods and events
  </Card>
</CardGroup>
