doory README

The project's README from github.com/shaongitbd/doory. As one markdown file: README.md.

Doory: the open-source Canva alternative

Doory

Doory is a design editor that runs in the browser: posters, social posts, stories, slides, invitations, resumes, thumbnails and business cards. You start from a template or a blank page, edit everything on it, and export PNG, JPG, SVG, PDF, MP4 or GIF. There is no paid plan, you don't need an account to start, and you can run it on your own server with Docker or without it.

It is a young project: the editor is complete, the template library is small (ten starter templates), and bug reports are very welcome.

Opening the sale post template from the gallery, changing the headline and the discount, and opening the Share menu

What it can do

The editor with the sale post template open and the Text panel beside it Editing. Tools sit in a rail on the left and open as panels; the toolbar above the page changes with what you select. The Animate panel with enter animations Animation. Enter, exit and emphasis animations, page transitions, music and sound effects. Present it, or export it as a video or GIF.
The Layers panel with its right-click menu Layers. Rename, lock, hide, group and reorder anything, or right-click it to copy, duplicate and arrange. The Share menu with sharing, downloads and presenting Share and export. One menu for share links, downloads and presenting. Downloads are made in the browser; print PDFs can have bleed and crop marks.
Text Headings, curved text, effects, font pairings, lists, an open-licensed font catalogue and your own fonts
Elements Shapes, lines and arrows, about 27,500 icons, emoji and illustrations, stickers, QR codes
Photos Upload, crop, masks and filters, background removal that runs in the browser, stock photo search with an Unsplash or Pexels key
Layout Layers, groups, alignment guides, snapping, rulers, several pages per design (each its own size), resize to another format
Motion and sound 27 enter and exit animations, 12 emphasis animations, 16 page transitions, 24 music loops and 88 sound effects (CC0), voiceover recording
Present Present mode with speaker notes, timing, auto-advance and a second presenter window
Together Share links that view, comment or edit (with an optional password), pinned comments, version history, real-time co-editing
Brand Brand kits with your colours, fonts and logos
Export PNG, JPG, SVG, PDF, MP4, WebM and GIF

Templates

The ten starter templates: a tour poster, an event story, a sale post, a pitch deck, a grand opening flyer, a wedding invitation, a YouTube thumbnail, a resume, a quote post and a business card

Ten starter templates ship with Doory. Each one follows the layout of the best-selling templates in its category, uses open-licensed fonts and, where it has a photo, a CC0 one. Everything on them can be edited. Admins can add more in the admin site.

The template gallery with category filters

Self-host it

cp .env.example .env     # set DJANGO_SECRET_KEY (openssl rand -base64 48) and POSTGRES_PASSWORD
docker compose up -d --build
docker compose exec api python manage.py createsuperuser   # your admin account; nothing creates one for you

Open http://localhost:8080 and start designing. The web container serves the app and forwards /api, /ws and /mcp to the backend, so the browser only ever talks to one address.

The admin site (templates, users, content)

The admin site is at /admin/ on your domain, e.g. http://localhost:8080/admin/. Log in with the account made by createsuperuser; accounts that only signed up on the site cannot open it. There you add and edit templates and their categories, manage users, designs and uploads, and look after fonts, element packs and audio.

To Run
Make an admin account docker compose exec api python manage.py createsuperuser
Reset an admin's password docker compose exec api python manage.py changepassword [email protected]
Make an existing account an admin docker compose exec api python manage.py shell -c "from django.contrib.auth import get_user_model as g; u = g().objects.get(email='[email protected]'); u.is_staff = u.is_superuser = True; u.save()"

Without Docker, run the same manage.py commands with the manage helper from the guide below.

Add the starter content (templates, icons, fonts, audio)

A new site starts empty. Add what you want once it is running, or set the DOORY_SYNC_* and DOORY_SEED_TEMPLATES options in .env to true and the API container does it on start:

docker compose exec api python manage.py seed_gallery         # template categories
docker compose exec api python manage.py sync_fonts --popular 100
docker compose exec api python manage.py seed_templates       # the starter templates (add --update to refresh them)
docker compose exec api python manage.py seed_elements        # shapes and graphics
docker compose exec api python manage.py sync_element_packs   # icons, emoji and illustrations (download)
docker compose exec api python manage.py sync_models          # background removal model
docker compose exec api python manage.py sync_audio           # music and sound effects
Your own size presets (sizes for creating and resizing designs)

