# How to Test OpenAI Plugins Locally Before Publishing: A Complete Developer Guide

> Learn how to test OpenAI plugins locally before publishing. This guide covers running the HTTP service, creating an HTTPS tunnel with ngrok, and loading your plugin into the ChatGPT Plugin Playground.

- Repository: [OpenAI/plugins](https://github.com/openai/plugins)
- Tags: how-to-guide
- Published: 2026-07-02

---

**You can test OpenAI plugins locally by running the HTTP service on your machine, exposing it via an HTTPS tunnel like ngrok, adding the tunnel domain to your partner console allowlist, and loading the plugin into the ChatGPT Plugin Playground using your tunnel URL.**

OpenAI plugins are ordinary HTTP services that expose a **plugin manifest** at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) and implement endpoints described in that manifest. Testing them locally before publishing requires simulating the production environment where HTTPS and domain verification are mandatory. According to the openai/plugins repository, the standard workflow involves tunneling your local development server through a publicly reachable HTTPS URL so that the ChatGPT interface can fetch your manifest and validate your API endpoints.

## Start Your Local Plugin Server

Most plugins in the openai/plugins repository are Node.js services that start with `npm start`, as defined in their [`package.json`](https://github.com/openai/plugins/blob/main/package.json). To begin local testing:

1. Clone the repository and navigate to your target plugin directory.

2. Install dependencies using the lockfile to ensure exact versions:

```bash
git clone https://github.com/openai/plugins.git
cd plugins/plugins/zoom  # example: Zoom plugin

npm ci

```

3. Start the server, which typically listens on port 3000 by default:

```bash
npm start

```

Your plugin is now running locally, but it is not yet accessible to the ChatGPT servers, which require public HTTPS endpoints.

## Expose Localhost via HTTPS Tunnel

OpenAI and partner platforms like Zoom require HTTPS endpoints even for local development. The de‑facto standard is to run **ngrok** (or a comparable tunneling tool) to generate a public URL.

Run ngrok to expose your local port:

```bash
ngrok http 3000

```

This generates a forwarding URL such as `https://abcd1234.ngrok.io`. Copy this address—you will need it for your manifest and domain allowlist.

## Configure the Plugin Manifest

Update your [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) to point the `api.base_url` to your ngrok address. The manifest must accurately reflect the tunnel domain for ChatGPT to fetch your OpenAPI specification.

Example configuration from the Zoom plugin:

```json
{
  "schema_version": "v1",
  "name_for_human": "Zoom",
  "name_for_model": "zoom",
  "description_for_human": "Interact with Zoom meetings",
  "description_for_model": "Plugin to retrieve meeting info, share apps, etc.",
  "auth": {
    "type": "oauth",
    "client_id": "YOUR_CLIENT_ID",
    "client_secret": "YOUR_CLIENT_SECRET",
    "authorization_url": "https://zoom.us/oauth/authorize",
    "token_url": "https://zoom.us/oauth/token",
    "scopes": ["zoomapp:inmeeting"]
  },
  "api": {
    "type": "openapi",
    "url": "https://abcd1234.ngrok.io/openapi.yaml",
    "is_user_authenticated": true
  },
  "logo_url": "https://raw.githubusercontent.com/openai/plugins/main/plugins/zoom/logo.png",
  "contact_email": "support@zoom.us",
  "legal_info_url": "https://zoom.us/terms"
}

```

Ensure the `api.url` field uses your current ngrok domain, as this changes every time you restart ngrok on the free tier.

## Whitelist Your Tunnel Domain

The marketplace UI validates the domain listed in your manifest against an allowlist. According to [`plugins/zoom/skills/zoom-apps-sdk/troubleshooting/debugging.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/troubleshooting/debugging.md), missing this step is the most common cause of a blank panel in the ChatGPT interface.

Navigate to your partner console (e.g., Zoom Marketplace or OpenAI Plugin Store) and add the ngrok sub‑domain (e.g., `abcd1234.ngrok.io`) to the **Domain Allowlist** page. Without this verification step, ChatGPT cannot fetch your manifest or API definitions.

## Set Up OAuth Credentials

If your plugin uses authentication, you must provide client credentials via environment variables. The Zoom SDK guide in [`plugins/zoom/skills/zoom-apps-sdk/references/full-guide.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/references/full-guide.md) specifies the required variables:

Create a `.env` file in your plugin root:

```bash
cat > .env <<EOF
ZOOM_APP_CLIENT_ID=YOUR_CLIENT_ID
ZOOM_APP_CLIENT_SECRET=YOUR_CLIENT_SECRET
ZOOM_APP_REDIRECT_URI=https://abcd1234.ngrok.io/auth
SESSION_SECRET=$(openssl rand -hex 16)
EOF

```

Restart your local server after creating this file so it loads the new environment variables. Ensure your OAuth scopes match those declared in your manifest to avoid silent authentication failures.

## Load and Test in ChatGPT Plugin Playground

With your tunnel active and domain whitelisted, you can test the full integration:

1. Open the **ChatGPT Plugin Playground** at `https://chat.openai.com/?model=gpt-4-plugins`.
2. Navigate to *Plugins* → *Develop your own plugin* → *Add a local plugin*.
3. Paste your HTTPS tunnel URL (e.g., `https://abcd1234.ngrok.io`) and the path to your manifest ([`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json)).
4. Click *Add*.

The playground will fetch your manifest, validate the schema, and let you issue API calls directly from the UI. You can now invoke your plugin by name (e.g., `/zoom …`) or via natural language prompts.

## Validate Endpoints with curl

While the Playground validates the full integration, you should verify individual endpoints independently using `curl` or Postman. This isolates network issues from plugin logic errors.

Test a specific endpoint:

```bash
curl -H "Authorization: Bearer $TOKEN" \
     https://abcd1234.ngrok.io/api/meetings/123456789

```

Replace `$TOKEN` with a valid access token obtained through your OAuth flow. If this returns the expected JSON, your local server is correctly handling requests and authentication.

## Troubleshoot Common Local Testing Issues

Several specific pitfalls documented in [`plugins/zoom/skills/zoom-apps-sdk/references/common-issues.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/references/common-issues.md) can break local testing:

- **Domain allowlist errors**: If you see a blank UI panel, verify your ngrok domain is added to the partner console allowlist and that you have restarted the tunnel since the last domain change.
- **Global variable conflicts**: The Zoom SDK’s CDN script defines `window.zoomSdk`. Redeclaring this triggers a `SyntaxError`. Use a different variable name or import via NPM: `import zoomSdk from '@zoom/appssdk'`.
- **Capability gating**: All SDK calls must be listed in the `config({ capabilities: [...] })` initialization. Unlisted capabilities raise runtime errors.
- **OAuth scope mismatch**: Missing scopes in the Marketplace console cause silent failures. Add required scopes via the *Scopes* tab in your partner dashboard.

## Summary

Testing OpenAI plugins locally before publishing requires coordinating a local HTTP server, an HTTPS tunnel, and the ChatGPT Plugin Playground. Key takeaways include:

- Run your plugin locally with `npm start` after installing dependencies with `npm ci`.
- Expose localhost via **ngrok** to satisfy HTTPS requirements, updating [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) with the tunnel URL.
- Add the ngrok domain to your partner console **Domain Allowlist** to prevent blank UI panels.
- Store OAuth credentials in a `.env` file using the variable names specified in your plugin’s SDK documentation.
- Load the plugin into the ChatGPT Plugin Playground to validate the manifest schema and test natural language interactions.
- Use `curl` to verify individual endpoints and isolate authentication or routing issues.

## Frequently Asked Questions

### Why does my plugin show a blank panel in ChatGPT?

A blank panel typically indicates a **domain allowlist** issue. The marketplace validates the domain in your [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) against an allowlist in your partner console. If you are using ngrok, you must add the current sub‑domain (e.g., `abcd1234.ngrok.io`) to the allowlist every time you restart ngrok, as the free tier generates new URLs for each session.

### Can I test OpenAI plugins without using ngrok?

While you can test the HTTP logic locally, ChatGPT requires a **publicly reachable HTTPS URL** to fetch your manifest and OpenAPI specification. Without ngrok or a similar tunneling service (such as Cloudflare Tunnel or localtunnel), ChatGPT cannot reach your localhost. Therefore, ngrok is the de‑facto standard for local end‑to‑end testing.

### What environment variables do I need for OAuth‑enabled plugins?

OAuth plugins require variables defined in the SDK documentation, such as `ZOOM_APP_CLIENT_ID`, `ZOOM_APP_CLIENT_SECRET`, and `ZOOM_APP_REDIRECT_URI` for the Zoom plugin. Store these in a `.env` file in your plugin root. Refer to [`plugins/zoom/skills/zoom-apps-sdk/references/full-guide.md`](https://github.com/openai/plugins/blob/main/plugins/zoom/skills/zoom-apps-sdk/references/full-guide.md) for the specific environment variables required for your integration.

### How do I fix "zoomSdk is not defined" errors during local testing?

This error occurs when you redeclare the global `window.zoomSdk` variable, which is defined by the Zoom SDK’s CDN script. Avoid redeclaration by using a different variable name for your instance, or switch to the NPM import: `import zoomSdk from '@zoom/appssdk'`. Additionally, ensure all SDK methods you call are listed in the `capabilities` array of your configuration object, as unlisted capabilities raise errors.