Skip to main content
This guide covers the two systems that control how your Cloud App renders and communicates inside the JTL platform:
  • Manifest: JSON configuration that tells JTL where to load your app, what capabilities it has, and what permissions it needs.
  • AppBridge: Runtime SDK that enables bidirectional communication between your app (in an iframe) and the host environment (JTL Hub or ERP).
It also covers the Platform UI 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 file that defines your app’s identity, lifecycle hooks, and capabilities. You provide it when registering your app in the Partner Portal.

Basic Properties

Every manifest starts with two version fields that identify the schema and your app’s release.
PropertyTypeDescription
manifestVersionstringVersion of the manifest schema (format: major.minor.patch)
versionstringVersion of the app itself (format: major.minor.patch)

Name

The name object provides both an internal identifier and the display name merchants see in the JTL Hub and App Store.
PropertyTypeDescription
name.shortstringBrief name or identifier (supports template keys like __TK_key1__)
name.fullstringDisplay name shown in the JTL Hub and App Store

Description

A short and long description for your app’s listing. The descriptions appears on the app’s detail page.
PropertyTypeDescription
description.shortstringBrief summary (50-150 characters recommended). Supports template keys.
description.fullstringDetailed description (150-1000 characters recommended). Supports template keys.

Localization

Define translations for your app’s name, description, and any other user-facing text. JTL selects the correct locale based on the merchant’s language preference.
PropertyTypeDescription
defaultLocalestringDefault locale code in ISO format (e.g., de-DE)
localesobjectMaps locale codes to translation key-value pairs
Template variables like __TK_key1__ in name and description fields are replaced with values from the matching locale at runtime.

Visual Elements (Icon)

The icon displayed on your app’s card in JTL Hub and App Store. Provide both a light and dark variant so the platform can match the merchant’s theme.
PropertyTypeDescription
icon.lightstringIcon URL for light themes (HTTPS, PNG/SVG recommended)
icon.darkstringIcon URL for dark themes (HTTPS, PNG/SVG recommended)
Icons must be publicly accessible URLs, not local file paths. If the image is unavailable, a placeholder is shown.

Support and Documentation

Links where merchants can get help or learn how to use your app. These appear on the app’s detail page in the JTL Hub and App Store.
PropertyTypeDescription
communication.supportUrlstringURL for technical help. Supports __ACTIVE_LOCALE__ variable.
communication.guideUrlstringURL to documentation. Supports __ACTIVE_LOCALE__ variable.
Required URLs for GDPR compliance and legal documentation. Merchants can access these from your app’s detail page.
PropertyTypeDescription
legal.gdprRequestUrlstringURL for GDPR data access requests
legal.gdprDeleteUrlstringURL for GDPR data deletion requests
legal.privacyUrlstringURL to the privacy policy
legal.termsOfUseUrlstringURL to the terms of service

Lifecycle

The lifecycle object defines URLs that JTL calls during app installation and removal:
PropertyTypeDescription
lifecycle.setupUrlstringURL loaded in an iframe during installation. Use this to display a login or onboarding screen and link the merchant’s tenant to your app.
lifecycle.connectUrlstringWebhook URL called when the app is installed.
lifecycle.disconnectUrlstringWebhook URL called when the app is uninstalled.
A working example of the setup flow is in the sample app’s SetupPage.tsx at src/hello-world-app/packages/frontend/src/setupPage/SetupPage.tsx and the corresponding /connect-tenant route in src/hello-world-app/packages/backend/src/index.ts.

Requirements

Specify the minimum Cloud API version your app is compatible with. JTL uses this to prevent installations on environments that don’t meet the requirement.
PropertyTypeDescription
requirements.minCloudApiVersionstringMinimum compatible Cloud API version

Capabilities

Capabilities define where and how your app integrates with JTL. Each key maps to a specific integration surface. See Architecture Overview for a full description of each integration type.

Hub

Controls how your app appears in the JTL Hub dashboard. When a merchant clicks your app’s card, the platform redirects them to this URL.
PropertyTypeDescription
capabilities.hub.appLauncher.redirectUrlstringURL the system redirects to when the app’s card is clicked in the Hub

ERP: Menu Items

Add entries to the ERP sidebar under App section. Each menu item links to a page in your app loaded inside the ERP iframe.
PropertyTypeDescription
idstringInternal identifier for the menu item
namestringDisplay name shown in the navigation
urlstring(optional) URL loaded when the item is clicked
childrenarray(optional) Nested sub-menu items with the same structure
Currently, only root-level menu items are supported. Nested children defined in children will not affect the app’s behavior.

ERP: Tabs

Add custom tabs to specific ERP views like product detail or customer detail. Each tab loads your app’s URL in context, with access to features scoped to that view.
PropertyTypeDescription
contextstringWhere the tab appears (e.g., productDetail, customerDetail)
namestringDisplay name shown on the tab
urlstringContent URL loaded when the tab is selected
featuresarrayPermission features needed for the tab to function