The sizes offered in the Create and Resize dialogs come from the server. A new site has the built-in ones (social posts, presentations, paper and print sizes, video); add your own, or offer only yours, in any of three ways:

Way How
Admin site Size presets in the admin: groups (nested to any depth) and presets, drag to reorder, switch them on or off
A JSON file, once docker compose exec api python manage.py import_size_presets sizes.json (a folder imports every .json in it; --dry-run only checks)
A JSON file, on every start Mount the file into the API container and set DOORY_SIZE_PRESETS_FILE in .env (see docker-compose.yml). The file stays out of the image and the repository.

--mode extend (the default, and DOORY_SIZE_PRESETS_MODE=extend) adds to the built-in sizes; a preset with the same slug replaces the built-in one. replace offers only the file's sizes and switches the others off (they are kept, not deleted). Importing is idempotent on the slug, so the same file can be imported again after editing.

{
  "version": 1,
  "groups": [
    {
      "slug": "social", "label": "Social media",
      "children": [
        {
          "slug": "platform-a", "label": "Platform A",
          "presets": [
            { "slug": "platform-a-portrait", "label": "Portrait post", "width": 1000, "height": 1250, "unit": "px",
              "tags": ["4:5"], "is_quick": true, "meta": { "safe_zone": { "top": 0.1, "bottom": 0.2 } } }
          ]
        }
      ]
    },
    {
      "slug": "print", "label": "Print",
      "presets": [{ "slug": "flyer-a6", "label": "Flyer (A6)", "width": 105, "height": 148, "unit": "mm" }]
    }
  ]
}

A preset needs slug, label, width and height; unit is px (default), mm, cm or in. Optional: icon (a Lucide name), tags (searchable, shown as filters), aliases (old slugs that still mean this preset), is_quick (a chip on the home page), is_active, order and meta (safe_zone holds the fractions of the page to keep clear on each side). Groups take slug, label, icon, order, is_active, children and presets. GET /api/v1/size-presets/ returns the current catalogue in the same format, so it can be saved, edited and imported again. manage.py seed_size_presets --update resets the built-in sizes.

Settings worth knowing (.env)
Setting What it does
SITE_URL The public address of the site, e.g. https://doory.example.com (no trailing slash)
DJANGO_ALLOWED_HOSTS The host names the site answers to, comma separated
SECURE_SSL_REDIRECT, SECURE_COOKIES true behind HTTPS, false for plain http://
EMAIL_URL, DEFAULT_FROM_EMAIL SMTP, e.g. smtp+tls://user:[email protected]:587 (without it, emails are printed to the API log)
WEB_PORT, WEB_BIND Where the site is published (default 0.0.0.0:8080)
INSTALL_CHROMIUM false skips the headless browser that server-side PDF export needs
GOOGLE_CLIENT_ID Turns on "Sign in with Google"
UNSPLASH_ACCESS_KEY, PEXELS_API_KEY, PIXABAY_API_KEY Turn on stock photo search, one tab per provider plus an "All" tab
STOCK_PROVIDERS, STOCK_ALL_PROVIDERS, STOCK_DEFAULT_TAB Which providers are offered, in which order, and which the "All" tab searches
STOCK_<PROVIDER>_RATE, STOCK_<PROVIDER>_CACHE_SECONDS, STOCK_<PROVIDER>_SAFESEARCH An optional cap on calls per API key (all users together, e.g. 5000/h; none by default, a provider is still paused when it answers 429), how long searches are cached, and safe search (Unsplash off, Pixabay on). Caps and cache times only hold across workers with a shared cache (the Docker setup uses the database cache; locmem is for development)
USE_S3 and AWS_* Store uploads in S3-compatible storage instead of a local volume
DOORY_SIZE_PRESETS_FILE, DOORY_SIZE_PRESETS_MODE Import your own size presets from a JSON file on every start (see above)
Run it without Docker (a Linux server with nginx and systemd)

