How Flux’s Plugin System Works

Leif Riksheim
Coasys
Published in
9 min readNov 16, 2023

With the latest release of ADAM, a new framework for building decentralised applications, we have also updated our main app, Flux, a decentralised alternative to Discord or Slack.

In this release, we have worked hard on a feature we think our community will be very excited about: Plugins!

Communities can now add new features to Flux, without asking us for permission. For example, if you want to play chess with your community, you can create a plugin, publish it, and anyone can now install it and try it out with their community. Think Wordpress plugins, but for social networks!

This is part of Coasys’s bigger vision of creating a new living web where users and communities are not just passive consumers of apps but can extend or invent their own communication tools. This way, the web becomes evolvable, extendable and unrestricted, giving communities control and the power to move their data across apps.

Background Context

1. The ADAM Layer

The first thing that makes Flux’s new plugin system possible is The ADAM Layer, an app development framework where all data is stored on your device and shared with others in Neighbourhoods. Each community in Flux is really just an ADAM Neighbourhood, and thus compatible with any other ADAM app.

You can think of each Neighbourhood as a shared database where users can collaborate on data. The data is organised in a graph, and is really just a collection of “links” created by users. These links can be added, updated or removed by individual users, and the history of all these changes are synced between the people in the Neighbourhood.

Let’s say we want to add a todo item to a channel called “communication” in a Flux community. A very simplified example of how we might create this data could look something like this:

const links = [
{ source: "communication", predicate: "has todo", target: "write blog post"},
{ source: "write blog post", predicate: "has status", target: "in progress"},
{ source: "write blog post", predicate: "is done", target: "false"}
];

someNeighbourhood.addLinks(links);

This would result in a graph structure like this:

A graph in a Neghbourhood

Under the hood, the specific storage of these links, and technical details like how to merge and sync the data is handled by the underlying Link Language that is installed on the Neighbourhood. ADAM by default provides a couple of implementations you can use, but you are also free to create your own. Joshua Parkin, the co-founder of ADAM, wrote an excellent and detailed blog post about how to make a Link Language here, if you are interested in going deeper.

With this architecture ADAM gives users the freedom to choose where and how the data is stored, while app developers have a unified API over any kind of backend technology. In addition to this they also get a data structure that can represent any data structure like SQL tables, NoSQL document stores, key-value pairs, and complex multi-relationship models like hypergraphs.

2. Web Components

The second thing that makes our new plugin system possible is web components. Web components are a web standard that lets you extend HTML with custom elements encapsulating interactive functionality.

<my-plugin></my-plugin>

When you’re done building a plugin, you just publish it as a web component on NPM. Flux can then find the plugin, load the web component in the UI on the fly, and pass down the relevant props (current neighbourhood, and current channel) for the plugin to use.

How Flux loads your plugin

The great thing about web components is that we now have a unified way of loading small interactive components using just regular javascript. Most javascript frameworks can compile to web components, so you can write a Flux plugin in your favorite Javascript framework. For instance, our main Flux app is written in Vue, but most of our plugins were written in Preact and then compiled to web components at build time!

Building Your First Plugin

1. Setup

First off, you’ll need to download ADAM and get it running. Once that’s done, run this command in your terminal:

npx @coasys/create-flux-plugin

The terminal will ask you for the name of your plugin, as well as what framework you want to use. Right now, we only support Preact, but we’ll be adding support for Vue, Svelte, Lit, and more.

Then install the dependencies and start your development server:

cd [app-name]
npm install
npm run dev

As a starting point the code includes a Todo example, the Flux UI component library, a testing environment that simulates the Flux app, and a build setup ready to convert your Preact app to a web component and publish it on NPM.

In the package.json of your plugin, you’ll need to update these fields create the correct metadata for your plugin:

"name": "my-plugin-name",
"fluxapp": {
"name": "Name of your plugin",
"description": "Description",
"icon": "bootstrap icons name"
},
"keywords": [
"flux-plugin",
"ad4m-view"
]

The keywords help the Flux app search the NPM registry and find your plugin.

