JetBrains Space Help

Link Unfurling

By default, after a user posts an external link to a chat channel, Space unfurls the link or, in other words, shows a preview of the page behind the link. Space supports link unfurling not only in chat messages but also in documents, issue descriptions, and commit and code review titles.

In order Space could build a link preview, the page behind the link must meet the following requirements:

  • The page must provide preview info in its social meta tags.

  • The page must be publicly available and doesn't require authentication.

If the page doesn't meet the requirements, Space won't be able to show its preview. For such cases, you can create an application that will authenticate in the corresponding external system, fetch the required content, and provide the content to Space.

Currently, Space supports only attachment previews. An attachment preview is a fragment of page content next to the chat message that contains the link to the page. If the link is a part of a document or issue description, Space shows the preview in the link tooltip. To provide attachment previews, applications require the Unfurl.App.ProvideAttachment permission.

Space unfurling queue

To make unfurling work, Space must somehow notify the application each time a user posts an external link. For every posted link, Space sends a short generic notification to the application and saves link data (a user ID, a link URL, etc.) to a special queue. To get the data on a particular link (i.e., queue item), the application must query the queue with an API call. The lifetime of a queue item is 30 minutes.

This approach reduces network workload and prevents the application from losing the list of not-yet-processed links during temporary outages.

Queue items are instances of the ApplicationUnfurlQueueItem type with the following properties:

  • etag: long is an entity tag that helps the application track its position in the queue. It is a unique number that is increased for each new item. The application must store this number in some persistent storage.

  • id: string is a unique string identifier of the item. Use it to identify the item when calling back to the Space API to provide a content or request authentication.

  • target: string is a URL of the posted link.

  • context: ApplicationUnfurlContext provides inforomation about the exact place in Space where the link was posted: a chat message, a blog article, a document, a code review title, a git commit message, or an issue description.

  • authorUserId: ProfileIdentifier.Id is an ID of the user who posted the link. Returns null if the user cannot be identified, e.g., a user who pushed a commit doesn't belong to the current organization.

When a new item appears in the queue, Space sends a notification with the payload of the NewUnfurlQueueItemsPayload type. The payload itself doesn't contain any information about the item. On receiving the notification, the application must poll the queue with a call to the applications/unfurls/queue Space API endpoint. We also recommend that the application polls the queue right after the start because there's a chance that some queue items were missed while the application was unavailable.

As the queue can contain a lot of items, you should request them in batches. For example, this is how you can get a batch of 100 items starting with the item with etag=50:

import space.jetbrains.api.runtime.resources.applications import space.jetbrains.api.runtime.types.NewUnfurlQueueItemsPayload // variable that stores the etag of the last processed item // in real application, it must be stored in a persistent storage private var lastEtag: Long? = null when (val payload = readPayload(body)) { // ... is NewUnfurlQueueItemsPayload -> { // poll the link queue val queueApi = spaceClient.applications.unfurls.queue // as the queue may contain many items, get items in batches var queueItems = queueApi.getUnfurlQueueItems(lastEtag, batchSize = 100) while (queueItems.isNotEmpty()) { queueItems.forEach { item -> // generate preview for each item // ... } // get ETag of the last processed item lastEtag = queueItems.last().etag queueItems = queueApi.getUnfurlQueueItems(lastEtag, batchSize = 100) } } }
GET Authorization: Bearer <here-goes-auth-token> Accept: application/json

Get an access token from the external system

