creating-zed-extensions

Creating Zed Extensions

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "creating-zed-extensions" with this command: npx skills add pr-pm/prpm/pr-pm-prpm-creating-zed-extensions

Creating Zed Extensions

Overview

Zed extensions are Rust programs compiled to WebAssembly that can provide slash commands, language support, themes, grammars, and MCP servers. Extensions implement the zed::Extension trait and are distributed via Zed's extension registry.

When to Use

Create a Zed extension when:

  • Adding custom slash commands to the Assistant (/deploy , /analyze , /fetch-docs )

  • Providing language support (syntax highlighting, LSP, formatting)

  • Creating custom color themes

  • Integrating external tools via slash commands

  • Providing MCP server integrations

Don't create for:

  • Simple rules or instructions (use .rules files)

  • One-time scripts (use terminal)

  • Project-specific configuration (use .zed/settings.json )

Quick Reference

Extension Structure

my-extension/ ├── Cargo.toml # Rust manifest ├── extension.toml # Extension metadata └── src/ └── lib.rs # Extension implementation

Minimal Slash Command Extension

extension.toml

id = "my-commands" name = "My Commands" version = "0.1.0" authors = ["Your Name"] repository = "https://github.com/username/my-commands" license = "MIT"

[slash_commands.echo] description = "echoes the provided input" requires_argument = true

[slash_commands.greet] description = "greets the user" requires_argument = false

// src/lib.rs use zed_extension_api::{self as zed, Result, SlashCommand, SlashCommandOutput};

struct MyExtension;

impl zed::Extension for MyExtension { fn run_slash_command( &self, command: SlashCommand, args: Vec<String>, _worktree: Option<&zed::Worktree>, ) -> Result<SlashCommandOutput> { match command.name.as_str() { "echo" => { if args.is_empty() { return Err("echo requires an argument".to_string()); } Ok(SlashCommandOutput { text: args.join(" "), sections: vec![], }) } "greet" => { Ok(SlashCommandOutput { text: "Hello! How can I help you today?".to_string(), sections: vec![], }) } _ => Err(format!("Unknown command: {}", command.name)), } } }

zed::register_extension!(MyExtension);

Cargo.toml

[package] name = "my-extension" version = "0.1.0" edition = "2021"

[lib] crate-type = ["cdylib"]

[dependencies] zed_extension_api = "0.1.0"

Implementation

Complete Example: Documentation Fetcher

// src/lib.rs use zed_extension_api::{self as zed, Result, SlashCommand, SlashCommandOutput, SlashCommandOutputSection}; use std::process::Command;

struct DocsExtension;

impl zed::Extension for DocsExtension { fn run_slash_command( &self, command: SlashCommand, args: Vec<String>, worktree: Option<&zed::Worktree>, ) -> Result<SlashCommandOutput> { match command.name.as_str() { "docs" => self.fetch_docs(args, worktree), "api" => self.fetch_api_reference(args), _ => Err(format!("Unknown command: {}", command.name)), } }

fn complete_slash_command_argument(
    &#x26;self,
    command: SlashCommand,
    _args: Vec&#x3C;String>,
) -> Result&#x3C;Vec&#x3C;zed::SlashCommandArgumentCompletion>> {
    match command.name.as_str() {
        "docs" => Ok(vec![
            zed::SlashCommandArgumentCompletion {
                label: "rust".to_string(),
                new_text: "rust".to_string(),
                run_command: true,
            },
            zed::SlashCommandArgumentCompletion {
                label: "typescript".to_string(),
                new_text: "typescript".to_string(),
                run_command: true,
            },
            zed::SlashCommandArgumentCompletion {
                label: "python".to_string(),
                new_text: "python".to_string(),
                run_command: true,
            },
        ]),
        _ => Ok(vec![]),
    }
}

}

impl DocsExtension { fn fetch_docs( &self, args: Vec<String>, worktree: Option<&zed::Worktree>, ) -> Result<SlashCommandOutput> { if args.is_empty() { return Err("docs requires a topic (e.g., /docs rust)".to_string()); }

    let topic = args.join(" ");
    let docs_url = format!("https://docs.rs/{}", topic);

    // Use worktree context if available
    let context = if let Some(wt) = worktree {
        format!("\nProject: {}", wt.root_path())
    } else {
        String::new()
    };

    let output_text = format!(
        "Documentation for: {}\nURL: {}{}\n\nFetching latest docs...",
        topic, docs_url, context
    );

