Skip to content

Instantly share code, notes, and snippets.

@EduardMe
Last active April 18, 2026 13:55
Show Gist options
  • Select an option

  • Save EduardMe/ed245978d79c6848194b198f58f99d35 to your computer and use it in GitHub Desktop.

Select an option

Save EduardMe/ed245978d79c6848194b198f58f99d35 to your computer and use it in GitHub Desktop.
API

HTMLView

/**
* Available in v3.6.2
* Open a modal sheet above the main window with the given html code
* @param { String }
* @param { Integer } (optional)
* @param { Integer } (optional)
*/
.showSheet(html, width, height)

/**
* Available in v3.7
* Open a non-modal window above the main window with the given html code and window title. It returns a promise with the created window object. Assign optionally the width and height.
* Run it with await window = showWindow(...), so you can adjust the window position and height later.
* @param { String }
* @param { String }
* @param { Integer } (optional)
* @param { Integer } (optional)
* @return { Promise(Window) }
*/
.showWindow(html, title, width, height)

/**
* Available in v3.9.1
* Open a non-modal window above the main window with the given html code and window title. It returns a promise with the created window object. Optionally, supply an object as the 3rd parameter to set window options:
* { width, height, x, y, customId, shouldFocus }
* By default, it will focus and bring to front the window on first launch
* If you are re-loading an existing HTML window's content, by default the window will not change z-order or focus (if it is in the back, it will stay in the back)
* you can override this by setting { shouldFocus: true } to bring to front on reload.
* If you are setting the customId in the options it will be assigned as the `customId` to the returning window.
* Assigning a customId will allow you to open multiple windows (one per customId). If you call `runJavaScript`, make sure to pass the same customID as the second variable.
* Run it with window = await showWindowWithOptions(...), so you can adjust the window position and height later.
* @param { String }
* @param { String }
* @param { Object({ x: Float, y: Float, width: Float, height: Float, customId: String, shouldFocus: Bool}) }
* @return { Promise(Window) }
*/
.showWindowWithOptions(html, title, options)
    
/**
* Available in v3.20
* Shows HTML content in the main application window, either in the main content area or as a split view (a sidebar entry will be added, so the user can open it also with opt+click as split view).
* @param { String } html - The HTML content to display
* @param { String } title - The title for the view
* @param { Object } options - (optional) Configuration options:
*   - splitView: Boolean - Show as split view (true) or in main content area (false, default)
*   - id/customId/customID: String - (optional) Unique identifier for reusing the same view
*   - icon: String - Font Awesome icon string for the navigation bar and sidebar (without "fa-")
*   - iconColor: String - Tailwind color name (e.g., "blue-500") or hex color (e.g., "#3b82f6")
*   - autoTopPadding: Boolean - Auto-add top padding for navigation bar (default: true)
*   - showReloadButton: Boolean - Show the reload button in the navigation bar (default: true)
*   - reloadPluginID: String - (optional) Plugin ID to use for reload (overrides auto-captured value)
*   - reloadCommandName: String - (optional) Command/function name to call on reload (overrides auto-captured value)
*   - reloadCommandArgs: Array - (optional) Arguments to pass to the reload command
* @returns { Promise } Returns a promise that resolves with { success: true, windowID: String }
*
* @example
* // Show HTML in main content area with icon
* await HTMLView.showInMainWindow(
*   '<h1>Hello World</h1><p>This is displayed in the main view.</p>',
*   'My View',
*   { icon: 'star', iconColor: 'blue-500' }
* )
* 
* @example
* // Show HTML as split view with custom icon and color
* await HTMLView.showInMainWindow(
*   '<div>Split view content</div>',
*   'Split View',
*   { 
*     splitView: true, 
*     icon: 'chart-line',
*     iconColor: '#3b82f6'
*   }
* )
* 
* @example
* // Reuse a view by ID
* await HTMLView.showInMainWindow(
*   '<div>Updated content</div>',
*   'Updated View',
*   { splitView: true }
* )
*/
.showInMainWindow(html, title, options)
    
/**
* Available in v3.8
* After opening an html window, make changes to the contents of the window by running JS code directly inside the opened window. Make sure to pass the customId as the second argument if you have opened the window with a customId.
* Returns a promise you can wait for with the return value, if any (depends if you added one to the JS code that is supposed to be executed).
* @param { String }
* @param { String? }
* @return { Promise }
*/
.runJavaScript(code, customId)

