Live Apps for Quip

Make Quip’s customers your customers.

Introduction

The Quip Live Apps API enables developers to extend the Quip document canvas with interactive, custom components. The goal of the platform is to expand the scope and capabilities of Quip's living documents:

  • Enable rich, visual forms of communication within documents
  • Enable end users to transform documents into tools for custom and complex workflows
  • Enable Quip documents to deeply integrate with third party services

Why Build on Quip?

We've spent years building a product that's changing the face of productivity and collaboration. Now, we're turning Quip into a platform where developers can integrate solutions directly into the Quip canvas. Apps work cross-platform and inherit collaboration and social features as well as notification and offline capabilities.

Getting Started

This tutorial will walk you through creating a Hello World app.

Prerequisites

  1. You must have a Quip account and be a member of a Quip site.
  2. Make sure you have Node >= v6 and NPM >= v3 installed.
  3. Run npm install -g create-quip-app in your terminal.
    Depending on how you installed node you may need to run it as root with:
    sudo npm install -g create-quip-app.
  4. You need to flip a flag in your browser to allow self-signed certificates on localhost for HTTPS:
    1. In Chrome, copy this URL and open it in a new tab:
      chrome://flags/#allow-insecure-localhost
      Then click "Enable" and click the "Relaunch" button at the bottom of the page.
    2. In Firefox and Safari, go to https://localhost:8888, then click "Advanced → Add Exception → Confirm Security Exception".

Create Your App

  1. First you need to create your app. Go to the Quip Developer Console and click "Create a Live App".
  2. Make note of your app ID.
  3. Run create-quip-app my-app in your terminal. This will create a directory called my-app inside the current folder. Inside that directory, it will generate the initial project structure:
    my-app
    ├── package.json
    ├── node_modules
    ├── webpack.config.js
    ├── app
    │   └── manifest.json
    └── src
        └── App.less
        └── App.jsx
        └── root.jsx
  4. Run cd my-app in your terminal.
  5. Open the file app/manifest.json in your editor.
  6. Paste in your app ID.
  7. Give your app the name Hello World.
    This is what users will see as the title of your app and the key they'll use to insert your app into their documents.
  8. Save your changes to the app/manifest.json file.

Your app is now ready for deployment!

Deploy Your App

