About The Project

Front PagePost
main screenchat screen

Lemmy is similar to sites like Reddit, Lobste.rs, Raddle, or Hacker News: you subscribe to forums you're interested in, post links and discussions, then vote, and comment on them. Behind the scenes, it is very different; anyone can easily run a server, and all these servers are federated (think email), and connected to the same universe, called the Fediverse.

For a link aggregator, this means a user registered on one server can subscribe to forums on any other server, and can have discussions with users registered elsewhere.

The overall goal is to create an easily self-hostable, decentralized alternative to reddit and other link aggregators, outside of their corporate control and meddling.

Each lemmy server can set its own moderation policy; appointing site-wide admins, and community moderators to keep out the trolls, and foster a healthy, non-toxic environment where all can feel comfortable contributing.

Note: Federation is still in active development

Why's it called Lemmy?

Built With

Features

  • Open source, AGPL License.
  • Self hostable, easy to deploy.
  • Clean, mobile-friendly interface.
    • Only a minimum of a username and password is required to sign up!
    • User avatar support.
    • Live-updating Comment threads.
    • Full vote scores (+/-) like old reddit.
    • Themes, including light, dark, and solarized.
    • Emojis with autocomplete support. Start typing :
    • User tagging using @, Community tagging using !.
    • Integrated image uploading in both posts and comments.
    • A post can consist of a title and any combination of self text, a URL, or nothing else.
    • Notifications, on comment replies and when you're tagged.
      • Notifications can be sent via email.
    • i18n / internationalization support.
    • RSS / Atom feeds for All, Subscribed, Inbox, User, and Community.
  • Cross-posting support.
    • A similar post search when creating new posts. Great for question / answer communities.
  • Moderation abilities.
    • Public Moderation Logs.
    • Can sticky posts to the top of communities.
    • Both site admins, and community moderators, who can appoint other moderators.
    • Can lock, remove, and restore posts and comments.
    • Can ban and unban users from communities and the site.
    • Can transfer site and communities to others.
  • Can fully erase your data, replacing all posts and comments.
  • NSFW post / community support.
  • High performance.
    • Server is written in rust.
    • Front end is ~80kB gzipped.
    • Front end works without javascript (read-only).
    • Supports arm64 / Raspberry Pi.

Goals

  • Come up with a name / codename.
  • Must have communities.
  • Must have threaded comments.
  • Must be federated: liking and following communities across instances.
  • Be live-updating: have a right pane for new comments, and a main pain for the full threaded view.
    • Use websockets for post / gets to your own instance.

Questions

  • How does voting work? Should we go back to the old way of showing up and downvote counts? Or just a score?
  • Decide on tech to be used
    • Backend: Actix, Diesel.
    • Frontend: inferno, typescript and bootstrap for now.
  • Should it allow bots?
  • Should the comments / votes be static, or feel like a chat, like flowchat?.
    • Two pane model - Right pane is live comments, left pane is live tree view.
    • On mobile, allow you to switch between them. Default?

Resources / Potential Libraries

Activitypub guides

Trending / Hot / Best Sorting algorithm

Goals

  • During the day, new posts and comments should be near the top, so they can be voted on.
  • After a day or so, the time factor should go away.
  • Use a log scale, since votes tend to snowball, and so the first 10 votes are just as important as the next hundred.

Reddit Sorting

Reddit's comment sorting algorithm, the wilson confidence sort, is inadequate, because it completely ignores time. What ends up happening, especially in smaller subreddits, is that the early comments end up getting upvoted, and newer comments stay at the bottom, never to be seen. Research showed that nearly all top comments are just the first ones posted.

Hacker News Sorting

The Hacker New's ranking algorithm is great, but it doesn't use a log scale for the scores.

My Algorithm

Rank = ScaleFactor * log(Max(1, 3 + Score)) / (Time + 2)^Gravity

Score = Upvotes - Downvotes
Time = time since submission (in hours)
Gravity = Decay gravity, 1.8 is default
  • Lemmy uses the same Rank algorithm above, in two sorts: Active, and Hot.
    • Active uses the post votes, and latest comment time (limited to two days).
    • Hot uses the post votes, and the post published time.
  • Use Max(1, score) to make sure all comments are affected by time decay.
  • Add 3 to the score, so that everything that has less than 3 downvotes will seem new. Otherwise all new comments would stay at zero, near the bottom.
  • The sign and abs of the score are necessary for dealing with the log of negative scores.
  • A scale factor of 10k gets the rank in integer form.

A plot of rank over 24 hours, of scores of 1, 5, 10, 100, 1000, with a scale factor of 10k.

Lemmy Guide

Start typing...

  • @a_user_name to get a list of usernames.
  • !a_community to get a list of communities.
  • :emoji to get a list of emojis.

Sorting

Applies to both posts and comments

TypeDescription
HotShows trending posts, based on the score, and the most recent comment time.
NewNewest posts.
TopShows the highest scoring posts in the given time frame.

For more detail, check the Post and Comment Ranking details.

Markdown Guide

TypeOr… to Get
*Italic*_Italic_Italic
**Bold**__Bold__Bold
# Heading 1Heading 1
=========

Heading 1

## Heading 2Heading 2
---------
Heading 2
[Link](http://a.com)[Link][1]

[1]: http://b.org
Link
![Image](http://url/a.png)![Image][1]

[1]: http://url/b.jpg
Markdown
> Blockquote
Blockquote
* List
* List
* List
- List
- List
- List
* List
* List
* List
1. One
2. Two
3. Three
1) One
2) Two
3) Three
1. One
2. Two
3. Three
Horizontal Rule
---
Horizontal Rule
***
Horizontal Rule

`Inline code` with backticksInline code with backticks
```
# code block
print '3 backticks or'
print 'indent 4 spaces'
```
····# code block
····print '3 backticks or'
····print 'indent 4 spaces'
# code block
print '3 backticks or'
print 'indent 4 spaces'
::: spoiler hidden or nsfw stuff
a bunch of spoilers here
:::
hidden or nsfw stuff

a bunch of spoilers here

Some ~subscript~ textSome subscript text
Some ^superscript^ textSome superscript text

CommonMark Tutorial

Administration info

Information for Lemmy instance admins, and those who want to run a server.

Install

Lemmy has two primary install methods, docker, and ansible. Ansible simplifies deploying to a remote server, while docker is best for local testing.

Manual install

Manual installs are possible, but not preferred, since Lemmy is dependent on other local services: The lemmy-ui, a Postgresql Database, pict-rs for images, and iframely for embeds. To see how these are wired together, look at the docker-compose.yml files. Due to the complexity of different systems, we will not support manual installs.

Docker Installation

Make sure you have both docker and docker-compose(>=1.24.0) installed. On Ubuntu, just run apt install docker-compose docker.io. Next,

# create a folder for the lemmy files. the location doesnt matter, you can put this anywhere you want
mkdir /lemmy
cd /lemmy

# download default config files
wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/prod/docker-compose.yml
wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/lemmy.hjson
wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/iframely.config.local.js

# Set correct permissions for pictrs folder
mkdir -p volumes/pictrs
sudo chown -R 991:991 volumes/pictrs

Open up your docker-compose.yml, and make sure LEMMY_EXTERNAL_HOST for lemmy-ui is set to your correct host.

- LEMMY_INTERNAL_HOST=lemmy:8536
- LEMMY_EXTERNAL_HOST=your-domain.com
- LEMMY_HTTPS=false

If you'd like a different database password, you should also change it in the docker-compose.yml before your first run.

After this, have a look at the config file named lemmy.hjson, and adjust it, in particular the hostname, and possibly the db password. Then run:

docker-compose up -d

You can access the lemmy-ui at http://localhost:1235

To make Lemmy available outside the server, you need to setup a reverse proxy, like Nginx. A sample nginx config, could be setup with:

wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/ansible/templates/nginx.conf
# Replace the {{ vars }}
# The default lemmy_port is 8536
# The default lemmy_ui_port is 1235
sudo mv nginx.conf /etc/nginx/sites-enabled/lemmy.conf

You will also need to setup TLS, for example with Let's Encrypt. After this you need to restart Nginx to reload the config.

Updating

To update to the newest version, you can manually change the version in docker-compose.yml. Alternatively, fetch the latest version from our git repo:

wget https://raw.githubusercontent.com/LemmyNet/lemmy/main/docker/prod/docker-compose.yml
docker-compose up -d

Ansible Installation

This is the same as the Docker installation, except that Ansible handles all of it automatically. It also does some extra things like setting up TLS and email for your Lemmy instance.

First, you need to install Ansible on your local computer (e.g. using sudo apt install ansible) or the equivalent for you platform.

Then run the following commands on your local computer:

git clone https://github.com/LemmyNet/lemmy.git
cd lemmy/ansible/
cp inventory.example inventory
nano inventory # enter your server, domain, contact email
# If the command below fails, you may need to comment out this line
# In the ansible.cfg file:
# interpreter_python=/usr/bin/python3
ansible-playbook lemmy.yml --become

To update to a new version, just run the following in your local Lemmy repo:

