Update a card from Slack

  • 8 June 2022
  • 9 replies

Userlevel 3

Is it possible to update an existing card from Slack? 


For ex, if there’s a card with weekly updates and we want to append another update. 
Existing card has: 

Updates for week of July 1
July 2 Bananas in the break room 

July 3 Stop using reply-all about the bananas in the break room 

And then on Slack on July 4 there’s a message that we want to be added at the end: 

“We’re getting apples tomorrow!”


How can we send that message so that the card would then look like this, with the new update at the end? Would this be a Zap? 


Updates for week of July 1
July 2 Bananas in the break room 

July 3 Stop using reply-all about the bananas in the break room 

July 4 We’re getting apples tomorrow!





Best answer by Joe Duffy 9 June 2022, 22:21

View original

9 replies

Hi Siobhan!

First off, thank you for the outreach and for sharing this question and use case. Unfortunately we do not have an “Update Card” action in Zapier today, just a “Create Card” action, so this would not be possible through a Zap. Although, you might be able to leverage our API for this use case. I am tagging in @Joe Duffy to see if he has any insights on how this might be accomplished. Thank you again for sharing this and I have noted this as a possible enhancement to our Zapier integration. 

Userlevel 3

Thanks @Lauren Musso!

Hi @Siobhan Hyser 👋

Lauren is correct that we don’t have a native action in Zapier to update a card. However, you could still  accomplish this through Zapier using our SDK if you are comfortable using Python!

If you are using a specific Slack channel to capture these messages, you could use the trigger and action below (you could also use a different trigger based on your use case): 

The Python code that you’d want to execute is here: 

import guru

g = guru.Guru("[]","[api_token]")

card = g.find_cards(title="[card_title]")

SLACK_MESSAGE = """[slack_message_variable_from_zapier]"""

update = False

if SLACK_MESSAGE not in card.content:
card.content = card.content + SLACK_MESSAGE
update = True

if update:

Let me know if this if helpful!

Userlevel 3

Thanks! I shared this with a support engineer. Cheers!

Thanks for asking this, Sio!

Hey @Joe Duffy thanks for the tip and snippet. I was wondering how you get the Guru SDK imported into Zapier. From what I read in their docs, only the Python standard library is available in their Code steps, and the error I get when trying to import guru indicates as much:


I’m wondering if perhaps there’s a way I can expose the Guru SDK to Zapier’s Code action. Do you know?

Otherwise, what environments can we run this code from that would be allowed by Guru? I get a 403 when I try to do it locally, so I’m interested in knowing where we could run this. Or is it that I need a specific user role in our Guru instance?

Here’s part of the stack I get back:

[Live] making a get call:[:CARDID]/extended
[Live] response status: 200
<p class="ghq-card-content__paragraph" data-ghq-card-content-type="paragraph">placeholder</p>
[Live] making a patch call:[:CARDID]?keepVerificationState=true {'preferredPhrase': 'test guru api update', 'content': '<p class="ghq-card-content__paragraph" data-ghq-card-content-type="paragraph">placeholder</p><p>test from VS Code</p>', 'verificationInterval': 90}
[Live] response status: 403 body: b''

The rest of it is like irrelevant as they’re exceptions related to parsing the JSON object in the response which seems to come in as what simplejson considers invalid probably due to the b’’ literal.


For context, we want to update Guru cards based on Slack messages relevant to specific product areas, so we really just need the content to go from Slack and into a Guru card, ideally with little to no intervention from ICs, but at most, a reaction emoji would trigger the update.


I also wanted to point out a few gotchas with this code in case someone else wants to try it:


  • g.find_cards() returns a list, you must select an element from it, which hopefully is the only card that matches your query so g.find_cards()[0] for instance.
  • For our use case, we know the card id, so g.get_card("[id]") works better.
  • There’s an SDK reference in Github that’s useful.

Hoping to hear from you, and open to suggestions as well. Thanks!

Userlevel 3

Hey @Joe Duffy ! My colleague @Joshua Ordehi  made a reply to this post some time ago and it’s not been approved yet. is that something you can do on your end? Thanks!

Userlevel 3

Thanks for flagging, @Siobhan Hyser!

@Joshua Ordehi - you are totally right in that Zapier can’t execute python against external packages - sorry for leading you down that path!

You may need to run this outside of Zapier in that case then. You should be able to install the Guru SDK on your local machine (or on the server that the code will be running) by following this documentation. Have you tried this and are still getting errors?

Also - thank you for providing those additional tips!


Hey @Joe Duffy thanks for the update!

Silly me, I didn’t realize I had to update the call to card.patch() you shared. It takes one argument keep_verification which expects a boolean.

The details are on the reference:

It’s working after I pass in what seems to be a required argument, so card.patch(keep_verification=False)


Hey @Joshua Ordehi I’m trying to do this now as well - would you be able to share the final code you ended up using, that made this zap work?


(emoji reaction in slack → update a card with the slack message)

Hey @Elena Weissmann, sorry for the late response, I’ve been on leave.

We ended up scrapping this project, so I only got it to work without much embellishment or optimization. It’s a bit convoluted for my liking, but I’ll share what I can here in case it helps your team or anyone else in the future.

Here’s how it works:

