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:

PresetAdds
conservativeNothing; baseline rich_text path only
2025-02markdownBlock
2025-08tableBlock
2026-03preformattedLanguage, headerLevel
2026-05dataTable
2026-06dataVisualization, 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.