> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/microsoft/markitdown/llms.txt
> Use this file to discover all available pages before exploring further.

# Plugin System

> Extend MarkItDown with third-party plugins for custom file format support

MarkItDown's plugin system allows you to extend its capabilities with custom document converters for file formats not supported by default.

## Using Plugins

### Enabling Plugins

Plugins are disabled by default and must be explicitly enabled.

<CodeGroup>
  ```bash Command Line theme={null}
  markitdown --use-plugins file.rtf
  markitdown -p file.rtf
  ```

  ```python Python API theme={null}
  from markitdown import MarkItDown

  md = MarkItDown(enable_plugins=True)
  result = md.convert("file.rtf")
  ```
</CodeGroup>

### Listing Installed Plugins

Check which plugins are installed:

```bash theme={null}
markitdown --list-plugins
```

Output:

```
Installed MarkItDown 3rd-party Plugins:

  * sample_plugin    	(package: markitdown_sample_plugin)

Use the -p (or --use-plugins) option to enable 3rd-party plugins.
```

If no plugins are installed:

```
Installed MarkItDown 3rd-party Plugins:

  * No 3rd-party plugins installed.

Find plugins by searching for the hashtag #markitdown-plugin on GitHub.
```

## Finding Plugins

Discover available plugins:

<Steps>
  <Step title="Search GitHub">
    Look for repositories tagged with `#markitdown-plugin`:

    [Search GitHub for #markitdown-plugin](https://github.com/topics/markitdown-plugin)
  </Step>

  <Step title="Check PyPI">
    Search PyPI for packages starting with `markitdown-`:

    ```bash theme={null}
    pip search markitdown-
    ```
  </Step>

  <Step title="Community Resources">
    Check the [MarkItDown repository](https://github.com/microsoft/markitdown) for plugin recommendations
  </Step>
</Steps>

## Installing Plugins

Plugins are installed as Python packages:

```bash theme={null}
# From PyPI
pip install markitdown-sample-plugin

# From GitHub
pip install git+https://github.com/user/markitdown-plugin-name.git

# From local directory
pip install -e /path/to/plugin
```

Verify installation:

```bash theme={null}
markitdown --list-plugins
```

## Creating Plugins

### Plugin Structure

A MarkItDown plugin is a Python package that implements a specific interface:

<Steps>
  <Step title="Create a DocumentConverter">
    ```python converter.py theme={null}
    from typing import BinaryIO, Any
    from markitdown import DocumentConverter, DocumentConverterResult, StreamInfo

    class RtfConverter(DocumentConverter):
        def accepts(
            self,
            file_stream: BinaryIO,
            stream_info: StreamInfo,
            **kwargs: Any,
        ) -> bool:
            """Check if this converter can handle the file."""
            extension = (stream_info.extension or "").lower()
            mimetype = (stream_info.mimetype or "").lower()
            
            if extension == ".rtf":
                return True
            if mimetype == "text/rtf":
                return True
            
            return False

        def convert(
            self,
            file_stream: BinaryIO,
            stream_info: StreamInfo,
            **kwargs: Any,
        ) -> DocumentConverterResult:
            """Convert the file to Markdown."""
            # Read the RTF content
            content = file_stream.read()
            
            # Convert to Markdown (simplified example)
            markdown = self._rtf_to_markdown(content)
            
            return DocumentConverterResult(
                markdown=markdown,
                title="RTF Document"
            )
        
        def _rtf_to_markdown(self, content: bytes) -> str:
            # Implement RTF parsing logic
            from striprtf.striprtf import rtf_to_text
            text = rtf_to_text(content.decode('utf-8'))
            return text
    ```
  </Step>

  <Step title="Create Plugin Interface">
    ```python __init__.py theme={null}
    from .converter import RtfConverter
    from markitdown import MarkItDown

    # Plugin interface version
    __plugin_interface_version__ = 1

    def register_converters(markitdown: MarkItDown, **kwargs):
        """Register converters with MarkItDown instance."""
        markitdown.register_converter(RtfConverter())
    ```
  </Step>

  <Step title="Configure Entry Point">
    ```toml pyproject.toml theme={null}
    [project]
    name = "markitdown-rtf-plugin"
    version = "0.1.0"
    dependencies = [
        "markitdown>=0.1.0",
        "striprtf",
    ]

    [project.entry-points."markitdown.plugin"]
    rtf_plugin = "markitdown_rtf_plugin"
    ```
  </Step>
</Steps>

### Entry Point Configuration

The entry point is critical for plugin discovery:

```toml theme={null}
[project.entry-points."markitdown.plugin"]
plugin_name = "package_name"
```

* **Entry point group**: Must be `"markitdown.plugin"`
* **Plugin name**: Any unique identifier (e.g., `rtf_plugin`)
* **Package name**: The fully qualified package name (e.g., `markitdown_rtf_plugin`)

### Plugin Interface Version

Your plugin must export the interface version:

```python theme={null}
__plugin_interface_version__ = 1
```

Currently, only version `1` is supported.

### Registration Function

Implement the `register_converters` function:

```python theme={null}
def register_converters(markitdown: MarkItDown, **kwargs):
    """
    Called when MarkItDown instances are created with plugins enabled.
    
    Args:
        markitdown: The MarkItDown instance to register converters with
        **kwargs: Additional arguments passed to MarkItDown constructor
    """
    # Register one or more converters
    markitdown.register_converter(MyConverter())
    markitdown.register_converter(AnotherConverter())
```

## Advanced Plugin Development

### Converter Priority

Control when your converter is tried:

```python theme={null}
from markitdown import PRIORITY_SPECIFIC_FILE_FORMAT, PRIORITY_GENERIC_FILE_FORMAT

def register_converters(markitdown: MarkItDown, **kwargs):
    # High priority (tried first) - for specific file types
    markitdown.register_converter(
        RtfConverter(),
        priority=PRIORITY_SPECIFIC_FILE_FORMAT  # 0.0
    )
    
    # Lower priority (tried later) - for generic file types
    markitdown.register_converter(
        GenericTextConverter(),
        priority=PRIORITY_GENERIC_FILE_FORMAT  # 10.0
    )
```

<Note>
  Lower priority values are tried first. Built-in converters use `0.0` for specific formats and `10.0` for generic formats.
</Note>

### Accessing File Content

The `file_stream` is seekable:

```python theme={null}
def accepts(self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs) -> bool:
    # Save position
    cur_pos = file_stream.tell()
    
    # Read header to check file type
    header = file_stream.read(100)
    
    # IMPORTANT: Reset position
    file_stream.seek(cur_pos)
    
    return header.startswith(b'{\\rtf')
```

<Warning>
  Always reset the file stream position after reading in `accepts()`. The `convert()` method expects the stream to be at the original position.
</Warning>

### Using Configuration Options

Access configuration passed to MarkItDown:

```python theme={null}
def register_converters(markitdown: MarkItDown, **kwargs):
    # Access custom configuration
    custom_setting = kwargs.get('custom_setting', 'default')
    
    markitdown.register_converter(
        MyConverter(setting=custom_setting)
    )
```

Pass configuration when creating MarkItDown:

```python theme={null}
md = MarkItDown(
    enable_plugins=True,
    custom_setting='value'
)
```

### Error Handling

Handle missing dependencies gracefully:

```python theme={null}
from markitdown import MissingDependencyException
import sys

_dependency_exc_info = None
try:
    import striprtf
except ImportError:
    _dependency_exc_info = sys.exc_info()

class RtfConverter(DocumentConverter):
    def __init__(self):
        if _dependency_exc_info is not None:
            raise MissingDependencyException(
                "RtfConverter requires 'striprtf' to be installed. "
                "Install with: pip install striprtf"
            ) from _dependency_exc_info[1].with_traceback(_dependency_exc_info[2])
```

## Example: Sample Plugin

The official sample plugin demonstrates best practices:

```python theme={null}
# From markitdown-sample-plugin
from typing import BinaryIO, Any
from markitdown import DocumentConverter, DocumentConverterResult, StreamInfo
import sys

# Check for dependencies
_dependency_exc_info = None
try:
    from striprtf.striprtf import rtf_to_text
except ImportError:
    _dependency_exc_info = sys.exc_info()

class RtfConverter(DocumentConverter):
    def accepts(self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any) -> bool:
        extension = (stream_info.extension or "").lower()
        
        if extension == ".rtf":
            return True
        
        # Check file magic
        cur_pos = file_stream.tell()
        header = file_stream.read(100)
        file_stream.seek(cur_pos)
        
        return header.startswith(b'{\\\\rtf')
    
    def convert(self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs: Any) -> DocumentConverterResult:
        if _dependency_exc_info is not None:
            raise MissingDependencyException(
                "RTF conversion requires 'striprtf'. Install with: pip install striprtf"
            )
        
        content = file_stream.read().decode('utf-8', errors='ignore')
        text = rtf_to_text(content)
        
        return DocumentConverterResult(markdown=text)

# Plugin interface
__plugin_interface_version__ = 1

def register_converters(markitdown, **kwargs):
    markitdown.register_converter(RtfConverter())
```

Install and use:

```bash theme={null}
pip install markitdown-sample-plugin
markitdown --use-plugins document.rtf
```

## Testing Plugins

Test your plugin:

```python theme={null}
from markitdown import MarkItDown
import io

def test_rtf_conversion():
    md = MarkItDown(enable_plugins=True)
    
    # Create test RTF content
    rtf_content = b"{\\rtf1 Hello World}"
    stream = io.BytesIO(rtf_content)
    
    result = md.convert_stream(stream, stream_info=StreamInfo(extension=".rtf"))
    
    assert "Hello World" in result.markdown
    print("✓ Plugin test passed")

if __name__ == "__main__":
    test_rtf_conversion()
```

## Publishing Plugins

<Steps>
  <Step title="Package Your Plugin">
    ```bash theme={null}
    python -m build
    ```
  </Step>

  <Step title="Test Locally">
    ```bash theme={null}
    pip install dist/markitdown_rtf_plugin-0.1.0-py3-none-any.whl
    markitdown --list-plugins
    ```
  </Step>

  <Step title="Publish to PyPI">
    ```bash theme={null}
    python -m twine upload dist/*
    ```
  </Step>

  <Step title="Tag Repository">
    Add `#markitdown-plugin` topic to your GitHub repository for discoverability
  </Step>
</Steps>

## Security Considerations

<Warning>
  Plugins execute arbitrary code during conversion. Only install plugins from trusted sources.
</Warning>

Best practices:

* Review plugin source code before installation
* Use virtual environments for testing new plugins
* Keep plugins updated
* Report security issues to plugin authors

## Troubleshooting

### Plugin Not Found

If `--list-plugins` doesn't show your plugin:

```bash theme={null}
# Check if package is installed
pip list | grep markitdown

# Verify entry points
python -c "from importlib.metadata import entry_points; print(list(entry_points(group='markitdown.plugin')))"

# Reinstall the plugin
pip uninstall markitdown-rtf-plugin
pip install markitdown-rtf-plugin
```

### Plugin Fails to Load

Check for errors:

```python theme={null}
import warnings
import traceback
from importlib.metadata import entry_points

for ep in entry_points(group='markitdown.plugin'):
    try:
        plugin = ep.load()
        print(f"✓ Loaded: {ep.name}")
    except Exception as e:
        print(f"✗ Failed: {ep.name}")
        traceback.print_exc()
```

### Converter Not Called

Ensure `accepts()` returns `True`:

```python theme={null}
def accepts(self, file_stream: BinaryIO, stream_info: StreamInfo, **kwargs) -> bool:
    print(f"Checking: {stream_info.extension} / {stream_info.mimetype}")
    return stream_info.extension == ".rtf"
```