Step 1 - Getting and categorizing updates

  • We have a Google Sheet that stores historic updates that we captured from Slack. We use a Zap to get the messages from Slack when someone reacts to it with a custom emoji.
  • We categorize updates either by using the aforementioned Slack Emoji, or doing some simple tf-idf matching for keywords in the text.
  • In the Google Sheet, we also store Guru Card IDs for cards that correspond with the categories we want to store updates for, i.e. Heatmaps, Recordings, etc. it looks like this:
    • The Markup column stores all of the content for that card (all updates from that category live under that card), when we add to it, we update the card with that ID with the content on the corresponding markup row which should always be the most up-to-date.


Step 2 - Updating cards in Guru

You get Slack messages as plain text, and you must convert that to some form of HTML/Markdown so that the Guru card looks right, this is what I’m doing in the following code:


const NEWLINE = "\n";const IDTEMP = 'id="$$"';const CLASSTEMP = 'class="$$"';const STYLETEMP = 'style="$$"';const DATATEMP = 'data-url="$$"';const PLACEHOLDER = "$$";const NOT_PRESENT = "";const EMPTY_TEMPLATE = () => ``;const PARA = "p";const UPDATE = "update";const ORIGINAL = "original";const THREAD = "thread";const REPLY = "reply";const ELEMENTS = {  update: {    tag: "article",    name: "update",    styles: "",  },  original: {    tag: "div",    name: "original",    styles:      "background-color:#ABC;color:#000;max-width:800px;padding:.1em 1em;margin-bottom:1em;",  },  thread: {    tag: "ul",    name: "thread",    styles: "list-style-type:none;max-width:600px;",  },  reply: {    tag: "li",    name: "reply",    styles: "padding:.5em;background-color:#fca311;color:#fff;margin:.5em;",  },  p: {    tag: "p",    name: "",    styles: "",  },};function insert(text, template = CLASSTEMP) {  return template.replace(PLACEHOLDER, text);}function createTag(isOpen = true, idStr, tagName, classStr, styleStr) {  return `<${    isOpen ? `${tagName} ${idStr} ${classStr} ${styleStr}` : `/${tagName}`  }>`;}function createAnchor(url, text) {  return `<a href="${url}" style="cursor:pointer;">${text}</a>`;}function createNode(content, id, template) {  let { tag, name, styles } = ELEMENTS[template];  let idStr = !!id ? insert(id, IDTEMP) : NOT_PRESENT;  let classStr = !!name ? insert(name, CLASSTEMP) : NOT_PRESENT;  let styleStr = !!styles ? insert(styles, STYLETEMP) : NOT_PRESENT;  return (    createTag(true, idStr, tag, classStr, styleStr) +    content +    createTag(false, NOT_PRESENT, tag)  );}function splitLines(text) {  return text.split(NEWLINE);}function buildParas(contentArray) {  return contentArray.reduce((html, paragraph) => {    html += createNode(paragraph, NOT_PRESENT, PARA);    return html;  }, EMPTY_TEMPLATE());}function buildUpdates(updateObj) {  return Object.keys(updateObj).reduce((html, id) => {    let { original } = updateObj[id];    let paras = buildParas(splitLines(original[id]["message"]));    let anchor = `<p>${createAnchor(original[id]["url"], "Slack URL")}</p>`;    let originalContainer = createNode(paras + anchor, id, ORIGINAL);    // let threadContainer = buildThread(thread, THREAD);    // let origNThread = originalContainer + threadContainer;    html += createNode(originalContainer, id, UPDATE);    return html;  }, EMPTY_TEMPLATE());}function buildUpdateObject(pid, message, url) {  let update = {};  update[pid] = { original: {} };  update[pid].original[pid] = { message: message, url: url };  return update;}let update = buildUpdateObject(, inputData.message, inputData.url);let injectThis = buildUpdates(update);output = [{ injectThis: injectThis, update: update }];



After the content is parsed, I store it in the Google Sheet I showed above under the markup column (this uses a Zapier step that updates the Google Sheet).

Then, I have another Zapier step that updates the card in Guru with this code:



const KEY = // you need your Guru API key here which you could get from a Zapier storage step or similar

function guruWrapper(content) {  let escapedContent = encodeURIComponent(content);  return `<div class="ghq-card-content__markdown" data-ghq-card-content-type="MARKDOWN" data-ghq-card-content-markdown-content="${escapedContent}">${content}</div>`;}function makeCardURL(cardId) {  return "" + cardId + "/extended";}async function updateCard(cardId, cardTitle, newContent) {  const UPDATE_OPTIONS = {    method: "PUT",    headers: {      Accept: "application/json",      "Content-Type": "application/json",      Authorization: KEY,    },    body: JSON.stringify({      preferredPhrase: cardTitle,      shareStatus: "TEAM",      suppressVerification: true,      content: newContent,      verificationState: "NEEDS_VERIFICATION",    }),  };  await fetch(makeCardURL(cardId), UPDATE_OPTIONS)    .then((response) => response.json())    .then((response) => console.log(response))    .catch((err) => console.error(err));}let contentToWrite = guruWrapper(inputData.markup);let result = await updateCard(inputData.cardId, inputData.cardTitle, contentToWrite);output = [{ result: result, content: contentToWrite }];


Note that there’s some input data there: `cardId`, `cardTitle`, `markup` which all come from the Google Sheet I mentioned earlier.

I didn’t have time to go into more detail about this, so I shared the parts that took me the most to figure out and relate to how you actually get the date into Guru, but let me know if you have any questions.