diff --git a/.changeset/data-table-block.md b/.changeset/data-table-block.md
new file mode 100644
index 000000000..0aa292665
--- /dev/null
+++ b/.changeset/data-table-block.md
@@ -0,0 +1,5 @@
+---
+"@slack/types": minor
+---
+
+feat: add `data_table` Block Kit block type (`DataTableBlock`) and the `raw_number` cell composition object (`RawNumberElement`)
diff --git a/packages/types/src/block-kit/blocks.ts b/packages/types/src/block-kit/blocks.ts
index 365492d93..23e7d6c14 100644
--- a/packages/types/src/block-kit/blocks.ts
+++ b/packages/types/src/block-kit/blocks.ts
@@ -28,6 +28,7 @@ import type {
import type {
MrkdwnElement,
PlainTextElement,
+ RawNumberElement,
RawTextElement,
SlackFileImageObject,
TextObject,
@@ -60,6 +61,7 @@ export type KnownBlock =
| CarouselBlock
| ContextBlock
| ContextActionsBlock
+ | DataTableBlock
| DividerBlock
| FileBlock
| HeaderBlock
@@ -226,6 +228,42 @@ export interface ContextActionsBlock extends Block {
elements: ContextActionsBlockElement[];
}
+/**
+ * A helper union type of all cell types that can be used in a {@link DataTableBlock} row. Cells can be of type
+ * `raw_text`, `raw_number`, or `rich_text`. Note that `rich_text` cells are not allowed in the header row.
+ */
+export type DataTableCell = RawTextElement | RawNumberElement | RichTextBlock;
+
+/**
+ * @description Displays structured, sortable, and paginated data in a table.
+ * @see {@link https://docs.slack.dev/reference/block-kit/blocks/data-table-block Data table block reference}.
+ */
+export interface DataTableBlock extends Block {
+ /**
+ * @description The type of block. For a data table block, `type` is always `data_table`.
+ */
+ type: 'data_table';
+ /**
+ * @description An array consisting of table rows, where the first row is the header row. Each row is an array of
+ * cells of type `raw_text`, `raw_number`, or `rich_text`. Minimum 2 rows (a header and one data row) and maximum 101
+ * rows (a header and 100 data rows). Each row must contain the same number of cells, with a minimum of 1 and a maximum
+ * of 20 columns. The `rich_text` cell type is not allowed in the header row.
+ */
+ rows: DataTableCell[][];
+ /**
+ * @description A description of the table used for the underlying HTML element.
+ */
+ caption: string;
+ /**
+ * @description The number of rows to display per page. Minimum 1, maximum 100. Defaults to 5 if not provided.
+ */
+ page_size?: number;
+ /**
+ * @description The zero-based index of the column used as the row identifier. Defaults to 0 if not provided.
+ */
+ row_header_column_index?: number;
+}
+
/**
* @description Visually separates pieces of info inside of a message. A content divider, like an `
`, to split up
* different blocks inside of a message. The divider block is nice and neat, requiring only a `type`.
diff --git a/packages/types/src/block-kit/composition-objects.ts b/packages/types/src/block-kit/composition-objects.ts
index 886830344..59e607226 100644
--- a/packages/types/src/block-kit/composition-objects.ts
+++ b/packages/types/src/block-kit/composition-objects.ts
@@ -188,6 +188,25 @@ export interface RawTextElement {
text: string;
}
+/**
+ * @description Defines an object containing a numeric value and its display text. Used for numeric cells in a
+ * {@link DataTableBlock}, allowing the column to be sorted numerically.
+ */
+export interface RawNumberElement {
+ /**
+ * @description The formatting to use for this object.
+ */
+ type: 'raw_number';
+ /**
+ * @description The numeric value used for sorting the column.
+ */
+ value: number;
+ /**
+ * @description The text used to display the value. The minimum length is 1 character.
+ */
+ text: string;
+}
+
interface BaseConversationFilter {
/**
* @description Indicates which type of conversations should be included in the list. When this field is provided, any
diff --git a/packages/types/test/blocks.test-d.ts b/packages/types/test/blocks.test-d.ts
index e154b6c1b..8023d2687 100644
--- a/packages/types/test/blocks.test-d.ts
+++ b/packages/types/test/blocks.test-d.ts
@@ -1,5 +1,5 @@
import { expectAssignable, expectError } from 'tsd';
-import type { AlertBlock, CardBlock, CarouselBlock, KnownBlock } from '../src/index';
+import type { AlertBlock, CardBlock, CarouselBlock, DataTableBlock, KnownBlock } from '../src/index';
// CardBlock
// -- sad path
@@ -62,3 +62,53 @@ expectAssignable({
type: 'carousel',
elements: [{ type: 'card' }],
});
+
+// DataTableBlock
+// -- sad path
+expectError({}); // missing type, rows, and caption
+expectError({ type: 'data_table' }); // missing required rows and caption
+expectError({
+ type: 'data_table',
+ rows: [[{ type: 'raw_text', text: 'Name' }]],
+}); // missing required caption
+expectError({
+ type: 'data_table',
+ caption: 'A list of fruit and their quantities',
+}); // missing required rows
+// -- happy path
+expectAssignable({
+ type: 'data_table',
+ caption: 'A list of fruit and their quantities',
+ rows: [
+ [
+ { type: 'raw_text', text: 'Fruit' },
+ { type: 'raw_text', text: 'Quantity' },
+ ],
+ [
+ { type: 'raw_text', text: 'Apples' },
+ { type: 'raw_number', value: 12, text: '12' },
+ ],
+ ],
+});
+expectAssignable({
+ type: 'data_table',
+ caption: 'A list of users',
+ block_id: 'users_table',
+ page_size: 10,
+ row_header_column_index: 0,
+ rows: [
+ [
+ { type: 'raw_text', text: 'User' },
+ { type: 'raw_text', text: 'Bio' },
+ ],
+ [
+ { type: 'raw_text', text: 'Mark' },
+ { type: 'rich_text', elements: [{ type: 'rich_text_section', elements: [{ type: 'text', text: 'Founder' }] }] },
+ ],
+ ],
+});
+expectAssignable({
+ type: 'data_table',
+ caption: 'A minimal table',
+ rows: [[{ type: 'raw_text', text: 'Header' }], [{ type: 'raw_text', text: 'Value' }]],
+});