---
title: Jodit Connector Python
description: FastAPI backend for the Jodit file browser and uploader. pip install or Docker, files on disk or in S3, Azure, GCS, FTP, SFTP and WebDAV, role-based access rules, thumbnails, PDF and DOCX export.
keywords: jodit python, fastapi file connector, python file upload, jodit-python, jodit uploader python, backend
---

# Jodit Connector Python

[![PyPI](https://img.shields.io/pypi/v/jodit-python)](https://pypi.org/project/jodit-python/) [![Documentation](https://img.shields.io/badge/docs-latest-blue.svg)](https://timurseyidov.github.io/jodit-python/) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/TimurSeyidov/jodit-python/blob/main/LICENSE)

![A Python FastAPI server connecting the Jodit file browser to cloud and disk storage](images/jodit-python-connector.webp 'hero')

The backend behind the Jodit file browser and uploader, written in Python on FastAPI. It stores files on disk or in S3, Azure, GCS, FTP, SFTP and WebDAV, enforces your access rules and makes thumbnails. Written and maintained by [Timur Seyidov](https://github.com/TimurSeyidov/jodit-python) under the MIT license; it speaks the same protocol as the [Node.js](./jodit-nodejs.html) and PHP connectors, so the editor configuration is identical.

```tldr
What: A server that answers the file requests Jodit sends: list, upload, rename, move, delete, thumbnails, image edits.
Install: `pip install "jodit-python[all]"` or `docker run w2fb/jodit-python`
Wire: Point `uploader.url` and `filebrowser.ajax.url` in the editor config at the connector.
Secure: One `check_authentication` callback returns a role; `accessControl` rules decide what each role may do.
Store: Local disk, S3 and compatible, Azure Blob, GCS, FTP, SFTP, WebDAV, or your own adapter.
```

## How it works

Every file action in Jodit is a POST to the connector with an `action` parameter: `files`, `fileUpload`, `folderCreate` and so on. The connector authenticates the request, applies your access rules, works on the configured storage and returns JSON the editor already understands. The free file browser in the Jodit core and the PRO Finder use the same protocol.

## Quick start

````steps
### Install the connector

The `[all]` extra pulls in S3, Azure, GCS, SFTP, PDF and DOCX support. Without an extra the connector still runs; an action that needs a missing extra answers `501` and names it. Python 3.14 or newer.

```bash
pip install "jodit-python[all]"

# or run the image without installing anything
docker run --rm -p 8081:8081 -v $(pwd)/files:/app/files w2fb/jodit-python
```

### Create the app and decide who is who

`create_app` builds a FastAPI application. The authentication callback runs on every request and returns a role name; everything else keys off that role.

```python
# main.py
from starlette.requests import Request

from jcpy import create_app


async def check_authentication(request: Request) -> str:
    token = request.headers.get("authorization")
    return "admin" if token == "Bearer secret" else "guest"


app = create_app("config.json", check_authentication=check_authentication)
```

```bash
uvicorn main:app --port 8081
```

### Describe your storage and rules

`config.json` overrides only what differs from the defaults (camelCase keys). A source is a folder or a bucket with a public `baseurl`. Access rules are matched last to first, and unlisted actions stay allowed, so list what you want to block.

```json
{
  "onlyPOST": true,
  "sources": {
    "uploads": {
      "title": "Uploads",
      "root": "/var/www/uploads",
      "baseurl": "https://example.com/uploads/"
    }
  },
  "defaultRole": "guest",
  "accessControl": [
    { "role": "guest", "FILE_UPLOAD": false, "FILE_REMOVE": false },
    { "role": "admin", "path": "/private", "FILES": true }
  ]
}
```

### Point the editor at it

Two options in the Jodit config. The same headers work for the free file browser and for the PRO Finder.

```javascript
Jodit.make('#editor', {
	uploader: {
		url: 'https://files.example.com/?action=fileUpload',
		headers: { Authorization: 'Bearer secret' }
	},
	filebrowser: {
		ajax: {
			url: 'https://files.example.com/',
			headers: { Authorization: 'Bearer secret' }
		}
	}
});
```
````

## What you get

```features
- **Any storage behind one API**: Local folders, AWS S3 and S3-compatible services (MinIO, Cloudflare R2, Yandex), Azure Blob, Google Cloud Storage, FTP, FTPS, SFTP and WebDAV. Custom adapters register by name.
- **Access rules you can reason about**: Rules by role, path and extension in config, computed in code, or loaded from a database on each request. The last matching rule wins.
- **Your authentication, not theirs**: One async callback receives the request and returns a role. Cookies, JWT or a session store all fit.
- **Hardened by default**: SSRF-safe remote downloads, SVG uploads lose their scripts, paths stay inside the source root (symlinks included), POST-only mode and a CORS allowlist.
- **Images and documents**: Thumbnails, resize and crop through Pillow; the image editor saves back to the same source. Optional PDF and DOCX generation from HTML.
- **Multi-tenant**: Resolve sources per request (for example by a tenant header) and serve many customers from one instance, with caching built in.
- **FastAPI native**: Run it standalone or mount it as a router with a prefix. Several isolated instances can live in one application.
- **Typed, tested, containerised**: mypy --strict with py.typed, a pytest suite at 100% coverage on real backends via Testcontainers, and a non-root multi-arch Docker image.
```

## Storage recipes

### Files in an S3 bucket

```json
{
	"sources": {
		"media": {
			"title": "Media",
			"baseurl": "https://my-bucket.s3.eu-central-1.amazonaws.com/media/",
			"storageAdapter": "s3",
			"s3": {
				"bucket": "my-bucket",
				"region": "eu-central-1",
				"prefix": "media"
			}
		}
	}
}
```

Without `credentials` the AWS default chain is used (environment, profile, instance role). MinIO, Cloudflare R2 and Yandex Object Storage work through `endpoint` (plus `forcePathStyle: true` where needed).

### Files on an SFTP server

```json
{
	"sources": {
		"site": {
			"title": "Website files",
			"baseurl": "https://www.example.com/uploads/",
			"storageAdapter": "sftp",
			"sftp": {
				"host": "files.example.com",
				"username": "editor",
				"privateKeyFile": "/run/secrets/editor_ed25519",
				"hostKey": "ssh-ed25519 AAAA...",
				"directory": "/var/www/uploads"
			}
		}
	}
}
```

`storageAdapter: "ftp"` with an `ftp` block works the same way (`tls: true` for FTPS), and so does `"webdav"` with a `webdav` block (`url`, `username`, `password`). The SFTP host key is always checked; get it with `ssh-keyscan`.

### Access rules from a database

```python
from jcpy import AccessControlRule, create_app


async def load_rules() -> list[AccessControlRule]:
    rows = await db.fetch_rules()
    return [AccessControlRule.model_validate(row) for row in rows]


app = create_app("config.json", access_control=load_rules)
```

### Multi-tenant sources

```python
from starlette.requests import Request

from jcpy import ResolvedSources, create_app


async def resolve_sources(request: Request) -> ResolvedSources | None:
    tenant = await find_tenant(request.headers.get("x-tenant-id"))
    if tenant is None:
        return None  # static "sources" apply
    return ResolvedSources(
        id=f"{tenant.id}:{tenant.updated_at}",
        sources={"files": tenant.source_settings},
    )


app = create_app("config.json", resolve_sources=resolve_sources)
```

### Several instances in one FastAPI app

```python
from fastapi import FastAPI

from jcpy import create_router

app = FastAPI()
app.include_router(create_router("public.json"), prefix="/public")
app.include_router(
    create_router("admin.json", check_authentication=admin_auth),
    prefix="/admin",
)
```

## Production notes

- Run the Docker image (non-root, `linux/amd64` and `linux/arm64`) or uvicorn behind nginx.
- Keep `onlyPOST` on and set the CORS allowlist to the origins that host your editor.
- Give the editor a token your `check_authentication` callback accepts, and block `FILE_UPLOAD` and `FILE_REMOVE` for the default role.

## FAQ

**Do I need Jodit PRO for this?** No. The connector speaks the same protocol as the free file browser and uploader built into the Jodit core. Jodit PRO adds the [Finder](/jodit/pro/docs/plugin/finder/) plugin, a richer file manager UI on top of the same backend.

**Does it work with MinIO or Cloudflare R2?** Yes. Use the `s3` adapter with an `endpoint`, and `forcePathStyle: true` where the service needs it.

**How do I keep anonymous users from uploading?** Return `"guest"` from `check_authentication` for requests without a valid token, and add a guest rule with `FILE_UPLOAD` and `FILE_REMOVE` set to `false`.

## Links

```cards
- [GitHub](https://github.com/TimurSeyidov/jodit-python): Source, issues, changelog. MIT license.
- [PyPI](https://pypi.org/project/jodit-python/): pip install jodit-python, with optional extras.
- [Documentation](https://timurseyidov.github.io/jodit-python/): Installation, authentication, access control, every storage backend.
- [Docker Hub](https://hub.docker.com/r/w2fb/jodit-python): w2fb/jodit-python, linux/amd64 and arm64.
- [API endpoints](https://timurseyidov.github.io/jodit-python/api/): Every action with its parameters and answers, plus Swagger UI.
- [Node.js connector](./jodit-nodejs.html): The same protocol implemented in TypeScript on Express.
```