ERP: Headless

Register a backend-only integration with no UI. Use this for background services like data sync, scheduled jobs, or webhook processors.
PropertyTypeDescription
capabilities.erp.headless.urlstringURL for headless mode (no UI, backend-to-backend only)

ERP: API Scopes

Declare which Cloud ERP API resources your app needs access to. These scopes are granted automatically when a merchant installs your app.
PropertyTypeDescription
capabilities.erp.api.scopesarrayAPI permission scopes required by the app (e.g., ["items.read", "items.write"])

Pane

Add sidebar panels that appear alongside ERP views. Panes are context-aware: they show up on specific pages and can react to what the merchant is viewing.
PropertyTypeDescription
urlstringURL loaded in the pane iframe
titlestringTitle shown in the pane header
contextstringDot-notation path where the pane appears (e.g., $tenantSlug.customers)
matchChildContextbooleanFlag that determines whether or not the whole context needs to match for the App to be displayed
requiredScopesarray(optional) Granular API permission specific to the pane

Template Variables

Use these placeholders in your manifest to support dynamic content. JTL replaces them at runtime with the appropriate values.
VariableDescription
__TK_key1__Replaced with translation values from the locales section
__ACTIVE_LOCALE__Replaced with the user’s active locale at runtime

Complete Manifest Example

{
  "manifestVersion": "1.0.0",
  "version": "1.0.0",
  "name": {
    "short": "Hello World example",
    "full": "Hello World example"
  },
  "description": {
    "short": "Official Hello World example.",
    "full": "This is the official Hello World example."
  },
  "defaultLocale": "de-DE",
  "locales": {
    "de-DE": {
      "key1": "abc"
    },
    "en": {
      "key1": "abc"
    }
  },
  "icon": {
    "light": "https://hub.jtl-cloud.com/assets/image-placeholder.png",
    "dark": "https://hub.jtl-cloud.com/assets/image-placeholder.png"
  },
  "communication": {
    "supportUrl": "https://example.com/support/__ACTIVE_LOCALE__/",
    "guideUrl": "https://example.com/guide?lang=__ACTIVE_LOCALE__"
  },
  "legal": {
    "gdprRequestUrl": "https://example.com/gdpr/request",
    "gdprDeleteUrl": "https://example.com/gdpr/delete",
    "privacyUrl": "https://example.com/privacy",
    "termsOfUseUrl": "https://example.com/terms-of-use"
  },
  "lifecycle": {
    "setupUrl": "http://localhost:50142/setup",
    "connectUrl": "https://example.com/lifecycle/connect",
    "disconnectUrl": "https://example.com/lifecycle/disconnect"
  },
  "capabilities": {
    "hub": {
      "appLauncher": {
        "redirectUrl": "http://localhost:50142/erp"
      }
    },
    "erp": {
      "headless": {
        "url": "https://example.com/erp"
      },
      "menuItems": [
        {
          "id": "my-app-menu",
          "name": "My App",
          "url": "http://localhost:50142/erp"
        }
      ],
      "tabs": [
        {
          "context": "productDetail",
          "name": "AI Descriptions",
          "url": "https://example.com/tabs/product",
          "features": ["details:full", "pricing:write"]
        }
      ],
      "api": {
        "scopes": ["items.read", "items.write"]
      },
      "pane": [
        {
          "url": "http://localhost:50142/pane",
          "title": "My Pane",
          "context": "customers",
          "matchChildContext": true
        }
      ]
    }
  }
}

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), call host methods, and expose functions to the host. 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).

Expose Methods to the Host

Make your app’s functions callable by the JTL environment. Returns a disposer function.
const disposer = appBridge.method.expose("calculateShippingCost", (weight, destination) => {
  const baseCost = 5.99;
  const zoneRate = destination === "international" ? 2.5 : 1;
  return baseCost + (0.1 * weight * zoneRate);
});

// Later, remove the exposed method
disposer();
What this does: Registers a function that the host (JTL) can call by name. This is how the host accesses logic implemented in your app (e.g., custom calculations, data lookups).

Check if a Method is Exposed

Prevent duplicate registrations by checking first:
if (!appBridge.method.isExposed("calculateTax")) {
  appBridge.method.expose("calculateTax", (amount, rate) => {
    return amount * (rate / 100);
  });
}

Call Host Methods

Invoke functions provided by the JTL:
const sessionToken = await appBridge.method.call("getSessionToken");

const orderDetails = await appBridge.method.call("getOrderDetails", "ORD-5678");
What this does: Sends a method call across the bridge to the host (JTL). The host executes the function and returns the result as a resolved promise.

Method API summary

MethodParametersReturnsDescription
method.expose(name, fn)name: string, fn: function() => void (disposer)Make a function callable by the host
method.isExposed(name)name: stringbooleanCheck if a method is already exposed
method.call(name, ...args)name: string, ...args: unknown[]Promise<T>Call a host method

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