---
title: "Quickstart: a Vite + SDK app"
description: "A small Business NXT App scaffolded with Vite and the @business-nxt/app-messaging SDK - it runs an authenticated GraphQL query and reads the user’s selection."
lang: en
languages:
  en: https://docs.vismasoftware.no/businessnxtapi/apps/sample/index.md
lastmod: 2026-07-10
---

> Documentation index: https://docs.vismasoftware.no/businessnxtapi/apps/llms.txt


# Quickstart: a Vite + SDK app

Last modified July 10, 2026

> A small Business NXT App scaffolded with Vite and the @business-nxt/app-messaging SDK - it runs an authenticated GraphQL query and reads the user’s selection.


The quickest way to a working Business NXT App: a Vite project plus the [`@business-nxt/app-messaging`](https://docs.vismasoftware.no/businessnxtapi/apps/index.md) SDK. The bundler inlines the SDK, so there is no CDN to allow-list in your CSP. The app reads the current company from the URL, runs a GraphQL query, and asks Business NXT for the current selection.

It also pulls in [`@vsn-ux/gaia-styles`](https://www.npmjs.com/package/@vsn-ux/gaia-styles), the Visma design system, so the app looks at home inside Business NXT - import the stylesheet once and apply `ga-*` classes to your elements.

## Scaffold

```bash
npm create vite@latest my-app -- --template vanilla-ts
cd my-app
npm install @business-nxt/app-messaging @vsn-ux/gaia-styles
npm install -D @vitejs/plugin-basic-ssl
```

## vite.config.ts

Business NXT serves over HTTPS and loads your app in an iframe, so the dev server must also be HTTPS - an HTTP child is blocked as mixed content.

```ts { title = "vite.config.ts" }
import { defineConfig } from "vite";
import basicSsl from "@vitejs/plugin-basic-ssl";

export default defineConfig({
  plugins: [basicSsl()],
});
```

The plugin attaches its generated certificate to `server.https` for you, so no extra `server` config is needed.

The plugin generates a self-signed certificate on first run. Visit `https://localhost:5173/` once in your browser and accept the warning so the iframe can load.

## index.html

```html { title = "index.html" }
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>Business NXT App sample</title>
  </head>
  <body>
    <h1>Associates</h1>
    <button id="load" class="ga-button ga-button--primary">Load associates</button>
    <p id="status"></p>
    <ul id="list"></ul>
    <script type="module" src="/src/main.ts"></script>
  </body>
</html>
```

## src/main.ts

```ts { title = "src/main.ts" }
import "@vsn-ux/gaia-styles/all.css";
import {
  ExecuteGraphQL,
  GraphQLRequestError,
  SendMessage,
} from "@business-nxt/app-messaging";

// Business NXT passes the current context in the iframe URL.
const companyNo = Number(new URLSearchParams(location.search).get("companyNo"));

const status = document.getElementById("status")!;
const list = document.getElementById("list")!;

const GetAssociates = /* GraphQL */ `
  query GetAssociates($companyNo: Int!, $first: Int!) {
    useCompany(no: $companyNo) {
      associate(first: $first) {
        items { associateNo name }
      }
    }
  }
`;

interface Associates {
  useCompany: { associate: { items: { associateNo: number; name: string }[] } };
}

document.getElementById("load")!.addEventListener("click", async () => {
  status.textContent = "Loading...";
  list.replaceChildren();
  try {
    const data = await ExecuteGraphQL<Associates>(
      GetAssociates,
      { companyNo, first: 10 },
      { operationName: "GetAssociates" },
    );
    const items = data.useCompany.associate.items;
    status.textContent = `${items.length} associate(s) in company ${companyNo}`;
    for (const a of items) {
      const li = document.createElement("li");
      li.textContent = `${a.associateNo} - ${a.name}`;
      list.appendChild(li);
    }
  } catch (err) {
    status.textContent =
      err instanceof GraphQLRequestError
        ? err.message
        : "The associates couldn't be loaded.";
  }
});

// Ask Business NXT for the current row selection on startup.
SendMessage({ messageType: "selection-state-request" })
  .then((selection) => console.log("Current selection:", selection))
  .catch((err) => console.warn("No selection yet:", err));
```

## Run it

```bash
npm run dev     # local dev server
npm run build   # outputs static files to dist/
```

Deploy the contents of `dist/` to the HTTPS origin your app is served from. Business NXT loads it inside an iframe.

> [!NOTE]
>
> The app only does something when it runs inside Business NXT - that is the `window.parent` the SDK talks to. Opened standalone, requests stay pending; open it from within Business NXT instead.

Building with React? `@business-nxt/app-messaging-react` ships the same API plus hooks like `useSelection` and `useEditStatus`.


---

[View this page](https://docs.vismasoftware.no/businessnxtapi/apps/sample/)