    Ok(SlashCommandOutput {
        text: output_text.clone(),
        sections: vec![
            SlashCommandOutputSection {
                range: (0..output_text.len()),
                label: format!("Docs: {}", topic),
            },
        ],
    })
}

fn fetch_api_reference(&#x26;self, args: Vec&#x3C;String>) -> Result&#x3C;SlashCommandOutput> {
    if args.is_empty() {
        return Err("api requires a library name".to_string());
    }

    let library = &#x26;args[0];

    // Execute external command to fetch API docs
    let output = Command::new("curl")
        .args(&#x26;["-s", &#x26;format!("https://api.github.com/repos/{}/readme", library)])
        .output()
        .map_err(|e| format!("Failed to execute curl: {}", e))?;

    if !output.status.success() {
        return Err("Failed to fetch API documentation".to_string());
    }

    let response = String::from_utf8_lossy(&#x26;output.stdout);

    Ok(SlashCommandOutput {
        text: format!("API Reference for {}\n\n{}", library, response),
        sections: vec![],
    })
}

}

zed::register_extension!(DocsExtension);

Extension Manifest with All Fields

extension.toml

id = "docs-fetcher" name = "Documentation Fetcher" description = "Fetch documentation and API references via slash commands" version = "1.0.0" authors = ["Developer Name <dev@example.com>"] repository = "https://github.com/username/docs-fetcher" license = "MIT"

[slash_commands.docs] description = "fetch documentation for a topic" requires_argument = true

[slash_commands.api] description = "fetch API reference for a library" requires_argument = true

[slash_commands.help] description = "show available documentation commands" requires_argument = false

Development Workflow

  1. Setup

Install Rust

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Add WASM target

rustup target add wasm32-wasip1

Create extension directory

mkdir -p ~/.local/share/zed/extensions/my-extension cd ~/.local/share/zed/extensions/my-extension

  1. Build

Compile to WASM

cargo build --release --target wasm32-wasip1

WASM output location

target/wasm32-wasip1/release/my_extension.wasm

  1. Test Locally

Zed automatically loads extensions from:

macOS: ~/Library/Application Support/Zed/extensions/

Linux: ~/.local/share/zed/extensions/

Copy extension files

cp extension.toml ~/Library/Application\ Support/Zed/extensions/my-extension/ cp target/wasm32-wasip1/release/my_extension.wasm ~/Library/Application\ Support/Zed/extensions/my-extension/extension.wasm

Restart Zed to load extension

  1. Publish

Extensions published via PR to zed-industries/extensions

https://github.com/zed-industries/extensions

Fork the repository

git clone https://github.com/zed-industries/extensions cd extensions

Add your extension

mkdir extensions/my-extension cp -r ~/path/to/my-extension/* extensions/my-extension/

Create PR with extension metadata

git checkout -b add-my-extension git add extensions/my-extension git commit -m "Add my-extension: Custom slash commands" git push origin add-my-extension

License Requirements

Required licenses (as of October 1st, 2025):

  • MIT

  • Apache-2.0

  • BSD-3-Clause

  • GPL-3.0

Extensions with other licenses will be rejected during review.

Slash Command API Reference

Types

// Command input struct SlashCommand { name: String, // Additional metadata }

// Command output struct SlashCommandOutput { text: String, sections: Vec<SlashCommandOutputSection>, }

struct SlashCommandOutputSection { range: (usize, usize), // Character range in text label: String, // Section label for UI }

// Argument completion struct SlashCommandArgumentCompletion { label: String, // Display in completion menu new_text: String, // Insert when selected run_command: bool, // Execute immediately after selection }

Methods

trait Extension { // Required for slash commands fn run_slash_command( &self, command: SlashCommand, args: Vec<String>, worktree: Option<&Worktree>, ) -> Result<SlashCommandOutput, String>;

// Optional: Argument autocompletion
fn complete_slash_command_argument(
    &#x26;self,
    command: SlashCommand,
    args: Vec&#x3C;String>,
) -> Result&#x3C;Vec&#x3C;SlashCommandArgumentCompletion>, String> {
    Ok(vec![])
}

}

Common Mistakes

Mistake Why It Fails Fix

Wrong crate type WASM compilation fails Use crate-type = ["cdylib"] in Cargo.toml

Missing error handling Extension crashes Return Err(String) for failures

Not validating args Silent failures Check args.is_empty() for required args

Hardcoded paths Extension not portable Use relative paths or worktree context

Missing default case Unhandled commands crash Add _ => Err(...) in match

Unlicensed extension Rejected by registry Include approved license in extension.toml

Blocking operations Freezes Zed UI Use async or spawn threads for long operations

Advanced Features

Using Worktree Context

fn run_slash_command( &self, command: SlashCommand, args: Vec<String>, worktree: Option<&zed::Worktree>, ) -> Result<SlashCommandOutput> { if let Some(wt) = worktree { let project_root = wt.root_path(); let config_path = format!("{}/config.json", project_root);

    // Read project-specific config
    let config = std::fs::read_to_string(config_path)
        .map_err(|e| format!("Failed to read config: {}", e))?;

    // Use config in command logic
}

// Continue command execution

}

Output Sections for Structured Results

let output_text = format!( "# Results\n\n## Section 1\nContent here\n\n## Section 2\nMore content" );

Ok(SlashCommandOutput { text: output_text.clone(), sections: vec![ SlashCommandOutputSection { range: (0..12), // "# Results" label: "Header".to_string(), }, SlashCommandOutputSection { range: (14..40), // "## Section 1\nContent here" label: "Section 1".to_string(), }, SlashCommandOutputSection { range: (42..output_text.len()), label: "Section 2".to_string(), }, ], })

Real-World Impact

Productivity: Custom /deploy command deploys directly from Assistant panel

Documentation: /docs rust Vec fetches Rust Vec documentation without leaving editor

Integration: /gh issue 123 fetches GitHub issue details inline

Workflow: /analyze-deps shows dependency tree and suggests updates

Schema Reference: packages/converters/schemas/zed-extension.schema.json

Documentation: https://zed.dev/docs/extensions/developing-extensions

Example Extension: https://github.com/zed-industries/zed/tree/main/extensions/slash-commands-example

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

human-writing

No summary provided by upstream source.

Repository SourceNeeds Review
General

self-improving

No summary provided by upstream source.

Repository SourceNeeds Review
General

postgres-migrations

No summary provided by upstream source.

Repository SourceNeeds Review
General

slash-command-builder

No summary provided by upstream source.

Repository SourceNeeds Review