# Kb Writefreely

> Background knowledge about WriteFreely, the open-source federated blogging platform written in Go (AGPL-3.0) by Matt Baer / Musing Studio. Covers the origin story (Write.as anonymous writing platform launched 2014, open-sourced as WriteFreely November 2018 after adding ActivityPub), the write-only/broadcast federation model (blogs can be followed but cannot follow others, no incoming comments/replies displayed, no moderation), the actor model (each blog/collection is a Person actor with handle @blog@instance.com), the Article vs Note object type distinction (double-line break heuristic, notes_only config option), supported activities (outbound Create/Update/Delete, inbound Follow/Undo/Like), RSA-SHA256 HTTP signatures with per-blog keypairs, the tech stack (Go/MySQL or SQLite/single binary deployment/256 MB RAM/Raspberry Pi), configuration (single-user vs multi-user modes, federation toggle, .ini config file), NodeInfo with WriteFreely-specific metadata, the RESTful API (Posts/Collections/Users with token aut

- Skill: `majiayu000/kb-writefreely` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add majiayu000/kb-writefreely`
- Raw SKILL.md: https://api.skillmd.com/api/skills/majiayu000/kb-writefreely/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: majiayu000 (https://skillmd.com/u/majiayu000)
- Updated: 2026-09-09
- Page: https://skillmd.com/skills/majiayu000/kb-writefreely

---


# WriteFreely — Complete Reference

## Overview

WriteFreely is a free, open-source, federated blogging platform written
in **Go**, created by **Matt Baer** and developed under **Musing Studio**
(formerly the Write.as team). Licensed **AGPL-3.0**.

WriteFreely originated from **Write.as**, a privacy-focused anonymous
writing platform launched in late **2014**. After adding ActivityPub
federation support, the codebase was refactored and released as open
source in **November 2018**. It describes itself as "the social media
hater's blogging platform" — deliberately rejecting feeds, alerts, and
engagement metrics in favor of a distraction-free writing environment.

WriteFreely's federation model is **write-only/broadcast**: blogs can
be followed from Mastodon and other ActivityPub platforms, but WriteFreely
cannot follow others, does not display incoming replies or comments, and
has no follower moderation. This is a deliberate design choice — it is a
writing platform, not a social network.

As of early 2026, Write.as has hosted over 600,000 users with 6.6 million
posts. The WriteFreely open-source software powers hundreds of independent
instances across the fediverse.

---

## 1. Technology Stack

| Component | Technology |
|-----------|-----------|
| Backend | Go (golang) — 64% of codebase |
| Templates | Go Template — 24% |
| Styling | LESS — 6.5% |
| Client-side | JavaScript — 3.8% |
| Database | MySQL/MariaDB 5.6+ or SQLite |
| Rich editor | ProseMirror (WYSIWYG "classic" mode) |
| License | AGPL-3.0 |

### Key Technical Characteristics

- **Single binary deployment** on any platform/architecture Go supports
- **Minimal resources**: runs on servers with 256 MB RAM, including
  Raspberry Pi
- **No external dependencies** in SQLite mode
- Built-in HTTP server with TLS support (Let's Encrypt auto-cert)
- Optional Gopher protocol server (since v0.13)
- Go 1.23+ and Node.js 15+ required for development builds

### Database

Two backends supported:

- **MySQL/MariaDB 5.6+**: `CREATE DATABASE writefreely CHARACTER SET
  latin1 COLLATE latin1_swedish_ci;`
- **SQLite**: Built-in, no external database needed

PostgreSQL is **not** supported.

Database tables include: user access tokens, public/private RSA keypairs
per collection (for signing ActivityPub requests), collections (blogs),
posts, remote users, and remote follows.

---

## 2. ActivityPub Implementation

### Actor Model

Each **blog (collection)** is an ActivityPub actor of type `Person`. In
single-user mode, the instance acts as the single blog actor.

The fediverse handle format is `@blog@instance.com` (or
`@username@instance.com` in single-user mode).

Each collection/blog has:
- A public/private RSA keypair for signing requests
- An inbox endpoint for receiving activities
- An outbox endpoint for published content
- A followers collection

### Object Types

WriteFreely federates posts as either `Article` or `Note`:

- **`Article`**: Posts containing a double-line break (paragraph break).
  The default for multi-paragraph blog posts. Includes the post title
  in the `name` property.
- **`Note`**: Posts without paragraph breaks (single-paragraph). Can be
  forced for all posts via `notes_only = true` configuration.

The heuristic: if the post body contains `\n\n` (double newline), it is
sent as `Article`; otherwise as `Note`. This was implemented in v0.13
(merged March 2021).

Since v0.16, posts include `preview`/`summary` fields so microblogging
platforms like Mastodon and Threads show excerpts with links rather than
just a link.

### Activities

WriteFreely supports a deliberately limited set of activities:

**Outbound (WriteFreely sends):**

| Activity | Trigger |
|----------|---------|
| `Create{Article}` or `Create{Note}` | Post published |
| `Update{Article}` or `Update{Note}` | Post edited |
| `Delete` | Post deleted |

**Inbound (WriteFreely receives):**

| Activity | Effect |
|----------|--------|
| `Follow` | Remote user follows a blog |
| `Undo{Follow}` | Remote user unfollows |
| `Like` | Remote user favorites a post (count tracked since v0.16) |

### Write-Only / Broadcast Nature

This is the most critical detail for interoperability:

- Blogs **can be followed** from Mastodon, Pleroma, and other AP platforms
- Posts **are delivered** to followers' timelines
- **No incoming replies/comments**: WriteFreely does not display replies
  from federated users on the blog itself
- **No outgoing follows**: Cannot follow other users from WriteFreely
- **No blocking/managing follows**: No moderation tools for followers
- **No Announce/boost**: WriteFreely does not send or process boosts
- **Mentions as workaround**: Authors can mention their own Mastodon
  account (`@handle@their.instance`) in posts so fediverse replies route
  to their microblogging account

### HTTP Signatures

RSA-SHA256 signatures with per-blog keypairs. Signed headers include:
request target, date, host, and digest. Remote user data stored: actor
ID, inbox URL, shared inbox URL, user handle, and public key.

### Libraries

- **go-fed/activity/streams**: ActivityPub library for Go
- **writefreely/go-nodeinfo**: Fork of NodeInfo with WriteFreely-specific
  metadata

---

## 3. Configuration

WriteFreely uses an `.ini` config file (`config.ini` by default).

### Instance Modes

- **Single-user**: One blog, the instance is the blog actor
- **Multi-user**: Multiple accounts, each with up to 5 blogs (default),
  local timeline/reader, admin dashboard, invitation system

### Key Federation Settings

| Field | Description | Default |
|-------|-------------|---------|
| `federation` | ActivityPub federation toggle | true |
| `notes_only` | Send all posts as Note (not Article) | false |
| `public_stats` | NodeInfo statistics visibility | true |

### Other Notable Settings

| Field | Description | Default |
|-------|-------------|---------|
| `single_user` | Single-blog instance mode | false |
| `editor` | Editor template: pad, bare, classic | pad |
| `open_registration` | Public signup (multi-user) | true |
| `max_blogs` | Blogs per account (multi-user) | 5 |
| `default_visibility` | New blog visibility | unlisted |
| `local_timeline` | Instance reader (multi-user) | true |
| `monetization` | Web Monetization support | true |
| `gopher_port` | Gopher server port (0 = disabled) | 0 |

---

## 4. NodeInfo

WriteFreely implements NodeInfo via `github.com/writefreely/go-nodeinfo`
(v1.2.0, MIT license), a fork with WriteFreely-specific metadata.

### Discovery

`/.well-known/nodeinfo` → points to full NodeInfo URL.

### WriteFreely-Specific Metadata

```json
{
  "metadata": {
    "nodeName": "My Blog",
    "nodeDescription": "A writing community",
    "private": false,
    "software": {
      "homepage": "https://writefreely.org",
      "github": "https://github.com/writefreely/writefreely",
      "follow": "https://writing.exchange/@writefreely"
    },
    "maxBlogs": 5,
    "publicReader": true,
    "invites": true
  },
  "protocols": ["activitypub"],
  "services": {
    "outbound": ["rss2.0"]
  },
  "software": {
    "name": "writefreely",
    "version": "0.16.0"
  }
}
```

---

## 5. API

WriteFreely provides a RESTful API (shared with Write.as) with full
read access and publishing.

### Authentication

Token-based: `Authorization: Token {access_token}` (UUID format).
Anonymous publishing is supported — no auth required for creating posts.

Accepts form data (default) or `application/json`. Returns JSON.

### Core Endpoints

**Posts:**

| Method | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| POST | `/api/posts` | Optional | Create post |
| GET | `/api/posts/{id}` | No | Retrieve post |
| POST | `/api/posts/{id}` | Optional | Update post |
| DELETE | `/api/posts/{id}` | Optional | Delete post |
| POST | `/api/posts/claim` | Required | Claim anonymous posts |

**Collections (Blogs):**

| Method | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| POST | `/api/collections` | Required | Create collection |
| GET | `/api/collections/{alias}` | No | Get metadata |
| POST | `/api/collections/{alias}/posts` | Required | Publish to blog |
| GET | `/api/collections/{alias}/posts` | No | List posts |
| GET | `/api/collections/{alias}/posts/{slug}` | No | Get single post |

**Users:**

| Method | Endpoint | Auth | Purpose |
|--------|----------|------|---------|
| POST | `/api/auth/login` | No | Authenticate |
| GET | `/api/me` | Required | Get user info |
| GET | `/api/me/posts` | Required | List user's posts |
| GET | `/api/me/collections` | Required | List user's blogs |

### Data Models

**Post**: id, slug, token (for anonymous updates), title, body
(Markdown), appearance (sans/serif/wrap/mono/code), language, rtl,
created, updated, views, tags, collection.

**Collection**: alias (URL slug), title, description, style_sheet,
script, visibility (0=Unlisted, 1=Public, 2=Private,
4=Password-protected), views, total_posts.

---

## 6. Features

### Writing and Publishing

- Distraction-free Markdown editor with three modes: pad (default),
  bare, classic (WYSIWYG via ProseMirror, since v0.13)
- MathJax formula rendering
- Hashtag organization
- Custom CSS and JavaScript per blog
- Font appearances: serif, sans-serif, monospace, wrapped
- RTL script support, 20+ language localizations
- Draft posts, static pinned pages, post signatures
- RSS feed per blog (automatic)
- SEO best practices encoded by default
- No native image hosting — must embed from external sources (Snap.as
  or other hosts)

### Multi-User Community

- Multiple blogs per account (configurable, default 5)
- Local timeline / reader for shared content
- Privacy levels: public, unlisted, private, password-protected
- Open or closed registration, invitation system
- Admin dashboard with instance stats and user management

### Federation Features

- Fediverse verification badges via rel="me" (since v0.14)
- Like counts visible to blog authors (since v0.16)
- Subscribers page showing fediverse followers
- fediverse:creator tag linking to another profile (since v0.16)
- Mentions via `@handle@their.instance` in post content

### Other Protocols and Integrations

- Gopher protocol server (since v0.13)
- Web Monetization micropayments (since v0.13)
- Email newsletters via Mailgun (since v0.15)
- SMTP for password resets and notifications (since v0.16)
- OAuth 2.0: Generic, Gitea, GitLab, Slack, Write.as

---

## 7. Ecosystem

WriteFreely is part of the **Musing Studio** ecosystem:

| Service | Purpose |
|---------|---------|
| **Write.as** | Hosted blogging platform ($1-10/month) |
| **WriteFreely** | Open-source software (self-hosted) |
| **WriteFreely.host** | Managed WriteFreely community hosting |
| **Snap.as** | Minimalist photo sharing |
| **Remark.as** | Commenting system for Write.as blogs |
| **Submit.as** | Accept writing submissions |
| **Read.as / ReadFreely** | ActivityPub reading client (paused) |

Musing Studio was founded by Matt Baer in 2015. He runs the company
solo. Core values: ad-free, tracking-free, customer-funded (no venture
capital), open web interoperability.

---

## 8. Comparisons

### WriteFreely vs Plume

| Aspect | WriteFreely | Plume |
|--------|------------|-------|
| Language | Go | Rust |
| Federation model | Write-only (broadcast) | Bidirectional (shows replies) |
| Customization | Custom CSS only | Themes, fonts, layouts |
| Maintenance | Actively maintained | Not actively maintained |
| Object types | Article + Note | Article |

### WriteFreely vs Ghost

| Aspect | WriteFreely | Ghost |
|--------|------------|-------|
| Language | Go | Node.js |
| AP integration | Since 2018 (core) | Since 2024 (core) |
| Focus | Minimalist writing | Full-featured CMS |
| Image hosting | None (external only) | Built-in |
| Newsletters | Mailgun integration | Built-in |
| Themes | Custom CSS | Full theme system |
| Resources | 256 MB RAM | 1 GB+ RAM |

---

## 9. Interoperability Notes for Implementers

1. **Blogs are `Person` actors** — each WriteFreely blog (collection)
   presents itself as a `Person` actor with a fediverse handle of
   `@alias@instance.com`. Follow a blog by sending `Follow` to this
   actor.

2. **Write-only federation** — WriteFreely will never send `Follow`,
   `Announce`, `Like`, or reply activities. It only sends `Create`,
   `Update`, and `Delete` for its own posts. If your software expects
   bidirectional interaction, WriteFreely will not reciprocate.

3. **Article vs Note distinction** — multi-paragraph posts arrive as
   `Article` objects (with `name` containing the title), single-paragraph
   posts arrive as `Note` objects (no `name`). If an instance has
   `notes_only = true`, all posts arrive as `Note`. Your software should
   handle both types from WriteFreely.

4. **No incoming comment support** — if your users reply to a WriteFreely
   post, their reply will be delivered to the WriteFreely inbox but will
   **not be displayed** on the blog. The reply exists only on your
   platform. Authors may work around this by mentioning their Mastodon
   account in posts.

5. **Like tracking** — since v0.16, WriteFreely tracks `Like` activities
   and shows a count to the blog author. Sending `Like` to a WriteFreely
   blog will be acknowledged, but `Undo{Like}` handling may vary.

6. **No shared inbox optimization** — WriteFreely stores shared inbox
   URLs from remote actors but primarily uses individual inboxes. Verify
   delivery works with both paths.

7. **Post excerpts on microblogging platforms** — since v0.16, WriteFreely
   includes `preview`/`summary` fields on `Article` objects. Mastodon and
   Threads will show an excerpt with a link rather than just a bare link.
   Older WriteFreely versions may send Articles without summaries.

8. **NodeInfo identifies the software** — look for
   `"name": "writefreely"` in the NodeInfo software block to identify
   WriteFreely instances. The metadata includes WriteFreely-specific
   fields like `maxBlogs`, `publicReader`, and `invites`.

9. **RSS is always available** — every WriteFreely blog has an RSS feed
   regardless of federation settings. If federation is disabled, RSS is
   still the primary syndication method.

10. **No image hosting** — WriteFreely posts reference externally-hosted
    images via Markdown image syntax. If your software processes
    WriteFreely content, images will be URLs to third-party hosts
    (including Snap.as), not to the WriteFreely instance itself.

