The createPage function allows you to register a custom React page in the Spotify client (via Spicetify).
It handles:
- Mounting/unmounting your React component into Spotify’s DOM.
- Navigating to your page using Spotify’s internal
History. - Returning navigation helpers (
goToPage,goBack) so you can trigger page transitions programmatically.
View `createPage.ts` source
import React, { createElement, type ReactNode } from "react";
import { type Root, createRoot } from "react-dom/client";
type HistoryLocation = {
pathname: string;
};
type PlatformHistory = {
location: HistoryLocation;
entries: HistoryLocation[];
push: (location: string) => void;
listen: (cb: (location: HistoryLocation | undefined) => void) => void;
};
type CreatePageProps = {
pathname: string;
container: ReactNode;
};
const History: PlatformHistory | undefined = Spicetify?.Platform?.History;
const createPage = ({ pathname, container }: CreatePageProps) => {
const urlPathname = `/${pathname}/`;
let lastPageLocation: string | null = null;
const rootId = `root-${pathname}`;
let reactRoot: Root | null = null;
const root = document.createElement("div");
root.id = rootId;
const addToDom = async () => {
const parent = await waitForElement(
".main-view-container__scroll-node div[data-overlayscrollbars-viewport]"
);
if (!parent.querySelector(`#${rootId}`)) {
parent.appendChild(root);
}
};
const mount = async () => {
if (reactRoot) return;
reactRoot = createRoot(root);
reactRoot.render(container);
await addToDom();
};
const unmount = () => {
if (!reactRoot) return;
reactRoot.unmount();
reactRoot = null;
root.remove();
};
const handlePageChange = (currentLocation: HistoryLocation | undefined) => {
if (!History || !currentLocation) return;
const lastEntry = History.entries.at(-2);
lastPageLocation = lastEntry?.pathname ?? "/";
if (currentLocation.pathname === urlPathname) {
void mount();
} else {
unmount();
}
};
handlePageChange(History?.location);
History?.listen(handlePageChange);
const goToPage = () => History?.push(urlPathname);
const goBack = () => History?.push(lastPageLocation ?? "/");
return { goToPage, goBack };
};
function waitForElement(
selector: string,
{ timeout = 3000 }: { timeout?: number } = {}
): Promise<Element> {
const startTime = performance.now();
return new Promise((resolve, reject) => {
function check(): void {
const element = document.querySelector(selector);
if (element) {
resolve(element);
return;
}
if (performance.now() - startTime > timeout) {
reject(null);
console.warn(`Timeout: Could not find element: ${selector}`);
return;
}
requestAnimationFrame(check);
}
check();
});
}const Page = () => <h1>Hello, world!</h1>;
const { goToPage, goBack } = createPage({
pathname: "my-custom-page",
container: <Page />,
});
// Example: open/close buttons
document.body.append(
Object.assign(document.createElement("button"), {
innerText: "Open Page",
onclick: goToPage,
}),
Object.assign(document.createElement("button"), {
innerText: "Close Page",
onclick: goBack,
})
);- Creates a root container (
<div id="root-{pathname}" />) for your page. - Waits for Spotify’s main DOM container (inside
.main-view-container__scroll-node) to become available. - Mounts your React component into that container when the Spotify history matches your custom
pathname. - Unmounts your component when navigating away (preventing memory leaks).
- Provides:
goToPage: Pushes your custompathnameinto Spotify’s history (loads your page).goBack: Pushes the previous history entry (returns to where you were).
pathnameshould be unique (e.g."my-lyrics-viewer-page"), or it may conflict with other pages.- The component is mounted only when the page is active – don’t store state globally in it if you want persistence across navigations.
Note: This documentation was generated with the help of AI—because, well, why not?