Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ data.db-wal
data.db-shm
uploads/
.env
.test-build/
70 changes: 69 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ Unlike Canva or Adobe Express, this runs entirely on your own infrastructure. No

## Features

- **Template fields** — name any text or image object, then fill it from data or an agent over the API; one name can drive several objects and pages at once
- **Fabric.js canvas** — full object manipulation with retina/HiDPI rendering (2x device pixel ratio)
- **Pre-built templates** — LinkedIn-optimized: Quote Card, Stats Highlight, Announcement, Tips List, Profile Card, Minimal Text
- **10 Google Fonts** — Inter, Playfair Display, Montserrat, Poppins, Roboto, Open Sans, Lora, Raleway, Source Sans Pro, Merriweather
Expand Down Expand Up @@ -98,7 +99,8 @@ src/
server/
schema.sql — SQLite schema (designs, templates) + template seeds
db.ts — SQLite wrapper (query, get, run, transaction)
index.ts — Hono REST API (designs CRUD, templates, uploads)
index.ts — Hono REST API (designs CRUD, templates, fields, uploads)
fields.ts — Template fields: read the fill schema, substitute values
uploads.ts — Local file upload management
dev.ts — Dev server with static file serving
client/
Expand Down Expand Up @@ -133,11 +135,77 @@ templates (id, name, category, canvas_json, width, height, thumbnail_url, sort_o
| GET | `/api/designs/:id` | Get a design |
| PUT | `/api/designs/:id` | Update a design |
| DELETE | `/api/designs/:id` | Delete a design |
| GET | `/api/designs/:id/fields` | List the design's fillable fields |
| POST | `/api/designs/:id/fill` | Fill those fields with values |
| GET | `/api/templates` | List all templates |
| GET | `/api/templates/:id` | Get a template |
| POST | `/api/uploads` | Upload an image file |
| GET | `/api/uploads/:filename` | Serve an uploaded image |

## Template Fields

Design something once, then produce as many variants of it as you have rows of
data — without anything on the outside having to understand canvas JSON.

Select a text or image object in the editor and give it a **Field name** in the
properties panel. That object is now a fill slot:

```bash
curl localhost:8787/api/designs/$ID/fields
```

```json
[
{ "name": "headline", "type": "text", "value": "Your inspiring quote goes here", "page_ids": ["p1"] },
{ "name": "logo", "type": "image", "value": "https://.../old.png", "page_ids": ["p1", "p2"] }
]
```

Fill it. Text fields take a string (numbers are accepted and stringified),
image fields take a URL:

```bash
curl -X POST localhost:8787/api/designs/$ID/fill \
-H 'content-type: application/json' \
-d '{"values": {"headline": "Q3 revenue up 40%", "logo": "https://.../new.png"}}'
```

The response carries the filled pages plus `filled` and `unmatched`, so a name
that matches nothing is reported rather than failing the request. The stored
design is left alone — add `"save": true` (and optionally `"name"`) to persist
the result as a new design instead.

### How far this scales today

`save: true` writes a **new editable design**, not a rendered image, because
there is no server-side rasterizer. That makes it the right tool for a handful
of variants — an agent fills a card, opens it, exports it — and the wrong one
for a spreadsheet:

- Each saved variant is a row in `designs`, and `GET /api/designs` returns every
row with its full `canvas_json` and no pagination. A three-object card is
~2.5 KB serialised; a branded design with an image is 10-40 KB. A few hundred
variants turn the gallery response into megabytes.
- So a few hundred saved variants give you a few hundred gallery entries, not a
few hundred finished graphics.

For real bulk output, drive `fill` without `save` and rasterize in the browser —
the canvas is already there, and that loop is the missing piece rather than a
server-side renderer. Until it exists, treat `save: true` as a small-N
convenience.

Worth knowing:

- A field name may repeat. One `logo` across five pages fills all five, which is
what you want for a carousel.
- What gets written depends on the object, not the declared type: text objects
take `text`, image objects take `src`.
- A replacement image keeps the original object's box, so a different aspect
ratio will be stretched. Size the slot for the images you intend to feed it.
- Only text and image objects can be fields. Naming a shape does nothing.
- Rendering to PNG still happens in the browser — there is no server-side
rasterizer, because Workers have no canvas.

## Community & Contributions

This project is part of the [OpenClaw](https://github.com/openclaw/openclaw) ecosystem. Contributions are welcome — open an issue or submit a PR.
Expand Down
22 changes: 22 additions & 0 deletions agent.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,32 @@ A design editor for creating professional social media graphics, especially Link
- Image uploads and placement
- Multiple canvas sizes (1080x1080 square, 1200x627 landscape)
- Save and manage multiple designs
- Template fields — named slots you can fill from data over the API

## Filling a design from data
Objects in a design can be given a field name in the editor, which turns the
design into a template you can drive without touching canvas JSON:

- `GET /api/designs/{id}/fields` — what this design can be filled with. Returns
`name`, `type` (`text` or `image`), the current `value`, and the pages the
field appears on.
- `POST /api/designs/{id}/fill` — `{"values": {"headline": "..."}}`. Returns the
filled pages plus `filled` and `unmatched`; the stored design is unchanged.
Add `"save": true` to write the result as a new design you can then open and
export.

Prefer these over editing `canvas_json` by hand. Text fields take a string,
image fields take a URL, and one name may cover several objects or pages.

`save: true` produces an editable design, not a rendered image — there is no
server-side renderer. Use it for a handful of variants. Do not loop it over a
large list: every variant becomes a row that `GET /api/designs` returns in full,
and the user ends up with gallery entries rather than finished graphics.

## When to use this template
Use this template when the user wants to:
- Create social media post images or graphics
- Design LinkedIn posts, quote cards, or announcement banners
- Build a simple graphic design tool
- Create branded visual content
- Generate on-brand variants of one design by filling it with data
2 changes: 1 addition & 1 deletion clawnify.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://app.clawnify.com/schema/v1/clawnify.json",
"version": 1,
"name": "OpenDesign",
"description": "Design editor for creating professional social media graphics, LinkedIn posts, and visual content with a canvas-based UI, templates, multi-page support, image uploads, and text/shape tools. Like Canva. Best for graphic design tools, social media post creators, banner makers, or any visual content editor.",
"description": "Design editor for creating professional social media graphics, LinkedIn posts, and visual content with a canvas-based UI, templates, multi-page support, image uploads, and text/shape tools. Named template fields let designs be filled from data or an agent over the API, so one design can produce on-brand variants. Like Canva. Best for graphic design tools, social media post creators, banner makers, or any visual content editor.",
"icon": "icon.svg",
"screenshot": "https://github.com/user-attachments/assets/55fcc0aa-4606-4f80-b56a-7734d7673914",
"tags": ["design", "graphics", "social-media", "canvas"],
Expand Down
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"type": "module",
"scripts": {
"dev": "wrangler d1 execute open-design-db --local --file=src/server/schema.sql && concurrently -n ui,api -c cyan,green \"vite\" \"wrangler dev --port 8787\"",
"test": "esbuild src/server/fields.test.ts --bundle --platform=node --format=esm --outfile=.test-build/fields.test.mjs --log-level=warning && node --test .test-build/fields.test.mjs",
"build": "vite build"
},
"dependencies": {
Expand All @@ -22,8 +23,10 @@
},
"devDependencies": {
"@preact/preset-vite": "^2.9.0",
"@types/node": "^26.4.1",
"@types/webfontloader": "^1.6.38",
"concurrently": "^9.0.0",
"esbuild": "^0.28.2",
"typescript": "^5.7.0",
"vite": "^6.0.0",
"wrangler": "^4.0.0"
Expand Down
Loading