2. Flux UI

When bootstrapping a new plugin using @fluxapp/create you also get Flux UI out of the box. This is the component library that Flux is using for all it’s UI, and we recommend using these elements to develop your app, as it keeps theming working for the end user, so your plugin can follow the users selected theme.

3. Main component

As shown in the introduction, Flux’s main UI will pass three props down to your plugin/web component:

PerspectiveProxy represents the ADAM Neighbourhood (or community in Flux) that the user is currently in. Neighbourhoods are fundamentally Shared Perspectives.

Source is the current position in the graph, and is essentially the “id” of the channel where the plugin is being used.

Agent is an instance of AgentClient, which gives you a way to get more information about different Agents.

import { PerspectiveProxy } from "@perspect3vism/ad4m";
import { AgentClient } from "@perspect3vism/ad4m/lib/src/agent/AgentClient";

type Props = {
agent: AgentClient;
perspective: PerspectiveProxy;
source: string;
};

export default function Plugin({ agent, perspective, source }: Props) {
return <div>Currently selected community is {perspective.name}</div>;
}

4. Subjects

With ADAM you don’t have to worry about creating a database, or maintain server infrastructure.

As we saw in the introduction, the data in a Neighbourhood is just a collection of links structured like a graph. Working with these links might be fine for very simple use cases, but it quickly becomes difficult to work with, especially if you are used to database schemas.

In the introduction, we demonstrated how to add a todo item in a Neighbourhood by adding links to the graph. Even if the data in a Neighbourhood is really just a collection of links, we do have a conceptual idea of what a Todo consist of:

A Todo Subject

The green cluster here represents the collection of links that our todo item consist of.

So how do we make this collection of associations explicit?

With ADAM we can create something called Subjects, that basically are “virtual objects” on top of our graph data. It enables us to define the relationships between data, and make it into a “thing”. Normally you would maybe call these Objects, but because Neighbourhoods can decide and define their own concepts we call them “Subjects”, as they are a subjective overlay to the specific Neighbourhood graph data.

You are free to create any kind of subject, but let’s start with the common types that are used by Flux. Because Flux Plugins share a lot of functionality, it comes bundled with several commonly used subjects that can be imported from @fluxapp/api:

  • Community
  • Channel
  • Message
  • Post

We also made hooks for React/Preact to make it easier for developers to get realtime updates, caching, pagination and more. Here is an example of how we could get some information about the current channel our plugin is loaded in:

import { PerspectiveProxy } from "@perspect3vism/ad4m";
import { AgentClient } from "@perspect3vism/ad4m/lib/src/agent/AgentClient";
import { useSubject} from "@fluxapp/react-web";
import { Channel } from "@fluxapp/api";

type Props = {
agent: AgentClient;
perspective: PerspectiveProxy;
source: string;
};

export default function Plugin({ agent, perspective, source }: Props) {

const { entry } = useSubject({
perspective,
id: source,
subject: Channel,
});

return <div>Currently selected channel: {entry.name}</div>;
}

As mentioned before, the source prop that get’s passed down from the main Flux app, is a reference to a location in our graph, and in this case the current channel “id”.

5. Custom Subjects

For our little plugin we are going to demonstrate how to make a very simple Todo app, so we need to create a new Subject called Todo:

import { SDNAClass, subjectProperty, subjectFlag } from "@perspect3vism/ad4m";

@SDNAClass({
name: "Todo",
})
export default class Todo {

@subjectProperty({
through: "todo://title",
writable: true,
resolveLanguage: "literal",
})
title: string;

@subjectProperty({
through: "todo://has_status",
writable: true,
resolveLanguage: "literal",
})
status: string;

@subjectProperty({
through: "todo://is_done",
writable: true,
resolveLanguage: "literal",
})
done: boolean;

}

We won’t go into detail in how this works now, but you can read more about the Subject Classes API here. What’s important to know is that this defines something similar to a database schema for our graph and describes what a Todo “is”.

Now we can import this Subject and use it in our plugin to add and display some todos:

