Zimlet Debugging
Comprehensive guide for debugging zimlets during development and troubleshooting production issues.
💡 Pro Tip: Mastering Zimbra debugging requires being a "Full-Stack Detective" - errors can occur in the browser, network transport (SOAP/GraphQL), or deep in the Java server.
The "Secret" URL Parameters
The most important debugging step is telling Zimbra to stop hiding its code:
| Parameter | Description |
|---|---|
?dev=1 |
Developer Mode - Forces unminified source files so breakpoints work |
?debug=1 |
Opens built-in Zimbra debug window showing SOAP traffic in real-time |
?debug=2 |
More verbose debug output |
?debug=3 |
Maximum verbosity for SOAP/request debugging |
?mode=mjsf |
Multiple JS Files - Loads every JS file separately for reliable breakpoints |
?zimletSlots=show |
Modern UI - Visualizes all available slot locations |
Example URLs:
https://mail.yourdomain.com/?dev=1
https://mail.yourdomain.com/?dev=1&debug=3
https://mail.yourdomain.com/modern/?zimletSlots=show
Development Setup
Modern Zimlets (Sideloader)
The sideloader enables local development without deploying to the server.
Install Sideloader Extension
- Build the sideloader extension:
git clone https://github.com/Zimbra/zm-x-sideloader
cd zm-x-sideloader
npm install
npm run build
- Load in Chrome:
- Navigate to
chrome://extensions/ - Enable "Developer mode"
- Click "Load unpacked"
- Select
zm-x-sideloader/dist
- Navigate to
Configure Sideloader
- Click the sideloader extension icon in Chrome
- Add your local zimlet URL (e.g.,
http://localhost:8081/index.js) - Navigate to your Zimbra Modern Web Client
- The sideloader injects your local zimlet code
Local Development Workflow
# Start zimlet dev server
cd my-zimlet
zimlet watch
# Output:
# Listening on http://localhost:8081
# Zimlet available at http://localhost:8081/index.js
# Add this URL to sideloader extension
# Refresh Zimbra web client to see changes
Classic Zimlets (Dev Mode)
Enable unminified JavaScript:
https://mail.domain.com/?dev=1
Or set server-wide:
zmprov ms mail.domain.com zimbraZimletDevMode TRUE
zmmailboxdctl restart
Browser Developer Tools
Console Logging
Modern Zimlet Logging
// Add logging throughout your code
export default function Zimlet(context) {
console.log('[MyZimlet] Initializing with context:', context);
const { plugins } = context;
plugins.register('my-zimlet', {
menu: {
handler: function(menu, ctx) {
console.log('[MyZimlet] Menu handler called');
console.log('[MyZimlet] Current account:', ctx.account);
return [];
}
}
});
}
Classic Zimlet Logging
com_mycompany_myzimlet_HandlerObject.prototype.init = function() {
// Option 1: Standard console (appears in browser DevTools)
console.log("[MyZimlet] init() called");
// Option 2: Zimbra Status Message (appears in UI - recommended!)
var appCtxt = this.getContext(); // or window.appCtxt
appCtxt.setStatusMsg("MyZimlet initialized!", ZmStatusView.LEVEL_INFO);
// Option 3: AjxDebug for verbose debugging
DBG.println(AjxDebug.DBG1, "[MyZimlet] Debug level message");
// Option 4: Debug window (requires ?debug=1 URL param)
AjxDebug.println("[MyZimlet] User properties: " + this.getUserProperty("allProperties"));
};
Tip: Prefer setStatusMsg() over console.log for quick visual feedback during development.
Network Tab Analysis
Monitor GraphQL and SOAP requests:
- Open DevTools → Network tab
- Filter by:
graphqlfor Modern zimletsservice/soapfor Classic zimlets
- Inspect request/response payloads
- Check for error responses (look for
faultcode)
Pro Tip - Replay Attacks: If an API call fails, don't refresh the page repeatedly:
- Right-click the request in Network tab
- Select "Copy as cURL"
- Paste into Postman or terminal
- Tweak parameters until it works
Apollo DevTools (Modern UI)
For Modern zimlets using GraphQL, install the Apollo Client DevTools browser extension:
- Install from Chrome/Firefox extension store
- Open DevTools → Apollo tab
- View the GraphQL cache state in memory
- Inspect all queries and their cached results
- See exactly what data is available to your components
This is essential because you can't debug GraphQL just by looking at network requests - you need to see the cache!
Breakpoint Debugging
Source Maps (Modern)
# Build with source maps
zimlet build --sourcemaps
# Or in zimlet watch mode (enabled by default)
zimlet watch
Then in DevTools:
- Go to Sources tab
- Find your source files under
webpack:// - Set breakpoints in original source code
Classic Zimlet Breakpoints
// Add debugger statement
com_mycompany_myzimlet_HandlerObject.prototype.singleClicked = function() {
debugger; // Browser pauses here when DevTools open
this._showDialog();
};
Or set breakpoints directly in browser:
- Sources → find
com_mycompany_myzimlet.js - Click line number to set breakpoint
Common Issues and Solutions
Zimlet Not Loading
Modern Zimlet
Symptoms: No errors, zimlet simply doesn't appear
Debugging steps:
// 1. Verify entry point is exporting correctly
export default function Zimlet(context) {
console.log('[DEBUG] Zimlet function called');
// ...
}
// 2. Check slot registration
plugins.register('my-zimlet', exports);
console.log('[DEBUG] Registered exports:', exports);
// 3. Verify slots are enabled in zimlet.json
// Check: "slots": { "menu": true }
Common causes:
- Missing
export defaulton zimlet function - Slot not enabled in
zimlet.json - JavaScript error preventing initialization
- Zimlet not enabled for user's COS
Check zimlet is deployed:
zmzimletctl listZimlets | grep my-zimlet
Classic Zimlet
Debugging steps:
# Check zimlet is deployed
zmzimletctl listZimlets
# Check zimlet is enabled for COS
zmprov gc default zimbraZimletAvailableZimlets | grep my-zimlet
# Check for JavaScript errors in console
# Enable dev mode: ?dev=1
Slot Not Rendering
// Debug slot handler
exports.menu = {
handler: function(menu, context) {
console.log('[DEBUG] Menu slot handler called');
console.log('[DEBUG] Menu param:', menu);
console.log('[DEBUG] Context:', context);
const items = [
<MenuItem => console.log('clicked')}>
Test Item
</MenuItem>
];
console.log('[DEBUG] Returning items:', items);
return items;
}
};
Common causes:
- Handler returning
nullorundefined - Handler throwing exception (check console)
- Slot name mismatch between code and manifest
- Component import error
GraphQL Errors
// Add error handling to queries
function MyComponent() {
const { data, loading, error } = useQuery(MY_QUERY);
if (error) {
console.error('[MyZimlet] GraphQL error:', error);
console.error('[MyZimlet] GraphQL error details:', error.graphQLErrors);
console.error('[MyZimlet] Network error:', error.networkError);
return <div>Error: {error.message}</div>;
}
// ... rest of component
}
Common GraphQL issues:
- Invalid field names (check schema)
- Missing required variables
- Authentication expired
- Permission denied
CORS Errors (External APIs)
// Symptom: Network requests to external APIs fail with CORS error
// Solution 1: Use proxy through Zimbra server
const response = await fetch('/service/proxy?target=' + encodeURIComponent(externalUrl));
// Solution 2: Configure external service to allow CORS
// Add headers: Access-Control-Allow-Origin: https://mail.domain.com
// Solution 3: Use server-side extension (if available)
State Not Updating
// Debug state changes
const [myState, setMyState] = useState(initialValue);
useEffect(() => {
console.log('[DEBUG] State changed:', myState);
}, [myState]);
const handleUpdate = (newValue) => {
console.log('[DEBUG] Setting state to:', newValue);
setMyState(newValue);
};
Common causes:
- Not using state setter function correctly
- Mutating state directly instead of creating new object
- useEffect dependency array issues
Performance Debugging
Profile Renders
import { Component } from 'preact';
class ProfiledComponent extends Component {
componentDidUpdate(prevProps) {
console.log('[PERF] Component updated');
console.log('[PERF] Previous props:', prevProps);
console.log('[PERF] New props:', this.props);
}
render() {
console.log('[PERF] Rendering component');
return <div>{/* ... */}</div>;
}
}
Check Bundle Size
# Analyze bundle
zimlet build --analyze
# Opens webpack-bundle-analyzer showing module sizes
Memory Leaks
// Clean up subscriptions and timers
function MyComponent() {
useEffect(() => {
const timer = setInterval(() => {
console.log('tick');
}, 1000);
// Cleanup function
return () => {
console.log('[DEBUG] Cleaning up timer');
clearInterval(timer);
};
}, []);
}
Server-Side Debugging
Enable Debug Logging for Specific Account
Turn on debug logging for a specific user without restarting the server:
# Enable debug logging for zimlet operations
zmprov addAccountLogger user@domain.com zimbra.zimlet debug
# View logs in real-time
tail -f /opt/zimbra/log/mailbox.log | grep -i "your_zimlet_name"
# Remove debug logging when done
zmprov removeAccountLogger user@domain.com zimbra.zimlet
Check Zimlet Logs
# Zimlet deployment logs
tail -f /opt/zimbra/log/mailbox.log | grep -i zimlet
# Search for specific zimlet
grep "my-zimlet" /opt/zimbra/log/mailbox.log
# Watch for errors in real-time
tail -f /opt/zimbra/log/mailbox.log | grep -E "(ERROR|WARN|zimlet)"
Verify Zimlet Configuration
# List deployed zimlets
zmzimletctl listZimlets
# Check zimlet properties
zmprov gaz my-zimlet
# Check zimlet enabled for COS
zmprov gc default zimbraZimletAvailableZimlets
# Check zimlet config
zmprov gcz com_mycompany_myzimlet
Redeploy Zimlet
# Undeploy first
zmzimletctl undeploy my-zimlet
# Clear cache
zmprov fc all
# Redeploy
zmzimletctl deploy my-zimlet.zip
# Restart mailbox (if needed)
zmmailboxdctl restart
Debug Checklist
Modern Zimlet Not Working
- Sideloader extension installed and enabled
- Local URL added to sideloader
-
zimlet watchrunning without errors - Console shows "Zimlet function called"
- No JavaScript errors in console
- Slots enabled in
zimlet.json -
export defaultpresent on main function - Zimlet registered with
plugins.register()
Classic Zimlet Not Working
- Zimlet deployed:
zmzimletctl listZimlets - Enabled for COS:
zmprov gc default zimbraZimletAvailableZimlets - No XML syntax errors (check mailbox.log)
- Handler class name matches package name
-
init()function called (add console.log) - No JavaScript errors in console
Quick Troubleshooting Table
| Symptom | Likely Cause | Solution |
|---|---|---|
| Zimlet doesn't appear | Cache or permissions | Run zmprov fc zimlet and check User Preferences → Zimlets tab |
| "Permission Denied" | allowedDomains not set |
Check config_template.xml and whitelist the domain |
| JS changes not showing | Browser caching | Use ?dev=1 or hard-refresh (Ctrl+F5) |
| SOAP Error 500 | Bad XML/JSON | Inspect Network tab for missing required SOAP headers |
| External API fails | CORS blocked | Route through Zimbra proxy or whitelist in config_template.xml |
| White screen after load | JS syntax error | Check for debugger; statements in production code |
| Slot not rendering | Wrong slot name | Use ?zimletSlots=show to verify slot names |
| "undefined is not a function" | Minification issue | Test with ?mode=mjsf to load individual files |
Additional Resources
Reference Files
references/sideloader-setup.md- Detailed sideloader configurationreferences/common-errors.md- Error message reference
Example Files
examples/debug-logging.js- Comprehensive logging setupexamples/error-boundary.js- Error handling component
Browser Extensions
- Preact DevTools - Inspect component state and props
- Apollo Client DevTools - Debug GraphQL cache and queries
- React Developer Tools - Works with Preact compatibility layer