Doory is three pieces: the API (Django on gunicorn), the real-time server for live co-editing (the same code on uvicorn) and the web app, a folder of static files that nginx serves while it forwards /api and /ws to the other two. The files in deploy/ set this up. These steps are for Debian 12 or Ubuntu 24.04 with the checkout in /opt/doory; the paths are easy to change. You need Python 3.12, Node.js 22 (only to build the web app) and PostgreSQL.

1. Packages, a user and the code

sudo apt install python3 python3-venv postgresql nginx git
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash - && sudo apt install nodejs
sudo useradd --system --create-home --home-dir /var/lib/doory --shell /usr/sbin/nologin doory
sudo mkdir /opt/doory && sudo chown doory:doory /opt/doory
sudo -u doory git clone https://github.com/shaongitbd/doory /opt/doory

2. The database

sudo -u postgres createuser doory --pwprompt     # pick a password of letters and digits
sudo -u postgres createdb doory --owner doory

3. The API

cd /opt/doory/backend
sudo -u doory python3 -m venv .venv
sudo -u doory .venv/bin/pip install -r requirements.txt
sudo -u doory nano .env                          # paste the settings below
DJANGO_SECRET_KEY=           # openssl rand -base64 48
SITE_URL=https://design.example.com
DJANGO_ALLOWED_HOSTS=design.example.com    # add the server's IP to try it by address
DATABASE_URL=postgres://doory:[email protected]:5432/doory
CACHE_URL=dbcache://doory_cache
MEDIA_ROOT=/opt/doory/media
# Plain http:// while you try it out; remove both lines once HTTPS works.
SECURE_SSL_REDIRECT=false
SECURE_COOKIES=false

Then prepare the database and add an admin account:

manage() { sudo -u doory .venv/bin/python manage.py "$@" --settings=doory.settings.prod; }
manage migrate
manage createcachetable
manage collectstatic --noinput
manage createsuperuser

The starter content is added with the same commands as above, e.g. manage seed_gallery, manage sync_fonts --popular 100 and manage seed_templates.

4. The web app

cd /opt/doory/frontend
sudo -u doory npm ci && sudo -u doory npm run build    # writes frontend/dist

5. Start everything

sudo cp /opt/doory/deploy/doory-api.service /opt/doory/deploy/doory-realtime.service /etc/systemd/system/
sudo systemctl daemon-reload && sudo systemctl enable --now doory-api doory-realtime
# Optional, for AI assistants (MCP_ENABLED=true in .env): the MCP server
# sudo cp /opt/doory/deploy/doory-mcp.service /etc/systemd/system/ && sudo systemctl enable --now doory-mcp
sudo cp /opt/doory/deploy/nginx.conf /etc/nginx/conf.d/doory.conf    # set server_name in it
sudo rm -f /etc/nginx/sites-enabled/default
sudo -u doory mkdir -p /opt/doory/media       # uploads; nginx serves them from here
sudo nginx -t && sudo systemctl reload nginx

Open your domain. For HTTPS, sudo apt install certbot python3-certbot-nginx && sudo certbot --nginx, then remove the two SECURE_ lines from .env and sudo systemctl restart doory-api doory-realtime (and doory-mcp if you run it).

Updating: sudo -u doory git pull, then repeat pip install, manage migrate, manage collectstatic --noinput and npm ci && npm run build, and sudo systemctl restart doory-api doory-realtime (and doory-mcp if you run it).

Optional: server-side PDF export needs a headless Chromium: sudo .venv/bin/playwright install-deps chromium, then sudo -u doory .venv/bin/playwright install chromium. Without it, set SERVER_PDF_ENABLED=false in .env.

Develop locally
make setup     # virtualenv, dependencies, local database
make dev-api   # API on http://localhost:8000
make dev-web   # app on http://localhost:5173 (second terminal)

make help lists the other targets (tests, lint, content).

Use it from an AI assistant (MCP)

Doory runs a Model Context Protocol server at /mcp on your domain, so Claude, Cursor and other MCP clients can create and edit your designs: "make a 1080 x 1350 poster for our summer sale with the brand colours", "translate the headlines on every page", "move the logo to the bottom right".