import styles from "./App.module.css";
import { PerspectiveProxy } from "@perspect3vism/ad4m";
import { AgentClient } from "@perspect3vism/ad4m/lib/src/agent/AgentClient";
import { useSubjects} from "@fluxapp/react-web";
import Todo from "./models/Todo";

type Props = {
agent: AgentClient;
perspective: PerspectiveProxy;
source: string;
};

export default function Plugin({ agent, perspective, source }: Props) {
const [inputValue, setInputValue] = useState("");

const { entries: todos, repo } = useSubjects({
perspective,
source,
subject: Todo,
});

function addTodo() {
repo.create({ title: inputValue, done: false });
setInputValue("");
}

return (
<div>
<ul>
{todos.map((todo) => (
<li>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={(e: any) =>
repo.update(todo.id, { done: e.target.checked })
}
/>
{todo.title}
</label>
</li>
))}
</ul>
<input
value={inputValue}
onChange={(e: any) => setInputValue(e.target.value)}
type="input"
/>
<button>Add todo</button>
</div>
);
}

6. Publishing your plugin

Once you’ve finished writing your plugin, you’ll want to publish it so that Flux can locate it. If you used @fluxapp/create, all you need to do is update the package.json with a unique NPM name, and complete the flux fields.

Run npm run build, followed by npm publish. Voila, your plugin will now be available for installation in any community!

What’s pretty mind-blowing is that you haven’t just created a new plugin for Flux, but also a web component that any ADAM app can use. Moreover, your plugin can work in a Neighbourhood running on various backends like Holochain, Hypercore, Gun.db, or even AWS. As a developer, you don’t have to fret about the underlying technology (unless you aim to create a new ADAM Language for a specific backend or p2p tech).

Plugins can also be used inside of plugins, which make them more powerful than first expected. For example we have a comment-section plugin that we are using inside of the Chat, Post and Kanban plugins. If we wanted to add comments to each todo item in our list, we could easily add it like this:

import styles from "./App.module.css";
import { PerspectiveProxy } from "@perspect3vism/ad4m";
import { AgentClient } from "@perspect3vism/ad4m/lib/src/agent/AgentClient";
import Todo from "./models/Todo";
import { useSubjects} from "@fluxapp/react-web";

import CommentSection from "@fluxapp/comment-section";

customElements.define("comment-section", CommentSection);

type Props = {
agent: AgentClient;
perspective: PerspectiveProxy;
source: string;
};

export default function Plugin({ agent, perspective, source }: Props) {
const [inputValue, setInputValue] = useState("");

const { entries: todos, repo } = useSubjects({
perspective,
source,
subject: Todo,
});

function addTodo() {
repo.create({ title: inputValue, done: false });
setInputValue("");
}

return (
<div>
<ul>
{todos.map((todo) => (
<li>
<label>
<input
type="checkbox"
checked={todo.done}
onChange={(e: any) =>
repo.update(todo.id, { done: e.target.checked })
}
/>
{todo.title}
</label>
<comment-section perspective={perspective} source={todo.id} agent={agent}></comment-section>
</li>
))}
</ul>
<input
value={inputValue}
onChange={(e: any) => setInputValue(e.target.value)}
type="input"
/>
<button>Add todo</button>
</div>
);
}

For a more documentation on creating a plugin, you can read more here:

https://docs.fluxsocial.io/
https://docs.ad4m.dev/

Looking to the future

We invite anyone who is interested in building custom plugins to join our Discord (oh yes, the irony) and to reach out so we can help you get started. This is definitely early stages, and we want to gather feedback on API’s and general DX as we go to ensure the best experience for developers as possible.

This is just the first step towards a bigger vision where digital communities can be in complete control over their communication tools, adapting, evolving their own collective sense making capabilities.

Future plans for Flux and ADAM include having deeper integrations with LLM’s so not only developers can start creating on ADAM, but anyone could start speaking apps and interaction patterns into existence by using plain english. Not worrying about setting up any infrastructure, but just deploy it directly to their communities.

We can’t wait to see what you are going to build!

--

--