git pull origin main
cd ansible
ansible-playbook lemmy.yml --become

Configuration

The configuration is based on the file defaults.hjson. This file also contains documentation for all the available options. To override the defaults, you can copy the options you want to change into your local config.hjson file.

The defaults.hjson and config.hjson files are located at config/defaults.hjson andconfig/config.hjson, respectively. To change these default locations, you can set these two environment variables:

  • LEMMY_CONFIG_LOCATION # config.hjson
  • LEMMY_CONFIG_DEFAULTS_LOCATION # defaults.hjson

Additionally, you can override any config files with environment variables. These have the same name as the config options, and are prefixed with LEMMY_. For example, you can override the database.password with LEMMY_DATABASE__POOL_SIZE=10.

An additional option LEMMY_DATABASE_URL is available, which can be used with a PostgreSQL connection string like postgres://lemmy:password@lemmy_db:5432/lemmy, passing all connection details at once.

If the Docker container is not used, manually create the database specified above by running the following commands:

cd server
./db-init.sh

Backup and Restore Guide

Docker and Ansible

When using docker or ansible, there should be a volumes folder, which contains both the database, and all the pictures. Copy this folder to the new instance to restore your data.

Incremental Database backup

To incrementally backup the DB to an .sql file, you can run:

docker-compose exec postgres pg_dumpall -c -U lemmy >  lemmy_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql

A Sample backup script

#!/bin/sh
# DB Backup
ssh MY_USER@MY_IP "docker-compose exec postgres pg_dumpall -c -U lemmy" >  ~/BACKUP_LOCATION/INSTANCE_NAME_dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql

# Volumes folder Backup
rsync -avP -zz --rsync-path="sudo rsync" MY_USER@MY_IP:/LEMMY_LOCATION/volumes ~/BACKUP_LOCATION/FOLDERNAME

Restoring the DB

If you need to restore from a pg_dumpall file, you need to first clear out your existing database

# Drop the existing DB
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"

# Restore from the .sql backup
cat db_dump.sql  |  docker exec -i FOLDERNAME_postgres_1 psql -U lemmy # restores the db

# This also might be necessary when doing a db import with a different password.
docker exec -i FOLDERNAME_postgres_1 psql -U lemmy -c "alter user lemmy with password 'bleh'"

Changing your domain name

If you haven't federated yet, you can change your domain name in the DB. Warning: do not do this after you've federated, or it will break federation.

Get into psql for your docker:

docker-compose exec postgres psql -U lemmy

-- Post
update post set ap_id = replace (ap_id, 'old_domain', 'new_domain');
update post set url = replace (url, 'old_domain', 'new_domain');
update post set body = replace (body, 'old_domain', 'new_domain');
update post set thumbnail_url = replace (thumbnail_url, 'old_domain', 'new_domain');

delete from post_aggregates_fast;
insert into post_aggregates_fast select * from post_aggregates_view;

-- Comments
update comment set ap_id = replace (ap_id, 'old_domain', 'new_domain');
update comment set content = replace (content, 'old_domain', 'new_domain');

delete from comment_aggregates_fast;
insert into comment_aggregates_fast select * from comment_aggregates_view;

-- User
update user_ set actor_id = replace (actor_id, 'old_domain', 'new_domain');
update user_ set avatar = replace (avatar, 'old_domain', 'new_domain');

delete from user_fast;
insert into user_fast select * from user_view;

-- Community
update community set actor_id = replace (actor_id, 'old_domain', 'new_domain');

delete from community_aggregates_fast;
insert into community_aggregates_fast select * from community_aggregates_view;

More resources

  • https://stackoverflow.com/questions/24718706/backup-restore-a-dockerized-postgresql-database

Federation

Note: ActivityPub federation is still under development. We recommend that you only enable it on test instances for now.

To enable federation, change the setting federation.enabled to true in lemmy.hjson, and restart Lemmy.

Federation does not start automatically, but needs to be triggered manually through the search. To do this you have to enter a reference to a remote object, such as:

  • !main@dev.lemmy.ml (Community)
  • @nutomic@dev.lemmy.ml (User)
  • https://dev.lemmy.ml/c/programming (Community)
  • https://dev.lemmy.ml/u/nutomic (User)
  • https://dev.lemmy.ml/post/123 (Post)

For an overview of how federation in Lemmy works on a technical level, check out our Federation Overview.

Instance allowlist and blocklist

The federation section of Lemmy's config has two variables allowed_instances and blocked_instances. These control which other instances Lemmy will federate with. Both settings take a comma separated list of domains, eg dev.lemmy.ml, example.com. You can either change those settings via /admin, or directly on the server filesystem.

It is important to note that these settings only affect sending and receiving of data between instances. If allow federation with a certain instance, and then remove it from the allowlist, this will not affect previously federated data. These communities, users, posts and comments will still be shown. They will just not be updated anymore. And even if an instance is blocked, it can still fetch and display public data from your instance.

By default, both allowed_instances and blocked_instances values are empty, which means that Lemmy will federate with every compatible instance. We do not recommend this, because the moderation tools are not yet ready to deal with malicious instances.

What we do recommend is putting a list of trusted instances into allowed_instances, and only federating with those. Note that both sides need to add each other to their allowed_instances to allow two-way federation.

Alternatively you can also use blocklist based federation. In this case, add the domains of instances you do not want to federate with. You can only set one of allowed_instances and blocked_instances, as setting both doesn't make sense.

Contributing

Information about contributing to Lemmy, whether it is translating, testing, designing or programming.

Issue tracking / Repositories

Translating

Check out Lemmy's Weblate for translations.

Architecture

Front end

  • The front end is written in typescript, using a react-like framework called inferno. All UI elements are reusable .tsx components.
  • The front end repository is lemmy-ui.
  • The routes are at src/shared/routes.ts.
  • The components are located in src/shared/components.

Back end

  • The back end is written in rust, using diesel, and actix.
  • The server source code is split into main sections in src. These include:
    • db - The low level database actions.
      • Database additions are done using diesel migrations. Run diesel migration generate xxxxx to add new things.
    • api - The high level user interactions (things like CreateComment)
    • routes - The server endpoints .
    • apub - The activitypub conversions.
    • websocket - Creates the websocket server.

Linting / Formatting

  • Every front and back end commit is automatically formatted then linted using husky, and lint-staged.
  • Rust with cargo fmt and cargo clippy.
  • Typescript with prettier and eslint.

Docker Development

Dependencies (on Ubuntu)

sudo apt install git docker-compose
sudo systemctl start docker
git clone https://github.com/LemmyNet/lemmy

Running

cd docker/dev
./docker_update.sh

and go to http://localhost:1235.

*Note: many features (like docs and pictures) will not work without using an nginx profile like that in ansible/templates/nginx.conf.

To speed up the Docker compile, add the following to /etc/docker/daemon.json and restart Docker.

{
  "features": {
    "buildkit": true
  }
}

If the build is still too slow, you will have to use a local build instead.

Install build requirements

Ubuntu

sudo apt install git cargo libssl-dev pkg-config libpq-dev yarn curl gnupg2 espeak
# install yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
sudo apt update && sudo apt install yarn

macOS

Install Rust using the recommended option on rust-lang.org (rustup).

Then, install Homebrew if you don't already have it installed.

Finally, install Node and Yarn.

brew install node yarn

Get the back end source code

git clone https://github.com/LemmyNet/lemmy.git
# or alternatively from gitea
# git clone https://yerbamate.ml/LemmyNet/lemmy.git

Build the backend (Rust)

cargo build
# for development, use `cargo check` instead)

Get the front end source code

git clone https://github.com/LemmyNet/lemmy-ui.git
# get the translations
git submodule init
git submodule update --remote

Setup postgresql

Ubuntu

sudo apt install postgresql
sudo systemctl start postgresql

# Either execute db-init.sh, or manually initialize the postgres database:
sudo -u postgres psql -c "create user lemmy with password 'password' superuser;" -U postgres
sudo -u postgres psql -c 'create database lemmy with owner lemmy;' -U postgres
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy

macOS

brew install postgresql
brew services start postgresql
/usr/local/opt/postgres/bin/createuser -s postgres

# Either execute db-init.sh, or manually initialize the postgres database:
psql -c "create user lemmy with password 'password' superuser;" -U postgres
psql -c 'create database lemmy with owner lemmy;' -U postgres
export LEMMY_DATABASE_URL=postgres://lemmy:password@localhost:5432/lemmy

Run a local development instance

cd lemmy
cargo run

Then open localhost:1235 in your browser. To reload back-end changes, you will have to rerun cargo run. You can use cargo check as a faster way to find compilation errors.

To do front end development:

cd lemmy-ui
yarn
yarn dev

and go to localhost:1234. Front end saves should rebuild the project.

Note that this setup doesn't include image uploads or link previews (provided by pict-rs and iframely respectively). If you want to test those, you should use the Docker development.

Tests

Rust

After installing local development dependencies, run the following commands:

psql -U lemmy -c "DROP SCHEMA public CASCADE; CREATE SCHEMA public;"
./test.sh

Federation