/**
 * To get the window, use NotePlan.htmlWindows or the return value of HTMLView.showWindow. 
 * Set / get the position and size of the window that contains the editor. Returns an object with x, y, width, height values.
 * If you want to change the coordinates or size, save the rect in a variable, modify the variable, then assign it to windowRect. 
 * The position of the window might not be very intuitive, because the coordinate system of the screen works differently (starts at the bottom left for example). Recommended is to adjust the size and position of the window relatively to it's values or other windows.
 *
 * Note this is available with v3.9.1 and works only on Mac
 * Example:
 *
 * const rect = Editor.windowRect
 * rect.height -= 50
 * Editor.windowRect = rect
 *
 * @type { x: Integer, y: Integer, width: Integer, height: Integer }
 */
.windowRect

API Access in HTML Views

/**
 * ============================================================================
 * NOTEPLAN API ACCESS FROM INSIDE HTML VIEWS
 * ============================================================================
 *
 * HTML views can access a subset of NotePlan's JavaScript APIs directly from
 * within the WebView — no jsBridge round-trip required. This enables rich
 * interactive experiences where your HTML can read calendar events, notes,
 * clipboard contents, and more.
 *
 * ----------------------------------------------------------------------------
 * RULES FOR CALLING APIs FROM HTML VIEWS
 * ----------------------------------------------------------------------------
 *
 * 1. EVERY CALL IS ASYNC. Every bridge call returns a Promise — always `await`
 *    it. Without `await`, the next line can race ahead of the call's effect.
 *
 *        ✅ const events = await Calendar.eventsToday()
 *        ❌ const events = Calendar.eventsToday()       // Promise, not data
 *
 * 2. METHOD-CALL FORM ONLY. Property writes do NOT cross the bridge. Use the
 *    matching method instead (e.g. `setContent`, `setString`, `rename`).
 *
 *        ✅ await note.setContent("new body")
 *        ❌ note.content = "new body"                   // assigns to local proxy, lost
 *
 *        ✅ await Clipboard.setString("hi")
 *        ❌ Clipboard.string = "hi"                     // same — silently dropped
 *
 * 3. PROPERTY READS ARE OK. Awaiting a property returns its current value.
 *
 *        const env = await NotePlan.environment
 *        const folder = await NotePlan.selectedSidebarFolder
 *
 * ----------------------------------------------------------------------------
 * CURRENTLY AVAILABLE APIs (from inside HTML views):
 * ----------------------------------------------------------------------------
 *
 * ✅ Calendar    - events and reminders (eventsToday, eventsBetween, add, ...)
 * ✅ DataStore   - notes (projectNotes, calendarNotes, projectNoteByTitle, ...)
 * ✅ Editor      - current editor content (paragraphs, selectedParagraphs, ...)
 * ✅ Note        - per-note mutations (insertParagraph, setContent, rename, ...)
 *                  Returned note objects from DataStore/Editor auto-proxy
 *                  their methods back through the bridge.
 * ✅ NotePlan    - environment, selectedSidebarFolder, openURL, ai, sidebar
 *                  controls, and more.
 * ✅ Clipboard   - string, types, setString, setStringForType,
 *                  stringForType, base64 helpers, clearContents, availableType.
 *
 * ✅ fetch()     - window.fetch is overridden with a CORS-free URLSession
 *                  implementation, so any external API can be called directly.
 *
 * ----------------------------------------------------------------------------
 * NOT YET ENABLED (must round-trip through jsBridge, see below):
 * ----------------------------------------------------------------------------
 *
 * ⏳ CommandBar  - prompts, options, forms
 * ⏳ HTMLView    - open/close windows, runJavaScript
 * ⏳ Any plugin-defined commands or exported functions
 *
 * ----------------------------------------------------------------------------
 * USAGE EXAMPLE (from inside HTML):
 * ----------------------------------------------------------------------------
 *
 */
    