After this step, your app will be live in a Quip document.

  1. Make sure you are in the my-app directory.
  2. Run npm run build in your terminal.
  3. Click the "Upload app.ele" button in the Quip Developer Console.
  4. Choose the app/app.ele file then click the "Update" button.
    You should now see "App deployed successfully" at the top of the page.
  5. In a new browser tab, open quip.com and create a new document.
  6. Type @Hello World in the document (or @ followed by your app's name from app/manifest.json). You should see your app listed in the insert menu.
  7. Hit ENTER to insert your app into the document.
  8. You should now see your app (with the text "Hello, world!") rendered into the Quip document!

Develop Your App

You've deployed your first app! The next step is iterating on your app locally to make it better. To do that, you're going to run a local webserver and connect your document's app to it. Then, you can edit your code and just reload your app to pick up changes.

  1. Run npm start in your terminal in your my-app directory.
    • Run this command whenever you work on your app, then leave it running continuously — it will automatically watch for code changes and update the compiled files as you edit.
    • If you have a syntax error in your code, it will show up here.
      This uses webpack to compile your .js/.jsx and .less files into a single .js file and .css file in my-app/app/dist.
  2. In your Quip document, focus your app by clicking on it. Your app's "Hello World" menu button will appear. Click "Hello World → Use Local Resources" in the Developer section of that menu.
    This will save a setting so that your app will load the code from the local server you're running with npm start on https://localhost:8888/.
  3. Now, reloading your app via "Hello World → Reload" will pick up any local changes you make without requiring you to upload a new app.ele to Quip!
    • Note if you make any changes to the manifest.json file, you will need to re-run npm run build and then upload your app.ele file to get those updates.
  4. Now open my-app/src/App.jsx in your editor, find the text "Hello, world!", and change it to something else:
    import Styles from "./App.less";
    export default class App extends React.Component {
        render() {
            return <div className={Styles.hello}>YES it worked!</div>;
        }
    }
  5. Click Reload again from your app's menu button and your new text should appear!
  6. Not seeing the updated text? Make sure that npm start is running continuously, and that it's not reporting any errors. Also make sure that "Hello World → Use Local Resources" is checked.

Understand Your App

Let's take a closer look at the code of your first app. The source code lives in the src/ directory, containing these three files:

// root.jsx
import quip from "quip";
import App from "./App.jsx";
quip.apps.initialize({
    initializationCallback: function(rootNode) {
        ReactDOM.render(<App/>, rootNode);
    },
});
// App.jsx
import Styles from "./App.less";
export default class App extends React.Component {
    render() {
        return <div className={Styles.hello}>YES it worked!</div>;
    }
}
// App.less
.hello {
    display: flex;
    justify-content: center;
}

What's going on

  • quip.apps.initialize() is the starting point. By calling it, your app notifies the Quip document that its code has loaded and it is ready for setup.
  • The Quip document then calls back into your app via initializationCallback to notify that setup is complete and all data for your app instance is ready.
  • Your app lives in an iframe — inside there, rootNode is the empty HTML element where your app must live. Your code can add anything to this HTML element, with or without React.
  • This code uses React to render <App/> into the HTML element.
    • Styles.hello refers to the .hello class name imported from App.less.
  • As you develop your app, you'll add new .jsx and .less files to the src/ directory (or subdirectories).

Building a Sticky Note App

Let's make your app into something more exciting — a sticky note app that anyone on the document can edit. You'll see firsthand some of the collaborative abilities of the Quip Live Apps API. We'll also demonstrate how to store real data in an app. First we'll set up the data model, then add a Quip rich text box, and finally tweak the CSS.

Note that the app is not ready until you reach the final step (don't try to reload until then).

The completed source code for this example is here:
github.com/quip/quip-apps/tree/master/examples/sticky-note

Storing Data — The Root Record

All the persistent data for your app lives in objects called Records. Every app automatically has its own RootRecord to start with.

Open src/root.jsx and update the code to access the RootRecord via the apps API:

quip.apps.initialize({
    initializationCallback: function(rootNode) {
        let rootRecord = quip.apps.getRootRecord();
        ReactDOM.render(<App/>, rootNode);
    },
});

Introducing Properties

All app data must live in a property of either the RootRecord or a descendant of the RootRecord. These properties can be primitives (number, string, boolean, etc), RecordList objects, or Record objects with properties of their own.

For this app, we want to set up a property for a special type of record designed to store collaborative text — a RichTextRecord. This property will be defined on the RootRecord.

To start adding properties to the root record, you must first define a custom class that extends quip.apps.RootRecord. Then, you must implement the getProperties() function to define the name and type of each property that can live on the record. The API reference explains more about records, properties, and data.

For now, update the code in root.jsx as follows to set up the property and pass it into <App/>:

class StickyNoteRoot extends quip.apps.RootRecord {
    static getProperties() {
        return {
            stickyNote: quip.apps.RichTextRecord
        };
    }
}
quip.apps.registerClass(StickyNoteRoot, "root");

quip.apps.initialize({
    initializationCallback: function(rootNode, params) {
        let rootRecord = quip.apps.getRootRecord();
        if (params.isCreation) {
            rootRecord.set("stickyNote", {});
        }
        ReactDOM.render(
            <App richTextRecord={rootRecord.get("stickyNote")}/>,
            rootNode);
    },
});

Notice how we first create StickyNoteRoot to extend quip.apps.RootRecord. We then implement getProperties() to define the stickyNote property. We say that the stickyNote property has type RichTextRecord, which we need to store our collaborative text.

Here's a deep dive into the rest of this code. If you'd like, just skim it for now to get your sticky note working first.

Deep Dive: Registering the Record Class

You can see that this code calls quip.apps.registerClass after creating StickyNoteRoot. For every record class you define, you also must call quip.apps.registerClass to inform the Quip API about the record class. This Quip API method takes as parameters the record class and a unique string identifier. The string identifier must not change once set (otherwise existing instances of your app may break). The call must happen before quip.apps.initialize.

You can only ever register one class that extends quip.apps.RootRecord. Once you do so, all calls to quip.apps.getRootRecord() will return instances of your custom root class. Note that more complicated apps will want to define descendant records that also have properties — to do so, just extend quip.apps.Record in the same fashion.

Deep Dive: Creating the Property

Now we're ready to actually create and use the stickyNote property! We create the property by calling rootRecord.set("stickyNote", {}). The first parameter of set is the name of the property. set knows that it should create a new RichTextRecord record for stickyNote because of the definition in getProperties. The second parameter is an object that contains child properties to set on the newly-created RichTextRecord. In this case, we don't have any special properties for stickyNote so we pass an empty object.

Once you set a property, it lives on the record for as long as the app exists in the document. That's why we only call set for stickyNote when the app is first created. We detect creation by checking params.isCreation in quip.apps.initialize, where the params object is passed to our app from the Quip document. When params.isCreation is false, quip.apps.initialize is being called on an existing app. In that case, our set-up code already ran and the stickyNote property already lives on the root record. We shouldn't set it up again since it might now contain data.

Deep Dive: Getting the Property

The last thing we do is call rootRecord.get("stickyNote") to access the RichTextRecord object stored in the property. We pass the result into <App/> as a React prop, ready to use. (If the idea of React props is new to you, the React tutorial will help.)

Adding Rich Text

Now we can add the RichTextBox React component to make the app collaborative!

A RichTextBox is one of the powerful Quip platform building blocks. It seamlessly handles edits from different users and provides all the functionality of the Quip editor, including styling and collaborative features like @mentions.

Adding a RichTextBox is incredibly easy. All we need to do is pass it the RichTextRecord — it handles everything else.

Update your code in App.jsx as follows:

import Styles from "./App.less";
export default class App extends React.Component {
    static propTypes = {
        richTextRecord: React.PropTypes.instanceOf(quip.apps.RichTextRecord).isRequired,
    }

    render() {
        return <div className={Styles.hello}>
            <quip.apps.ui.RichTextBox
                record={this.props.richTextRecord}/>
        </div>;
    }
}

Adding Style

All we need to do now is make the app look like a Sticky Note. Let's size the RichTextBox and add a background color and border — we'll use standard CSS and official Quip Yellow for this:

import Styles from "./App.less";
export default class App extends React.Component {
    static propTypes = {
        richTextRecord: React.PropTypes.instanceOf(
            quip.apps.RichTextRecord).isRequired,
    }

    render() {
        const style = {
            backgroundColor: `${quip.apps.ui.ColorMap.YELLOW.VALUE_LIGHT}`,
            border: `1px solid ${quip.apps.ui.ColorMap.YELLOW.VALUE}`,
            boxShadow: "0 2px 5px 5px rgba(0, 0, 0, 0.1)",
            padding: 10,
        };
        return <div className={Styles.hello} style={style}>
            <quip.apps.ui.RichTextBox
                record={this.props.richTextRecord}
                width={280}
                minHeight={280}
                maxHeight={280}/>
        </div>;
    }
}

You Did It!

Your app is ready! Go to your Quip document from earlier and reload your app (review Develop Your App if needed). You should see a yellow sticky note show up. (If you're stuck, try these debugging pointers or compare against the complete code.)

Check out what your app can do with just one Rich Text Box:

  • Add a sentence and style every word.
  • Try typing @ to mention a person and send them a notification.
  • You could type @ followed by a document title to add a link to it.
  • Record the special day you made this note by typing @Today.

But there's more! First go ahead and deploy your new changes.

Now try for some real-time collaboration. Your changes automatically sync for everyone viewing, without needing to refresh:

  • Share the document with a friend and watch their edits appear.
  • Open the document in another tab, and see changes reflect back in the original tab.
  • Open the document using the Quip app on your phone. Changes show up there too!

Next Steps

There's so much more you can do with the Quip Live Apps API. Read a Recipe, check out an example app, or browse through the full API.

We can't wait to see what you build!

Questions?

Ask our team and the Quip developer community over at the Salesforce Stack Exchange.