> ## Documentation Index
> Fetch the complete documentation index at: https://developer.jtl-software.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Quickstart: From Template

> Clone the sample app, add your credentials, and have a running JTL Cloud App in under 15 minutes.

**What you'll build:** A full-stack Cloud App (React frontend + Express/.NET/PHP backend) that authenticates with the JTL platform and runs inside the App Shell.

## Prerequisites

Before you start, make sure you have:

<Steps>
  <Step title="Account and Access">
    You need:

    * ✅ **A JTL ID** (your login to the JTL ecosystem)
    * ✅ **Access to an organization (tenant) in the [Partner Portal](https://partner.jtl-cloud.com)** (created automatically on first login if you don’t have one yet)

    If you don't have these yet, follow the step-by-step guide: [Create a Developer Account](/get-started/create-developer-account).

    You'll also need:

    * ✅ **[JTL-Wawi](https://www.jtl-software.com/de/warenwirtschaftssystem-software/jtl-wawi-download)** installed and running locally
  </Step>

  <Step title="Tools Installed">
    You'll need the following tools installed on your machine:

    * [Node.js](https://nodejs.org/) **v24.16.0 or higher** (current LTS, includes `npm`). Verify with `node --version` and `npm --version`
    * For .NET template: [.NET SDK](https://dotnet.microsoft.com/en-us/download/dotnet/8.0). Run `dotnet --version` to check
    * For PHP template: [PHP](https://www.php.net/downloads) **8.1 or higher** with `ext-sodium` enabled. Run `php --version` and `php -m | grep sodium` to check
  </Step>
</Steps>

## 1. Create the Project Using Cloud Apps CLI

```bash theme={null}
npm create @jtl-software/cloud-app@latest
```

Follow the prompts to create your app:

* **Ok to proceed? (y):** `y`
* **App name:** `my-jtl-app`
* **Description:** `My JTL Sample App`
* **Backend:** `Node.js` or `.NET` or `PHP`
* **Frontend:** `React (Vite + Tailwind + JTL UI)`

<Tip>Use the **Arrow Keys** to navigate and **Enter** to select.</Tip>

The CLI generates a project that matches your backend choice. The frontend is the same for both.

## 2. Install Dependencies

From the project root, install all dependencies:

```bash theme={null}
cd my-jtl-app
npm install
```

This installs dependencies for the project.

## 3. Register your App

The `register` command signs you in with your [JTL ID](/get-started/create-developer-account#step-1-set-up-your-partner-portal) and lets you select a tenant. It then registers the app and writes the generated credentials to your local environment file. From the project root, run:

```bash theme={null}
npm run register
```

The CLI opens a browser tab to sign you in with your JTL ID. After you sign in, return to the terminal. The CLI then walks you through three short prompts:

* **Register the app in which tenant?** Pick the tenant you want the app registered under. If your account has access to more than one, the CLI lists them all.
* **Review.** The CLI prints a summary of the app it is about to create: `App name`, `Technical name`, `Version`, `Description`, `Tenant`, and `Action`. Verify the values are correct.
* **Create app "\<app-name>" in tenant "\<tenant>"?** Confirm with `Yes` to proceed.

Once the app is created, the CLI writes your client ID and client secret to the appropriate local file for your backend:

| Backend | File written                                            |
| ------- | ------------------------------------------------------- |
| Node.js | `/packages/backend/.env`                                |
| .NET    | `/packages/backend/MyJtlApp.Api/appsettings.Local.json` |
| PHP     | `/packages/backend/.env`                                |

The CLI also returns a link to the app in JTL Hub.

<Warning>
  **Don't commit your environment files.** They contain your client secrets. The CLI writes them to paths that the template's `.gitignore` already excludes, but verify this before pushing to a shared repository.
</Warning>

<Tip>
  If the browser does not open automatically, the CLI prints the sign-in URL in the terminal. Copy it into a browser on a machine that can reach it, complete the sign-in, and return to the terminal to continue.
</Tip>

## 4. Start the App

Run the following command from the root directory to start the development server:

```bash theme={null}
npm run dev
```

This starts both the backend and frontend:

| Service                              | URL                     | Port |
| ------------------------------------ | ----------------------- | ---- |
| Backend (Express or .NET API or PHP) | `http://localhost:3005` | 3005 |
| Frontend (React App)                 | `http://localhost:3004` | 3004 |

## 5. Open the App

Open [http://localhost:3004](http://localhost:3004) in your browser. You should see an instruction page. It's intentional, as the sample app is designed to run inside the JTL Cloud.

## 6. Verify the Connection

Your app is now running locally and connected to the JTL platform using your credentials. To verify everything is working:

1. Open [JTL Hub](https://hub.jtl-cloud.com/) in your browser
2. Navigate to the **Manage apps** menu and click the **Apps in development** tab
3. You should see the `my-jtl-app` listed

<img src="https://mintcdn.com/jtlsoftwaregmbh/F69llFsBfWamC4bD/images/verify_app.png?fit=max&auto=format&n=F69llFsBfWamC4bD&q=85&s=e8ea13ea9b0fba3e4c77c89c46d5c128" alt="Discover apps" width="2604" height="1170" data-path="images/verify_app.png" />

4. Click the **Install** button to install the app.

   Once the app is installed, you'll see a success screen with the option to configure the app. Click the **Configure app** button to proceed to the next step.

<img src="https://mintcdn.com/jtlsoftwaregmbh/F69llFsBfWamC4bD/images/install_on_JTL.png?fit=max&auto=format&n=F69llFsBfWamC4bD&q=85&s=b747d45cc0de7c55139810ef8f6bdca0" alt="Install app on JTL" width="2654" height="1194" data-path="images/install_on_JTL.png" />

Complete the installation process by clicking the **Complete setup** button in the installation wizard.

<img src="https://mintcdn.com/jtlsoftwaregmbh/F69llFsBfWamC4bD/images/complete_setup.png?fit=max&auto=format&n=F69llFsBfWamC4bD&q=85&s=44c8abe5e667e985a5bf1509c8763290" alt="Complete setup" width="2692" height="1508" data-path="images/complete_setup.png" />

## 7. Fetch Data from JTL-Wawi

Your app is running and connected to the platform, but it's not pulling real ERP data yet. The template includes a working demo page that queries Wawi through the ERP's GraphQL API. Let's walk through how it works.

### Open the GraphQL Demo Page

The template ships with a demo page at `packages/frontend/src/pages/graphql-demo-page/` that runs several example queries against your JTL-Wawi (items, orders, stock data).

To view it, see the [Test your App](/get-started/test-your-app) guide.

### How it Works

The frontend never talks to the JTL-Wawi API directly. All requests go through your backend, which handles authentication transparently.

```mermaid theme={null}
sequenceDiagram
  participant FE as Frontend (demo page)
  participant BE as Backend (/graphql proxy)
  participant API as JTL-Wawi API

  FE->>BE: POST /graphql (with X-Session-Token)
  BE->>BE: Verify session token, get tenant ID
  BE->>BE: Fetch access token with client credentials
  BE->>API: POST /erp/v2/graphql (with Bearer token)
  API-->>BE: GraphQL response
  BE-->>FE: Forward response
```

The backend's `POST /graphql` endpoint does five things:

1. Reads the session token from the `X-Session-Token` header
2. Verifies the token and extracts the tenant ID
3. Obtains an access token using your app's client credentials
4. Forwards the GraphQL request to the JTL-Wawi API with the correct `Authorization` and `X-Tenant-ID` headers
5. Returns the response as-is

<Note>
  The template's `/graphql` proxy verifies the session token signature, which is enough to get started. Before deploying to production, add an authorization layer that authorize the tenant and user against your own application data. See [Authentication & Login](/guides/cloud-apps/authentication-login) for the full verification pattern and the recommended request flow.
</Note>

### The Query Code

The demo page lives at `packages/frontend/src/pages/graphql-demo-page/GraphqlDemoPage.tsx`. It keeps a `QUERIES` object with one entry per example query and runs whichever entry you click. `topItems` and `stockOverview` both query `QueryItems`. `recentOrders` queries `QuerySalesOrders`, which returns different fields.

```typescript theme={null}
import { GraphQLClient, gql } from 'graphql-request';
import { apiUrl } from '../../common/constants';

function createClient(sessionToken: string) {
	return new GraphQLClient(`${apiUrl}/graphql`, {
		headers: { 'X-Session-Token': sessionToken },
	});
}

const QUERIES = {
	topItems: gql`
		query TopItems {
			QueryItems(first: 5, order: [{ stockInOrders: DESC }]) {
				totalCount
				nodes {
					sku
					name
					stockInOrders
					salesPriceGross
				}
			}
		}
	`,
	recentOrders: gql`
		query RecentOrders {
			QuerySalesOrders(first: 5, order: [{ salesOrderDate: DESC }]) {
				totalCount
				nodes {
					salesOrderNumber
					salesOrderDate
					totalGrossAmount
					currencyIso
					companyName
				}
			}
		}
	`,
	stockOverview: gql`
		query StockOverview {
			QueryItems(first: 5, order: [{ stockTotal: DESC }]) {
				totalCount
				nodes {
					sku
					name
					stockTotal
					stockAvailable
					stockInOrders
				}
			}
		}
	`,
};

const sessionToken = await appBridge.method.call<string>('getSessionToken');
const client = createClient(sessionToken);
const data = await client.request(QUERIES.topItems);
```

A sample response looks like this:

```json theme={null}
{
	"data": {
		"QueryItems": {
			"totalCount": 674,
			"nodes": [
				{
					"sku": "AR2016041-VKO",
					"name": "Men's T-shirt",
					"stockInOrders": 42,
					"salesPriceGross": 29.99
				},
				{
					"sku": "AR2016041-002",
					"name": "Men's T-shirt orange S",
					"stockInOrders": 38,
					"salesPriceGross": 29.99
				}
			]
		}
	}
}
```

<Note>
  This demo runs a minimal query to verify the connection. The GraphQL API
  supports filtering, sorting, pagination, and mutations. Explore the full
  schema interactively in the [GraphQL
  Playground](/api-reference/graphql-playground), or read the [Using Platform
  APIs](/guides/cloud-apps/using-platform-apis) guide for more patterns.
</Note>

## What Just Happened?

Here's what's running under the hood:

```mermaid theme={null}
flowchart LR
    FE["React Frontend:3004 (HTTPS)"] --> BE["Express/.NET Backend:3005 (HTTP)"]
    BE --> AUTH["OAuth 2.0 Authentication"]
    AUTH --> API["Cloud-ERP API"]
    API --> DATA["Merchant Data Products, Orders, etc."]

    style FE fill:#FFF2EB,stroke:#FB581F,stroke-width:2px,color:#0B1B45
    style BE fill:#FFF2EB,stroke:#FB581F,stroke-width:2px,color:#0B1B45
    style AUTH fill:#E8F4FF,stroke:#89D2FF,stroke-width:2px,color:#0B1B45
    style API fill:#E8F4FF,stroke:#89D2FF,stroke-width:2px,color:#0B1B45
    style DATA fill:#EEEEE7,stroke:#0B1B45,stroke-width:2px,color:#0B1B45
```

* The **React frontend** renders your app's UI (running on port 3004)
* The **Express/.NET/PHP backend** handles authentication and API calls (running on port 3005)
* The backend uses your **client ID and secret** to authenticate with JTL via OAuth 2.0
* Once authenticated, it can call the **JTL-Wawi API** to read and write data

## Project Structure

Here's what's inside `my-jtl-app/`:

```
my-jtl-app/
├── packages/
│   ├── backend/          # Express or .NET or PHP server
│   └── frontend/         # React application
├── manifest.json         # App manifest
├── package.json          # Monorepo config
└── README.md
```

## Common Issues

<AccordionGroup>
  <Accordion title="Errors after `npm install` or `npm run dev`">
    The template requires Node.js v24.16.0 or higher (current LTS). Node 18 and earlier are known to fail during install or at runtime. Check your version with `node --version`. If you need to upgrade, use [nvm](https://github.com/nvm-sh/nvm) or download the current LTS from [nodejs.org](https://nodejs.org/).
  </Accordion>

  <Accordion title="Browser did not open during npm run register">
    The CLI tries to open your default browser to start the JTL ID sign-in. If your environment blocks that, the CLI prints the sign-in URL to the terminal instead. Copy the URL into a browser on a machine that can reach it. After you complete the sign-in, the CLI continues with the tenant selection and review steps.
  </Accordion>

  <Accordion title="Wrong tenant selected during registration">
    If you registered the app under the wrong tenant, the simplest path is to remove the app from the [Partner Portal](https://partner.jtl-cloud.com/) and rerun `npm run register`. The CLI prompts you to pick a tenant each time, so you can pick the correct one on the next pass.
  </Accordion>

  <Accordion title="Certificate warning on localhost:3004">
    The frontend runs on HTTPS, which requires a certificate. In development, this is a self-signed certificate that your browser does not trust. Click "Advanced" then "Proceed to localhost" (Chrome) or "Accept the Risk" (Firefox) to continue. This is safe for local development.
  </Accordion>

  <Accordion title="Port 3004 or 3005 already in use">
    Another process is using the port. Find and stop it:

    ```bash theme={null}
    # macOS / Linux
    lsof -i :3004
    kill -9 <PID>
     
    # Windows
    netstat -ano | findstr :3004
    taskkill /PID <PID> /F
    ```
  </Accordion>

  <Accordion title="Authentication error (401 or invalid_client)">
    This usually means the credentials in your local environment file do not match the app registered in the Partner Portal. The fastest way to fix it is to rerun `npm run register`, which regenerates credentials and rewrites the local file. If the problem persists, regenerate the credentials manually in the [Partner Portal](https://partner.jtl-cloud.com/) under your app's client credentials, then update the file yourself.
  </Accordion>

  <Accordion title="GraphQL returns 401 Unauthorized">
    Your API request is missing required headers. Verify you are sending:

    * `Authorization: Bearer <accessToken>`
    * `X-Tenant-ID: <tenantId>` (from session token payload)
      Make sure the session token is still valid and has not expired.
  </Accordion>

  <Accordion title="Empty data or null response">
    If you get `{ "data": null }` or empty results:

    * Verify the `tenantId` is correct (check what is in your session token)
    * Confirm you have data in your Wawi for the query you are making
    * Check that your GraphQL query syntax is valid (no missing brackets or commas)
  </Accordion>
</AccordionGroup>

## What's Next?

Your app is running. Here's where to go from here:

<CardGroup cols={2}>
  <Card title="Test your App" icon="flask-conical" href="/get-started/test-your-app">
    Use the cloud environment to test your app with real (test) data.
  </Card>

  <Card title="Using Platform APIs" icon="database" href="/guides/cloud-apps/using-platform-apis">
    Use the JTL-Wawi REST and GraphQL APIs, handle responses, and work with
    tenant-scoped data.
  </Card>

  <Card title="GraphQL Playground" icon="share-2" href="/api-reference/graphql-playground">
    Try queries and mutations interactively against your ERP instance.
  </Card>

  <Card title="Build from Scratch" icon="code" href="/get-started/quick-start/from-scratch">
    Want to understand every piece? Build a Cloud App step by step.
  </Card>

  <Card title="App Shell & UI" icon="app-window" href="/guides/cloud-apps/app-shell-ui-integration">
    Integrate deeper with the JTL UI using AppBridge and Platform UI components.
  </Card>
</CardGroup>
