Linear Integration

Note: Retool currently only supports connecting to Linear's API via our GraphQL API integration.

Connecting to the Linear API

Retool can connect to Linear using GraphQL via their API. When doing so you can choose to authenticate either using OAuth 2.0 or a personal API key. If you want to restrict access given through the resource you’ll need to use OAuth 2.0, a resource set up with a personal API key will automatically have the same permissions as the user who generated the key.
To get started you’ll need to create a new GraphQL resource in Retool. This can be done by navigating to the Resource page clicking the Create new button in the top right and selecting GraphQL.

Selecting a GraphQL resourceSelecting a GraphQL resource

Selecting a GraphQL resource

For the base URL you’ll use Linear's GraphQL endpoint: https://api.linear.app/graphql

And then follow the instructions in either the Personal API Key, or OAuth 2.0 section depending on the authorization you’d like to use. (Note: Linear’s GraphQL API supports introspection so it does not have to be disabled.)

Shared settings for configuring a Linear resourceShared settings for configuring a Linear resource

Shared settings for configuring a Linear resource

Personal API Key

In order to authenticate using a personal API key you’ll need an account with the appropriate permissions on your workspace to generate the key. All requests made to the API will be seen as actions taken by this account.

Once you have the personal key you’ll enter it as an Authorization header and be good to go!

Linear resource set up using a personal API keyLinear resource set up using a personal API key

Linear resource set up using a personal API key

You can generate a personal key in the API tab of the settings section in said account on Linear.

Linear > Account Menu > Settings > API > Personal API Keys > Create Key

Accessing the settings menu in the linear consoleAccessing the settings menu in the linear console

Accessing the settings menu in the linear console

Menu for managing api authenticationMenu for managing api authentication

Menu for managing api authentication

In order to create a key you’ll need to give it a label and once you do you’ll only be able to see it until you navigate away from, or reload, the page (you can generate multiple keys, but nevertheless it’s good to keep yours in a safe place).

View right after creating api keyView right after creating api key

View right after creating api key

With this key in hand head back to your resource setup in Retool and enter it as an Authorization header and you're good to go!

OAuth 2.0

Start by setting authentication to OAuth 2.0 as shown below and described here.

Linear resource set up with OAuth 2.0 selected for authenticationLinear resource set up with OAuth 2.0 selected for authentication

Linear resource set up with OAuth 2.0 selected for authentication

Important things to note are that

  • the base URL is set to Linear’s GraphQL endpoint
  • the authorization header must be set exactly to Bearer OAUTH2_TOKEN
  • authentication is set to OAuth 2.0.

You’ll then configure your OAuth 2.0 setup as follows. Note you’ll either need access to an existing OAuth2 app in linear or the ability to create a new one (the configuration of the app is covered below).

OAuth2 set up for linear in retool with example scopesOAuth2 set up for linear in retool with example scopes

OAuth2 set up for linear in retool with example scopes

Important things to note here are

  • OAuth callback URL - gets copied into your Linear OAuth2 app
  • Authorization URL - should always be https://linear.app/oauth/authorize
  • Access Token URL - should always be https://api.linear.app/oauth/token
  • Client ID - copied from your Linear OAuth2 app
  • Client Secret - copied from your Linear OAuth2 app
  • Scopes - Linear provides 5 optional scopes for OAuth access that are passed as a comma separated list
    • read - (Default) Read access for the user's account. This scope will always be present.
    • write - Write access for the user's account. If your application only needs to create comments, use a more targeted scope
    • issues:create - Allows creating new issues and their attachments
    • comments:create - Allows creating new issue comments
    • admin - Full access to admin level endpoints. You should never ask for this permission unless it's absolutely needed

📘

Note

Retool verifies OAuth authentication by checking if anything other than a 20x code is returned. Unfortunately, Linear will always return a 20x code regardless of whether or not a user is properly authenticated so we ignore this option for this setup.

In order to either access an existing OAuth app or create a new one start by navigating to the account settings section in Linear.

Linear > Account Menu > Settings > API > Your Applications

Menu for accessing existing OAuth2 apps and creating new onesMenu for accessing existing OAuth2 apps and creating new ones

Menu for accessing existing OAuth2 apps and creating new ones

Once you’ve either accessed an existing app or created your own you’ll want to

  • copy the Client id and Client secret from Linear to your resource setup in Retool
  • copy the OAuth callback URL from your resource setup in Retool to the Callback URLs field in Linear

The rest of the fields can be configured as you wish.

OAuth2 app set up with client id/secret and callback URLs highlightedOAuth2 app set up with client id/secret and callback URLs highlighted

OAuth2 app set up with client id/secret and callback URLs highlighted

Writing a Query

Basic Read/Write Query

