# Logseq Experiments API Guide This guide covers the **experimental APIs** available in the Logseq Plugin SDK. These APIs provide advanced functionality for creating custom renderers, loading external scripts, and accessing internal utilities. > **⚠️ WARNING**: These are experimental features that may change at any time. Plugins using these APIs may not be supported on the Marketplace temporarily. --- ## Overview The Experiments API is accessed via `logseq.Experiments` and provides: 1. **React Integration** - Access to React and ReactDOM from the host 2. **Custom Renderers** - Register custom code block, route, and daemon renderers 3. **Component Access** - Access to internal Logseq components 4. **Utilities** - ClojureScript interop utilities (toClj, toJs, etc.) 5. **Script Loading** - Dynamic loading of external scripts 6. **Extension Enhancers** - Enhance libraries like KaTeX and CodeMirror --- ## 1. React Integration Access React and ReactDOM from the Logseq host environment. ### Properties #### `logseq.Experiments.React` Returns the React instance from the host scope. ```typescript const React = logseq.Experiments.React ``` #### `logseq.Experiments.ReactDOM` Returns the ReactDOM instance from the host scope. ```typescript const ReactDOM = logseq.Experiments.ReactDOM ``` ### Example Usage ```typescript const React = logseq.Experiments.React const ReactDOM = logseq.Experiments.ReactDOM // Use React to create components const MyComponent = React.createElement('div', null, 'Hello from plugin!') ``` --- ## 2. Components Access internal Logseq components for advanced UI integration. ### `logseq.Experiments.Components.Editor` A page editor component that can render Logseq page content. **Type**: `(props: { page: string } & any) => any` **Parameters**: - `page` (string): The page name to render ```typescript const Editor = logseq.Experiments.Components.Editor // Render a page editor const editor = Editor({ page: 'My Page Name' }) ``` --- ## 3. Utilities ClojureScript interop utilities for data conversion between JavaScript and ClojureScript. ### `logseq.Experiments.Utils` Provides conversion utilities: #### `toClj(input: any)` Convert JavaScript data to ClojureScript data structures. ```typescript const cljData = logseq.Experiments.Utils.toClj({ key: 'value' }) ``` #### `jsxToClj(input: any)` Convert JSX/JavaScript objects to ClojureScript, preserving JSX structures. ```typescript const cljData = logseq.Experiments.Utils.jsxToClj(
Content
) ``` #### `toJs(input: any)` Convert ClojureScript data structures to JavaScript. ```typescript const jsData = logseq.Experiments.Utils.toJs(cljData) ``` #### `toKeyword(input: any)` Convert a string to a ClojureScript keyword. ```typescript const keyword = logseq.Experiments.Utils.toKeyword('my-key') ``` #### `toSymbol(input: any)` Convert a string to a ClojureScript symbol. ```typescript const symbol = logseq.Experiments.Utils.toSymbol('my-symbol') ``` --- ## 4. Script Loading ### `logseq.Experiments.loadScripts(...scripts: string[])` Dynamically load external scripts into the Logseq environment. **Parameters**: - `scripts` (string[]): Array of script URLs or relative paths **Returns**: `Promise` **Behavior**: - Relative paths are resolved using the plugin's resource path - HTTP/HTTPS URLs are loaded directly - Scripts are loaded in order ```typescript // Load external library await logseq.Experiments.loadScripts( 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js' ) // Load local script from plugin resources await logseq.Experiments.loadScripts('./my-script.js') // Load multiple scripts await logseq.Experiments.loadScripts( 'https://cdn.example.com/lib1.js', 'https://cdn.example.com/lib2.js', './local-script.js' ) ``` --- ## 5. Custom Renderers ### 5.1 Fenced Code Renderer Register a custom renderer for code blocks with specific language tags. #### `logseq.Experiments.registerFencedCodeRenderer(lang: string, opts: object)` **Parameters**: - `lang` (string): The language identifier for the code block (e.g., 'mermaid', 'chart') - `opts` (object): - `render` (function, required): Render function that receives props - `edit` (boolean, optional): Whether the block is editable - `before` (function, optional): Async function to run before rendering - `subs` (string[], optional): Subscriptions to state changes **Render Props**: - `content` (string): The content of the code block ```typescript // Register a custom code block renderer logseq.Experiments.registerFencedCodeRenderer('my-chart', { edit: false, before: async () => { // Load dependencies before rendering await logseq.Experiments.loadScripts( 'https://cdn.jsdelivr.net/npm/chart.js' ) }, render: (props) => { const React = logseq.Experiments.React return React.createElement('div', { ref: (el) => { if (el) { // Parse content and render chart const config = JSON.parse(props.content) new Chart(el, config) } } }) } }) ``` **Usage in Logseq**: ````markdown ```my-chart { "type": "bar", "data": { "labels": ["A", "B", "C"], "datasets": [{"data": [10, 20, 30]}] } } ``` ```` ### 5.2 Daemon Renderer Register a renderer that runs continuously in the background (daemon). #### `logseq.Experiments.registerDaemonRenderer(key: string, opts: object)` **Parameters**: - `key` (string): Unique identifier for the daemon renderer - `opts` (object): - `render` (function, required): Render function - `sub` (string[], optional): Subscriptions to state changes ```typescript // Register a daemon renderer for persistent UI logseq.Experiments.registerDaemonRenderer('my-status-bar', { sub: ['ui/theme', 'ui/sidebar-open'], render: (props) => { const React = logseq.Experiments.React return React.createElement('div', { style: { position: 'fixed', bottom: 0, right: 0, padding: '10px', background: '#333', color: '#fff' } }, 'Status: Active') } }) ``` ### 5.3 Route Renderer Register a custom renderer for specific routes in Logseq. #### `logseq.Experiments.registerRouteRenderer(key: string, opts: object)` **Parameters**: - `key` (string): Unique identifier for the route renderer - `opts` (object): - `path` (string, required): Route path (e.g., '/my-plugin-page') - `render` (function, required): Render function - `name` (string, optional): Display name for the route - `subs` (string[], optional): Subscriptions to state changes ```typescript // Register a custom route logseq.Experiments.registerRouteRenderer('my-custom-page', { path: '/my-plugin-dashboard', name: 'Dashboard', subs: ['ui/theme'], render: (props) => { const React = logseq.Experiments.React return React.createElement('div', { className: 'my-plugin-dashboard' }, [ React.createElement('h1', null, 'Plugin Dashboard'), React.createElement('p', null, 'Custom content here') ]) } }) // Navigate to the route logseq.App.pushState('page', { name: 'my-plugin-dashboard' }) ``` --- ## 6. Extension Enhancers Enhance external libraries that Logseq uses (like KaTeX for math rendering). ### `logseq.Experiments.registerExtensionsEnhancer(type: string, enhancer: function)` **Parameters**: - `type` ('katex' | 'codemirror'): The extension type to enhance - `enhancer` (function): Async function that receives the library instance and can modify it **Returns**: `Promise` ```typescript // Enhance KaTeX with custom macros logseq.Experiments.registerExtensionsEnhancer('katex', async (katex) => { // Add custom KaTeX macros katex.macros = { ...katex.macros, '\\RR': '\\mathbb{R}', '\\NN': '\\mathbb{N}', '\\ZZ': '\\mathbb{Z}' } console.log('KaTeX enhanced with custom macros') }) ``` --- ## 7. Plugin Local Access ### `logseq.Experiments.pluginLocal` Access the internal plugin instance (PluginLocal) for advanced operations. **Type**: `PluginLocal` ```typescript const pluginLocal = logseq.Experiments.pluginLocal // Access plugin-specific internal state console.log('Plugin ID:', pluginLocal.id) ``` --- ## 8. Advanced: Invoke Experimental Methods ### `logseq.Experiments.invokeExperMethod(type: string, ...args: any[])` Directly invoke experimental methods from the host scope. **Parameters**: - `type` (string): Method name (converted to snake_case) - `...args`: Arguments to pass to the method **Returns**: `any` ```typescript // Invoke a custom experimental method const result = logseq.Experiments.invokeExperMethod( 'someExperimentalFeature', arg1, arg2 ) ``` --- ## Complete Example: Custom Chart Renderer Here's a complete example combining multiple APIs: ```typescript import '@logseq/libs' async function main() { console.log('Chart Plugin Loaded') // Register fenced code renderer for charts logseq.Experiments.registerFencedCodeRenderer('chart', { edit: false, before: async () => { // Load Chart.js before rendering await logseq.Experiments.loadScripts( 'https://cdn.jsdelivr.net/npm/chart.js@3.9.1/dist/chart.min.js' ) }, render: (props) => { const React = logseq.Experiments.React return React.createElement('div', null, [ React.createElement('canvas', { ref: (canvas) => { if (canvas && window.Chart) { try { const config = JSON.parse(props.content) new window.Chart(canvas, config) } catch (e) { console.error('Chart rendering error:', e) } } } }) ]) } }) } logseq.ready(main).catch(console.error) ``` **Usage**: ````markdown ```chart { "type": "line", "data": { "labels": ["Jan", "Feb", "Mar", "Apr"], "datasets": [{ "label": "Sales", "data": [10, 20, 15, 30], "borderColor": "rgb(75, 192, 192)" }] } } ``` ```` --- ## Best Practices 1. **Check Host Scope**: Always ensure the host scope is accessible before using experimental APIs 2. **Error Handling**: Wrap experimental API calls in try-catch blocks 3. **Dependencies**: Load external scripts in `before` hooks to ensure they're ready 4. **Memory Management**: Clean up event listeners and subscriptions in daemon renderers 5. **Compatibility**: Test thoroughly as these APIs may change between Logseq versions 6. **Documentation**: Document which experimental APIs your plugin uses 7. **Marketplace**: Be aware that plugins using these APIs may not be accepted on the Marketplace --- ## Limitations - **Experimental Status**: These APIs are not stable and may change without notice - **Marketplace Support**: Plugins using experimental APIs may not be approved for the Marketplace - **Security**: Be cautious when loading external scripts or accessing host scope - **Performance**: Custom renderers can impact performance if not optimized - **Compatibility**: Limited backwards compatibility guarantees --- ## See Also - [Starter Guide](./starter_guide.md) - Getting started with plugin development - [DB Properties Guide](./db_properties_guide.md) - Working with database properties - [DB Query Guide](./db_query_guide.md) - Querying the Logseq database --- ## Support For questions and issues: - [Logseq Discord](https://discord.gg/logseq) - #plugin-dev channel - [GitHub Discussions](https://github.com/logseq/logseq/discussions) - [Plugin API Documentation](https://plugins-doc.logseq.com/) Remember: These are experimental features. Use at your own risk and always test thoroughly!