The server is off by default. To turn it on, add MCP_ENABLED=true to .env; with Docker also add COMPOSE_PROFILES=mcp (it starts the mcp service), then docker compose up -d. Without Docker, install deploy/doory-mcp.service too (see step 5 above) and restart doory-api.

  1. In Doory, open Settings → AI assistants (MCP) and create a token. Choose what it may do (read designs, edit them, trash them, add images, comment, manage comments, change brand kits, manage share links) and when it expires (at most a year), and confirm with your password. The token is shown once. Each permission is listed with what it allows; a token never gains a permission it was not given, even when new tools are added.

  2. Add the server to your client with the URL https://your-domain/mcp and the header Authorization: Bearer <token>. For Claude Code:

    claude mcp add --transport http doory https://your-domain/mcp --header "Authorization: Bearer doory_…"
    

    In a JSON configuration (Claude Code's .mcp.json, Cursor and others) that is {"type": "http", "url": "https://your-domain/mcp", "headers": {"Authorization": "Bearer doory_…"}}.

The assistant can list, read, create, rename, duplicate and edit designs; star them, organise them in folders, trash and restore them; add text (curved, with shadows, outlines, glows or highlights), shapes (including the catalogue's hearts, arrows, speech bubbles…), lines, pictures (cropped to a shape if wanted), QR codes and pages; group and ungroup objects; set backgrounds and speaker notes; search templates, the element catalogue, fonts and stock photos, and copy images from public https URLs; read, add, edit, resolve and delete comments; create and change brand kits; create and revoke share links; restore earlier versions; and look at a page as a PNG, JPG or WebP image (export_design), drawn on the server exactly as the editor draws it. Designs are also MCP resources (doory://designs/{id}), and two prompts, "Make a design from a brief" and "Review a design", show up in clients that offer prompts. Creating or changing a share link is marked as destructive, so clients ask you before an assistant does it, and comment text reaches the assistant labelled as untrusted. The state before every edit is kept in the design's version history, so anything an assistant did can be undone from there. If the design is open in your editor, the change appears live, like a co-editor's; with Redis it arrives at once, otherwise within a few seconds.

Text heights reported to the assistant are estimates until the design is opened in the editor, which measures text exactly. export_design is off by default (SERVER_IMAGE_EXPORT_ENABLED=true in .env turns it on; it needs INSTALL_CHROMIUM=true, the default, and reaches the web container's render.html at EXPORT_RENDER_PAGE_URL); images over 1 MB come back as a download link that expires after ten minutes. The same render is available to signed-in users at POST /api/v1/designs/{id}/export/image/. Revoke a token in the same settings page. Changing or resetting your password revokes all your tokens.

FAQ

Is it really free?
Yes. Doory has no paid plan, no locked features and no ads.
Do I need an account?
No. Guests design straight away and their work is saved in the browser. An account keeps designs in the cloud, lets you share them and use brand kits; signing up moves your guest designs over in one step.
Can I use it for my business or host it for my users?
Yes, as long as you credit the author as the licence asks: keep the licence and NOTICE with it and show the credit in the running app. See License and credit.
Where are my designs stored?
Guest designs and their images live in the browser on that device. Signed-in designs are stored on the server that runs Doory: yours, if you self-host. Uploads can go to a local volume or to S3-compatible storage.

Built with

React 19, TypeScript, Vite, Fabric.js, Yjs and Zustand in the browser; Django 5, Django REST Framework, Channels and PostgreSQL on the server; Docker Compose with nginx, optional Redis and S3-compatible storage.

Contributing

Bug reports, ideas and pull requests are welcome. Open an issue to report a bug or suggest a feature. For a security problem, please report it privately through the repository's Security tab instead.

License and credit

Doory is released under the Attribution Assurance License. You may use, change and redistribute it, including commercially, but you must credit the author: keep the licence and NOTICE with it, and display this credit prominently in the running app:

Doory by Siam, creator and maintainer of Doory — https://github.com/shaongitbd/doory

Third-party software, fonts and content keep their own licences; see THIRD_PARTY.md.



Made with care by Siam and the Doory bunny.

doory (shaongitbd/doory)

Searching Database…

1.5K ·GitHub

Doory is a design editor that runs in the browser: posters, social posts, stories, slides, invitations, resumes, thumbnails and business cards. You start from a template or a blank page, edit everything on it, and export PNG, JPG, SVG, PDF, MP4 or GIF.

Creator
shaongitbd
Size
29.1 MB
Stars
1.5K
Updated
CommitOct 18
Age
8d
Commits · 90 Days