Software for the next ten years: formats, lock-in and longevity
How we try to build software that still works in ten years: open file formats, plain text and SQLite, real exports, no account required, single binaries and fewer dependencies.
Most software does not die in a dramatic way. A service shuts down with thirty days' notice. A file format stops opening after an operating system update. A tool still runs, but only if you sign in, and the sign-in server is gone. We want the things we make to avoid those endings, so we think a lot about software longevity: what a tool needs in order to still work, and still be useful, ten years from now.
Nobody can promise ten years. Hardware changes, operating systems drop old features, and companies, including small studios like ours, do not last forever. What you can do is design so that your users are fine if you disappear. That turns out to be a fairly concrete list of decisions, and most of them are cheap if you make them early.
The data should outlive the app
The single most useful rule: the user's data must be readable without our software. If our app vanished tomorrow, could someone open their files with tools that come with any computer?
That points to a small set of formats we reach for first:
- Plain text, including Markdown, for anything a person writes. Text files from the 1970s still open today, and there is no reason to think that will stop.
- CSV and JSON for tables and structured records. Both are dull, well specified and readable by every language and spreadsheet.
- SQLite when the data needs queries, indexes or transactions. A SQLite database is a single file with a documented, stable format. The SQLite developers have said they intend to support it through the year 2050, and the US Library of Congress lists it as a recommended format for storing datasets.
- Standard media formats such as PNG, JPEG and PDF for images and documents, with metadata written in the standard places (EXIF, XMP, IPTC) rather than in a private sidecar database.
A proprietary binary format is sometimes faster or smaller. That can be a fair trade, but only if the format is documented and there is an export to one of the formats above. An undocumented format is a bet that the app will be around to read it.
Exports that actually work
An export button is only useful if the export is complete. We have all seen the version that produces a PDF of the first page, or a CSV that leaves out attachments, tags and dates. That is a gesture, not an export.
A real export gives back everything the user put in, in a format another program can import. The easiest way to make sure of it is to make the export the same shape as the data. If an app already keeps its data as a folder of text files or one SQLite file, the export is a copy. If it keeps data somewhere else, a round trip test helps: export, import into a fresh install, compare. If the round trip loses anything, the export is broken.
This is also the answer to lock-in. People stay with software they like. They should not stay because leaving would cost them their work.
No account, and local first
Every account is a dependency on a server. If the sign-in service is down, or the company behind it is gone, a tool that requires an account stops working, even if everything it does happens on your own machine.
So we start from the question: does this tool need an account at all? For a local file server, a metadata writer or a disk image tool, the honest answer is no. Nothing to sign up for, no licence check that phones home, no trial that expires because a server says so. When a tool does talk to a network service, such as an AI model in the cloud, that should be an option the user chooses, with a local path that keeps working without it.
The broader idea has a name. The Ink & Switch research lab described local-first software in 2019: the primary copy of your data lives on your own device, sync is an addition rather than a requirement, and the app keeps working offline. Sync and collaboration are genuinely useful, and some products need a server. The test is what happens when that server goes away. If the answer is "the user still has their data and a working app", the design is sound.
Binaries that still run
Software also rots from the inside. An app that needs a particular runtime, a particular version of a shared library or a package manager that has since changed its rules can fail to start years later, even though its own code never changed.
A single self-contained binary avoids most of that. It carries its dependencies inside it, so there is nothing to install first and nothing to break underneath it. We write our tools that way, in Rust, and ship one file per platform. That does not make a binary immortal. Operating systems still drop things. macOS removed support for 32-bit apps in 2019, and CPU architectures come and go. But a self-contained binary fails for fewer reasons, and when it does, a rebuild from source is usually all it takes.
Which means the source matters as much as the binary. Keep the build simple enough that someone can reproduce it years later:
- Commit the lockfile, so the exact dependency versions are recorded.
- Pin the toolchain version in the repository, not only in someone's head.
- Prefer fewer, well maintained dependencies over many small ones. Each dependency is a project that might be abandoned, renamed or removed from its registry.
- For long-lived client projects, consider vendoring dependencies into the repository, so the build does not depend on a registry being online.
The install path counts too. A one-line installer that fetches a checksummed file from a stable release URL, like the one we described in curl | sh, done carefully, keeps working as long as the release page does. An installer that pulls from five services is only as durable as the least durable of them.
A documented command line is a contract
Graphical interfaces change with fashion. Command-line interfaces, once people have scripted against them, are effectively a public API. We treat them that way.
That means flags that do not change meaning between versions, exit codes that are documented and consistent, and output meant for machines (JSON, CSV) kept separate from output meant for people. A script someone wrote around one of our tools should still work after an upgrade. When something has to change, the old form should keep working with a warning for a good while before it goes.
A command line also keeps a tool useful in places we never planned for: a cron job, a CI pipeline, a shell loop over ten thousand files. Those uses often last longer than any interface we design.
Licences as a safety net
Open source licences are the last line of defence for longevity. If the code is available under MIT or Apache 2.0, and several of our tools are, then the software can outlive the people who wrote it. Someone else can fix a bug, rebuild it for a new platform or keep a fork going. Nobody has to ask permission, and nobody depends on us still being around.
Not everything we make is open source, and not everything should be. For closed software, the earlier rules carry more weight: open formats, full exports, no required account. For client work, we suggest that the client owns the source and the build instructions from day one, so the software is theirs in practice and not only on paper.
The drawing at the top is a guess about the next ten years, and the dates on it are made up. The shape is not. Plain text, CSV, SQLite and PDF will almost certainly still open in 2035. Plenty of the services around today will not be here. When we choose where a user's data lives, we try to pick from the top half of that drawing, and to make sure anything in the bottom half has an arrow pointing up.