How to Handle User Input and Parameters in OpenAI Plugins: Manifests, Validation, and Security

To handle user input and parameters in OpenAI plugins, declare expected inputs in the .codex-plugin/plugin.json manifest, validate the JSON payload inside your skill handlers, and sanitize all data before processing.

The openai/plugins repository provides a framework for building extensions that interact with external APIs like Figma, Zoom, and Zotero. Properly handling user input and parameters requires understanding the contract defined in the plugin manifest and the transport layer that delivers data to your skills. This guide walks through the end-to-end flow—from schema declaration to runtime validation—using actual source files from the repository.

Declare Input Schemas in the Plugin Manifest

Every plugin defines its interface through a .codex-plugin/plugin.json manifest file located at the plugin root. This manifest acts as the single source of truth for the platform, specifying the expected shape of incoming data via inputSchema or parameters. Declaring these schemas enables the runtime to validate arguments before they reach your code and automatically generate UI components like forms or autocomplete fields.

The Figma plugin demonstrates this pattern by defining a strict schema for node operations:

{
  "name": "figma-use",
  "description": "Interact with the Figma design system",
  "inputSchema": {
    "type": "object",
    "properties": {
      "nodeId": { "type": "string", "description": "ID of the Figma node to target" },
      "action":  { "type": "string", "enum": ["select","delete"] }
    },
    "required": ["nodeId", "action"]
  }
}

This manifest resides in plugins/figma/.codex-plugin/plugin.json. By marking nodeId and action as required and restricting action to specific enum values, the platform can reject malformed requests before invoking the skill logic.

Transport Mechanisms: How Input Reaches Your Skills

The platform delivers user input through different transport mechanisms depending on the integration type. Understanding these delivery methods ensures you parse the payload correctly in your skill handlers.

HTTP JSON Payloads

Most modern skills receive parameters via HTTP POST requests containing a JSON body. The payload typically nests user input under a params key. Python and Node.js skill handlers in plugins/<name>/skills/<skill>/scripts/ should expect this structure and extract values accordingly.

CLI Arguments

Command-line skills parse input from argument vectors. The Zotero plugin illustrates this approach in plugins/zotero/skills/zotero/scripts/zotero.py, where the skill uses argparse to define mutually exclusive groups:


# Simplified excerpt from zotero.py

parser.add_argument('--file', help='Path to a file to process')
parser.add_argument('--text', help='Raw text string to analyze')

# Logic enforces that only one of --file or --text is provided

This pattern ensures that users cannot supply conflicting inputs when invoking the plugin from a shell environment.

Webhook Form Submissions

Zoom Team-Chat plugins receive interactive form data via webhook events. When a user submits a form, the platform sends a JSON payload to your endpoint where the typed text resides in the payload.cmd field. The reference implementation in plugins/zoom/skills/team-chat/examples/form-submissions.md demonstrates parsing this field to extract user commands, while plugins/zoom/skills/team-chat/references/webhook-events.md documents the full contract and security considerations for treating these payloads as untrusted.

Validate and Sanitize Inside the Skill Handler

Even when the platform validates the schema against your manifest, always re-validate inside the skill to prevent injection attacks from forged webhook requests or malicious HTTP calls. Use schema validation libraries followed by business-logic checks and output encoding.

The following Python example validates the Figma parameters using jsonschema, then applies HTML escaping to prevent XSS when rendering the node ID:

import jsonschema
import html
from typing import Dict, Any

def handler(event: Dict[str, Any]):
    # Extract payload from the transport layer

    payload = event.get("params", {})
    
    # Schema validation (matches the manifest definition)

    schema = {
        "type": "object",
        "properties": {
            "nodeId": {"type": "string"},
            "action": {"type": "string", "enum": ["select", "delete"]}
        },
        "required": ["nodeId", "action"]
    }
    jsonschema.validate(payload, schema)  # Raises ValidationError if invalid

    
    # Sanitization: escape HTML and strip whitespace

    node_id = html.escape(payload["nodeId"].strip())
    action = payload["action"]
    
    # Business logic execution

    return {
        "type": "message",
        "content": f"Node **{node_id}** will be {action}ed."
    }

Never echo raw input directly into responses. Always pass user-generated content through an escaping function appropriate for your output format.

Return Structured Responses

After processing validated input, return a structured JSON object that the platform can render as a chat message, interactive card, or file download. The response format varies by integration—Zoom expects card definitions while generic skills use simple message objects.

{
  "type": "message",
  "content": "Operation completed successfully."
}

For Zoom-specific card responses, consult the schema definitions in the repository to ensure your payload renders correctly in the Team-Chat interface.

Summary

  • Declare expected parameters in .codex-plugin/plugin.json using JSON Schema to enable platform-level validation and UI generation.
  • Transport layers vary by skill type: HTTP JSON for web skills, CLI arguments for shell scripts, and webhook payloads for Zoom integrations.
  • Validate all incoming data inside the skill handler using libraries like jsonschema (Python) or ajv (Node) to protect against forged requests.
  • Sanitize user content with HTML escaping and length restrictions before including it in responses or database queries.
  • Reference implementation files like plugins/figma/.codex-plugin/plugin.json and plugins/zotero/skills/zotero/scripts/zotero.py for production-ready patterns.

Frequently Asked Questions

Where do I define what parameters my plugin accepts?

Define them in the .codex-plugin/plugin.json manifest file using the inputSchema field. This JSON Schema acts as a contract that tells the platform what arguments to expect, enables automatic form generation, and triggers validation before your skill code executes.

How does the Zoom Team-Chat plugin receive form submissions?

Zoom Team-Chat plugins receive form data via webhooks where user input resides in the payload.cmd field. Your skill handler must parse this JSON payload, validate the cmd string, and can then respond with an updated card or message as shown in plugins/zoom/skills/team-chat/examples/form-submissions.md.

Should I validate input again if the platform already checks the schema?

Yes. Always re-validate inside your skill using libraries like jsonschema because webhooks and HTTP endpoints can receive forged requests that bypass platform checks. Additional sanitization with HTML escaping prevents XSS attacks when displaying user content in responses.

How do I handle mutually exclusive CLI arguments in a plugin?

Use standard argument parsing libraries like Python's argparse with mutually exclusive groups. The Zotero plugin demonstrates this pattern in plugins/zotero/skills/zotero/scripts/zotero.py, where --file and --text arguments cannot be used simultaneously, ensuring clean parameter handling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →