Acting in hubs
Embeds v2 (Blocks)
Composable message layouts: containers, columns, tabs, pages, charts, tables, countdowns, galleries and controls anywhere.
Classic embeds are one fixed card. Blocks let you build the whole message out of pieces: put a header, live stats and a chart in a tinted container; split it into columns; hide details behind tabs, an accordion or flippable pages; and drop buttons or menus wherever they make sense. Tabs, pages, accordions, spoilers, carousels, image zoom and countdowns run in the member's app — no round trip to your bot, no rate limit spent.
Design and test layouts visually in the Embed Studio: live preview, validation against the real limits, and code you can paste.
Sending blocks
Add a blocks array anywhere you can send embeds: new messages, edits, interaction replies (reply and update, ephemeral included) and webhooks. A message may combine content, embeds, components and blocks. Send blocks: [] in an edit to remove them.
await ctx.reply({ blocks: [ { type: "container", accent: ["#7c5cff", "#22d3ee"], // one color or a gradient tint: true, blocks: [ { type: "header", eyebrow: "Weekly report", title: "Hub health", subtitle: "Last 7 days" }, { type: "stats", items: [ { label: "Members", value: "1,204", delta: "+38", trend: "up" }, { label: "Messages", value: "9.8k", trend: "flat" }, ] }, { type: "chart", kind: "line", title: "Messages per day", data: [ { label: "Mon", value: 1200 }, { label: "Tue", value: 1480 }, { label: "Wed", value: 1710 }, ] }, { type: "tabs", tabs: [ { label: "Top members", blocks: [{ type: "table", columns: [{ label: "Member" }, { label: "XP", align: "right" }], rows: [["Ana", "12,480"], ["Leo", "9,870"]] }] }, { label: "Next event", blocks: [{ type: "countdown", to: "2026-12-31T20:00:00Z", style: "big" }] }, ] }, { type: "section", blocks: [{ type: "text", content: "Want the full report?" }], accessory: { type: "button", style: "primary", label: "Open", customId: "report:open" } }, ], }, ],});embedLinks permission (not needed when answering an interaction). Text inside blocks goes through the hub's AutoMod like any message.Content blocks
| type | Fields | Notes |
|---|---|---|
text | content, size (sm·md·lg·xl), tone, align | Markdown, mentions and links; up to 4000 chars |
header | title, subtitle, eyebrow, iconUrl, url | A title with an optional small caps line above |
image | url, alt, caption, spoiler, aspect (auto·wide·square·banner) | Click to zoom; spoilers blur until clicked |
gallery | items[1–10] {url, alt, caption, spoiler}, layout (grid·carousel) | Smart grid or swipeable carousel with a lightbox |
fields | items[1–25] {name, value}, columns (1–3) | Like classic embed fields, in a real grid |
stats | items[1–6] {label, value, delta, trend (up·down·flat), emoji} | KPI cards with colored trends |
progress | value, max (100), label, color, showValue, segments (2–20) | Animated bar or segmented meter |
table | columns[1–6] {label, align}, rows[1–20][cells], striped | Each row needs one cell per column; scrolls on phones |
chart | kind (bar·line·donut), data[1–24] {label, value, color}, title, unit | Hover shows values; donut takes up to 8 slices |
callout | tone (info·success·warning·danger·tip), content, title, emoji | Highlighted note |
code | content, language, filename | Highlighted, with a copy button |
countdown | to (ISO date), label, doneText, style (inline·big) | Ticks live on every member's screen |
badges | items[1–12] {label, color, emoji} | Colored pills |
list | items[1–25] {text, checked, emoji}, style (bullet·number·check) | Checklists strike done items |
separator | line, spacing (sm·md·lg), style (line·gradient) | A divider or spacer; gradient follows the v3 theme |
actions | components[1–5] (buttons, or one select) | Same buttons and menus as components |
Layout blocks
| type | Fields | Notes |
|---|---|---|
container | blocks[1–30], accent (hex or [hex, hex]), tint, spoiler · v3: theme, gradient, animated | A card with an accent bar (or an animated gradient frame in v3). Top level only. Spoiler blurs the whole card |
section | blocks[1–5], accessory (thumbnail {url} or a button) | Text next to an image or a button. Holds text, header, badges, list, progress, countdown and fields |
columns | columns[2–3] {width (1–3), blocks[1–10]} | Side by side on wide screens, stacked on phones |
Interactive blocks (run in the app)
| type | Fields | Notes |
|---|---|---|
tabs | tabs[2–6] {label, emoji, blocks[1–20]} | Switching tabs is instant and private to each member |
accordion | items[1–8] {title, emoji, open, blocks[1–20]} | Collapsible sections (FAQ, release notes) |
pages | pages[2–10] {title, blocks[1–20]} | A wizard or slideshow with Back / Next and dots |
Buttons and menus inside blocks
Put them in an actions block or as a section accessory, at any depth. Clicks reach your app exactly like classic components (an INTERACTION_CREATE with type: "component" and the customId). Answer with update to swap the layout in place — for example to move a member to the next step. customIds must be unique across the whole message (rows and blocks together).
Limits
- Up to 12 top-level blocks and 80 blocks in total.
- Nesting up to 4 levels (container → tabs → accordion → content). A
containercan't be inside another block. - Up to 10,000 characters of text across the message, and 25 buttons and menus.
- Every URL must be
http(s); unknown fields are rejected so typos surface immediately. - Webhooks can use link buttons only (they can't receive clicks).
Validate without sending
POST https://api.voznest.com/api/v1/blocks/validate with { "blocks": [...] } returns valid, the issues (path and message) and usage against each limit. No authentication; nothing is stored. Handy in CI.
From a webhook
curl -X POST "https://api.voznest.com/api/v1/webhooks/<id>/<token>" \ -H "Content-Type: application/json" \ -d '{"blocks":[{"type":"callout","tone":"success","title":"Deploy finished","content":"**v2.4.0** is live."}, {"type":"actions","components":[{"type":"button","style":"link","label":"Changelog","url":"https://example.com"}]}]}'