Theatre Script Builder

March 2025 → June 2025

Code source

Theatre Script Builder is a writing tool for the performing arts: composing a complete theatre script, with its dialogue but also its lighting, sound, images and actor movements, then exporting it to PDF for rehearsals.

The project came out of a master's module on communication and team management, where we had to run a collective theatre project. The play never actually made it to the stage, but we worked on the text and ran rehearsals, and I took the chance to build the tool we were missing. It's one of my first genuinely complex projects, and I pushed it a long way before stopping, running into a stack I was discovering almost from scratch.

A theatre script isn't just text

A theatre script is a score. It mixes text to read and speak, technical cues and movements, all in a precise order. That's the starting point, and it shaped the whole data model.

A script holds elements of very different natures:

Element What it carries
Dialogue Text, attached to a character
Narration Read-out text, optionally attributed to a character
Lighting A position on stage, a colour, an on/off state
Sound A file, a URL or a YouTube video, with timecode, duration and a stop marker
Image A projected image, with caption and dimensions
Staging A set element (a curtain), its position, a description
Movement An actor moving from one point to another

Each has its own fields, yet all must live in the same ordered list, because on stage everything unfolds in a single order. A lighting cue slots between two lines, a sound fires during a movement.

The solution is a ScriptItem entity carrying order and type, paired with a specialised table per element nature. The sequence is homogeneous, the data isn't. That's what lets you insert any type of element at any position without the model objecting.

Characters add a nuance I wouldn't have thought of on my own: each has a real name and a stage name. In rehearsal you call people by their real name, but the script talks about the characters. The tool shows both, side by side, so everyone can follow.

Reordering, over and over

Writing a script is mostly moving things around. So the interface rests on drag and drop, using dnd-kit, chosen for its accessibility: reordering also works from the keyboard, which matters for a tool you use for long stretches.

It's simple with ten elements. Much less so with five hundred. Our play's test script had close to seven hundred, and that's the scale where things get tricky: every move has to renumber positions and reflect on screen without slowing everything down. I ended up memoising element rendering and batching position updates, but it's the kind of problem I hadn't anticipated at the start.

Saving, my real wall

Persistence is where I learned the most, discovering everything at once: Next.js App Router, Server Actions, Prisma, a serverless Postgres database on Neon.

The idea is that you edit a script freely, then save, with an indicator flagging pending changes. A Zustand store holds the client-side state, Server Actions write to the database. On paper it's clean; in practice I spent a lot of time on edge cases, and above all on volume. Saving a large script triggered transactions so long that I had to push their timeout to several minutes.

Part of the problem came from a choice I'd make differently today: imported images and sounds were stored directly in the database, base64-encoded. Convenient at first, since everything fits in one table, but each media element is heavy and drags down every operation around it. On an image-heavy script, the database balloons fast.

Built to be used by several people, and fast

The project was collective, and the tool had to keep up. Two requirements shaped the interface. First, speed: in rehearsal you change a script constantly, so adding an element, moving it or fixing it had to happen without thinking about the mechanics. Second, sharing: a script doesn't belong to one person. There are no accounts, each script lives at its own URL that the whole team could open.

JSON export and import played a double role, backup and exchange. You export a script, keep a copy, reimport it elsewhere. On a project where the file represents hours of collective work, being able to make a backup you control mattered as much as the rest.

PDF export

PDF export wasn't a bonus, it was the whole point. In rehearsal nobody works in front of a screen: you have the script on paper, annotated, dog-eared. Until the tool produced a clean printable document, it wasn't much use.

Export generates a client-side PDF with react-pdf, composing the document like a React interface. The difficulty isn't technical but typographic. A theatre script has layout conventions: where the character name sits, how stage directions are indented, how technical cues are set apart from spoken text. Reproducing those conventions means first understanding them, which forced me to study how real scripts are laid out.

What I took from it

This is the project that moved me up a level. Before it, I'd mostly written scripts and small tools; here I built a real full-stack application, with a database, a rich interface and a stack I didn't know. I struggled a lot, with saving, with sorting, with large volumes, and those struggles are exactly what made me progress.

It also taught me to listen to a trade I didn't know. The decisions that matter here, the single order, the split between technical and spoken, the real name next to the stage name, the paper export, don't come from engineering considerations. They come from how people work in rehearsal.

The project stops at the prototype stage, since the play was never staged, but it served us while we rehearsed, and it's the one I learned the most from as a developer at that point.

Technologies used