Skip to main content
What you’ll build: A full-stack Cloud App (React frontend + Express/.NET backend) that authenticates with the JTL platform and runs inside the App Shell.

Prerequisites

Before you start, make sure you have:
1

Account and Access

You need:
  • A JTL ID (your login to the JTL ecosystem)
  • Access to an organization (tenant) in the Partner Portal (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.You’ll also need:
  • JTL-Wawi installed and running locally
2

Tools Installed

You’ll need the following tools installed on your machine:
  • Node.js v24.16.0 or higher (current LTS, includes npm). Verify with node --version and npm --version
  • For the C# template only: .NET SDK. Run dotnet --version to check

1. Create the Project Using Cloud Apps CLI

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 (Express + TypeScript) or .NET
  • Frontend: React (Vite + Tailwind + JTL UI)
Use the Arrow Keys to navigate and Enter to select.
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:
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 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:
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:
BackendFile written
Node.js/packages/backend/.env
.NET/packages/backend/MyJtlApp.Api/appsettings.Local.json
The CLI also returns a link to the app in JTL Hub.
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.
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.

4. Start the App

Run the following command from the root directory to start the development server:
npm run dev
This starts both the backend and frontend:
ServiceURLPort
Backend (Express or .NET API)http://localhost:30053005
Frontend (React App)http://localhost:30043004

5. Open the App

Open 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 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
Discover apps
  1. 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.
Install app on JTL Complete the installation process by clicking the Complete setup button in the installation wizard. Complete setup

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 guide.

How it Works

The frontend never talks to the JTL-Wawi API directly. All requests go through your backend, which handles authentication transparently. 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
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 for the full verification pattern and the recommended request flow.

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.
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:
{
	"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
				}
			]
		}
	}
}
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, or read the Using Platform APIs guide for more patterns.

What Just Happened?

Here’s what’s running under the hood:
  • The React frontend renders your app’s UI (running on port 3004)
  • The Express/.NET 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 API server
│   └── frontend/         # React application
├── manifest.json         # App manifest
├── package.json          # Monorepo config
└── README.md

Common Issues

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 or download the current LTS from nodejs.org.
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.
If you registered the app under the wrong tenant, the simplest path is to remove the app from the Partner Portal 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.
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.
Another process is using the port. Find and stop it:
# macOS / Linux
lsof -i :3004
kill -9 <PID>
 
# Windows
netstat -ano | findstr :3004
taskkill /PID <PID> /F
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 under your app’s client credentials, then update the file yourself.
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.
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)

What’s Next?

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

Test your App

Use the cloud environment to test your app with real (test) data.

Using Platform APIs

Use the JTL-Wawi REST and GraphQL APIs, handle responses, and work with tenant-scoped data.

GraphQL Playground

Try queries and mutations interactively against your ERP instance.

Build from Scratch

Want to understand every piece? Build a Cloud App step by step.

App Shell & UI

Integrate deeper with the JTL UI using AppBridge and Platform UI components.