# Creating App-Backed Plugins with External Service Integrations: A Complete Guide

> Build app-backed plugins with external service integrations using OpenAI's plugins. Learn to connect SaaS services with OAuth and modular skill bundles for language model agents.

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

---

**App-backed plugins connect language model agents to external SaaS services through a declarative manifest system that automatically handles OAuth authentication while isolating functional logic in modular skill bundles.**

The openai/plugins repository provides a standardized framework for building integrations that bridge LLM-driven agents with external APIs like HeyGen, Figma, or Google Calendar. This architecture separates authentication concerns from business logic through a manifest-based approach, enabling developers to build secure, modular integrations without hard-coding credentials or scattered configuration.

## Core Architectural Components

The plugin system relies on three primary components that work together to create a seamless bridge between the Codex runtime and external services.

### The Plugin Manifest ([`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json))

The manifest file serves as the entry point for the Codex runtime, declaring the plugin's metadata, skill locations, and app configuration. Located at [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json), this file contains the `interface` object that supplies UI metadata including display names, descriptions, icons, and default prompts.

According to the HeyGen implementation in [[`plugins/heygen/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/heygen/.codex-plugin/plugin.json)](https://github.com/openai/plugins/blob/main/plugins/heygen/.codex-plugin/plugin.json), the manifest references both the skills directory and the app descriptor while defining capabilities like "Read" and "Write" permissions.

### The App Descriptor ([`.app.json`](https://github.com/openai/plugins/blob/main/.app.json))

The [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) file holds the authentication configuration required by the external service, including OAuth client IDs, scopes, redirect URIs, or API keys. This file is read by the Codex SDK when the plugin loads, allowing the runtime to provision credentials before any skill executes.

In the HeyGen example at [[`plugins/heygen/.app.json`](https://github.com/openai/plugins/blob/main/plugins/heygen/.app.json)](https://github.com/openai/plugins/blob/main/plugins/heygen/.app.json), the descriptor defines the OAuth flow parameters that enable secure token exchange without exposing secrets in skill code.

### The Skills Directory (`skills/`)

All functional logic resides in the `skills/` directory, with each capability isolated in its own folder containing a [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) file, optional `agents/` for surface-specific prompts, and `references/` for documentation. For example, the HeyGen plugin separates avatar creation from video generation into distinct skill bundles at [`plugins/heygen/skills/heygen-avatar/`](https://github.com/openai/plugins/tree/main/plugins/heygen/skills/heygen-avatar).

## Runtime Execution Flow

When a user interacts with an LLM through an app-backed plugin, the Codex runtime orchestrates the interaction through four distinct phases:

1. **Plugin Discovery** – The runtime discovers the plugin via the marketplace manifest and reads the [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) to locate skill bundles and the [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) configuration.

2. **App Initialization** – For OAuth-based services, the runtime launches the authorization flow on first use, storing resulting tokens in the user's session and injecting them into the skill environment.

3. **Skill Execution** – The LLM calls a specific skill endpoint (e.g., `heygen-avatar`), which accesses the app credentials through the context object to call the external service API, process the response, and return structured data.

4. **Result Rendering** – The model incorporates the external service response into its output, whether that's a generated video URL, updated avatar file, or other artifacts.

## Implementation Examples

### Minimal Plugin Manifest

Create a [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) file that declares your plugin's metadata and points to your skills and app configuration:

```json
{
  "name": "myservice",
  "version": "1.0.0",
  "description": "Connects ChatGPT to MyService API.",
  "author": {
    "name": "MyCompany",
    "email": "dev@mycompany.com",
    "url": "https://mycompany.com"
  },
  "homepage": "https://myservice.com",
  "repository": "https://github.com/mycompany/myservice-plugin",
  "license": "MIT",
  "keywords": ["myservice","api","integration"],
  "skills": "./skills/",
  "apps": "./.app.json",
  "interface": {
    "displayName": "MyService",
    "shortDescription": "Do thing with MyService",
    "longDescription": "Allows the agent to create resources on MyService and retrieve data.",
    "developerName": "MyCompany",
    "category": "Productivity",
    "capabilities": ["Read","Write"],
    "websiteURL": "https://myservice.com",
    "privacyPolicyURL": "https://myservice.com/privacy",
    "termsOfServiceURL": "https://myservice.com/terms",
    "defaultPrompt": [
      "Create a new MyService project called \"Demo\"",
      "List all my MyService resources"
    ],
    "brandColor": "#123456",
    "composerIcon": "./assets/icon.png",
    "logo": "./assets/logo.png",
    "logoDark": "./assets/logo-dark.png"
  }
}

```

See the production example in the HeyGen manifest at [[`plugins/heygen/.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/plugins/heygen/.codex-plugin/plugin.json)](https://github.com/openai/plugins/blob/main/plugins/heygen/.codex-plugin/plugin.json).

### OAuth App Configuration

Define your external service authentication in [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json):

```json
{
  "name": "myservice",
  "type": "oauth",
  "clientId": "YOUR_CLIENT_ID",
  "clientSecret": "YOUR_CLIENT_SECRET",
  "authUrl": "https://myservice.com/oauth/authorize",
  "tokenUrl": "https://myservice.com/oauth/token",
  "scopes": ["read", "write"],
  "redirectUri": "https://codex.openai.com/oauth/callback"
}

```

Reference the HeyGen implementation at [[`plugins/heygen/.app.json`](https://github.com/openai/plugins/blob/main/plugins/heygen/.app.json)](https://github.com/openai/plugins/blob/main/plugins/heygen/.app.json) for a complete OAuth configuration.

### Skill Definition Structure

Each capability requires a [`SKILL.md`](https://github.com/openai/plugins/blob/main/SKILL.md) file in its respective folder under `skills/`:

```markdown

# My Action Skill

## Description

Creates a resource on MyService and returns its ID.

## Parameters

- `name` (string) – Name of the resource.
- `type` (string) – Type of the resource.

## Output

- `resourceId` (string) – Identifier of the created resource.

## Implementation (pseudo-code)

```python
def run(params, context):
    token = context.get_oauth_token()          # injected by runtime

    headers = {"Authorization": f"Bearer {token}"}
    resp = http.post("https://api.myservice.com/v1/resources",
                     json={"name": params.name, "type": params.type},
                     headers=headers)
    return {"resourceId": resp.json()["id"]}

```

```

View the production HeyGen avatar skill at [[`plugins/heygen/skills/heygen-avatar/SKILL.md`](https://github.com/openai/plugins/blob/main/plugins/heygen/skills/heygen-avatar/SKILL.md)](https://github.com/openai/plugins/blob/main/plugins/heygen/skills/heygen-avatar/SKILL.md).

## Key Design Patterns

The openai/plugins repository demonstrates several consistent patterns across successful integrations:

- **Declarative Manifests** – All configuration resides in [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) and [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json), eliminating hard-coded paths and scattered secrets.

- **Isolated Skill Bundles** – Each capability lives in its own folder with dedicated documentation, making features easy to version, test, and remove independently.

- **Reference-Driven Documentation** – Skills include `references/` folders containing API documentation, troubleshooting guides, and design rationales, as seen in [`heygen-video/references/troubleshooting.md`](https://github.com/openai/plugins/blob/main/heygen-video/references/troubleshooting.md).

- **Automatic Credential Injection** – The runtime handles token storage and injection via the context object, keeping secrets out of skill implementation code.

- **Surface-Specific Agents** – Optional `agents/` directories contain LLM-specific prompt configurations (e.g., [[`plugins/heygen/agents/openai.yaml`](https://github.com/openai/plugins/blob/main/plugins/heygen/agents/openai.yaml)](https://github.com/openai/plugins/blob/main/plugins/heygen/agents/openai.yaml)) that optimize how different models invoke the same skill.

## Summary

- **App-backed plugins** use a three-tier architecture: the manifest ([`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json)), the app descriptor ([`.app.json`](https://github.com/openai/plugins/blob/main/.app.json)), and the skills directory (`skills/`).
- **Authentication is declarative** – OAuth and API key configuration lives in [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json), allowing the runtime to manage tokens transparently.
- **Skills are modular** – Each capability is self-contained with its own documentation, references, and implementation, enabling independent versioning.
- **File paths matter** – The runtime expects specific locations: [`.codex-plugin/plugin.json`](https://github.com/openai/plugins/blob/main/.codex-plugin/plugin.json) for metadata, [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) for auth, and `skills/<name>/SKILL.md` for logic.

## Frequently Asked Questions

### What is the difference between an app-backed plugin and a simple function-calling plugin?

An app-backed plugin specifically integrates with external SaaS services that require persistent authentication (OAuth or API keys), while simple function-calling plugins execute local code or stateless operations. The app-backed architecture stores credentials in [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) and automatically injects them into the skill context, whereas function-calling plugins typically handle authentication manually within the skill code or don't require it at all.

### How does the Codex runtime handle OAuth token refresh?

The runtime reads the `tokenUrl` and refresh parameters from [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) and manages the token lifecycle automatically. When a skill accesses `context.get_oauth_token()`, the runtime returns a valid token, refreshing it from the external service's token endpoint if the current token has expired. This eliminates the need for skills to implement refresh logic or store long-term credentials.

### Can a single plugin support multiple external services?

Yes, by declaring multiple apps in the `apps` array within [`plugin.json`](https://github.com/openai/plugins/blob/main/plugin.json) and creating corresponding [`.app.json`](https://github.com/openai/plugins/blob/main/.app.json) files for each service. However, best practice suggests keeping the scope focused; the modular architecture makes it easy to create separate plugins for different services while maintaining consistent patterns across the ecosystem.

### Where should I store API documentation and troubleshooting guides?

Store reference documentation in the `references/` subdirectory within each skill folder. For example, the HeyGen plugin places troubleshooting guides at [`skills/heygen-video/references/troubleshooting.md`](https://github.com/openai/plugins/blob/main/skills/heygen-video/references/troubleshooting.md). This keeps implementation logic separate from documentation while ensuring context-specific help remains accessible to both developers and the LLM when debugging skill behavior.