Slack compatibility
Slack rolled out its newer block types (markdown, table, data_table,
data_visualization, container, …) over time, and not every surface supports
all of them yet. Capability profiles tell slackmark which block types the
destination accepts; anything gated off falls back to the closest supported
form, with a degradation record.
The default is latest, every capability on. If a surface rejects one of the
newer block types or fields, pick the dated preset matching what that surface
supports instead of hand-filtering output. invalid_blocks has other causes
too, so do not assume every rejection is a capability mismatch.
Presets
Presets are cumulative and keyed to Slack changelog dates:
| Preset | Adds |
|---|---|
conservative | Nothing; baseline rich_text path only |
2025-02 | markdownBlock |
2025-08 | tableBlock |
2026-03 | preformattedLanguage, headerLevel |
2026-05 | dataTable |
2026-06 | dataVisualization, containerBlock |
latest (default) | Everything |
import { convert } from "slackmark";
const markdown = "# Status\n\n- [x] deployed";
const result = await convert(markdown, { capabilities: "2025-08" });conservative is the lowest-capability preset: output uses only rich_text, header,
section, image, context, and divider blocks. It avoids slackmark's
optional newer fields, but it is not a promise that every block works on every
Slack surface; check the block reference for your target surface. See the
capability presets reference
for what each flag gates and its fallback.
Here is one document under both extremes, default latest on the left and
capabilities: "conservative" on the right:
#### Rollout status
- [x] Canary
- [ ] Full fleet
```ts
await deploy("prod");
```Block Kit JSON
…
#### Rollout status
- [x] Canary
- [ ] Full fleet
```ts
await deploy("prod");
```Block Kit JSON
…
Three things changed. The header dropped its level, the task list became unicode
checkboxes in a rich text list, and the code fence lost its language tag. The first two are
visible in the render; the third is a field in the payload, and all three are named in the
degradations under the right-hand figure.
Fine-grained flags and overrides
Pass a flags object instead of a preset name, or layer capabilityOverrides
on top of any base. A partial flags object is merged over latest:
import { convert } from "slackmark";
const markdown = "# Status\n\n- [x] deployed";
// Preset plus one override.
await convert(markdown, {
capabilities: "2026-03",
capabilityOverrides: { tableBlock: false },
});
// Partial flags merged over `latest`.
await convert(markdown, {
capabilities: { dataVisualization: false },
});Unknown preset names throw a ConfigurationError when the conversion runs, so a
typo will not silently enable every capability.