Since the Linear GraphQL API supports introspection, it’s possible to use Retool’s schema browser to examine the structure of your database. An example of a read query is given below with the schema exposed (note that there are many more possible fields to query than shown here).

Querying Linear for issuesQuerying Linear for issues

Querying Linear for issues

Data from this query is then returned in the following format

Structure of the data property in a sample GraphQL queryStructure of the data property in a sample GraphQL query

Structure of the data property in a sample GraphQL query

Writing to GraphQL is done through mutations that are passed variables using the $variable_name : Type syntax. Unlike with some other queries values aren’t passed directly to the query with {{}} flags, instead declaring a variable within a GraphQL query generates a field in the GUI in which a value can be passed.

Mutation with variable fields shown in the GUIMutation with variable fields shown in the GUI

Mutation with variable fields shown in the GUI

Setting Up Server-Side Pagination

Relay Cursor Based Pagination in Retool

The Linear GraphQL database is compatible with relay cursor based pagination. Pagination can happen either forward or backward, in either case, we’ll need to pass a specifically named variable for the cursor and the number of records to pull.

  • Forward - after (cursor) / first (number of records)
  • Backward - before(cursor) / last (number of records)
    In the case of forward pagination the query will pull the first n records after the cursor, and when backwards paginating the query pulls the last n records before the cursor.

When selecting relay cursor based pagination in the table component the table checks for three values

  • Previous page cursor
  • Next page cursor
  • Whether or not there is more data to load

And returns three addition values

  • beforeCursor
  • afterCursor
  • pageSize

When paging forward, the value of afterCursor will be set to whatever is currently in Next page cursor and the value of beforeCursor will be set to null. Similarly, when paging forward, the beforeCursor is set to the value of Previous page cursor and afterCursor is set to null. The two values Previous and Next page cursor are the cursor values for the records at, respectively, the top and bottom of the current page. The value pageSize corresponds to the number of records in a page and so gets passed to both first and last. The table also checks the value Whether or not there is more data to load to determine whether or not to allow continued pagination in the current direction.

Passing beforeCursor and afterCursor as inputs to the pagination query and setting it to run on input change ensures that it runs on every page change.

Pagination with Linear

When using cursor based pagination in Linear we’ll want to query for issues for edges as they contain cursor data as well as the node itself. Additionally, Linear lets us query the following values from pageInfo to aid with pagination:

  • hasNextPage
  • hasPreviousPage
  • startCursor
  • endCursor

We can therefore write our pagination query as follows:

query($last: Int, $before_cursor: String, $first: Int, $after_cursor: String){
  issues(last: $last, before: $before_cursor, first: $first, after: $after_cursor){
    edges{
      cursor
      node{
        ...nodeFields #pass your own fields here!
      }
    }
    pageInfo{
      hasNextPage
      hasPreviousPage
      startCursor
      endCursor
    }
  }  
}

The Linear GraphQL API will throw an error when trying to pass any variable from first/after with any variable from last/before, however, so when using one pair the values for the other pair need to be passed as undefined. We can do this as follows:

Expressions for last, before_cursor, first, and after_cursorExpressions for last, before_cursor, first, and after_cursor

Expressions for last, before_cursor, first, and after_cursor

Note: The table loads with both beforeCursor and afterCursor set to null and we’ve set up the ternary expressions this way so that the table defaults to loading the first page of data. If first is passed without an afterCursor it begins at the start of the table, similarly if last is passed with a beforeCursor it begins at the end.

From here the nodes can be passed as data to the table, and the values from pageInfo to the three values that the table checks for to handle pagination. However! When paginating backwards the Linear GraphQL API also returns nodes in reverse order, additionally startCursor will correspond with node closest to the end of the table and endCursor with the node closest to the front.

In order to remedy this, we’ll set up a transformer to ensure the values get passed correctly.

const backwardPaginated = {{!!paginatedTable.beforeCursor}}
const {pageInfo, edges} = data.issues
const nodes = edges.map(({node}) => node);

const results = backwardPaginated
        ? {
            nodes: nodes.reverse(), 
            prevPageCursor: pageInfo.endCursor, 
            nextPageCursor: pageInfo.startCursor, 
            moreToLoad: pageInfo.hasNextPage
        }
        : {
            nodes, 
            prevPageCursor: pageInfo.startCursor, 
            nextPageCursor: pageInfo.endCursor, 
            moreToLoad: pageInfo.hasPreviousPage
        }
        
return results

And then pass values to the table as follows:

Passing Previous page cursor, Next page cursor, and Whether or not there is more data to load to tablePassing Previous page cursor, Next page cursor, and Whether or not there is more data to load to table

Passing Previous page cursor, Next page cursor, and Whether or not there is more data to load to table

From there we're good to go! A full screenshot of the setup is shown below:

Full set up for table with server side pagination queryFull set up for table with server side pagination query

Full set up for table with server side pagination query


Did this page help you?