Message Decorations
Add visual decorations and accessories to Discord messages using Plexcord's Message Decorations and Accessories APIs.
Message Decorations
Plexcord provides two ways to add UI elements to Discord messages:
- Decorations: Elements that appear next to usernames
- Accessories: Elements that appear below message content
Message Decorations
Decorations are small elements that appear beside a user's name in messages. Common uses: badges, icons, status indicators.
Registering a Decoration
import { addDecoration, removeDecoration } from "@api/MessageDecorations";
// Define the decoration component
function MyDecoration({ user, message }: {
user: { id: string; username: string };
message: { id: string; content: string };
}) {
return (
<span style={{
color: "#FFD700",
fontSize: "12px",
marginLeft: "4px"
}}>
★
</span>
);
}
export default definePlugin({
name: "StarDecorator",
description: "Adds a star next to usernames",
authors: [{ name: "You", id: 0n }],
start() {
// The first arg is a unique identifier string
addDecoration("star-decorator", MyDecoration);
},
stop() {
removeDecoration("star-decorator");
}
});Conditional Decorations
Show the decoration only for specific users or conditions:
import { definePluginSettings } from "@api/Settings";
import { OptionType } from "@utils/types";
const settings = definePluginSettings({
targetUserId: {
type: OptionType.STRING,
description: "Show star next to this user ID",
default: ""
}
});
function StarForUser({ user }: { user: { id: string } }) {
if (user.id !== settings.store.targetUserId) return null;
return <span style={{ color: "#FFD700" }}>★</span>;
}Message Accessories
Accessories appear below message content. They're ideal for adding extra info, previews, or action buttons below messages.
Registering an Accessory
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
function MessageStats({ message }: { message: any }) {
const wordCount = message.content.split(/\s+/).filter(Boolean).length;
const charCount = message.content.length;
// Return null to not show anything for this message
if (!message.content) return null;
return (
<div style={{
fontSize: "11px",
color: "var(--text-muted)",
marginTop: "4px",
padding: "4px 8px",
background: "var(--background-secondary)",
borderRadius: "4px",
display: "inline-block"
}}>
{wordCount} words · {charCount} characters
</div>
);
}
export default definePlugin({
name: "MessageStats",
description: "Shows word and character count below messages",
authors: [{ name: "You", id: 0n }],
start() {
// Priority number controls ordering among multiple accessories
addMessageAccessory("message-stats", MessageStats, 1);
},
stop() {
removeMessageAccessory("message-stats");
}
});Accessory Priority
When multiple plugins add accessories, the priority number controls the order:
// Lower number = rendered first (higher up)
addMessageAccessory("plugin-a", ComponentA, 1); // Appears first
addMessageAccessory("plugin-b", ComponentB, 10); // Appears after
addMessageAccessory("plugin-c", ComponentC, 5); // Appears between A and BAccessing Message Data
Both decorations and accessories receive props about the message and author:
// Typical props you receive
interface MessageProps {
message: {
id: string;
content: string;
author: {
id: string;
username: string;
bot: boolean;
};
embeds: any[];
attachments: any[];
timestamp: Date;
};
channel: {
id: string;
name?: string;
guildId?: string;
};
}Using React State in Decorations
Decorations and accessories are full React components:
import { React, useState } from "@webpack/common";
function ExpandableAccessory({ message }: { message: any }) {
const [expanded, setExpanded] = useState(false);
if (!message.content.length > 200) return null;
return (
<div>
{expanded ? (
<div style={{ marginTop: "4px", color: "var(--text-muted)" }}>
Full message details here...
<button onClick={() => setExpanded(false)}>Collapse</button>
</div>
) : (
<button
onClick={() => setExpanded(true)}
style={{ color: "var(--interactive-normal)", cursor: "pointer" }}
>
Show details
</button>
)}
</div>
);
}Complete Example: Link Preview Badge
Shows a "🔗 Contains links" badge below messages with URLs:
import definePlugin from "@utils/types";
import { addMessageAccessory, removeMessageAccessory } from "@api/MessageAccessories";
const URL_REGEX = /https?:\/\/[^\s]+/g;
function LinkBadge({ message }: { message: any }) {
const urls = message.content.match(URL_REGEX);
if (!urls || urls.length === 0) return null;
return (
<span style={{
display: "inline-flex",
alignItems: "center",
gap: "4px",
fontSize: "11px",
color: "var(--text-link)",
background: "var(--background-secondary-alt)",
padding: "2px 6px",
borderRadius: "10px",
marginTop: "4px"
}}>
🔗 {urls.length} link{urls.length !== 1 ? "s" : ""}
</span>
);
}
export default definePlugin({
name: "LinkBadge",
description: "Shows link count below messages containing URLs",
authors: [{ name: "You", id: 0n }],
start() {
addMessageAccessory("link-badge", LinkBadge, 1);
},
stop() {
removeMessageAccessory("link-badge");
}
});Styling Best Practices
Use Discord's CSS variables for themed colors:
// Text colors
color: "var(--text-normal)"
color: "var(--text-muted)"
color: "var(--text-link)"
color: "var(--interactive-normal)"
// Background colors
background: "var(--background-primary)"
background: "var(--background-secondary)"
background: "var(--background-secondary-alt)"
background: "var(--background-floating)"
// Brand colors
color: "var(--brand-experiment)" // Discord blurpleThis ensures your decoration/accessory looks correct in both dark and light themes.