

How to Archive Slack Messages to Notion with Pipedream
When a Slack message is starred or reacted to with a specific emoji, Pipedream catches the event via webhook and creates a new Notion page with the message text, author, channel, timestamp, and a link back to the original thread.
Steps and UI details are based on platform versions at time of writing — check each platform for the latest interface.
Best for
Engineering or product teams who make decisions in Slack threads and need those decisions searchable in a Notion knowledge base without copy-pasting manually.
Not ideal for
Teams that want to archive every message in a channel — that volume will exhaust credits fast; use a dedicated logging tool like Datadog or a database sink instead.
Sync type
real-timeUse case type
backupReal-World Example
A 22-person product team at a B2B SaaS company uses a bookmark emoji (🔖) in Slack to flag decisions made during sprint planning threads. Before this workflow, engineers screenshotted threads and pasted them into Notion manually — a process that took 5-10 minutes per thread and happened inconsistently. Now, reacting with 🔖 creates a Notion page in their 'Decisions' database within 15 seconds, with the full thread context and a direct Slack link attached.
What Will This Cost?
Drag the slider to your expected monthly volume.
Each platform counts differently — Zapier: 1 task per trigger. Make: 1 operation per module per record. n8n: 1 execution per run.





Prices shown for annual billing. Based on published pricing as of April 2026.
Estimated ROI
1000
min saved/mo
$583
labor value/mo
Free
no platform cost
Based on ~2 min manual effort per operation at $35/hr fully loaded labor cost.
Implementation
Import this workflow directly into Pipedream
Copy the pre-built Pipedream blueprint and paste it straight into Pipedream. All modules, filters, and field mappings are already configured — you just need to connect your accounts.
Before You Start
Make sure you have everything ready.
Field Mapping
Map these fields between your apps.
| Field | API Name | |
|---|---|---|
| Required | ||
| Message Text | ||
| Author Display Name | ||
| Channel Name | ||
| Slack Permalink | ||
| Archived At | ||
| Message Timestamp | ||
3 optional fields▸ show
| Thread Replies | |
| Reaction Emoji | |
| Archived By |
Step-by-Step Setup
pipedream.com > Workflows > New Workflow
Create a new Pipedream workflow
Go to pipedream.com and sign in. Click 'Workflows' in the left sidebar, then click the blue 'New Workflow' button in the top right. You'll land on the workflow canvas with an empty trigger slot at the top. This is where you'll connect the Slack event source. Give the workflow a name like 'Slack → Notion Archiver' using the pencil icon at the top.
- 1Click 'Workflows' in the left sidebar
- 2Click the blue 'New Workflow' button
- 3Click the pencil icon next to the default workflow name
- 4Type 'Slack → Notion Archiver' and press Enter
Workflow Canvas > Add a trigger > Slack > New Reaction Added
Add the Slack trigger for emoji reactions
Click the 'Add a trigger' block. In the search bar that appears, type 'Slack' and select it. You'll see a list of Slack trigger types — choose 'New Reaction Added'. This trigger fires via Slack's Events API the moment a user reacts to any message in a channel your bot is a member of. You'll filter to a specific emoji in a later step.
- 1Click the grey 'Add a trigger' block
- 2Type 'Slack' in the app search bar
- 3Select 'Slack' from the results
- 4Choose 'New Reaction Added' from the trigger list
- 5Click 'Connect Slack' and authorize with your workspace admin account
Workflow Canvas > + > Built-in > Filter
Filter to your archive emoji only
After the trigger, click the '+' button below the trigger block and choose 'Add a step'. Select 'Filter' (built-in Pipedream step). You'll write a condition that checks whether the reaction name equals your chosen archive emoji — use 'bookmark' for 🔖 or 'white_check_mark' for ✅. Pipedream stops the workflow here if the condition is false, so only messages reacted to with your chosen emoji proceed to Notion.
- 1Click the '+' button below the Slack trigger step
- 2Click 'Built-in' in the step type selector
- 3Select 'Filter'
- 4In the condition field, set: steps.trigger.event.reaction === 'bookmark'
- 5Click 'Continue' to confirm the filter
Workflow Canvas > + > Node.js Code Step
Fetch the full message text via Slack API
The reaction event payload gives you the channel ID and message timestamp but not the message text itself. Add a Node.js code step to call Slack's conversations.history API and retrieve the actual message content. You'll pass the channel and timestamp from the trigger event. This step also grabs the thread replies if the message is a thread parent, giving you the full conversation context for Notion.
- 1Click '+' below the Filter step
- 2Select 'Run Node.js code'
- 3Paste the fetch code into the code editor (see Pro Tip section)
- 4Click 'Test' to confirm the step returns message text in the exports object
Workflow Canvas > + > Run Node.js code
Resolve the Slack user ID to a display name
The message payload contains a user ID like U04XYZ123, not a human-readable name. Add another Node.js code step that calls Slack's users.info endpoint with that ID and returns the user's real_name and display_name. Without this step, your Notion pages will show raw user IDs in the Author field, which makes the archive hard to read.
- 1Click '+' below the message fetch step
- 2Select 'Run Node.js code'
- 3Use the Slack OAuth token from your Connected Account to call users.info
- 4Export { real_name, display_name } from the step result
This code step handles the three Slack API calls needed before you touch Notion: fetching message text via conversations.history, fetching thread replies if a thread exists, and calling chat.getPermalink — all with proper error handling. Paste this into a single Node.js code step placed between your Filter step and the Notion steps. It uses Pipedream's built-in $ helper and your connected Slack account token via process.env.SLACK_BOT_TOKEN (set this in your workflow's environment variables).
JavaScript — Code Stepimport { axios } from "@pipedream/platform";▸ Show code
import { axios } from "@pipedream/platform";
export default defineComponent({
async run({ steps, $ }) {... expand to see full code
import { axios } from "@pipedream/platform";
export default defineComponent({
async run({ steps, $ }) {
const token = process.env.SLACK_BOT_TOKEN;
const channel = steps.trigger.event.item.channel;
const messageTs = steps.trigger.event.item.ts;
const reactingUserId = steps.trigger.event.user;
// 1. Fetch the original message text
const historyResp = await axios($, {
method: "GET",
url: "https://slack.com/api/conversations.history",
params: {
channel,
latest: messageTs,
inclusive: true,
limit: 1,
},
headers: { Authorization: `Bearer ${token}` },
});
if (!historyResp.ok) {
throw new Error(`conversations.history failed: ${historyResp.error}`);
}
const message = historyResp.messages?.[0];
if (!message) throw new Error("Message not found in channel history");
const rawText = message.text || "[No text content]";
const authorId = message.user || message.bot_id || "unknown";
// Sanitize Slack mrkdwn for Notion compatibility
const sanitizedText = rawText
.replace(/<@([A-Z0-9]+)>/g, "@user")
.replace(/<#([A-Z0-9]+)\|([^>]+)>/g, "#$2")
.replace(/<([^|>]+)\|([^>]+)>/g, "$2")
.replace(/<([^>]+)>/g, "$1");
// 2. Fetch thread replies if this message has a thread
let threadReplies = [];
if (message.thread_ts && message.reply_count > 0) {
const repliesResp = await axios($, {
method: "GET",
url: "https://slack.com/api/conversations.replies",
params: { channel, ts: message.thread_ts, limit: 100 },
headers: { Authorization: `Bearer ${token}` },
});
if (repliesResp.ok) {
// Skip the first item — it's the parent message repeated
threadReplies = repliesResp.messages
.slice(1)
.map((r) => ({
text: r.text?.replace(/<[^>]+>/g, "") || "",
user: r.user || r.bot_id || "unknown",
ts: r.ts,
}));
}
}
// 3. Fetch permalink
const permalinkResp = await axios($, {
method: "GET",
url: "https://slack.com/api/chat.getPermalink",
params: { channel, message_ts: messageTs },
headers: { Authorization: `Bearer ${token}` },
});
if (!permalinkResp.ok) {
throw new Error(`chat.getPermalink failed: ${permalinkResp.error}`);
}
// 4. Resolve author name
let authorName = authorId;
if (authorId.startsWith("U")) {
const userResp = await axios($, {
method: "GET",
url: "https://slack.com/api/users.info",
params: { user: authorId },
headers: { Authorization: `Bearer ${token}` },
});
if (userResp.ok) {
authorName = userResp.user.profile.display_name || userResp.user.real_name;
}
} else if (authorId.startsWith("B")) {
const botResp = await axios($, {
method: "GET",
url: "https://slack.com/api/bots.info",
params: { bot: authorId },
headers: { Authorization: `Bearer ${token}` },
});
if (botResp.ok) authorName = botResp.bot.name + " (bot)";
}
// 5. Resolve archiving user name
let archivedByName = reactingUserId;
const archiverResp = await axios($, {
method: "GET",
url: "https://slack.com/api/users.info",
params: { user: reactingUserId },
headers: { Authorization: `Bearer ${token}` },
});
if (archiverResp.ok) {
archivedByName = archiverResp.user.profile.display_name || archiverResp.user.real_name;
}
return {
message_text: sanitizedText,
message_text_truncated: sanitizedText.slice(0, 100),
author_id: authorId,
author_name: authorName,
archived_by: archivedByName,
channel_id: channel,
message_ts: messageTs,
permalink: permalinkResp.permalink,
thread_replies: threadReplies,
archived_at: new Date(parseFloat(steps.trigger.event.event_ts) * 1000).toISOString(),
};
},
});
Workflow Canvas > + > Run Node.js code
Fetch the Slack permalink for the message
Call Slack's chat.getPermalink endpoint using the channel ID and message timestamp from the trigger. This gives you a permanent URL that deep-links directly to the original message in Slack — critical for the Notion page so readers can jump back to context. Store the permalink in this step's exports so the Notion step can reference it as steps.get_permalink.permalink.
- 1Click '+' below the user resolution step
- 2Select 'Run Node.js code'
- 3Call chat.getPermalink with channel and message_ts from the trigger event
- 4Export { permalink } from the step
Workflow Canvas > + > Notion > Create Page
Connect your Notion account
Click '+' below the permalink step and search for 'Notion'. Select the 'Create Page' action. Click 'Connect Notion' and complete the OAuth flow — Notion will ask which pages and databases your integration can access. You must explicitly grant access to the specific database you want to archive into. If you skip granting access to the right database, Notion's API returns a 404 even though the database exists.
- 1Click '+' and search for 'Notion'
- 2Select 'Notion' from the app list
- 3Choose 'Create Page' as the action
- 4Click 'Connect Notion account'
- 5In the Notion OAuth dialog, check the box next to your 'Archived Decisions' database before clicking Allow
Workflow Canvas > Notion Create Page > Configure
Configure the Notion page fields
In the 'Create Page' action, set the Parent Database ID to your archive database's ID (found in the Notion URL after the workspace name and before the '?v=' parameter). Map each Notion database property to the corresponding Pipedream step export. Set the page title to the first 100 characters of the message text, the Author property to the resolved display name, Channel to the channel name, and the Slack Link URL property to the permalink.
- 1Paste your Notion database ID into the 'Parent Database ID' field
- 2Set 'Title' to: {{steps.fetch_message.message_text.slice(0, 100)}}
- 3Set 'Author' property to: {{steps.resolve_user.real_name}}
- 4Set 'Slack Link' URL property to: {{steps.get_permalink.permalink}}
- 5Set 'Archived At' date property to: {{new Date(steps.trigger.event.event_ts * 1000).toISOString()}}
Workflow Canvas > + > Notion > Append Block Children
Add the full message as Notion page body
The Notion 'Create Page' action creates the page record but leaves the body empty. Add a second Notion step — 'Append Block Children' — immediately after. Use the page ID output from the previous step (steps.create_page.$return_value.id) as the Block ID. Add a paragraph block containing the full message text and, if thread replies exist, append each reply as a separate quote block. This makes the Notion page readable without having to go back to Slack.
- 1Click '+' below the Create Page step
- 2Search for 'Notion' and select 'Append Block Children'
- 3Set Block ID to: {{steps.create_notion_page.$return_value.id}}
- 4Add a paragraph block with content: {{steps.fetch_message.message_text}}
- 5Add a loop over steps.fetch_message.thread_replies to append each reply as a quote block
channel: {{channel}}
ts: {{ts}}
pipedream.com > Workflows > [Your Workflow] > Event History
Test end-to-end with a real Slack message
In Slack, go to a channel your bot is in and react to any message with your archive emoji. Return to Pipedream and click the workflow's 'Event History' tab — you should see a new execution appear within 5-10 seconds. Click the execution to expand each step and confirm green checkmarks on the Filter, all code steps, and both Notion steps. Then open Notion and verify the page was created in the correct database with all fields populated.
- 1React to a Slack message with your archive emoji (e.g. 🔖)
- 2Click the 'Event History' tab in your Pipedream workflow
- 3Click the newest execution entry to expand the step-by-step results
- 4Confirm all steps show green checkmarks
- 5Open Notion and verify the new page exists in your archive database
Workflow Canvas > Deploy > Settings > Notifications
Deploy and set error alerting
Click the grey 'Deploy' button in the top right to activate the workflow. Pipedream will now process every matching Slack reaction event in production. Go to Settings > Notifications inside the workflow and enable email alerts on workflow errors — this way you'll know immediately if a Slack API rate limit or Notion auth failure causes a run to fail silently. Set the notification threshold to 1 error (not the default 10) for a workflow this small.
- 1Click the 'Deploy' button in the top right corner
- 2Confirm the deployment dialog
- 3Click 'Settings' in the left panel of the workflow
- 4Toggle on 'Email on error'
- 5Set error threshold to 1 and save
Going live
Production Checklist
Before you turn this on for real, confirm each item.
Troubleshooting
Common errors and how to fix them.
Frequently Asked Questions
Common questions about this workflow.
Analysis
Use Pipedream for this if your team has any technical comfort at all. The webhook from Slack lands in under 2 seconds, and the Node.js code step means you can handle the four API calls this workflow needs (conversations.history, users.info, chat.getPermalink, Notion create) in a single step with real error handling — not four separate no-code blocks with no visibility into what failed. The other scenario where Pipedream wins hard here: you want to parse Slack's mrkdwn syntax before it hits Notion. That text sanitization is three lines of regex in Node.js. In Zapier, it's a Formatter step that doesn't handle all the edge cases. Pick a different platform only if your whole team is non-technical and nobody will maintain the code step — in that case Make is more accessible.
On the free tier, Pipedream gives you 10,000 credits per month. This workflow uses 3 credits per run (roughly one credit per outbound API call). At 3,333 archives per month you hit the ceiling — that's about 110 archived messages per day. Most teams land well under that. If you're over it, Pipedream's Basic plan is $29/month for 100,000 credits, which supports 33,000 archives/month. Zapier's comparable plan for real-time triggers starts at $49/month and caps at 2,000 tasks — which at one task per Zap step means this 4-step workflow consumes 4 tasks per run, capping you at 500 archives/month. Pipedream is cheaper and higher-volume by a significant margin here.
Zapier has a cleaner UI for connecting Slack and Notion without writing code — if you can live with its Notion integration's limited block type support (it won't append quote blocks for thread replies, only plain paragraphs). Make handles the message text transformation better than Zapier using its text parsing modules, and its Notion module has slightly richer block type options. n8n gives you the same Node.js flexibility as Pipedream but requires self-hosting or their cloud plan — for a workflow this simple, that overhead isn't worth it unless you're already running n8n. Power Automate has no native Slack trigger worth using; you'd need a webhook and a custom connector, which is more work than just using Pipedream. Pipedream is still the right call because the Slack webhook delivery is instant, the code step handles the mrkdwn sanitization cleanly, and the connected accounts system means you don't manage OAuth tokens manually.
Three things you'll run into after the first week: First, Slack's reaction_added event fires even when someone removes and re-adds their reaction — you'll see duplicate archives from the same person on the same message if they toggle the emoji. Build the deduplication query into your workflow before you think you need it. Second, the Notion API's rate limit is 3 requests per second per integration. This workflow makes two Notion calls (create page + append blocks) in quick succession — under normal load it's fine, but if multiple people are archiving messages simultaneously you'll hit 429 errors. Add a 400ms delay between the two Notion steps. Third, Slack's conversations.history only returns messages from public channels and private channels where the bot is invited — it will not return messages from channels where the bot was added after the message was posted, even if the message timestamp is recent. This burns people who add the bot to an existing channel and expect to retroactively archive old messages by reacting to them.
Ideas for what to build next
- →Tag Notion pages by reaction type — Use different emojis (🔖 for decisions, ✅ for action items, 💡 for ideas) and map each to a different Notion database or a 'Category' select property, so archived messages are automatically sorted by type.
- →Add a daily digest notification — Build a second Pipedream workflow on a scheduled trigger that queries your Notion archive database for pages created in the last 24 hours and posts a summary back to a Slack channel — closes the loop so the team sees what was archived.
- →Attach file previews from Slack messages — Extend the message fetch step to check for files[] in the Slack message payload, download the file using the Slack Files API, and upload it to Notion as an attachment on the archived page — useful for capturing screenshots and diagrams posted alongside decisions.
Related guides
How to Archive Slack Messages to Notion with Power Automate
~15 min setup
How to Archive Slack Messages to Notion with n8n
~20 min setup
How to Archive Slack Messages to Notion with Zapier
~8 min setup
How to Archive Slack Messages to Notion with Make
~12 min setup
How to Share Notion Meeting Notes to Slack with Pipedream
~15 min setup
How to Share Notion Meeting Notes to Slack with Power Automate
~15 min setup