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.

This guide covers the two systems that control how your Cloud App renders and communicates inside the JTL platform:
  • Manifest: Configuration document with two sections. The manifest section defines lifecycle hooks, capabilities, and API scopes. The listing section defines name, description, icons, legal URLs, and support links shown in the JTL Hub and App Store.
  • AppBridge: Runtime SDK that enables bidirectional communication between your app (in an iframe) and the host environment (JTL Hub or ERP Cloud).
  • Platform UI: React component library for building interfaces that match JTL’s design system.
For authentication flows, see Authentication & Login. For calling the JTL Cloud and JTL-Wawi APIs, see Using Platform APIs.

App Manifest

The manifest is a JSON document that defines your app. It has two sections that work together: manifest for technical runtime configuration, and listing for the marketing content shown in the App Store and JTL Hub. You need both to deploy an app through the Partner Portal.

Manifest Section

The manifest section holds technical runtime configuration: identity, lifecycle hooks, and capabilities.

Top-level Fields

Two fields identify the app and its release.
PropertyTypeRequiredDescription
technicalNamestringYesStable internal identifier for the app. Max 100 characters. Used as a fallback display name.
versionstringYesApp release version. Strict semver, digits only (e.g., 1.0.0). Pre-release tags are not allowed.

Requirements

Declare the minimum platform API version your app needs. JTL prevents installation on environments that don’t meet the requirement.
PropertyTypeRequiredDescription
requirements.minCloudApiVersionstringNoMinimum compatible Cloud API version

Lifecycle

The lifecycle object defines URLs JTL calls during installation, connection, and removal of your app.
PropertyTypeRequiredDescription
lifecycle.configurationUrlstringNoURL rendered in an iframe during initial setup or onboarding. Use this to present UI, confirm the connection, or collect any required configuration from the merchant.
lifecycle.connectUrlstringNoURL called when JTL establishes a connection. Handle your OAuth callback or credential exchange here, and persist any tokens or identifiers required for future API calls.
lifecycle.disconnectUrlstringNoURL called when the app is uninstalled. Use this to revoke access, delete stored credentials, and clean up any tenant-specific data.
All three URLs must be standard http:// or https:// URLs and are validated against the pattern ^https?:\/\/(?:[^{}\s]|\{\s*metadata\.[a-zA-Z0-9_-]+\s*})+$ when you register your manifest. Typical Flow
  1. Merchant installs your app → configurationUrl (optional onboarding UI)
  2. JTL establishes a connection → connectUrl
  3. Merchant uninstalls your app → disconnectUrl
Identifying the App The lifecycle URLs are often the only reliable signal your handlers have about which app a request belongs to. If you publish multiple apps and they share the same configurationUrl, connectUrl, or disconnectUrl, your handlers cannot determine which manifest a request belongs to.
  • For configurationUrl, the wrong setup UI may be rendered.
  • For connectUrl, credentials may be registered against the wrong app.
  • For disconnectUrl, tokens and tenant data may be revoked for the wrong app.
Each app should have a unique, identifiable URL for every lifecycle endpoint. Give each app a distinct URL, either by using separate routes:
"lifecycle": {
  "configurationUrl": "https://example.com/setup/inventory-sync",
  "connectUrl": "https://example.com/connect/inventory-sync",
  "disconnectUrl": "https://example.com/disconnect/inventory-sync"
}
Or by encoding the app’s identity in the query string:
"lifecycle": {
  "configurationUrl": "https://example.com/setup?app=inventory-sync",
  "connectUrl": "https://example.com/connect?app=inventory-sync",
  "disconnectUrl": "https://example.com/disconnect?app=inventory-sync"
}
Both approaches are valid. Use separate routes if you control routing and want cleaner URLs. Use query parameters if you prefer a single handler that switches behavior dynamically. What matters is that the URL itself carries enough context for your backend to know which app it’s serving.
A working configuration flow is provided when you use the CLI npm create @jtl-software/cloud-app@latest to create a new app.

Capabilities

Capabilities define where and how your app integrates with JTL. Each key under capabilities maps to a specific surface. See Architecture Overview for a full description of each integration type. Hub Controls how the app appears in the JTL Hub dashboard. When a merchant clicks the app’s card, the platform redirects to this URL.
PropertyTypeRequiredDescription
capabilities.hub.appLauncher.redirectUrlstringRequired if hub is presentURL the system redirects to when the app card is clicked in the Hub
Without a redirectUrl, the app card has no destination. ERP: Menu Items Add entries to the ERP Cloud sidebar under the App section. Each menu item links to a page in your app loaded inside the ERP iframe.
PropertyTypeRequiredDescription
idstringYesStable internal identifier for the menu item
namestringYesDisplay name shown in the navigation
urlstringYesURL loaded when the item is clicked
ERP: API Scopes Declare which JTL-Wawi API resources your app needs. Scopes are displayed and granted at install time.
PropertyTypeRequiredDescription
capabilities.erp.api.scopesarray of stringNoAPI permission scopes required by the app
The platform accepts only 41 specific scope values. Any value outside this list is rejected at registration. A subset for reference:
"scopes": [
  "items.read", "items.write",
  "orders.read", "orders.write",
  "customers.read", "customers.write",
  "inventory.read", "inventory.write"
]
Pane Add sidebar panels that appear alongside ERP views. Panes are context-aware and show up only on matching pages.
PropertyTypeRequiredDescription
idstringNoInternal identifier for the pane
contextstringYesDot-notation path where the pane appears. The path mirrors the route: /customers/details becomes customers.details
matchChildContextbooleanNoIf true, the pane also matches subpaths of the context (e.g., customers matches customers.details)
titlestringYesTitle shown in the pane header and in the pane dropdown
urlstringYesURL loaded inside the pane iframe
requiredScopesarray of stringNoPer-pane scope override. Uses the same 41-value enum as capabilities.erp.api.scopes