Install the Docker development dependencies, and execute:

cd docker/federation
./run-tests.bash

Federation Development

Running locally

Install the dependencies as described in Docker development. Then run the following

cd docker/federation
./start-local-instances.bash

The federation test sets up 5 instances:

InstanceUsernameLocationNotes
lemmy-alphalemmy_alpha127.0.0.1:8540federated with all other instances
lemmy-betalemmy_beta127.0.0.1:8550federated with all other instances
lemmy-gammalemmy_gamma127.0.0.1:8560federated with all other instances
lemmy-deltalemmy_delta127.0.0.1:8570only allows federation with lemmy-beta
lemmy-epsilonlemmy_epsilon127.0.0.1:8580uses blocklist, has lemmy-alpha blocked

You can log into each using the instance name, and lemmy as the password, IE (lemmy_alpha, lemmy).

To start federation between instances, visit one of them and search for a user, community or post, like this. Note that the Lemmy backend runs on a different port than the frontend, so you have to increment the port number from the URL bar by one.

  • !main@lemmy-alpha:8541
  • http://lemmy-beta:8551/post/3
  • @lemmy-gamma@lemmy-gamma:8561

Firefox containers are a good way to test them interacting.

Running on a server

Note that federation is currently in alpha. Only use it for testing, not on any production server, and be aware that turning on federation may break your instance.

Follow the normal installation instructions, either with Ansible or manually. Then replace the line image: dessalines/lemmy:v0.x.x in /lemmy/docker-compose.yml with image: dessalines/lemmy:federation. Also add the following in /lemmy/lemmy.hjson:

    federation: {
        enabled: true
        tls_enabled: true,
        allowed_instances: example.com,
    }

Afterwards, and whenever you want to update to the latest version, run these commands on the server:

cd /lemmy/
sudo docker-compose pull
sudo docker-compose up -d

Security Model

  • HTTP signature verify: This ensures that activity really comes from the activity that it claims
  • check_is_apub_valid : Makes sure its in our allowed instances list
  • Lower level checks: To make sure that the user that creates/updates/removes a post is actually on the same instance as that post

For the last point, note that we are not checking whether the actor that sends the create activity for a post is actually identical to the post's creator, or that the user that removes a post is a mod/admin. These things are checked by the API code, and its the responsibility of each instance to check user permissions. This does not leave any attack vector, as a normal instance user cant do actions that violate the API rules. The only one who could do that is the admin (and the software deployed by the admin). But the admin can do anything on the instance, including send activities from other user accounts. So we wouldnt actually gain any security by checking mod permissions or similar.

Lemmy API

Note: this may lag behind the actual API endpoints here. The API should be considered unstable and may change any time.

Data types

Lemmy types

The fields for objects like Users, Comments, Communities, and Posts, are in the lemmy_db folder, with the suffix _view. The main types are:

The requests and responses, although copied below, are most up to date in lemmy_structs.

Lower-level types

  • i16, i32 and i64 are respectively 16-bit, 32-bit and 64-bit integers.
  • Option<SomeType> designates an option which may be omitted in requests and not be present in responses. It will be of type SomeType.
  • Vec<SomeType> is a list which contains objects of type SomeType.
  • chrono::NaiveDateTime is a timestamp string in ISO 8601 format. Timestamps will be UTC.

Basic usage

Request and response strings are in JSON format.

WebSocket

Connect to ws://host/api/v1/ws to get started.

If the host supports secure connections, you can use wss://host/api/v1/ws.

To receive websocket messages, you must join a room / context. The three available are:

  • UserJoin. Receives replies, private messages, etc.
  • PostJoin. Receives new comments on a post.
  • CommunityJoin. Receives front page / community posts.

Testing with Websocat

Websocat link

websocat ws://127.0.0.1:8536/api/v1/ws -nt

A simple test command: {"op": "ListCategories"}

Testing with the WebSocket JavaScript API

WebSocket JavaScript API

var ws = new WebSocket("ws://" + host + "/api/v1/ws");
ws.onopen = function () {
  console.log("Connection succeed!");
  ws.send(JSON.stringify({
    op: "ListCategories"
  }));
};

HTTP

Endpoints are at http://host/api/v1/endpoint. They'll be listed below for each action.

Testing with Curl

Get Example
curl /community/list?sort=Hot
Post Example
curl -i -H \
"Content-Type: application/json" \
-X POST \
-d '{
  "comment_id": X,
  "post_id": X,
  "score": X,
  "auth": "..."
}' \
/comment/like

Rate limits

  • 1 per hour for signups and community creation.
  • 1 per 10 minutes for post creation.
  • 30 actions per minute for post voting and comment creation.
  • Everything else is not rate-limited.

Errors


#![allow(unused)]
fn main() {
{
  op: String,
  message: String,
}
}

API documentation

Sort Types

These go wherever there is a sort field. The available sort types are:

  • Active - the hottest posts/communities, depending on votes, and newest comment publish date.
  • Hot - the hottest posts/communities, depending on votes and publish date.
  • New - the newest posts/communities
  • TopDay - the most upvoted posts/communities of the current day.
  • TopWeek - the most upvoted posts/communities of the current week.
  • TopMonth - the most upvoted posts/communities of the current month.
  • TopYear - the most upvoted posts/communities of the current year.
  • TopAll - the most upvoted posts/communities on the current instance.

Undoing actions

Whenever you see a deleted: bool, removed: bool, read: bool, locked: bool, etc, you can undo this action by sending false.

Websocket vs HTTP

  • Below are the websocket JSON requests / responses. For HTTP, ignore all fields except those inside data.
  • For example, an http login will be a POST {username_or_email: X, password: X}

User / Authentication / Admin actions

Login

The jwt string should be stored and used anywhere auth is called for.

Request