To build the link preview, the application must fetch the content of the external page behind the link. If this external system requires authentication, the application must first authenticate in the system on behalf of the user who posted the link.

  1. To request user authentication, use the /api/http/applications/unfurls/queue/request-external-auth API call. In the call, the application must provide means for the user to authenticate. For this purpose, you can use the Space message constructor and an instance of the NavigateUrlAction type. This action navigates the user to the specified URL which must be some endpoint of your application.

    For example, this is how you can create a message with a single Authenticate button that leads to https://myapplication.url/oauth?user=space-user-id:

    spaceClient.applications.unfurls.queue.requestExternalSystemAuthentication(, unfurl { section { text("Authenticate in Slack to get link previews in Space") controls { button( "Authenticate", NavigateUrlAction( "https://myapplication.url/oauth?user=$spaceUserId", // BackUrl lets the app return the user back to the // page where they clicked the button withBackUrl = true, openInNewTab = false ) ) } } } )
    POST Authorization: Bearer here-goes-access-token Accept: application/json Content-Type: application/json { "queueItemId": "here-goes-item-id", "message": { "style": "PRIMARY", "sections": [ { "className": "MessageSectionV2", "elements": [ { "className": "MessageControlGroup", "elements": [ { "className": "MessageButton", "text": "", "style": "REGULAR", "action": { "className": "NavigateUrlAction", "url": "https://myapplication.url/oauth?user=here-goes-space-user-id", "withBackUrl": true, "openInNewTab": false } } ] } ] } ] } }
  2. In the application, add an endpoint (in our example it's /oauth) that will handle the authentication call. The endpoint must redirect the user to the external service OAuth endpoint. After the user authenticates in the system, the system must issue an access token for our application.

    The exact implementation of this functionality depends on the OAuth flow implemented in the external system. Therefore, we will not provide any generic instructions in this guide. You can find an example in our tutorial on how to unfurl Slack messages.

Send unfurled content to Space

After the application gets authorized in the external system and fetches the required content, it can prepare and send this content to Space. To do this, send a POST request to the applications/unfurls/queue/content Space API endpoint. The request must contain an instance of the ApplicationUnfurl class with the following data:

  • queueItemID: string is the unfurling queue ID of the original message.

  • content: ApplicationUnfurlContent is the message content. Two types of content are possible:

    • ApplicationUnfurlContent.Message is a message built with the message constructor DSL.

    • ApplicationUnfurlContent.Image is an image. Space downloads and saves the image when processing the preview. Updates of the image on the external server don't update the preview in Space.

For example:

// Message val content: ApplicationUnfurlContent.Message = unfurl { MessageOutlineV2( elements = listOf( MessageIcon( icon = ApiIcon("bug"), style = MessageStyle.PRIMARY ), MessageInlineText( text = "Title goes here", style = null ) ) ) section { MessageSectionV2( elements = listOf( MessageText( accessory = null, style = MessageStyle.PRIMARY, size = null, content = "Here goes unfurled content." ) ), style = null, textSize = null ) } } spaceClient.applications.unfurls.queue.postUnfurlsContent( listOf(ApplicationUnfurl("here-goes-item-id", content)) )
// Image spaceClient.applications.unfurls.queue.postUnfurlsContent( unfurls = listOf(ApplicationUnfurl( // item represents a queue item queueItemId =, content = ApplicationUnfurlContent.Image( icon = null, title = "Title goes here", url = "https://externalservice.url/image.png" ) )) )
# Message POST Authorization: Bearer here-goes-access-token Accept: application/json Content-Type: application/json { "unfurls": [ { "queueItemId": "here-goes-item-id", "content": { "className": "ApplicationUnfurlContent.Message", "style": "PRIMARY", "outline": { "className": "MessageOutlineV2", "elements": [ { "className": "MessageIcon", "icon": { "icon": "bug" }, "style": "PRIMARY" }, { "className": "MessageInlineText", "text": "Title goes here" } ] }, "sections": [ { "className": "MessageSectionV2", "elements": [ { "className": "MessageText", "style": "PRIMARY", "content": "Here goes unfurled content." } ] } ] } } ] }
# Image POST Authorization: Bearer here-goes-access-token Accept: application/json Content-Type: application/json { "unfurls": [ { "queueItemId": "here-goes-item-id", "content": { "className": "ApplicationUnfurlContent.Image", "title": "Title goes here", "url": "https://externalservice.url/image.png" } } ] }

Respond to user actions in previews

If the application uses the message constructor DSL, it can add UI elements (currently, only buttons) to the unfurled content. When a user interacts with these elements (e.g., clicks a button), Space sends a request to the application. The request contains a payload of the UnfurlActionPayload type. The payload content is almost identical to that of MessageActionPayload. The only difference is the action source. Instead of the original message, UnfurlActionPayload provides two properties that point to the origin:

  • link: string is the URL of the unfurled link that provided the triggered action.

  • context: ApplicationUnfurlContext provides inforomation about the exact place in Space where the link was posted: a chat message, a blog article, a document, a code review title, a git commit message, or an issue description. It is the same context as in an unfurling queue item.

Last modified: 19 July 2023