Listing Section

The listing section holds the marketing content shown to merchants in the App Store and JTL Hub: localized names and descriptions, icons, support URLs, and legal links.

Localized Name and Description

Names and descriptions are keyed by locale. The defaultLocale is the fallback when a merchant’s preferred locale is not available.
PropertyTypeDescription
defaultLocalestringFallback locale (e.g., de-DE)
name.{locale}.shortstringShort name shown in lists and cards. Max 30 characters.
name.{locale}.fullstringFull name shown on the app detail page. Max 80 characters.
description.{locale}.shortstringTeaser shown in cards. Max 200 characters.
description.{locale}.fullstringFull description shown on the app detail page. No max length.

Media

Icons render in light and dark themes. Both URLs must be publicly reachable at registration and during installation.
PropertyTypeDescription
media.icons.lightstringIcon URL for light backgrounds (HTTPS, PNG/SVG)
media.icons.darkstringIcon URL for dark backgrounds (HTTPS, PNG/SVG)

Support and Documentation

Links shown on the app detail page. Both fields are localized.
PropertyTypeDescription
support.url.{locale}stringSupport link (HTTPS)
support.documentation.{locale}stringDocumentation link (HTTPS)
Required legal URLs for compliance. GDPR endpoints accept data access and deletion requests.
PropertyTypeDescription
legal.privacyPolicystringURL to the privacy policy
legal.termsOfUsestringURL to the terms of service
legal.gdpr.requeststringURL for GDPR data access requests
legal.gdpr.deletestringURL for GDPR data deletion requests

Complete Manifest Example

The example below shows both sections together: a manifest declaring Hub redirect, one ERP menu item, three API scopes, and one pane scoped to the orders view, alongside a listing covering the German locale.
{
  "manifest": {
    "technicalName": "my-example-app",
    "version": "1.0.0",
    "requirements": {
      "minCloudApiVersion": "1.0.0"
    },
    "lifecycle": {
      "configurationUrl": "https://example.com/configure",
      "connectUrl": "https://example.com/api/lifecycle/connect",
      "disconnectUrl": "https://example.com/api/lifecycle/disconnect"
    },
    "capabilities": {
      "hub": {
        "appLauncher": {
          "redirectUrl": "https://example.com/hub/launch"
        }
      },
      "erp": {
        "menuItems": [
          {
            "id": "menu-item-1",
            "name": "Mein Menüpunkt",
            "url": "https://example.com/erp/menu"
          }
        ],
        "api": {
          "scopes": ["orders.read", "orders.write", "items.read"]
        },
        "pane": [
          {
            "id": "pane-1",
            "context": "orders",
            "matchChildContext": false,
            "title": "Bestellübersicht",
            "url": "https://example.com/erp/pane/orders",
            "requiredScopes": ["orders.read"]
          }
        ]
      }
    }
  },
  "listing": {
    "version": "1.0.0",
    "defaultLocale": "de-DE",
    "name": {
      "de-DE": {
        "short": "Hello World example",
        "full": "Hello World example"
      }
    },
    "description": {
      "de-DE": {
        "short": "Official Hello World example.",
        "full": "This is the official Hello World example."
      }
    },
    "media": {
      "icons": {
        "light": "https://picsum.photos/200/200",
        "dark": "https://picsum.photos/200/200"
      }
    },
    "support": {
      "url": {
        "de-DE": "https://support.example.com/tickets"
      },
      "documentation": {
        "de-DE": "https://docs.example.com/user-guide"
      }
    },
    "legal": {
      "privacyPolicy": "https://example.com/legal/privacy",
      "termsOfUse": "https://example.com/legal/terms",
      "gdpr": {
        "request": "https://example.com/gdpr/data-request",
        "delete": "https://example.com/gdpr/account-deletion"
      }
    }
  }
}

AppBridge Communication

The AppBridge is provided by the @jtl-software/cloud-apps-core package. It establishes a secure communication channel between your app’s frontend (running in an iframe) and the host JTL environment (JTL Hub or ERP Cloud). The bridge allows your app to request data (like session tokens) and call host methods. Publish/subscribe event handling is in development (see below).

