npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

vite-plugin-chatgpt-widgets

v0.0.9

Published

A vite plugin for automatically bundling ChatGPT widget outputs within a vite project.

Readme

⚡️ vite-plugin-chatgpt-widgets

A vite plugin for automatically bundling ChatGPT widget outputs within a vite project.

Installation

npm install vite-plugin-chatgpt-widgets
# or
pnpm add vite-plugin-chatgpt-widgets

Usage

1. Configure Vite

Add the plugin to your vite.config.ts and enable the build manifest:

import { defineConfig } from "vite";
import { chatGPTWidgetPlugin } from "vite-plugin-chatgpt-widgets";

export default defineConfig({
  plugins: [
    chatGPTWidgetPlugin({
      widgetsDir: "web/chatgpt", // default: 'web/chatgpt'
      baseUrl: "https://example.com", // if not using a vite `base`, this is required because the chatgpt iframe is sandboxed and absolute URL links are required
    }),
  ],
  build: {
    manifest: true, // Required for production mode
  },
  server: {
    cors: {
      // allow cross origin requests for development assets so the ChatGPT sandbox can access dev-time assets
      origin: true,
    },
  },
});

2. Create Widget Components

Create React components in your widgets directory:

// in web/chatgpt/Hello.tsx
export default function Hello() {
  return <div>Hello from ChatGPT Widget!</div>;
}

// in web/chatgpt/ListFoobars.tsx
export default function ListFoobars() {
  return (
    <ul>
      {window.openai.tool_output.foobars.map((foobar) => (
        <li>{foobar}</li>
      ))}
    </ul>
  );
}

Optional: Root Layout Component

You can optionally create a root layout component that will wrap all widgets. If a file named root.tsx (or root.ts, root.jsx, root.js) exists in the widgets directory, it will automatically wrap all other widget components:

// web/chatgpt/root.tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <SomeProvider>
      <header>Common Header</header>
      {children}
    </SomeProvider>
  );
}

The root layout component:

  • Must accept a children prop
  • Will automatically wrap every widget component
  • Is optional - if not present, widgets render without a wrapper

3. Serve Widgets in Your Application

After setting up the plugin and writing some widgets, you need to expose the widget HTML snippets generated by this plugin as MCP server resources. In development, the HTML snippets will be generated by Vite dynamically, and in production, they'll be built by your Vite build process and read off the disk.

import { getWidgets } from "vite-plugin-chatgpt-widgets";

// In development, pass the Vite dev server instance from wherever you can get it
const widgets = await getWidgets("web/chatgpt", viteDevServer);

// In production, pass a path to the vite manifest, where we'll load precompiled versions from
const widgets = await getWidgets("web/chatgpt", {
  manifestPath: "dist/.vite/manifest.json",
});

// Register each widget on an MCP server as a resource for exposure to ChatGPT
for (const widget of widgets) {
  const resourceName = `widget-${widget.name.toLowerCase()}`;
  const resourceUri = `ui://widget/${widget.name}.html`;

  // assuming you are using @modelcontextprotocol/sdk, will be similar for other MCP implementations
  mcpServer.registerResource(
    resourceName,
    resourceUri,
    {
      title: widget.name,
      description: `ChatGPT widget for ${widget.name}`,
    },
    async () => {
      return {
        contents: [
          {
            uri: resourceUri,
            mimeType: "text/html+skybridge",
            text: widget.content,
          },
        ],
      };
    }
  );
}

Sandboxed iframes require fully qualified URLs

When serving widgets in sandboxed iframes like ChatGPT's UI, asset links must be fully qualified URLs with protocol and domain. The user's browser loads up your widget on an OpenAI controlled domain, so asset loads must refer directly back to your hosting provider. The plugin enforces this requirement and will throw an error if an absolute base URL is not configured.

You must configure an absolute base URL in one of these ways:

  1. Vite's base config: If you've already configured Vite with base: "https://example.com/", the plugin will use it automatically.
// Option 1: In Vite config (affects both dev and build)
export default defineConfig({
  plugins: [chatGPTWidgetPlugin({})],
  base: "https://example.com",
});
  1. baseUrl option to this plugin:: If Vite's base is not set, or must be relative,, provide the baseUrl option to this plugin directly:
// Option 1: In Vite config (affects both dev and build)
export default defineConfig({
  plugins: [
    chatGPTWidgetPlugin({
      baseUrl: "https://example.com",
    }),
  ],
  base: "/",
});

How It Works

The plugin creates virtual modules for each widget component:

  1. Virtual HTML file: virtual:chatgpt-widget-{name}.html - A standalone HTML page
  2. Virtual JS entrypoint: virtual:chatgpt-widget-{name}.js - Imports and renders your React component

During build, these are added as entrypoints and bundled into separate HTML files with hashed asset names. The getWidgetHTML helper:

  • In dev mode: Uses Vite's plugin container to load and transform the HTML in real-time
  • In production: Reads the built HTML files using Vite's manifest.json to locate them

Support

Plain React SPAs: Well supported

React Router v6 or React Router v7 in Declarative mode: Well supported

React Router v7 in Data or Framework Mode: Hackily supported

API

chatGPTWidgetPlugin(options?)

The Vite plugin.

Options:

  • widgetsDir (string, optional): Directory containing widget components. Default: 'web/chatgpt'
  • baseUrl (string, optional): Base URL for widget assets. Required if Vite's base config is not an absolute URL and you need fully qualified URLs for sandboxed iframes. Should include protocol and domain (e.g., "https://example.com"). Note: Does not require trailing slash.

getWidgets(widgetsDir, viteHandle)

Get the HTML content for a widget.

Parameters:

  • widgetsDir (string): The path to the directory on disk with your widget components
  • viteHandle (DevelopmentViteBuild | ProductionViteBuild): A reference to a Vite context we can use for getting widget content.
    • In dev: Pass an object like { devServer: ViteDevServer } to give the Vite dev server to use to build HTML
    • In prod: Pass an object like { mainfest: "some/path/to/.vite/manifest.json" } to list all the entrypoints built by the vite build process