Skip to main content

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.

Clone JTL’s sample Cloud App, connect it to your developer account, and run it locally. By the end, you’ll have a working app talking to the JTL platform. Time: ~15 minutes 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 v18 or higher (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. Create your App on the Partner Portal

In the /packages/frontend directory, you’ll find a manifest.json file. This file defines your app’s configuration, including its name, description, icon, and other settings. You can update these values to match your app. For a full reference, see the manifest.json documentation. Next, go to the Partner Portal, create a new app, then paste the contents of your manifest.json into the Manifest Editor and click Register app. Partner Portal app creation screenshot

4. Add your Credentials

Once you register your app, you’ll see your client ID and client secret in the Partner Portal. Copy these values to a safe place. Where you put the credentials depends on your backend.
Create the environment file /packages/backend/.env and add your credentials:
CLIENT_ID=your-client-id-here
CLIENT_SECRET=your-client-secret-here
VariableWhere to find it
CLIENT_IDPartner Portal > your app > client credentials
CLIENT_SECRETPartner Portal > your app > client credentials
Don’t commit your environment files. They contain your client secrets.

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

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

7. 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 your Discover apps
  3. You should see your Hello World app listed
Discover apps
  1. Click the Install button to install the app.
Install app on JTL Complete the installation process by clicking the Complete setup button in the installation wizard. Complete setup

8. 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 is not secured beyond session token verification. In a production app, you should add your own authentication layer (e.g., validate the session token against your own user database) to prevent unauthorized access.

The Query Code

The frontend uses graphql-request to send queries through the proxy. Here’s the pattern used in the demo page:
import { GraphQLClient, gql } from 'graphql-request';

// Create a client pointing at your backend's /graphql proxy
function createClient(sessionToken: string) {
	return new GraphQLClient(`${apiUrl}/graphql`, {
		headers: { 'X-Session-Token': sessionToken },
	});
}

// Define your query
const GET_ITEMS = gql`
	query TopItems {
		QueryItems(first: 5, order: [{ stockInOrders: DESC }]) {
			totalCount
			nodes {
				sku
				name
				stockInOrders
				salesPriceGross
			}
		}
	}
`;

// Execute it. Get the session token from AppBridge first
const sessionToken = await appBridge.method.call<string>('getSessionToken');
const client = createClient(sessionToken);
const data = await client.request(GET_ITEMS);
What this does: Gets a fresh session token from the AppBridge, creates a GraphQL client pointing at the backend’s /graphql proxy, and sends a query to fetch the top 5 items sorted by stock in orders. The backend handles all authentication before the request reaches the JTL-Wawi API. 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
│       ├── src/          # React components
│       └── manifest.json # App manifest
├── package.json          # Monorepo config
└── README.md

Common Issues

The frontend runs on HTTPS, which requires a certificate. In development, this is a self-signed certificate that your browser doesn’t trust. Click “Advanced” → “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
Double-check your .env or appsettings.Local.json file:
  • Is CLIENT_ID correct? (no extra spaces or quotes)
  • Is CLIENT_SECRET correct?
  • Is the .env or appsettings.Local.json file in packages/backend/, not the project root?
If you’re still stuck, regenerate your credentials in the Partner Portal and try again.
Your API request is missing required headers. Verify you’re sending:
  • Authorization: Bearer <accessToken>
  • X-Tenant-ID: <tenantId> (from session token payload)
Make sure the session token is still valid and hasn’t expired.
If you get { "data": null } or empty results:
  • Verify the tenantId is correct (check what’s in your session token)
  • Confirm you have data in your Wawi for the query you’re 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.