Installation

npm install @jtl-software/cloud-apps-core

Initializing the AppBridge

Initialize the AppBridge before rendering your React app, then pass the instance as a prop or store it in context:
import { createAppBridge } from '@jtl-software/cloud-apps-core';
import { createRoot } from 'react-dom/client';

createAppBridge().then((appBridge) => {
	createRoot(document.getElementById('root')!).render(
		<StrictMode>
			<App appBridge={appBridge} />
		</StrictMode>,
	);
});
What this does: Calls createAppBridge() to establish the iframe-to-host connection, waits for the bridge to be ready, then passes it to your app as a prop. This guarantees the communication channel is open before any component tries to use it.
Do not initialize the AppBridge inside a component (e.g., in useEffect). This can cause race conditions where components render before the bridge is ready. Always initialize it at the application entry point.
For Next.js apps that need SSR compatibility, use a dynamic import inside a client-side provider instead. See the From Scratch Quickstart for the full pattern.
You can use AppBridge in any JavaScript frontend framework, including React, Vue, and Angular.

Communication Flow

The AppBridge creates a two-way channel: your app’s appBridge instance communicates with a corresponding hostAppBridge instance inside the JTL Cloud service. All communication is asynchronous and non-blocking. Your app continues executing while messages are delivered between the iframe and the host. There are no HTTP requests or polling involved.

API Reference

The AppBridge provides two main interfaces: methods (expose/call) and events (publish/subscribe).
AppBridge methods provide a request/response channel between your app and the host. There are two directions:
  • Call host methods (available today): your app invokes functions the host exposes, like getSessionToken. See Environment-specific Methods for the full list.
  • Expose methods to the host (in development): your app registers functions that the host can invoke by name, used for things like custom calculations or data lookups.

Calling Host Methods

Invoke a function provided by JTL. The call returns a promise that resolves with the host’s result.
const sessionToken = await appBridge.method.call("getSessionToken");
What this does: Sends a method call across the bridge. The host executes the function and returns the result as a resolved promise.

Exposing Methods to the Host

In development: method.expose and method.isExposed are in active development and not yet available. This section will be updated when the API ships.
Once available, exposing methods will let the host invoke logic that lives in your app. For example, a calculateShippingCost function the ERP calls before showing a quote, or a validateOrder hook called before checkout. The shape will follow a name + handler registration pattern with a disposer for cleanup, mirroring the event subscription model.

Method API Summary

The table marks which APIs are available today and which are planned.
MethodStatusDescription
method.call(name, ...args)AvailableCall a host method. Returns Promise<T>.
method.expose(name, fn)In developmentRegister a function the host can call by name.
method.isExposed(name)In developmentCheck whether a method is already registered.

Environment-specific Methods

JTL exposes different built-in methods depending on which environment your app is running in.
These methods are available when your app loads via the lifecycle.configurationUrl during installation:
MethodParametersReturnsDescription
getSessionTokenNonePromise<string>Returns the current session token (contains user and tenant info)
setupCompletedNonePromise<void>Signals that setup is done and activates the app in the Hub
getSessionToken is available in both environments. It’s the primary way your frontend identifies the current user and tenant. For details on verifying session tokens on your backend, see Authentication & Login.

Platform UI Components

JTL provides a React component library that matches the platform’s design system. Using these components ensures your app looks and feels consistent with the rest of the JTL interface.

Installation

npm install @jtl-software/platform-ui-react
The library requires TailwindCSS 4. Add the CSS import to your global stylesheet:
@import '@jtl-software/platform-ui-react/dist/main.css';
@source 'node_modules/@jtl-software/platform-ui-react/dist';

Available Components

The library includes form controls, layout containers, and typography components. Here’s the full list: Layout components
  • Box , Grid , Stack , Layout , LayoutSection , Card
Form components
  • Button , Checkbox , Input , InputOTP , Radio , Select , Textarea , Switch , Toggle , ToggleGroup , FormGroup , Form
Data display
  • Text , Badge , Avatar , Table , DataTable , Progress
Navigation
  • Link , Breadcrumb , Tab , Dropdown
Feedback
  • Alert , Dialog , AlertDialog , Tooltip , Skeleton
Utility
  • Icon , Separator , ScrollArea , Collapsible , Popover , Sheet

Usage

Import components directly from the package:
import { Button, Card, Text } from '@jtl-software/platform-ui-react';

export default function MyComponent() {
	return (
		<Card>
			<Text>Welcome to my JTL App</Text>
			<Button
				onClick={() => console.log('clicked')}
				label='Get Started'
			/>
		</Card>
	);
}
What this does: Renders a card with JTL-styled typography and a button.

What’s Next

Authentication & Login

Implement OAuth 2.0 and session token verification in your app.

Using Platform APIs

Call the JTL Cloud REST and GraphQL APIs from your backend.

Handling Webhooks

Respond to lifecycle events and AppBridge messages.

Best Practices

Patterns for AppBridge initialization, error handling, and production readiness.