Markdown platform · writer-ir.v1

Write Markdown.
Get HTML, PDF and IR — the same everywhere.

Writer is a premium, framework-agnostic Markdown editor with Web Components, a stable intermediate model and SDKs for JavaScript/TypeScript, PHP and Python. You write once; the same document comes out the same everywhere.

  • No framework
  • CDN or npm
  • JS/TS · PHP · Python
  • MIT
writer.gr/playground live
Write here — it is real. Full playground
32/32conformance fixtures × 3 languages
3SDKs: JS/TS, PHP, Python
7renderers: HTML, IR, JSON, MD, text, print, PDF
≈130 kBgzip · full editor + core
v0.2.0 Production & Trust

Version 0.2.0: security, PDF and production.

Sanitisation at the IR boundary in JS, PHP and Python, a first-party @writer/pdf, React/Vue/Svelte wrappers, server examples and 32 conformance fixtures. No DOCX/EPUB or CRDT — only what is actually ready.

See the changelog

Features

An editor that does not close the door on you.

What you see is Markdown. What comes out is structured. No framework, no lock-in, no second draft for the preview.

Framework-agnostic

Web Components, not a wrapper. Works in plain HTML, React, Vue, Svelte, Astro, Rails, WordPress — anything that can load a script.

0 dependencies

WYSIWYG + source + split

One document, five modes. DOM changes are written back to Markdown — not to a second, out-of-sync draft.

one document

Writer IR

A stable JSON contract (writer-ir.v1) between parser, editor and renderers. Verified across 32 fixtures × 3 languages.

frozen contract

Typography that travels

Five themes, variables, font faces and print CSS. The same typography from the editor to HTML and PDF — not a different “sort of like this”.

5 themes

Real tables

Alignment, insert/delete rows and columns, header toggle, CSV/TSV paste and keyboard navigation. Tables, not piles of pipes.

real tables

Media, outside the editor

Providers, not hardcoded uploads: memory, HTTP, PHP/Python (local/FTP/SFTP/S3) or the Writer Media Protocol. The media manager also runs as a standalone component.

outside the editor

CSS frameworks

Bootstrap, Tailwind, Bulma, Foundation, UIkit. Detection, grid classes, buildGrid() and lint — without guessing class names.

5 frameworks

Plugins with real IR

Callouts, footnotes, task lists, TOC, math, mark, attrs, figures, code fences with line highlights. In the preset or opt-in — always structured.

FULL_PRESET

PDF without fake bytes

@writer/pdf provides a first-party engine with embedded fonts (DejaVu); alternatively Dompdf/ReportLab or print HTML. Without a renderer or engine, asPDF() returns NO_PDF_ENGINE — it does not disguise HTML as PDF.

honest output

Architecture

From keystroke to renderer, without magic.

Writer does not convert HTML to Markdown hoping for the best. It keeps a normalized IR and builds every output from it.

input Markdown CommonMark + GFM
parse unified / remark mdast
contract Writer IR canonical JSON frozen · v1
render Renderers + SDKs
HTML IR JSON Markdown Text Print / PDF
The same IR from JS/TS, PHP and Python — verified across 32 conformance fixtures.
01

You write

WYSIWYG, source or split. The same document, not copies.

02

Normalized

mdast → IR with stable IDs, order and attributes. Deterministic.

03

Processes

Commands, plugins, tables, media. Every change goes through history.

04

Comes out

HTML, IR, JSON, Markdown, text, print/PDF. Same structure, same content.

Render lab

Watch the IR being born.

Write on the left. The preview, HTML, IR and JSON are the same document — no server, no build step.

input.md

SDKs

One language for every layer. One contract for all.

The editor in the browser, rendering on the server. All three SDKs share the same IR and the same conformance fixtures.

JavaScript / TypeScript

@writer/core
render.ts
import { WriterRender } from '@writer/core';

const html = WriterRender(markdown).asHTML();
const ir   = WriterRender(markdown).asIR();
const json = WriterRender(markdown).asJSON({}, true);
const pdf  = await WriterRender(markdown).asPDF();

PHP

writer/writer
render.php
use Writer\Writer;

$html = WriterRender($markdown)->asHTML();
$ir   = WriterRender($markdown)->asIR();
$json = WriterRender($markdown)->asJSON(true);
$pdf  = WriterRender($markdown)->asPDF(); // dompdf

Python

writer
render.py
from writer import WriterRender

html = WriterRender(markdown).as_html()
ir   = WriterRender(markdown).as_ir()
json = WriterRender(markdown).as_json(pretty=True)
pdf  = WriterRender(markdown).as_pdf()
Parity: 32 fixtures × every language, checked against the canonical IR. JS/TS PHP Python writer-ir.v1

Quickstart

From zero to render, in one tab.

The same bundle is self-hosted at /assets/writer/. One script tag, or one import — your choice.

index.html
<link rel="stylesheet" href="https://writer.gr/assets/writer/writer.css">
<script src="https://writer.gr/assets/writer/writer.min.js"></script>

<div id="editor"></div>
<script>
  const editor = Writer.mount('#editor', {
    value: '# Hello',
    theme: 'serif-editorial'
  });

  editor.addEventListener('wr-change', () => {
    console.log(editor.getMarkdown());
  });
</script>

FAQ

The questions everyone asks.

Is it free?
Yes. Writer is MIT licensed. You can self-host the bundles, put them on your CDN or use them as-is. There is no locked cloud.
Do I need React/Vue/Svelte?
No. The editor is a Web Component. In React/Vue/Svelte you use it with a ref — no wrapper library. The same applies to static HTML or server-rendered pages.
Where are media stored?
Wherever you say. The browser talks to a provider: memory for demos, HTTP for your own API, local/FTP/SFTP/S3 in the PHP/Python editions, or the Writer Media Protocol. The editor makes no filesystem assumptions.
Does it produce PDF?
Yes — with @writer/pdf in JS (embedded fonts, straight from the IR) or an engine/print HTML; the PHP/Python editions include Dompdf/ReportLab. If neither is available, asPDF() fails with NO_PDF_ENGINE instead of returning HTML with a .pdf extension.
Is it compatible with CommonMark/GFM?
Yes: CommonMark + GFM (tables, task lists, strikethrough, autolinks, footnotes), plus the Writer extensions (callouts, math, attrs, marks, TOC, figures). Markdown → IR → Markdown round-trip is part of the tests.
How large is the bundle?
The full writer.min.js is ≈130 kB gzip (editor + core). writer-core.min.js, for render-only pages, is ≈100 kB gzip. Both are self-contained — no framework runtime.
Can I use it in WordPress or Laravel?
Yes. In WordPress you load the script and the custom element in a template/block. In Laravel you use the PHP SDK for server rendering and the JS bundle for the editor. No specific CMS is required.

Your next document does not need a framework.

Try the editor, inspect the IR, pick an SDK — or download the bundle and start.