#![allow(unused)]
fn main() {
{
  op: "Login",
  data: {
    username_or_email: String,
    password: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "Login",
  data: {
    jwt: String,
  }
}
}
HTTP

POST /user/login

Register

Only the first user will be able to be the admin.

Request

#![allow(unused)]
fn main() {
{
  op: "Register",
  data: {
    username: String,
    email: Option<String>,
    password: String,
    password_verify: String,
    admin: bool,
    captcha_uuid: Option<String>, // Only checked if these are enabled in the server
    captcha_answer: Option<String>,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "Register",
  data: {
    jwt: String,
  }
}
}
HTTP

POST /user/register

Get Captcha

These expire after 10 minutes.

Request

#![allow(unused)]
fn main() {
{
  op: "GetCaptcha",
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetCaptcha",
  data: {
    ok?: { // Will be undefined if captchas are disabled
      png: String, // A Base64 encoded png
      wav: Option<String>, // A Base64 encoded wav audio file
      uuid: String,
    }
  }
}
}
HTTP

GET /user/get_captcha

Get User Details

username can only be used for local users. To get details for a federated user, pass user_id instead.

Request

#![allow(unused)]
fn main() {
{
  op: "GetUserDetails",
  data: {
    user_id: Option<i32>,
    username: Option<String>,
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    community_id: Option<i32>,
    saved_only: bool,
    auth: Option<String>,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetUserDetails",
  data: {
    user: UserView,
    follows: Vec<CommunityFollowerView>,
    moderates: Vec<CommunityModeratorView>,
    comments: Vec<CommentView>,
    posts: Vec<PostView>,
  }
}
}
HTTP

GET /user

Save User Settings

Request

#![allow(unused)]
fn main() {
{
  op: "SaveUserSettings",
  data: {
    show_nsfw: bool,
    theme: String, // Default 'darkly'
    default_sort_type: i16, // The Sort types from above, zero indexed as a number
    default_listing_type: i16, // Post listing types are `All, Subscribed, Community`
    lang: String,
    avatar: Option<String>,
    banner: Option<String>,
    preferred_username: Option<String>,
    email: Option<String>,
    bio: Option<String>,
    matrix_user_id: Option<String>,
    new_password: Option<String>,
    new_password_verify: Option<String>,
    old_password: Option<String>,
    show_avatars: bool,
    send_notifications_to_email: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "SaveUserSettings",
  data: {
    jwt: String
  }
}
}
HTTP

PUT /user/save_user_settings

Get Replies / Inbox

Request

#![allow(unused)]
fn main() {
{
  op: "GetReplies",
  data: {
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    unread_only: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetReplies",
  data: {
    replies: Vec<ReplyView>,
  }
}
}
HTTP

GET /user/replies

Get User Mentions

Request

#![allow(unused)]
fn main() {
{
  op: "GetUserMentions",
  data: {
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    unread_only: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetUserMentions",
  data: {
    mentions: Vec<UserMentionView>,
  }
}
}
HTTP

GET /user/mention

Mark User Mention as read

Only the recipient can do this.

Request

#![allow(unused)]
fn main() {
{
  op: "MarkUserMentionAsRead",
  data: {
    user_mention_id: i32,
    read: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "MarkUserMentionAsRead",
  data: {
    mention: UserMentionView,
  }
}
}
HTTP

POST /user/mention/mark_as_read

Get Private Messages

Request

#![allow(unused)]
fn main() {
{
  op: "GetPrivateMessages",
  data: {
    unread_only: bool,
    page: Option<i64>,
    limit: Option<i64>,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetPrivateMessages",
  data: {
    messages: Vec<PrivateMessageView>,
  }
}
}
HTTP

GET /private_message/list

Create Private Message

Request

#![allow(unused)]
fn main() {
{
  op: "CreatePrivateMessage",
  data: {
    content: String,
    recipient_id: i32,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreatePrivateMessage",
  data: {
    message: PrivateMessageView,
  }
}
}
HTTP

POST /private_message

Edit Private Message

Request

#![allow(unused)]
fn main() {
{
  op: "EditPrivateMessage",
  data: {
    edit_id: i32,
    content: String,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "EditPrivateMessage",
  data: {
    message: PrivateMessageView,
  }
}
}
HTTP

PUT /private_message

Delete Private Message

Request

#![allow(unused)]
fn main() {
{
  op: "DeletePrivateMessage",
  data: {
    edit_id: i32,
    deleted: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "DeletePrivateMessage",
  data: {
    message: PrivateMessageView,
  }
}
}
HTTP

POST /private_message/delete

Mark Private Message as Read

Only the recipient can do this.

Request

#![allow(unused)]
fn main() {
{
  op: "MarkPrivateMessageAsRead",
  data: {
    edit_id: i32,
    read: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "MarkPrivateMessageAsRead",
  data: {
    message: PrivateMessageView,
  }
}
}
HTTP

POST /private_message/mark_as_read

Mark All As Read

Marks all user replies and mentions as read.

Request

#![allow(unused)]
fn main() {
{
  op: "MarkAllAsRead",
  data: {
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "MarkAllAsRead",
  data: {
    replies: Vec<ReplyView>,
  }
}
}
HTTP

POST /user/mark_all_as_read

Delete Account

Permanently deletes your posts and comments

Request

#![allow(unused)]
fn main() {
{
  op: "DeleteAccount",
  data: {
    password: String,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "DeleteAccount",
  data: {
    jwt: String,
  }
}
}
HTTP

POST /user/delete_account

Add admin

Request

#![allow(unused)]
fn main() {
{
  op: "AddAdmin",
  data: {
    user_id: i32,
    added: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "AddAdmin",
  data: {
    admins: Vec<UserView>,
  }
}
}
HTTP

POST /admin/add

Ban user

Request

#![allow(unused)]
fn main() {
{
  op: "BanUser",
  data: {
    user_id: i32,
    ban: bool,
    remove_data: Option<bool>, // Removes/Restores their comments, posts, and communities
    reason: Option<String>,
    expires: Option<i64>,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "BanUser",
  data: {
    user: UserView,
    banned: bool,
  }
}
}
HTTP

POST /user/ban

User Join

Request

#![allow(unused)]
fn main() {
{
  op: "UserJoin",
  data: {
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "UserJoin",
  data: {
    joined: bool,
  }
}
}
HTTP

POST /user/join

Site

List Categories

Request

#![allow(unused)]
fn main() {
{
  op: "ListCategories"
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "ListCategories",
  data: {
    categories: Vec<Category>
  }
}
}
HTTP

GET /categories

Search

Search types are All, Comments, Posts, Communities, Users, Url

Request

#![allow(unused)]
fn main() {
{
  op: "Search",
  data: {
    q: String,
    type_: String,
    community_id: Option<i32>,
    community_name: Option<String>,
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    auth?: Option<String>,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "Search",
  data: {
    type_: String,
    comments: Vec<CommentView>,
    posts: Vec<PostView>,
    communities: Vec<CommunityView>,
    users: Vec<UserView>,
  }
}
}
HTTP

GET /search

Get Modlog

Request

#![allow(unused)]
fn main() {
{
  op: "GetModlog",
  data: {
    mod_user_id: Option<i32>,
    community_id: Option<i32>,
    page: Option<i64>,
    limit: Option<i64>,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetModlog",
  data: {
    removed_posts: Vec<ModRemovePostView>,
    locked_posts: Vec<ModLockPostView>,
    removed_comments: Vec<ModRemoveCommentView>,
    removed_communities: Vec<ModRemoveCommunityView>,
    banned_from_community: Vec<ModBanFromCommunityView>,
    banned: Vec<ModBanView>,
    added_to_community: Vec<ModAddCommunityView>,
    added: Vec<ModAddView>,
  }
}
}
HTTP

GET /modlog

Create Site

Request

#![allow(unused)]
fn main() {
{
  op: "CreateSite",
  data: {
    name: String,
    description: Option<String>,
    icon: Option<String>,
    banner: Option<String>,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreateSite",
    data: {
    site: SiteView,
  }
}
}
HTTP

POST /site

Edit Site

Request

#![allow(unused)]
fn main() {
{
  op: "EditSite",
  data: {
    name: String,
    description: Option<String>,
    icon: Option<String>,
    banner: Option<String>,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "EditSite",
  data: {
    site: SiteView,
  }
}
}
HTTP

PUT /site

Get Site

Request

#![allow(unused)]
fn main() {
{
  op: "GetSite"
  data: {
    auth: Option<String>,
  }

}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetSite",
  data: {
    site: Option<SiteView>,
    admins: Vec<UserView>,
    banned: Vec<UserView>,
    online: usize, // This is currently broken
    version: String,
    my_user: Option<User_>, // Gives back your user and settings if logged in
  }
}
}
HTTP

GET /site

Transfer Site

Request

#![allow(unused)]
fn main() {
{
  op: "TransferSite",
  data: {
    user_id: i32,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "TransferSite",
  data: {
    site: Option<SiteView>,
    admins: Vec<UserView>,
    banned: Vec<UserView>,
  }
}
}
HTTP

POST /site/transfer

Get Site Config

Request

#![allow(unused)]
fn main() {
{
  op: "GetSiteConfig",
  data: {
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetSiteConfig",
  data: {
    config_hjson: String,
  }
}
}
HTTP

GET /site/config

Save Site Config

Request

#![allow(unused)]
fn main() {
{
  op: "SaveSiteConfig",
  data: {
    config_hjson: String,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "SaveSiteConfig",
  data: {
    config_hjson: String,
  }
}
}
HTTP

PUT /site/config

Community

Get Community

Request

#![allow(unused)]
fn main() {
{
  op: "GetCommunity",
  data: {
    id: Option<i32>,
    name: Option<String>,
    auth: Option<String>
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetCommunity",
  data: {
    community: CommunityView,
    moderators: Vec<CommunityModeratorView>,
  }
}
}
HTTP

GET /community

Create Community

Request

#![allow(unused)]
fn main() {
{
  op: "CreateCommunity",
  data: {
    name: String,
    title: String,
    description: Option<String>,
    icon: Option<String>,
    banner: Option<String>,
    category_id: i32,
    nsfw: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreateCommunity",
  data: {
    community: CommunityView
  }
}
}
HTTP

POST /community

List Communities

Request

#![allow(unused)]
fn main() {
{
  op: "ListCommunities",
  data: {
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    auth: Option<String>
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "ListCommunities",
  data: {
    communities: Vec<CommunityView>
  }
}
}
HTTP

GET /community/list

Ban from Community

Request

#![allow(unused)]
fn main() {
{
  op: "BanFromCommunity",
  data: {
    community_id: i32,
    user_id: i32,
    ban: bool,
    remove_data: Option<bool>, // Removes/Restores their comments and posts for that community
    reason: Option<String>,
    expires: Option<i64>,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "BanFromCommunity",
  data: {
    user: UserView,
    banned: bool,
  }
}
}
HTTP

POST /community/ban_user

Add Mod to Community

Request

#![allow(unused)]
fn main() {
{
  op: "AddModToCommunity",
  data: {
    community_id: i32,
    user_id: i32,
    added: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "AddModToCommunity",
  data: {
    moderators: Vec<CommunityModeratorView>,
  }
}
}
HTTP

POST /community/mod

Edit Community

Only mods can edit a community.

Request

#![allow(unused)]
fn main() {
{
  op: "EditCommunity",
  data: {
    edit_id: i32,
    title: String,
    description: Option<String>,
    icon: Option<String>,
    banner: Option<String>,
    category_id: i32,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "EditCommunity",
  data: {
    community: CommunityView
  }
}
}
HTTP

PUT /community

Delete Community

Only a creator can delete a community

Request

#![allow(unused)]
fn main() {
{
  op: "DeleteCommunity",
  data: {
    edit_id: i32,
    deleted: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "DeleteCommunity",
  data: {
    community: CommunityView
  }
}
}
HTTP

POST /community/delete

Remove Community

Only admins can remove a community.

Request

#![allow(unused)]
fn main() {
{
  op: "RemoveCommunity",
  data: {
    edit_id: i32,
    removed: bool,
    reason: Option<String>,
    expires: Option<i64>,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "RemoveCommunity",
  data: {
    community: CommunityView
  }
}
}
HTTP

POST /community/remove

Follow Community

Request

#![allow(unused)]
fn main() {
{
  op: "FollowCommunity",
  data: {
    community_id: i32,
    follow: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "FollowCommunity",
  data: {
    community: CommunityView
  }
}
}
HTTP

POST /community/follow

Get Followed Communities

Request

#![allow(unused)]
fn main() {
{
  op: "GetFollowedCommunities",
  data: {
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetFollowedCommunities",
  data: {
    communities: Vec<CommunityFollowerView>
  }
}
}
HTTP

GET /user/followed_communities

Transfer Community

Request

#![allow(unused)]
fn main() {
{
  op: "TransferCommunity",
  data: {
    community_id: i32,
    user_id: i32,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "TransferCommunity",
  data: {
    community: CommunityView,
    moderators: Vec<CommunityModeratorView>,
    admins: Vec<UserView>,
  }
}
}
HTTP

POST /community/transfer

Community Join

The main / frontpage community is community_id: 0.

Request

#![allow(unused)]
fn main() {
{
  op: "CommunityJoin",
  data: {
    community_id: i32
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CommunityJoin",
  data: {
    joined: bool,
  }
}
}
HTTP

POST /community/join

Post

Create Post

Request

#![allow(unused)]
fn main() {
{
  op: "CreatePost",
  data: {
    name: String,
    url: Option<String>,
    body: Option<String>,
    nsfw: bool,
    community_id: i32,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreatePost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post

Get Post

Request

#![allow(unused)]
fn main() {
{
  op: "GetPost",
  data: {
    id: i32,
    auth: Option<String>
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetPost",
  data: {
    post: PostView,
    comments: Vec<CommentView>,
    community: CommunityView,
    moderators: Vec<CommunityModeratorView>,
  }
}
}
HTTP

GET /post

Get Posts

Post listing types are All, Subscribed, Community

community_name can only be used for local communities. To get posts for a federated community, pass community_id instead.

Request

#![allow(unused)]
fn main() {
{
  op: "GetPosts",
  data: {
    type_: String,
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    community_id: Option<i32>,
    community_name: Option<String>,
    auth: Option<String>
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetPosts",
  data: {
    posts: Vec<PostView>,
  }
}
}
HTTP

GET /post/list

Create Post Like

score can be 0, -1, or 1

Request

#![allow(unused)]
fn main() {
{
  op: "CreatePostLike",
  data: {
    post_id: i32,
    score: i16,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreatePostLike",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/like

Edit Post

Request

#![allow(unused)]
fn main() {
{
  op: "EditPost",
  data: {
    edit_id: i32,
    name: String,
    url: Option<String>,
    body: Option<String>,
    nsfw: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "EditPost",
  data: {
    post: PostView
  }
}
}
HTTP

PUT /post

Delete Post

Request

#![allow(unused)]
fn main() {
{
  op: "DeletePost",
  data: {
    edit_id: i32,
    deleted: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "DeletePost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/delete

Remove Post

Only admins and mods can remove a post.

Request

#![allow(unused)]
fn main() {
{
  op: "RemovePost",
  data: {
    edit_id: i32,
    removed: bool,
    reason: Option<String>,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "RemovePost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/remove

Lock Post

Only admins and mods can lock a post.

Request

#![allow(unused)]
fn main() {
{
  op: "LockPost",
  data: {
    edit_id: i32,
    locked: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "LockPost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/lock

Sticky Post

Only admins and mods can sticky a post.

Request

#![allow(unused)]
fn main() {
{
  op: "StickyPost",
  data: {
    edit_id: i32,
    stickied: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "StickyPost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/sticky

Save Post

Request

#![allow(unused)]
fn main() {
{
  op: "SavePost",
  data: {
    post_id: i32,
    save: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "SavePost",
  data: {
    post: PostView
  }
}
}
HTTP

POST /post/save

Post Join

Request

#![allow(unused)]
fn main() {
{
  op: "PostJoin",
  data: {
    post_id: i32
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "PostJoin",
  data: {
    joined: bool,
  }
}
}
HTTP

POST /post/join

Comment

Create Comment

Request

#![allow(unused)]
fn main() {
{
  op: "CreateComment",
  data: {
    content: String,
    parent_id: Option<i32>,
    post_id: i32,
    form_id: Option<String>, // An optional form id, so you know which message came back
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreateComment",
  data: {
    comment: CommentView
  }
}
}
HTTP

POST /comment

Edit Comment

Only the creator can edit the comment.

Request

#![allow(unused)]
fn main() {
{
  op: "EditComment",
  data: {
    content: String,
    edit_id: i32,
    form_id: Option<String>,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "EditComment",
  data: {
    comment: CommentView
  }
}
}
HTTP

PUT /comment

Delete Comment

Only the creator can delete the comment.

Request

#![allow(unused)]
fn main() {
{
  op: "DeleteComment",
  data: {
    edit_id: i32,
    deleted: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "DeleteComment",
  data: {
    comment: CommentView
  }
}
}
HTTP

POST /comment/delete

Remove Comment

Only a mod or admin can remove the comment.

Request

#![allow(unused)]
fn main() {
{
  op: "RemoveComment",
  data: {
    edit_id: i32,
    removed: bool,
    reason: Option<String>,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "RemoveComment",
  data: {
    comment: CommentView
  }
}
}
HTTP

POST /comment/remove

Get Comments

Comment listing types are All, Subscribed, Community

community_name can only be used for local communities. To get posts for a federated community, pass community_id instead.

Request

#![allow(unused)]
fn main() {
{
  op: "GetComments",
  data: {
    type_: String,
    sort: String,
    page: Option<i64>,
    limit: Option<i64>,
    community_id: Option<i32>,
    community_name: Option<String>,
    auth: Option<String>
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "GetComments",
  data: {
    comments: Vec<CommentView>,
  }
}
}
HTTP

GET /comment/list

Mark Comment as Read

Only the recipient can do this.

Request

#![allow(unused)]
fn main() {
{
  op: "MarkCommentAsRead",
  data: {
    edit_id: i32,
    read: bool,
    auth: String,
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "MarkCommentAsRead",
  data: {
    comment: CommentView
  }
}
}
HTTP

POST /comment/mark_as_read

Save Comment

Request

#![allow(unused)]
fn main() {
{
  op: "SaveComment",
  data: {
    comment_id: i32,
    save: bool,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "SaveComment",
  data: {
    comment: CommentView
  }
}
}
HTTP

PUT /comment/save

Create Comment Like

score can be 0, -1, or 1

Request

#![allow(unused)]
fn main() {
{
  op: "CreateCommentLike",
  data: {
    comment_id: i32,
    score: i16,
    auth: String
  }
}
}
Response

#![allow(unused)]
fn main() {
{
  op: "CreateCommentLike",
  data: {
    comment: CommentView
  }
}
}
HTTP

POST /comment/like

RSS / Atom feeds

All

/feeds/all.xml?sort=Hot

Community

/feeds/c/community-name.xml?sort=Hot

User

/feeds/u/user-name.xml?sort=Hot

Images

Lemmy forwards image requests to a locally running Pictrs.

Get

Format and thumbnail are optional.

GET /pictrs/image/{filename}?format={webp, jpg, ...}&thumbnail={96}

Create

Request

Uploaded content must be valid multipart/form-data with an image array located within the images[] key.

POST /pictrs/image

Response
{
  "files": [
    {
      "delete_token": "{token}",
      "file": "{file}.jpg"
    }
  ],
  "msg": "ok"
}

Delete

GET /pictrs/image/delete/{delete_token}/{file}

Federation

This document is for anyone who wants to know how Lemmy federation works, without being overly technical. It is meant provide a high-level overview of ActivityPub federation in Lemmy. If you are implementing ActivityPub yourself and want to be compatible with Lemmy, read our ActivityPub API outline.

Documentation conventions

To keep things simple, sometimes you will see things formatted like Create/Note or Delete/Event or Undo/Follow. The thing before the slash is the Activity, and the thing after the slash is the Object inside the Activity, in an object property. So these are to be read as follows:

  • Create/Note: a Create activity containing a Note in the object field
  • Delete/Event: a Delete activity containing an Event in the object field
  • Undo/Follow: an Undo activity containing a Follow in the object field

In Lemmy we use some specific terms to refer to ActivityPub items. They are essentially our specific implementations of well-known ActivityPub concepts:

  • Community: Group
  • User: Person
  • Post: Page
  • Comment: Note

This document has three main sections:

  • Federation philosophy lays out the general model of how this is intended to federate
  • User Activities describes which actions that a User can take to interact
  • Community Activities describes what the Community does in response to certain User actions

Federation philosophy

The primary Actor in Lemmy is the Community. Each community resides on a single instance, and consists of a list of Posts and a list of followers. The primary interaction is that of a User sending a Post or Comment related activity to the Community inbox, which then announces it to all its followers.

Each Community has a specific creator User, who is responsible for setting rules, appointing moderators, and removing content that violates the rules.

Besides moderation on the community level, each instance has a set of administrator Users, who have the power to do site-wide removals and bans.

Users follow Communities that they are interested in, in order to receive Posts and Comments. They also vote on Posts and Comments, as well as creating new ones. Comments are organised in a tree structure and commonly sorted by number of votes. Direct messages between Users are also supported.

Users can not follow each other, and neither can Communities follow anything.

Our federation implementation is already feature complete, but so far we haven't focused at all on complying with the ActivityPub spec. As such, Lemmy is likely not compatible with implementations which expect to send and receive valid activities. This is something we plan to fix in the near future. Check out #698 for an overview of our deviations.

User Activities

Follow a Community

Each Community page has a "Follow" button. Clicking this triggers a Follow activity to be sent from the user to the Community inbox. The Community will automatically respond with an Accept/Follow activity to the user inbox. It will also add the user to its list of followers, and deliver any activities about Posts/Comments in the Community to the user.

Unfollow a Community

After following a Community, the "Follow" button is replaced by "Unfollow". Clicking this sends an Undo/Follow activity to the Community inbox. The Community removes the User from its followers list and doesn't send any activities to it anymore.

Create a Post

When a user creates a new Post in a given Community, it is sent as Create/Page to the Community inbox.

Create a Comment

When a new Comment is created for a Post, both the Post ID and the parent Comment ID (if it exists) are written to the in_reply_to field. This allows assigning it to the correct Post, and building the Comment tree. It is then sent to the Community inbox as Create/Note

The origin instance also scans the Comment for any User mentions, and sends the Create/Note to those Users as well.

Edit a Post

Changes the content of an existing Post. Can only be done by the creating User.

Edit a Comment

Changes the content of an existing Comment. Can only be done by the creating User.

Likes and Dislikes

Users can like or dislike any Post or Comment. These are sent as Like/Page, Dislike/Note etc to the Community inbox.

Deletions

The creator of a Post, Comment or Community can delete it. It is then sent to the Community followers. The item is then hidden from all users.

Removals

Mods can remove Posts and Comments from their Communities. Admins can remove any Posts or Comments on the entire site. Communities can also be removed by admins. The item is then hidden from all users.

Removals are sent to all followers of the Community, so that they also take effect there. The exception is if an admin removes an item from a Community which is hosted on a different instance. In this case, the removal only takes effect locally.

Revert a previous Action

We don't delete anything from our database, just hide it from users. Deleted or removed Communities/Posts/Comments have a "restore" button. This button generates an Undo activity which sets the original delete/remove activity as object, such as Undo/Remove/Post or Undo/Delete/Community.

Clicking on the upvote button of an already upvoted post/comment (or the downvote button of an already downvoted post/comment) also generates an Undo. In this case and Undo/Like/Post or Undo/Dislike/Comment.

Create private message

User profiles have a "Send Message" button, which opens a dialog permitting to send a private message to this user. It is sent as a Create/Note to the user inbox. Private messages can only be directed at a single User.

Edit private message

Update/Note changes the text of a previously sent message

Delete private message

Delete/Note deletes a private message.

Restore private message

Undo/Delete/Note reverts the deletion of a private message.

Community Activities

The Community is essentially a bot, which will only do anything in reaction to actions from Users. The User who first created the Community becomes the first moderator, and can add additional moderators. In general, whenever the Community receives a valid activity in its inbox, that activity is forwarded to all its followers.

Accept follow

If the Community receives a Follow activity, it automatically responds with Accept/Follow. It also adds the User to its list of followers.

Unfollow

Upon receiving an Undo/Follow, the Community removes the User from its followers list.

Announce

If the Community receives any Post or Comment related activity (Create, Update, Like, Dislike, Remove, Delete, Undo), it will Announce this to its followers. For this, an Announce is created with the Community as actor, and the received activity as object. Following instances thus stay updated about any actions in Communities they follow.

Delete Community

If the creator or an admin deletes the Community, it sends a Delete/Group to all its followers.

Activitypub API outline

This document is targeted at developers who are familiar with the ActivityPub and ActivityStreams protocols. It gives a detailed outline of the actors, objects and activities used by Lemmy.

Before reading this, have a look at our Federation Overview to get an idea how Lemmy federation works on a high level.

Lemmy does not yet follow the ActivityPub spec in all regards. For example, we don't set a valid context indicating our context fields. We also ignore fields like inbox, outbox or endpoints for remote actors, and assume that everything is Lemmy. For an overview of deviations, read #698. They will be fixed in the near future.

Lemmy is also really inflexible when it comes to incoming activities and objects. They need to be exactly identical to the examples below. Things like having an array instead of a single value, or an object ID instead of the full object will result in an error.

In the following tables, "mandatory" refers to whether or not Lemmy will accept an incoming activity without this field. Lemmy itself will always include all non-empty fields.

Actors

Community

An automated actor. Users can send posts or comments to it, which the community forwards to its followers in the form of Announce.

Sends activities to user: Accept/Follow, Announce

Receives activities from user: Follow, Undo/Follow, Create, Update, Like, Dislike, Remove (only admin/mod), Delete (only creator), Undo (only for own actions)

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/c/main",
    "type": "Group",
    "preferredUsername": "main",
    "name": "The Main Community",
    "category": { 
        "identifier": "1",
        "name": "Discussion"
    },
    "sensitive": false,
    "attributedTo": [
        "https://enterprise.lemmy.ml/u/picard",
        "https://enterprise.lemmy.ml/u/riker"
    ],
    "content": "Welcome to the default community!",
    "icon": {
        "type": "Image",
        "url": "https://enterprise.lemmy.ml/pictrs/image/Z8pFFb21cl.png"
    },
    "image": {
        "type": "Image",
        "url": "https://enterprise.lemmy.ml/pictrs/image/Wt8zoMcCmE.jpg"
    },
    "inbox": "https://enterprise.lemmy.ml/c/main/inbox",
    "outbox": "https://enterprise.lemmy.ml/c/main/outbox",
    "followers": "https://enterprise.lemmy.ml/c/main/followers",
    "endpoints": {
        "sharedInbox": "https://enterprise.lemmy.ml/inbox"
    },
    "published": "2020-10-06T17:27:43.282386+00:00",
    "updated": "2020-10-08T11:57:50.545821+00:00",
    "publicKey": {
        "id": "https://enterprise.lemmy.ml/c/main#main-key",
        "owner": "https://enterprise.lemmy.ml/c/main",
        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA9JJ7Ybp/H7iXeLkWFepg\ny4PHyIXY1TO9rK3lIBmAjNnkNywyGXMgUiiVhGyN9yU7Km8aWayQsNHOkPL7wMZK\nnY2Q+CTQv49kprEdcDVPGABi6EbCSOcRFVaUjjvRHf9Olod2QP/9OtX0oIFKN2KN\nPIUjeKK5tw4EWB8N1i5HOuOjuTcl2BXSemCQLAlXerLjT8xCarGi21xHPaQvAuns\nHt8ye7fUZKPRT10kwDMapjQ9Tsd+9HeBvNa4SDjJX1ONskNh2j4bqHHs2WUymLpX\n1cgf2jmaXAsz6jD9u0wfrLPelPJog8RSuvOzDPrtwX6uyQOl5NK00RlBZwj7bMDx\nzwIDAQAB\n-----END PUBLIC KEY-----\n"
    }
}
Field NameMandatoryDescription
preferredUsernameyesName of the actor
nameyesTitle of the community
categoryyesHardcoded list of categories, see https://dev.lemmy.ml/api/v1/categories
sensitiveyesTrue indicates that all posts in the community are nsfw
attributedToyesFirst the community creator, then all the remaining moderators
contentnoText for the community sidebar, usually containing a description and rules
iconnoIcon, shown next to the community name
imagenoBanner image, shown on top of the community page
inboxnoActivityPub inbox URL
outboxnoActivityPub outbox URL, only contains up to 20 latest posts, no comments, votes or other activities
followersnoFollower collection URL, only contains the number of followers, no references to individual followers
endpointsnoContains URL of shared inbox
publishednoDatetime when the community was first created
updatednoDatetime when the community was last changed
publicKeyyesThe public key used to verify signatures from this actor

User

A person, interacts primarily with the community where it sends and receives posts/comments. Can also create and moderate communities, and send private messages to other users.

Sends activities to Community: Follow, Undo/Follow, Create, Update, Like, Dislike, Remove (only admin/mod), Delete (only creator), Undo (only for own actions)

Receives activities from Community: Accept/Follow, Announce

Sends and receives activities from/to other users: Create/Note, Update/Note, Delete/Note, Undo/Delete/Note (all those related to private messages)

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/u/picard",
    "type": "Person",
    "preferredUsername": "picard",
    "name": "Jean-Luc Picard",
    "summary": "The user bio",
    "icon": {
        "type": "Image",
        "url": "https://enterprise.lemmy.ml/pictrs/image/DS3q0colRA.jpg"
    },
    "image": {
        "type": "Image",
        "url": "https://enterprise.lemmy.ml/pictrs/image/XenaYI5hTn.png"
    },
    "inbox": "https://enterprise.lemmy.ml/u/picard/inbox",
    "endpoints": {
        "sharedInbox": "https://enterprise.lemmy.ml/inbox"
    },
    "published": "2020-10-06T17:27:43.234391+00:00",
    "updated": "2020-10-08T11:27:17.905625+00:00",
    "publicKey": {
        "id": "https://enterprise.lemmy.ml/u/picard#main-key",
        "owner": "https://enterprise.lemmy.ml/u/picard",
        "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyH9iH83+idw/T4QpuRSY\n5YgQ/T5pJCNxvQWb6qcCu3gEVigfbreqZKJpOih4YT36wu4GjPfoIkbWJXcfcEzq\nMEQoYbPStuwnklpN2zj3lRIPfGLht9CAlENLWikTUoW5kZLyU6UQtOGdT2b1hDuK\nsUEn67In6qYx6pal8fUbO6X3O2BKzGeofnXgHCu7QNIuH4RPzkWsLhvwqEJYP0zG\nodao2j+qmhKFsI4oNOUCGkdJejO7q+9gdoNxAtNNKilIOwUFBYXeZJb+XGlzo0X+\n70jdJ/xQCPlPlItU4pD/0FwPLtuReoOpMzLi20oDsPXJBvn+/NJaxqDINuywcN5p\n4wIDAQAB\n-----END PUBLIC KEY-----\n"
    }
}
Field NameMandatoryDescription
preferredUsernameyesName of the actor
namenoThe user's displayname
summarynoUser bio
iconnoThe user's avatar, shown next to the username
imagenoThe user's banner, shown on top of the profile
inboxnoActivityPub inbox URL
endpointsnoContains URL of shared inbox
publishednoDatetime when the user signed up
updatednoDatetime when the user profile was last changed
publicKeyyesThe public key used to verify signatures from this actor

Objects

Post

A page with title, and optional URL and text content. The URL often leads to an image, in which case a thumbnail is included. Each post belongs to exactly one community.

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://voyager.lemmy.ml/post/29",
    "type": "Page",
    "attributedTo": "https://voyager.lemmy.ml/u/picard",
    "to": "https://voyager.lemmy.ml/c/main",
    "summary": "Test thumbnail 2",
    "content": "blub blub",
    "url": "https://voyager.lemmy.ml:/pictrs/image/fzGwCsq7BJ.jpg",
    "image": {
        "type": "Image",
        "url": "https://voyager.lemmy.ml/pictrs/image/UejwBqrJM2.jpg"
    },
    "commentsEnabled": true,
    "sensitive": false,
    "stickied": false,
    "published": "2020-09-24T17:42:50.396237+00:00",
    "updated": "2020-09-24T18:31:14.158618+00:00"
}
Field NameMandatoryDescription
attributedToyesID of the user which created this post
toyesID of the community where it was posted to
summaryyesTitle of the post
contentnoBody of the post
urlnoAn arbitrary link to be shared
imagenoThumbnail for url, only present if it is an image link
commentsEnabledyesFalse indicates that the post is locked, and no comments can be added
sensitiveyesTrue marks the post as NSFW, blurs the thumbnail and hides it from users with NSFW settign disabled
stickiedyesTrue means that it is shown on top of the community
publishednoDatetime when the post was created
updatednoDatetime when the post was edited (not present if it was never edited)

Comment

A reply to a post, or reply to another comment. Contains only text (including references to other users or communities). Lemmy displays comments in a tree structure.

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/comment/95",
    "type": "Note",
    "attributedTo": "https://enterprise.lemmy.ml/u/picard",
    "to": "https://enterprise.lemmy.ml/c/main",
    "content": "mmmk",
    "inReplyTo": [
        "https://enterprise.lemmy.ml/post/38",
        "https://voyager.lemmy.ml/comment/73"
    ],
    "published": "2020-10-06T17:53:22.174836+00:00",
    "updated": "2020-10-06T17:53:22.174836+00:00"
}
Field NameMandatoryDescription
attributedToyesID of the user who created the comment
toyesCommunity where the comment was made
contentyesThe comment text
inReplyToyesIDs of the post where this comment was made, and the parent comment. If this is a top-level comment, inReplyTo only contains the post
publishednoDatetime when the comment was created
updatednoDatetime when the comment was edited (not present if it was never edited)

Private Message

A direct message from one user to another. Can not include additional users. Threading is not implemented yet, so the inReplyTo field is missing.

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/private_message/34",
    "type": "Note",
    "attributedTo": "https://enterprise.lemmy.ml/u/picard",
    "to": "https://voyager.lemmy.ml/u/janeway",
    "content": "test",
    "published": "2020-10-08T19:10:46.542820+00:00",
    "updated": "2020-10-08T20:13:52.547156+00:00"
}
Field NameMandatoryDescription
attributedToID of the user who created this private message
toID of the recipient
contentyesThe text of the private message
publishednoDatetime when the message was created
updatednoDatetime when the message was edited (not present if it was never edited)

Activities

Follow

When the user clicks "Subscribe" in a community, a Follow is sent. The community automatically responds with an Accept/Follow.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/follow/2e4784b7-4edf-4fa1-a352-674d5d5f8891",
    "type": "Follow",
    "actor": "https://enterprise.lemmy.ml/u/picard",
    "to": "https://ds9.lemmy.ml/c/main",
    "object": "https://ds9.lemmy.ml/c/main"
}
Field NameMandatoryDescription
actoryesThe user that is sending the follow request
objectyesThe community to be followed

Accept Follow

Automatically sent by the community in response to a Follow. At the same time, the community adds this user to its followers list.

Sent by: Community

Sent to: User

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://ds9.lemmy.ml/activities/accept/5314bf7c-dab8-4b01-baf2-9be11a6a812e",
    "type": "Accept",
    "actor": "https://ds9.lemmy.ml/c/main",
    "to": "https://enterprise.lemmy.ml/u/picard",
    "object": {
        "@context": "https://www.w3.org/ns/activitystreams",
        "id": "https://enterprise.lemmy.ml/activities/follow/2e4784b7-4edf-4fa1-a352-674d5d5f8891",
        "type": "Follow",
        "object": "https://ds9.lemmy.ml/c/main",
        "actor": "https://enterprise.lemmy.ml/u/picard"
    }
}
Field NameMandatoryDescription
actoryesThe same community as in the Follow activity
tonoID of the user which sent the Follow
objectyesThe previously sent Follow activity

Unfollow

Clicking on the unsubscribe button in a community causes an Undo/Follow to be sent. The community removes the user from its follower list after receiving it.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "http://lemmy-alpha:8541/activities/undo/2c624a77-a003-4ed7-91cb-d502eb01b8e8",
    "type": "Undo",
    "actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
    "to": "http://lemmy-beta:8551/c/main",
    "object": {
        "@context": "https://www.w3.org/ns/activitystreams",
        "id": "http://lemmy-alpha:8541/activities/follow/f0d732e7-b1e7-4857-a5e0-9dc83c3f7ee8",
        "type": "Follow",
        "actor": "http://lemmy-alpha:8541/u/lemmy_alpha",
        "object": "http://lemmy-beta:8551/c/main"
    }
}

Create or Update Post

When a user creates a new post, it is sent to the respective community. Editing a previously created post sends an almost identical activity, except the type being Update. We don't support mentions in posts yet.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/create/6e11174f-501a-4531-ac03-818739bfd07d",
    "type": "Create",
    "actor": "https://enterprise.lemmy.ml/u/riker",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
      "https://ds9.lemmy.ml/c/main/"
    ],
    "object": ...
}
Field NameMandatoryDescription
typeyeseither Create or Update
ccyesCommunity where the post is being made
objectyesThe post being created

Create or Update Comment

A reply to a post, or to another comment. Can contain mentions of other users. Editing a previously created post sends an almost identical activity, except the type being Update.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/create/6f52d685-489d-4989-a988-4faedaed1a70",
    "type": "Create",
    "actor": "https://enterprise.lemmy.ml/u/riker",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "tag": [{
        "type": "Mention",
        "name": "@sisko@ds9.lemmy.ml",
        "href": "https://ds9.lemmy.ml/u/sisko"
    }],
    "cc": [
        "https://ds9.lemmy.ml/c/main/",
        "https://ds9.lemmy.ml/u/sisko"
    ],
    "object": ...
}
Field NameMandatoryDescription
tagnoList of users which are mentioned in the comment (like @user@example.com)
ccyesCommunity where the post is being made, the user being replied to (creator of the parent post/comment), as well as any mentioned users
objectyesThe comment being created

Like Post or Comment

An upvote for a post or comment.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/like/8f3f48dd-587d-4624-af3d-59605b7abad3",
    "type": "Like",
    "actor": "https://enterprise.lemmy.ml/u/riker",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
        "https://ds9.lemmy.ml/c/main/"
    ],
    "object": ...
}
Field NameMandatoryDescription
ccyesID of the community where the post/comment is
objectyesThe post or comment being upvoted

Dislike Post or Comment

A downvote for a post or comment.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/dislike/fd2b8e1d-719d-4269-bf6b-2cadeebba849",
    "type": "Dislike",
    "actor": "https://enterprise.lemmy.ml/u/riker",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
      "https://ds9.lemmy.ml/c/main/"
    ],
    "object": ...
}
Field NameMandatoryDescription
ccyesID of the community where the post/comment is
objectyesThe post or comment being upvoted

Delete Post or Comment

Deletes a previously created post or comment. This can only be done by the original creator of that post/comment.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://enterprise.lemmy.ml/activities/delete/f1b5d57c-80f8-4e03-a615-688d552e946c",
    "type": "Delete",
    "actor": "https://enterprise.lemmy.ml/u/riker",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
        "https://enterprise.lemmy.ml/c/main/"
    ],
    "object": "https://enterprise.lemmy.ml/post/32"
}
Field NameMandatoryDescription
ccyesID of the community where the post/comment is
objectyesID of the post or comment being deleted

Remove Post or Comment

Removes a post or comment. This can only be done by a community mod, or by an admin on the instance where the community is hosted.

Sent by: User

Sent to: Community

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://ds9.lemmy.ml/activities/remove/aab93b8e-3688-4ea3-8212-d00d29519218",
    "type": "Remove",
    "actor": "https://ds9.lemmy.ml/u/sisko",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
        "https://ds9.lemmy.ml/c/main/"
    ],
    "object": "https://enterprise.lemmy.ml/comment/32"
}
Field NameMandatoryDescription
ccyesID of the community where the post/comment is
objectyesID of the post or comment being removed

Undo

Reverts a previous activity, can only be done by the actor of object. In case of a Like or Dislike, the vote count is changed back. In case of a Delete or Remove, the post/comment is restored. The object is regenerated from scratch, as such the activity ID and other fields are different.

Sent by: User

Sent to: Community

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://ds9.lemmy.ml/activities/undo/70ca5fb2-e280-4fd0-a593-334b7f8a5916",
  "type": "Undo",
  "actor": "https://ds9.lemmy.ml/u/sisko",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "cc": [
    "https://ds9.lemmy.ml/c/main/"
  ],
  "object": ...
}
Field NameMandatoryDescription
objectyesAny Like, Dislike, Delete or Remove activity as described above

Announce

When the community receives a post or comment activity, it wraps that into an Announce and sends it to all followers.

Sent by: Community

Sent to: User

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "https://ds9.lemmy.ml/activities/announce/b98382e8-6cb1-469e-aa1f-65c5d2c31cc4",
  "type": "Announce",
  "actor": "https://ds9.lemmy.ml/c/main",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "cc": [
    "https://ds9.lemmy.ml/c/main/followers"
  ],
  "object": ...
}
Field NameMandatoryDescription
objectyesAny Create, Update, Like, Dislike, Delete Remove or Undo activity as described above

Remove or Delete Community

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "http://ds9.lemmy.ml/activities/remove/e4ca7688-af9d-48b7-864f-765e7f9f3591",
  "type": "Remove",
  "actor": "http://ds9.lemmy.ml/c/some_community",
  "cc": [
    "http://ds9.lemmy.ml/c/some_community/followers"
  ],
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "object": "http://ds9.lemmy.ml/c/some_community"
}
Field NameMandatoryDescription
typeyesEither Remove or Delete

Restore Removed or Deleted Community

{
  "@context": "https://www.w3.org/ns/activitystreams",
  "id": "http://ds9.lemmy.ml/activities/like/0703668c-8b09-4a85-aa7a-f93621936901",
  "type": "Undo",
  "actor": "http://ds9.lemmy.ml/c/some_community",
  "to": "https://www.w3.org/ns/activitystreams#Public",
  "cc": [
    "http://ds9.lemmy.ml/c/testcom/followers"
  ],
  "object": {
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "http://ds9.lemmy.ml/activities/remove/1062b5e0-07e8-44fc-868c-854209935bdd",
    "type": "Remove",
    "actor": "http://ds9.lemmy.ml/c/some_community",
    "object": "http://ds9.lemmy.ml/c/testcom",
    "to": "https://www.w3.org/ns/activitystreams#Public",
    "cc": [
      "http://ds9.lemmy.ml/c/testcom/followers"
    ]
  }
}

Field NameMandatoryDescription
object.typeyesEither Remove or Delete

Create or Update Private message

Creates a new private message between two users.

Sent by: User

Sent to: User

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://ds9.lemmy.ml/activities/create/202daf0a-1489-45df-8d2e-c8a3173fed36",
    "type": "Create",
    "actor": "https://ds9.lemmy.ml/u/sisko",
    "to": "https://enterprise.lemmy.ml/u/riker/inbox",
    "object": ...
}
Field NameMandatoryDescription
typeyesEither Create or Update

Delete Private Message

Deletes a previous private message.

Sent by: User

Sent to: User

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://ds9.lemmy.ml/activities/delete/2de5a5f3-bf26-4949-a7f5-bf52edfca909",
    "type": "Delete",
    "actor": "https://ds9.lemmy.ml/u/sisko",
    "to": "https://enterprise.lemmy.ml/u/riker/inbox",
    "object": "https://ds9.lemmy.ml/private_message/341"
}

Undo Delete Private Message

Restores a previously deleted private message. The object is regenerated from scratch, as such the activity ID and other fields are different.

Sent by: User

Sent to: User

{
    "@context": "https://www.w3.org/ns/activitystreams",
    "id": "https://ds9.lemmy.ml/activities/undo/b24bc56d-5db1-41dd-be06-3f1db8757842",
    "type": "Undo",
    "actor": "https://ds9.lemmy.ml/u/sisko",
    "to": "https://enterprise.lemmy.ml/u/riker/inbox",
    "object": ...
}

Theming Guide

Lemmy uses Bootstrap v4, and very few custom css classes, so any bootstrap v4 compatible theme should work fine.

Creating

  • Use a tool like bootstrap.build to create a bootstrap v4 theme. Export the bootstrap.min.css once you're done, and save the _variables.scss too.

Testing

  • To test out a theme, you can either use your browser's web tools, or a plugin like stylus to copy-paste a theme, when viewing Lemmy.

Adding

  1. Fork the lemmy-ui.
  2. Copy {my-theme-name}.min.css to src/assets/css/themes. (You can also copy the _variables.scss here if you want).
  3. Go to src/shared/utils.ts and add {my-theme-name} to the themes list.
  4. Test locally
  5. Do a pull request with those changes.

Lemmy Council

  • A group of lemmy developers and users that use a well-defined democratic process to steer the project in a positive direction, keep it aligned to community goals, and resolve conflicts.
  • Council members are also added as administrators to any official Lemmy instances.

1. What gets voted on

This section describes all the aspects of Lemmy where the council has decision making power, namely:

2. Feedback and Activity Reports

Every week, the council should make a thread on Lemmy that details its activity during the past week, be it development, moderation, or anything else mentioned in 1.

At the same time, users can give feedback and suggestions in this thread. This should be taken into account by the council. Council members can call for a vote on any controversial issues, if they can't be resolved by discussion.

2. Voting Process

Most of the time, we keep each other up to date through the Matrix chat, and take informal decisions on uncontroversial issues. For example, a user clearly violating the site rules could be banned by a single person, or ideally after discussing it with at least one other member.

If an issue can not be resolved in this way, then any council member can call for a vote, which works in the following way:

  • Any council member can call for a vote, on any topic mentioned in 1.
  • This should be used if there is any controversy in the community, or between council members.
  • Before taking any decision, there needs to be a discussion where every council member can explain their position.
  • Discussion should be taken with the goal of reaching a compromise that is acceptable for everyone.
  • After the discussion, voting is done through Matrix emojis (👍: yes, 👎: no, X: abstain) and must stay open for at least two days.
  • All members of the Lemmy council have equal voting power.
  • Decisions should be reached unanimously, or nearly so. If this is not possible, at least 2/3 of votes must be in favour for the motion to pass.
  • Once a decision is reached in this way, every member needs to abide by it.

4. Joining

  • We use the following process: anyone who is active around Lemmy can recommend any other active person to join the council. This has to be approved by a majority of the council.
  • Active users are defined as those who contribute to Lemmy in some way for at least an hour per week on average, doing things like reporting bugs, discussing rules and features, translating, promoting, developing, or doing other things that aim to improve Lemmy as a whole. -> people should have joined at least a month ago.
  • The member list is public.
  • Note: we would like to have a process where community members can elect candidates for the council, but this is not realistic because a single user could easily create multiple accounts and cheat the vote.
  • Limit growth to one new member per month at most.

5. Removing members

  • Inactive members should be removed from the council after a few months of inactivity, and after receiving a notification about this.
  • Members that dont follow binding council decisions should be removed.
  • Any member can be removed in a vote.

6. Goals

  • We encourage the membership of groups such as LGBT, religious or ethnic minorities, abuse victims, etc etc, and strive to create a safe space for them to express their opinions. We also support measures to increase participation by the previously mentioned groups.
  • The following are banned, and will always be harshly punished: fascism, abuse, racism, sexism, etc etc,

7. Communication

  • A private Matrix chat for all council members.
  • (Once private communities are done) A private community on dev.lemmy.ml for issues.

8. Member List / Contact Info

General Contact @LemmyDev Mastodon