Introduction
Plugins are a way to extend K3 through programming. Please note: Plugins are currently available as
Some plugins are already available in the K3 Plugin Store—Grouping, Positioning, Boolean Operations, Hotspots, and Veranda. What they can do and how to set them up is described in the Plugin-Übersicht. This section explains how to build your own plugins.
Creating a Plugin
You can create a new plugin with a single command:
npm create k3-plugin
The Scaffolder prompts you for a plugin name and generates a fully configured project with all necessary dependencies.
Install dependencies and start the plugin:
cd <plugin-name>
cp .env.example .env # API_TOKEN und PLUGIN_ID eintragen
pnpm install
pnpm run dev
pnpm run dev builds the plugin and starts a local server. The plugin is then accessible at
http://localhost:5002/<FEDERATION_NAME>/remoteEntry.js
— you specify the FEDERATION_NAME in the .env.
Vite Configuration
The vite.config.ts of the sample plugin is already fully configured. Here’s an overview of what you’ll find there and why:
__PROTECTED_1__
__PROTECTED_2__
__PROTECTED_3__
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), "");
const federationName = env.FEDERATION_NAME ?? "k3-plugin";
const federationModule = env.FEDERATION_MODULE ?? "Plugin";
return {
base: `/${federationName}/`,
plugins: [
react(),
federation({
name: federationName,
filename: "remoteEntry.js",
exposes: {
[`./${federationModule}`]: `./src/${federationModule}.tsx`,
},
shared: {
react: { singleton: true, requiredVersion: "19.1.1" },
"react-dom": { singleton: true, requiredVersion: "19.1.1" },
"@mui/material": { singleton: true, requiredVersion: "^7.1.1" },
"@mui/styled-engine": { singleton: true, requiredVersion: "^7.1.1" },
"@emotion/react": { singleton: true, requiredVersion: "^11.10.0" },
"@emotion/styled": { singleton: true, requiredVersion: "^11.10.5" },
"@emotion/cache": { singleton: true, requiredVersion: "^11.14.0" },
"@react-three/drei": { singleton: true, requiredVersion: "^10.1.2" },
"@react-three/fiber": { singleton: true, requiredVersion: "^9.1.2" },
three: { singleton: true, requiredVersion: "^0.177.0" },
"k3-plugin-api": { singleton: true, requiredVersion: "^2.0.0" },
},
}),
],
preview: {
cors: true,
port: 5002,
strictPort: true,
},
build: {
modulePreload: false,
target: "esnext",
minify: false,
cssCodeSplit: false,
assetsInlineLimit: 0,
},
};
});
The following is specified here:
- The name, which is specified when creating the plugin in K3.
- The filename is always
remoteEntry.js; the file is generated when the plugin is built. - exposes is a list of modules that are exported. A module consists of:
- the name specified when creating the plugin in K3.
- the name of the file that implements the module.
- Under shared, you specify which libraries should not be deployed in the plugin but should instead be used by K3.
The MUI and Emotion entries in the shared section are only required if you use Material-UI components in your plugin (e.g., for custom inputs or dialogs).
If you are only creating 3D models, you can omit these lines:
"@mui/material": { singleton: true, requiredVersion: "^7.1.1" },
"@mui/styled-engine": { singleton: true, requiredVersion: "^7.1.1" },
"@emotion/react": { singleton: true, requiredVersion: "^11.10.0" },
"@emotion/styled": { singleton: true, requiredVersion: "^11.10.5" },
"@emotion/cache": { singleton: true, requiredVersion: "^11.14.0" },
Important: If you leave these entries in the shared section, you must install the corresponding packages; otherwise, the build will fail.
TypeScript Configuration
The sample plugin already contains a working tsconfig.json. Here is an overview of the most important settings:
tsconfig.json of the sample plugin:
{
"compilerOptions": {
"target": "ES2023",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2023", "DOM", "DOM.Iterable"],
"skipLibCheck": true,
"jsx": "react-jsx",
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedSideEffectImports": true
},
"include": ["src"]
}
If you want to use JSX elements such as <group>, <mesh>, or <cylinderGeometry>, you must import something from @react-three/fiber in at least one file so that the global JSX type definitions are loaded:
__PROTECTED_4__
This empty type import loads the JSX types for all .tsx files in the project without adding code to the bundle.
After making changes to the tsconfig.json:
Restart the TypeScript Language Server:
- Open the Command Palette (
Cmd+Shift+Pon macOS /Ctrl+Shift+Pon Windows/Linux) - Search for:
TypeScript: Restart TS Server - Confirm by pressing Enter
Plugin Declaration
In the sample plugin, src/Plugin.tsx is already present. This file is the entry point for your plugin and can be customized directly.
It contains a default export with a K3PluginDescriptor:
__PROTECTED_5__
export default {
id: "vendor.my-plugin",
version: "1.0.0",
// UI Erweiterungen: Layout, Inputs, Dialoge
ui: {
layout: { /* Header, Sidebar, Footer usw. */ },
inputs: { /* Custom Variable Visualisierungen */ },
dialogs: { /* Bestell-Dialog, Warnungen usw. */ }
},
// 3D/Viewer Erweiterungen
viewer: {
canvas: { /* Canvas HOC */ },
customLayoutComponents: { /* Custom React Komponenten */ },
models: [ /* Dynamische 3D-Modelle */ ],
labels: { /* Label/Hotspot Overlays */ }
},
// Logik & Callbacks
logic: {
config: { /* onUpdate, onSave, onSaveFiles, onSaveEvent */ },
camera: { /* Screenshot Kameras, Dimensionen */ },
core: { /* preprocessFullApp, onOpenPdf, onExportAR */ }
}
} satisfies K3PluginDescriptor;
The plugin descriptor consists of:
- id: Unique identifier for the plugin (e.g., "vendor.my-plugin")
- version: Semantic versioning string (e.g., "1.0.0")
- settings: Configuration component for plugin-specific settings (optional)
- ui: UI extension points for layout, inputs, and dialogs (optional)
- viewer: 3D/viewer extension points (optional)
- logic: Callbacks and event hooks (optional)
All extension points are optional. You can implement only the areas you need.
Plugin Settings
Plugins can store and manage their own configuration settings. This is useful for:
- API keys and credentials
- Customizable behavior patterns
- User-specific preferences
- Plugin configurations that may vary per configurator
Defining the Settings Component
Add a Settings component to the plugin descriptor:
__PROTECTED_6__
__PROTECTED_7__
export default {
id: "vendor.my-plugin",
version: "1.0.0",
settings: SettingsComponent,
// ... weitere Extension Points
} satisfies K3PluginDescriptor;
The Settings component receives two props:
interface SettingsProps {
settings: unknown; // Aktuelle Settings (oder undefined beim ersten Aufruf)
onSave: (settings: unknown) => void; // Callback zum Speichern
}
const SettingsComponent: React.FC<SettingsProps> = ({ settings, onSave }) => {
const [apiKey, setApiKey] = React.useState(settings?.apiKey || "");
return (
<div>
<h3>Plugin Konfiguration</h3>
<input
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
placeholder="API Key"
/>
<button onClick={() => onSave({ apiKey })}>
Speichern
</button>
</div>
);
};
Settings UI in K3
When a Settings component is defined:
- A pencil icon appears next to the plugin in the plugin table
- Clicking it opens a popup with your Settings component
- The popup automatically contains Cancel and Save buttons
- Clicking Save calls
onSave()
Retrieving Settings
To use the saved settings in your plugin:
__PROTECTED_8__
// In Ihrer Komponente oder Logik:
const pluginSettings = getSettings("vendor.my-plugin");
console.log(pluginSettings?.apiKey);
Important: The plugin ID in getSettings() must match the id in the plugin descriptor.
Example: Complete settings implementation
// SettingsComponent.tsx
__PROTECTED_9__
interface MyPluginSettings {
apiKey: string;
maxRetries: number;
enableDebug: boolean;
}
interface SettingsProps {
settings: unknown;
onSave: (settings: unknown) => void;
}
export const SettingsComponent: React.FC<SettingsProps> = ({
settings,
onSave
}) => {
const currentSettings = settings as MyPluginSettings | undefined;
const [apiKey, setApiKey] = useState(currentSettings?.apiKey || "");
const [maxRetries, setMaxRetries] = useState(currentSettings?.maxRetries || 3);
const [enableDebug, setEnableDebug] = useState(currentSettings?.enableDebug || false);
const handleSave = () => {
onSave({
apiKey,
maxRetries,
enableDebug
} satisfies MyPluginSettings);
};
return (
<div style={{ padding: "20px" }}>
<h3>Plugin Einstellungen</h3>
<div>
<label>API Key:</label>
<input
type="password"
value={apiKey}
onChange={(e) => setApiKey(e.target.value)}
/>
</div>
<div>
<label>Max Retries:</label>
<input
type="number"
value={maxRetries}
onChange={(e) => setMaxRetries(Number(e.target.value))}
/>
</div>
<div>
<label>
<input
type="checkbox"
checked={enableDebug}
onChange={(e) => setEnableDebug(e.target.checked)}
/>
Debug-Modus aktivieren
</label>
</div>
<button onClick={handleSave}>Speichern</button>
</div>
);
};
// Plugin.tsx
__PROTECTED_10__
__PROTECTED_11__
__PROTECTED_12__
__PROTECTED_13__
export default {
id: "vendor.my-plugin",
version: "1.0.0",
settings: SettingsComponent,
logic: {
config: {
onUpdate: () => {
const settings = getSettings("vendor.my-plugin") as MyPluginSettings;
if (settings?.enableDebug) {
console.log("Debug-Modus aktiv");
}
// API-Key verwenden
if (settings?.apiKey) {
// API-Aufruf mit apiKey...
}
}
}
}
} satisfies K3PluginDescriptor;
Common Issues and Solutions
After you have developed and deployed your plugin locally, you can register it in K3.
You register a plugin in the options of a configurator under Plugins. It will then be loaded when the configurator starts. You can specify two different URLs for this:
- The actual URL must be accessible to the configurator user; the plugin will be loaded from there.
- You can use the Development URL while developing the plugin. It typically points to
localhost (e.g.,
http://localhost:5002/<FEDERATION_NAME>/remoteEntry.js); in this case, the plugin is loaded from your local development environment.
In addition to the title and description, you must fill in the following technical fields:
- Name: The name from the
vite.config.ts(e.g.,k3-plugin-test) - Module: The module name from the
exposesconfiguration (typically./Plugin)
The values for Name and Module must match the entries in your vite.config.ts exactly; otherwise, the plugin cannot be loaded.
TypeScript error: "Property 'group' does not exist on type 'JSX.IntrinsicElements'"
This issue occurs when you use React-Three-Fiber JSX elements such as <group>, <mesh>, or <cylinderGeometry>.
Cause: TypeScript does not recognize the JSX type definitions for @react-three/fiber.
Solution: Add an empty type import to the relevant .tsx file:
__PROTECTED_14__
Alternatively, you can import something from @react-three/fiber (e.g., useFrame, useThree) in any file in the project; this will automatically make the JSX types globally available.
TypeScript error: "Cannot use JSX unless the '--jsx' flag is provided"
Cause: The tsconfig.json file does not contain any JSX configuration.
Solution: In tsconfig.json, add the following under compilerOptions:
"jsx": "react-jsx"
Then restart the TypeScript Language Server (see TypeScript Configuration).
TypeScript error: "Could not find a declaration file for module 'react/jsx-runtime'"
Cause: The TypeScript types for React are not installed.
Solution:
pnpm install @types/react @types/react-dom --save-dev
Build Error: "Module not found" or Vite configuration not recognized
Cause: The vite.config.ts file does not exist or is corrupted.
Solution: Ensure that the vite.config.ts file exists in the project root with the correct configuration (see Vite Configuration).
In newer versions of Vite (8.x+), the file is not always created automatically and must be created manually.
Build error: "Cannot find module '@mui/material'" or "@emotion/react"
Cause: In the vite.config.ts file, MUI or Emotion packages are listed in the shared section but are not installed.
Error message:
[Module Federation] Error: Cannot find module '@mui/material/package.json'
[UNLOADABLE_DEPENDENCY] Error: Could not load @emotion/cache
Solution 1 - Install MUI (if you want to use it):
pnpm install @mui/material @mui/styled-engine @emotion/react @emotion/styled @emotion/cache --save-dev
Solution 2 - Remove MUI from shared (if you don’t need it):
Remove the MUI and Emotion lines from the shared section of vite.config.ts:
// Diese Zeilen entfernen:
"@mui/material": { singleton: true, requiredVersion: "^7.1.1" },
"@mui/styled-engine": { singleton: true, requiredVersion: "^7.1.1" },
"@emotion/react": { singleton: true, requiredVersion: "^11.10.0" },
"@emotion/styled": { singleton: true, requiredVersion: "^11.10.5" },
"@emotion/cache": { singleton: true, requiredVersion: "^11.14.0" },
After removing them, you should clear the build cache:
rm -rf .__mf__temp dist node_modules/.vite
Changes in tsconfig.json are not being applied
Cause: The TypeScript Language Server must be restarted after configuration changes.
Solution:
Cmd+Shift+P(macOS) orCtrl+Shift+P(Windows/Linux)- Search for:
TypeScript: Restart TS Server - Press Enter
CORS Issues When Loading
CORS errors may occur during local development. Add the following configuration to your vite.config.ts:
export default defineConfig({
// ... andere Einstellungen
server: {
cors: true,
},
preview: {
cors: true,
headers: {
"Access-Control-Allow-Origin": "*",
},
},
});
Then restart the server.
Next Steps
- Runtime Hooks – Read and write configuration state, render features
- Layout-Komponenten – Customize the header bar, sidebar, footer, and more
- Input-Komponenten – Create custom variable visualizations
- Dialoge – Customize order dialogs and warnings
- 3D/Viewer – Canvas, custom layout components, and labels
- Logik & Events – Config, camera, and core callbacks
- Dynamische 3D-Modelle – Create your own 3D models with React-Three-Fiber