Skip to main content

Guide | Build Multipage Apps

Compose scales easily to support multipage internal tools through two flexible approaches.

  1. Add a navigation pane. Best for linking between top level pages.
  2. Use page.link() to programatically navigate between apps and pass data using query params. Best for linking from top level to details pages.

Most developers will use a combination of both when building!

Approach 1: Navigation pane

The simplest approach is to add a navigation pane to your apps, which is possible in just a couple lines of code.

Navigation panes were introduced in SDK v0.26.0.

import { Compose } from "@composehq/sdk";

// Create a navigation pane.
const nav = new Compose.Navigation(
// list of app routes to include in the navigation pane
["home-page", "settings"],
{
// optional: a URL to a logo image to display in the navigation pane.
logoUrl: "https://composehq.com/dark-logo-with-text.svg",
}
);

const homePage = new Compose.App({
route: "home-page",
handler: () => page.add(() => ui.header("Home Page"));
navigation: nav, // show nav pane on home page
});

const settings = new Compose.App({
route: "settings",
handler: () => page.add(() => ui.header("Settings Page"));
navigation: nav, // show nav pane on settings page
});

For the logo, the easiest approach is to go to your website, right click on your logo, and click "copy link address". Then just paste the link into Compose!

Use page.link() to navigate between routes programatically, and pass data between pages using the params argument.

import { Compose } from "@composehq/sdk"
import { db } from "./database"

const homePage = new Compose.App({
route: "users",
handler: async ({ page, ui }) => {
const users = await db.selectUsers();
page.add(() => ui.table("users", users, {
actions: [
{
label: "View details",
onClick: (user) => page.link("user-details", { params: { id: user.id } })
}
]
})
}
})

const settingsPage = new Compose.App({
route: "user-details",
handler: async ({ page, ui }) => {
const userId = page.params.id;

const user = await db.selectUser(userId);
page.add(() => ui.json(user))

page.add(() =>
ui.button("return-btn", {
onClick: () => page.link("users")
})
)
}
})

Pass data between pages

Data is passed between pages using the params argument, which is then read from the page.params object.

params are turned into query parameters in the URL, which allows them to persist across browser refreshes. This also means that the data will be serialized, so only basic data types should be passed (i.e. no functions or complex objects). A common pattern is to pass ids between pages, which can then be used to fetch data from a database.

import { Compose } from "@composehq/sdk"

const homePage = new Compose.App({
route: "home-page",
handler: async ({ page, ui }) => {
page.add(() => ui.header("Home"))

let email = null;

page.add(() => ui.textInput("email", {
label: "Enter your email",
onEnter: (value) => email = value
}))

page.add(() => ui.button(
"settings-btn",
{
label: "Settings",
onClick: () => page.link("settings-page", {
params: {
email
}
})
}
))
}
})

const settingsPage = new Compose.App({
route: "settings-page",
handler: async ({ page, ui }) => {
const email = page.params.email;

if (email) {
const user = await fetchUser(email);
page.add(() => ui.header(`Settings for ${user.name}`))
} else {
page.add(() => ui.header("Settings"))
}

page.add(() => ui.button(
"return-btn",
{
label: "Return to home",
onClick: () => page.link("home-page")
}
))
}
})

Open pages in new tabs

You can open pages in new tabs by passing newTab: true to page.link():

page.link("settings-page", { newTab: true })

Share multipage apps

note

Sharing apps is available on the Pro plan.

For users with more complex sharing needs, you can add a parentAppRoute property to your app to declare it as a sub-page of another app. Now, anyone who has access to the parent app will also have access to the sub-page.

By default, all apps are available to all users in your organization. Hence, this feature is useful when (a) sharing certain apps with external users, or (b) more complex setups where users in your organization have access only to certain apps based on their assigned permissions.

import { Compose } from "@composehq/sdk"

const homePage = new Compose.App({
route: "home-page",
handler: async ({ page, ui }) => {
// ...
}
})

const settingsPage = new Compose.App({
route: "settings-page",
parentAppRoute: "home-page",
handler: async ({ page, ui }) => {
// ...
}
})

Note: adding this property will also hide the sub-app from the Compose homepage to avoid clutter. You can override this by passing hidden: false to the constructor for the sub-app.