<html>
  <head>
    <meta charset="utf-8">
    <style>
      body { font-family: -apple-system, sans-serif; padding: 20px; }
      .event { padding: 8px; margin: 4px 0; background: #f0f0f0; border-radius: 4px; }
    </style>
  </head>
  <body>
    <h1>Today's Events</h1>
    <div id="events">Loading...</div>
    
    <script>
      async function loadEvents() {
        try {
          // Simply call the Calendar API - it's already available!
          const events = await Calendar.eventsToday();
          
          const container = document.getElementById('events');
          if (events.length === 0) {
            container.innerHTML = '<p>No events today</p>';
            return;
          }
          
          container.innerHTML = events.map(event => 
            '<div class="event">' +
              '<strong>' + event.title + '</strong><br>' +
              '<small>' + new Date(event.date).toLocaleTimeString() + '</small>' +
            '</div>'
          ).join('');
        } catch (error) {
          console.error('Error:', error);
          document.getElementById('events').innerHTML = '<p>Error loading events</p>';
        }
      }
      
      // Check if Calendar API is available, otherwise wait for it
      if (typeof Calendar !== 'undefined') {
        loadEvents();
      } else {
        window.addEventListener('notePlanBridgeReady', loadEvents);
      }
    </script>
  </body>
</html>

/**
 * ----------------------------------------------------------------------------
 * COMMUNICATION BACK TO NOTEPLAN (Legacy jsBridge method):
 * ----------------------------------------------------------------------------
 * 
 * For running plugin code from inside HTML views, use the jsBridge:
 */

  const openNote = () => {
    window.webkit.messageHandlers.jsBridge.postMessage({
       code: ${openNote},
       onHandle: "onHandleUpdateNoteCount",
       id: "1"
     });
  };

  function onHandleUpdateNoteCount(result, id) {
    // result: The return value from the executed code (may be a string or JSON object)
    // id: The identifier you passed in the postMessage call
    document.getElementById("openNoteResultLabel").innerHTML = "Done! Result: " + (result || "no return value");
    console.log("Result:", result);
    console.log("Call ID:", id);
  }

Lifecycle Events

/**
 * ============================================================================
 * HTML VIEW LIFECYCLE EVENTS
 * ============================================================================
 *
 * Available in v3.21
 *
 * HTML views receive DOM events when they become visible or are about to be
 * hidden. This lets plugins refresh data, resume/pause timers, or update their
 * UI without requiring a full reload.
 *
 * These events fire when:
 * - The user switches between notes and the plugin view
 * - The user switches between different plugins
 * - A standalone plugin window is closed (hidden) or reopened
 * - A split view plugin is added or removed
 *
 * The view content is NOT reloaded — only the event is dispatched.
 *
 * ----------------------------------------------------------------------------
 * AVAILABLE EVENTS:
 * ----------------------------------------------------------------------------
 *
 * onViewDidAppear    - Fired when the HTML view becomes visible again
 * onViewWillDisappear - Fired when the HTML view is about to be hidden
 *
 * ----------------------------------------------------------------------------
 * USAGE:
 * ----------------------------------------------------------------------------
 */

// Listen for visibility changes
window.addEventListener('onViewDidAppear', () => {
    // The view is now visible — refresh data, resume timers, etc.
    console.log('Plugin view appeared');
    refreshData();
});

window.addEventListener('onViewWillDisappear', () => {
    // The view is about to be hidden — pause timers, save state, etc.
    console.log('Plugin view will disappear');
    pauseUpdates();
});

/**
 * These work alongside the existing notePlanBridgeReady event:
 * - notePlanBridgeReady fires once when the API bridge is first initialized
 * - onViewDidAppear fires each time the view becomes visible (not on first load)
 * - onViewWillDisappear fires each time the view is about to be hidden
 */

Examples

const openNote = JSON.stringify(`
  (function() {
    Editor.openNoteByFilename("10 - Projects/HTML View.md");
  })()
  `)

try {

  HTMLView.showWindow(
    `<html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
        </head>
        <body>
          <p id="openNoteResultLabel">0</p>
          <button onclick=openNote()>Open Note</button>
        </body>
        <script>
          const openNote = () => {
            window.webkit.messageHandlers.jsBridge.postMessage({
               code: ${openNote},
               onHandle: "onHandleuUpdateNoteCount",
               id: "1"
             });
            };

           function onHandleuUpdateNoteCount(re, id) {
             document.getElementById("openNoteResultLabel").innerHTML = "done"
           }
        </script>
      </html>`, "Test Plugin")

} catch(error) {
  console.log(error)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment