stayput-mcp
<div align="center">
<a href="https://stayput.dev"><img src="public/favicon.svg" width="72" height="72" alt="Stayput logo"></a>
# Stayput
**Your files stay put.**
Free, open-source tools for images, PDFs, video and audio that run entirely in your browser.<br>
Your files are not uploaded, there are no accounts or file size caps beyond what your device can hold, and it works offline after one visit (the AI-model tools need one online run to download their model).
**[Open stayput.dev](https://stayput.dev)** · [Tools](#tools) · [Verify the claim](#open-the-network-tab-it-stays-empty) · [How it works](#how-it-works) · [MCP server](#mcp-server) · [Privacy](https://stayput.dev/privacy)
[](https://github.com/keenanlk/stayput/actions/workflows/ci.yml)
[](LICENSE)
[](#how-it-works)
<a href="https://stayput.dev"><img src="docs/readme/home.png" width="800" alt="The Stayput home page: Your files stay put. Convert, compress and clean images, PDFs, video and audio right here in your browser."></a>
</div>
Convert HEIC photos from an iPhone, merge, split, sign and compress PDFs, shrink images and strip GPS and EXIF data. Every tool is a static page and a bit of JavaScript and WebAssembly that does the work in your tab. There is no server that could receive your files.
## Open the network tab. Your files never appear in it.
That is the whole pitch, and you can check it in three ways:
1. **Watch the network.** Open your browser's developer tools (F12, or Cmd-Option-I on a Mac), pick the Network tab, open any tool and drop a file. You will see requests for the page's own code, the site's own decoder files on the pages that need them, and one anonymous page count. You will never see a request carrying your file, because there is nowhere for it to go. Every tool page also counts its own requests after you add files and shows you the list.
2. **Turn the network off.** Load a tool, switch to airplane mode, and use it. It keeps working, because after one visit the site is cached by a service worker and the work happens in your tab.
3. **Read the code.** This repository is the site. It builds to static HTML, CSS and JavaScript with no backend; the deploy has no server-side code at all. The [privacy page](https://stayput.dev/privacy) lists every request the site makes.
<p align="center"><img src="docs/readme/compress-image.png" width="640" alt="Compress Images: three 1.2 MB phone photos shrunk to about 190 KB each in the browser, with the panel reading: Open the network tab. Your files never appear in it."></p>
## Tools
| Images | PDFs |
| --- | --- |
| [HEIC to JPG or PNG](https://stayput.dev/tools/heic-to-jpg) (batch, keep or drop EXIF) | [Merge PDF](https://stayput.dev/tools/merge-pdf) |
| [Convert images](https://stayput.dev/tools/convert-image) (PNG, JPG, WebP, AVIF, JPEG XL, HEIC, SVG in; JPG, PNG, WebP out) | [Split PDF / extract pages](https://stayput.dev/tools/split-pdf) |
| [Compress and resize images](https://stayput.dev/tools/compress-image) (plus [Compress PNG](https://stayput.dev/tools/compress-png), transparency kept, [Compress GIF](https://stayput.dev/tools/compress-gif), [Resize GIF](https://stayput.dev/tools/resize-gif) and [Crop GIF](https://stayput.dev/tools/crop-gif)) | [Compress PDF](https://stayput.dev/tools/compress-pdf) (lossless cleanup, image recompression, or flatten) |
| [Remove EXIF and GPS data](https://stayput.dev/tools/strip-exif) (image data unchanged; sideways JPGs re-encoded only with Apply orientation on) | [Rotate PDF pages](https://stayput.dev/tools/rotate-pdf) |
| [Crop image](https://stayput.dev/tools/crop-image) (ratios, exact pixels, circle) | [Images to PDF](https://stayput.dev/tools/image-to-pdf) |
| [EXIF viewer](https://stayput.dev/tools/exif-viewer) (location, camera, date and every field) | [PDF to images](https://stayput.dev/tools/pdf-to-image) (PNG, JPG or one multi-page TIFF) |
| [Video to GIF](https://stayput.dev/tools/video-to-gif) (MP4, MOV, WebM; trim, size, frame rate) | [Reorder and delete pages](https://stayput.dev/tools/reorder-pdf) |
| [Blur or pixelate image](https://stayput.dev/tools/blur-image) (faces, plates, text; blur, pixelate or black box) | [Sign PDF](https://stayput.dev/tools/sign-pdf) (draw or type, place on any page) |
| [Rotate or flip image](https://stayput.dev/tools/rotate-image) (quarter turns, mirror, batch) | [Add page numbers](https://stayput.dev/tools/pdf-page-numbers) |
| [Video or audio to MP3](https://stayput.dev/tools/video-to-mp3) (MP4, MOV, M4A, WAV; or to WAV) | [PDF to Word or text](https://stayput.dev/tools/pdf-to-word) (paragraphs and headings, not layout) |
| [Image to text (OCR)](https://stayput.dev/tools/image-to-text) (photos, screenshots, scans; copy or .txt) | [Unlock PDF](https://stayput.dev/tools/unlock-pdf) (remove a password you know, or print/copy restrictions) |
| [Color picker from image](https://stayput.dev/tools/color-picker) (HEX, RGB, HSL; main-colour palette) | [Password protect PDF](https://stayput.dev/tools/protect-pdf) (AES-256) |
| [GIF to MP4](https://stayput.dev/tools/gif-to-mp4) (animated GIF to video, up to 90% smaller) | [Extract images from PDF](https://stayput.dev/tools/extract-pdf-images) (every picture at its stored size) |
| [Compress video](https://stayput.dev/tools/compress-video) (MP4, MOV, WebM; fit 10 MB for Discord or 25 MB for email) | |
| [Video to MP4](https://stayput.dev/tools/video-to-mp4) (MOV, MKV, WebM; H.264 copied with no quality loss) | |
| [Video to MOV](https://stayput.dev/tools/video-to-mov) (MP4, WebM, MKV; H.264 and AAC copied with no quality loss) | |
| [Trim video](https://stayput.dev/tools/trim-video) (cut a clip without re-encoding) | |
| [Video splitter](https://stayput.dev/tools/video-splitter) (cut one video into parts at times, every N seconds or into equal parts, in one zip) | |
| [Mute video](https://stayput.dev/tools/mute-video) (remove the sound, picture copied untouched) | |
| [Resize video](https://stayput.dev/tools/resize-video) (1080p, 720p, custom size) | |
| [Rotate video](https://stayput.dev/tools/rotate-video) (90°, 180°, flip) | |
| [Crop video](https://stayput.dev/tools/crop-video) (any box, 9:16, 1:1) | |
| [Change video speed](https://stayput.dev/tools/video-speed) (0.25× to 8×, pitch kept) | |
| [Merge videos](https://stayput.dev/tools/merge-videos) (join clips into one MP4) | |
| [Side by side video](https://stayput.dev/tools/side-by-side-video) (two clips left and right or top and bottom in one MP4, with the sound you choose) | |
| [Add audio to video](https://stayput.dev/tools/add-audio-to-video) (music or voice-over, picture copied untouched) | |
| [Reverse video](https://stayput.dev/tools/reverse-video) (play a clip backwards) | |
| [Video to JPG](https://stayput.dev/tools/video-to-jpg) (save frames as JPG or PNG) | |
| [Trim audio](https://stayput.dev/tools/trim-audio) (cut MP3, WAV, M4A with a waveform) | |
| [Audio splitter](https://stayput.dev/tools/audio-splitter) (split an MP3 or WAV into parts at times or every N minutes) | |
| [Screen recorder](https://stayput.dev/tools/screen-recorder) (screen, window or tab, with sound and microphone) | |
| [Collage maker](https://stayput.dev/tools/collage-maker) (grids, side by side, stitched screenshots) | |
| [Split image](https://stayput.dev/tools/split-image) (Instagram grids and carousels) | |
| [Add text to image](https://stayput.dev/tools/add-text-to-image) (captions, memes, watermarks) | [Add text to GIF](https://stayput.dev/tools/add-text-to-gif) (a caption on every frame) |
| [AI image upscaler](https://stayput.dev/tools/upscale-image) (2×, 3× or 4× with Real-ESRGAN, on the device) | |
| [Remove object from photo](https://stayput.dev/tools/remove-object) (paint over it, MI-GAN fills it in on the device) | |
| [Adjust photo](https://stayput.dev/tools/adjust-image) (brightness, contrast, saturation, warmth, sharpen, invert) | |
| [Emote resizer](https://stayput.dev/tools/emote-resizer) (Twitch emotes and sub badges, Discord emoji and stickers, Slack emoji) | |
| [Image to ASCII art](https://stayput.dev/tools/image-to-ascii) (width, character sets, invert, colour PNG, .txt) | |
| [Pixel art converter](https://stayput.dev/tools/pixel-art-converter) (auto or retro palettes, dithering, grid) | |
| [Photo to sketch](https://stayput.dev/tools/photo-to-sketch) (pencil, charcoal, coloured pencil) | |
| [Add border to image](https://stayput.dev/tools/add-border-to-image) (border colour and width, rounded corners) | |
| [Compress audio](https://stayput.dev/tools/compress-audio) (smaller MP3 or OGG, or fit a size limit) | |
| [Volume booster](https://stayput.dev/tools/volume-booster) (louder, quieter or normalized audio and video) | |
| [Pitch and speed changer](https://stayput.dev/tools/pitch-changer) (change key by semitones, speed up or slow down) | |
| [BPM and key finder](https://stayput.dev/tools/bpm-key-finder) (tempo, key and Camelot code from an audio file) | |
| [Audio converter](https://stayput.dev/tools/audio-converter) (MP3, WAV, FLAC, M4A, OGG) | |
| [QR code generator](https://stayput.dev/tools/qr-code-generator) (link, Wi-Fi, contact, email, phone; PNG or SVG) | |
| [Barcode generator](https://stayput.dev/tools/barcode-generator) (Code 128, Code 39, UPC, EAN, ITF-14; PNG, SVG or PDF) | |
| [Signature maker](https://stayput.dev/tools/signature-maker) (draw, type or photo; transparent PNG, SVG) | |
| [QR code scanner](https://stayput.dev/tools/qr-code-scanner) (image, paste, drop or camera; links are shown, never opened) | |
| [Voice recorder](https://stayput.dev/tools/voice-recorder) (microphone to MP3, WAV or M4A) | |
| [Merge audio](https://stayput.dev/tools/merge-audio) (join MP3, WAV, M4A with silence or crossfades) | |
| [Metronome](https://stayput.dev/tools/metronome) (any time signature, subdivisions, tap tempo) | |
| [GIF maker](https://stayput.dev/tools/gif-maker) (animated GIF from photos or screenshots; timing, size, loop, there-and-back) | |
| [Merge GIFs](https://stayput.dev/tools/merge-gifs) (join animated GIFs end to end, fitted to the first one's size) | |
| [Image to SVG](https://stayput.dev/tools/image-to-svg) (trace PNG or JPG into vector shapes; black and white, logo or detailed) | |
| [Transcribe audio to text](https://stayput.dev/tools/transcribe) (Whisper on your device; text, SRT or WebVTT; 16+ languages) | |
| [Add subtitles to video](https://stayput.dev/tools/add-subtitles-to-video) (captions from the speech or your SRT, burned into the picture) | |
| [Vocal remover](https://stayput.dev/tools/vocal-remover) (karaoke instrumental or acapella, AI model on your device) | |
| [Video background remover](https://stayput.dev/tools/video-background-remover) (blur, colour or a picture behind the person) | |
| [OCR PDF](https://stayput.dev/tools/ocr-pdf) (make a scanned PDF searchable) | |
| [Document scanner](https://stayput.dev/tools/document-scanner) (phone photos of paper to a clean PDF) | |
| [EPUB to PDF](https://stayput.dev/tools/epub-to-pdf) (DRM-free ebooks to a paginated PDF) | |
| [Text to PDF](https://stayput.dev/tools/text-to-pdf) (type, paste or drop .txt files; A4 or Letter) | |
| [PDF to EPUB](https://stayput.dev/tools/pdf-to-epub) (reflowable ebook from a PDF's text) | |
| [Open ZIP, RAR and 7z](https://stayput.dev/tools/archive-extractor) (extract archives; password-protected ZIPs open with the password) | |
| [Create ZIP](https://stayput.dev/tools/create-zip) (files or folders, optional AES-256 password) | |
| [Remove silence](https://stayput.dev/tools/remove-silence) (shorten pauses, trim quiet ends) | |
| [Remove background noise](https://stayput.dev/tools/remove-noise) (hiss, hum, fans and traffic out of voice recordings and videos) | |
| [Add watermark to video](https://stayput.dev/tools/add-watermark-to-video) (text or logo, corner or centre, size and opacity; sound kept) | |
| [Blur faces in video](https://stayput.dev/tools/blur-face-video) (automatic, frame by frame; blur, pixelate, box or emoji) | |
| [Resize PDF pages](https://stayput.dev/tools/resize-pdf) (A4, Letter, Legal, A3, A5, Tabloid; content scaled to fit, text stays text) | |
| [Flatten PDF](https://stayput.dev/tools/flatten-pdf) (form fields, comments, stamps and signatures into the page; text stays selectable) | |
| [Fill PDF form](https://stayput.dev/tools/fill-pdf-form) (type into fillable fields, tick boxes, optional flatten) | |
| [Online tuner](https://stayput.dev/tools/tuner) (guitar, bass, ukulele, violin, chromatic; reference notes) | |
| [Mic test](https://stayput.dev/tools/mic-test) (level meter, verdict, record and play back) | |
| [Webcam test](https://stayput.dev/tools/webcam-test) (preview, real resolution and frame rate, snapshot) | |
| [Keyboard test](https://stayput.dev/tools/keyboard-tester) (on-screen ANSI or ISO keyboard, key and code log) | |
| [Speaker test](https://stayput.dev/tools/speaker-test) (left, right or both channels, tones, sweep, pink noise) | |
| [Dead pixel test](https://stayput.dev/tools/dead-pixel-test) (full-screen black, white, red, green and blue; click or arrow keys, cycle mode) | |
| [Audio to video](https://stayput.dev/tools/audio-to-video) (MP3 plus a cover picture to MP4; 16:9, square, 9:16) | |
Plus dedicated pages for the jobs people search for: image conversions such as [HEIC to PNG](https://stayput.dev/heic-to-png), [PNG to JPG](https://stayput.dev/png-to-jpg), [WebP to PNG](https://stayput.dev/webp-to-png), [AVIF to JPG](https://stayput.dev/avif-to-jpg) and [JXL to PNG](https://stayput.dev/jxl-to-png); tool presets such as [JPG to PDF](https://stayput.dev/jpg-to-pdf), [PDF to JPG](https://stayput.dev/pdf-to-jpg), [Combine PDF](https://stayput.dev/combine-pdf), [Resize image](https://stayput.dev/resize-image), [Crop to circle](https://stayput.dev/crop-image-to-circle) and [Remove location from photos](https://stayput.dev/remove-location-from-photos); and [guides](https://stayput.dev/guides) that answer the question behind the tool ("is it safe to merge PDFs online?", "how do I remove location data from photos?").
## MCP server
Stayput also ships as a local [Model Context Protocol](https://modelcontextprotocol.io) server, [`stayput-mcp`](mcp/), so an LLM client can call the same PDF and photo tools as tool calls, still with no network access and no uploads:
```json
{ "mcpServers": { "stayput": { "command": "npx", "args": ["-y", "stayput-mcp"] } } }
```
See [`mcp/README.md`](mcp/README.md) for the full tool list, or [stayput.dev/mcp](https://stayput.dev/mcp).
## How it works
The site is a static [Astro](https://astro.build) build: one page per tool, a shared tool shell, and a TypeScript module per tool that does the work in the tab. The MCP server (above) is the one part of this repo that does run as a local process; everything else described below is the browser-only website.
- **HEIC decoding**: [heic-to](https://github.com/hoppergee/heic-to), a WebAssembly build of libheif, served by the site itself from `/vendor/` (copied out of node_modules at build time by `scripts/vendor.mjs`). The request fetches only the decoder; no image data is sent.
- **PDF editing**: [pdf-lib](https://pdf-lib.js.org) for merge, split, rotate, reorder, page numbers (standard fonts, nothing embedded), signature stamps, image embedding and rewriting image streams.
- **PDF to Word**: pdf.js reads the positioned text runs; `src/lib/pdftext.ts` rebuilds lines, paragraphs and headings and `src/lib/docx.ts` writes a minimal Office Open XML document with fflate. Text and structure only, no layout.
- **PDF rendering**: [pdf.js](https://mozilla.github.io/pdf.js/) (legacy build for wide browser support) on a dedicated web worker.
- **Image resize and encode**: the canvas API, with stepped downscaling for sharp results.
- **Encrypted PDFs**: many PDFs carry "owner" encryption with an empty user password (statements, forms with printing restrictions). pdf-lib cannot read those streams, so every PDF tool first checks the trailer for `/Encrypt` and, when found, runs [qpdf](https://github.com/qpdf/qpdf) compiled to WebAssembly (`@neslinesli93/qpdf-wasm`, served from `/vendor/` and loaded only when needed) to strip the encryption in the tab. Files with a user password prompt for it once; outputs are written without encryption. See `src/lib/unlock.ts`.
- **AVI decoding**: most video runs on your browser's WebCodecs through Mediabunny. For old formats the browser cannot read, the page loads a decoder built from FFmpeg's libraries (LGPL) only when you convert such a file. Today that is AVI (Xvid and DivX 4+, Motion JPEG; MP3, MP2, AC-3, PCM sound) and 3GP (H.263 and MPEG-4 picture, AMR sound; builds in `vendor-builds/libav-3gp/`, worker `src/lib/threegp-decode.worker.ts`). The decoder is [libav.js](https://github.com/Yahweasel/libav.js) 6.10.9.0 with a short component list; the build script, the list and the patent-expiry evidence for each codec are in `vendor-builds/libav-avi/` and `docs/research/avi-decoder-codec-patents-2026-10.md`. The built files are committed and `scripts/vendor.mjs` copies them to `/vendor/libav.js@6.10.9.0/`; the conversion runs in a module worker (`src/lib/avi-decode.worker.ts`), and the file never leaves the tab.
- **WMA decoding**: a second, smaller build of the same FFmpeg libraries (LGPL) with only the ASF demuxer and the Windows Media Audio 1 and 2 decoders, loaded only when you convert a WMA to MP3. WMA Pro, WMA Lossless and WMA Voice are left out on purpose and refused by name. The build script, the component list and the reasoning are in `vendor-builds/libav-wma/` and `docs/research/wma-decoder-codec-patents-2026-10.md`; the conversion runs in a module worker (`src/lib/wma-decode.worker.ts`), and the file never leaves the tab.
- **FLV reading**: FLV is a container no browser opens, so the page loads a second, smaller libav.js build (the FLV demuxer and the MP3 decoder, nothing else) only when you convert an FLV. The H.264 picture and AAC sound are copied into the MP4 packet by packet and not decoded; MP3 is decoded and written as AAC. VP6, Sorenson Spark and other codecs have no decoder in the build and are refused by name. The build script, config and patent reasoning are in `vendor-builds/libav-flv/` and `docs/research/flv-reader-codec-patents-2026-10.md`; the worker is `src/lib/flv-decode.worker.ts`.
- **JPEG XL and AVIF decoding**: the browser's own decoder when it has one (AVIF everywhere, JXL in Safari); otherwise the [jSquash](https://github.com/jamsinclair/jSquash) WebAssembly builds of libjxl and libavif, served from `/vendor/` like the HEIC decoder. Tool pages that accept these formats ask the service worker to cache the decoders they may need (only JXL/AVIF decoders the browser lacks) and mark `<html data-decoders="cached">` when they are in, so they work offline after one visit; the service worker does not precache them for every visitor because together they are about 6 MB. See `src/lib/vendor.ts`.
- **Signatures**: drawn on a canvas with pointer events (pressure-aware for pens) or typed in the self-hosted Caveat font, cropped to a transparent PNG and placed at preview coordinates mapped into PDF user space, page rotation included.
- **EXIF removal**: a hand-written, lossless segment and chunk editor for JPEG, PNG and WebP in `src/lib/exif.ts`. It deletes only the metadata segments and writes the image data back unchanged, so the pixels are identical and the file only gets smaller. The one exception is the tool's "Apply orientation" option (on by default), which re-encodes a JPEG stored sideways at quality 95 so it displays upright; the MCP server never does this. It also extracts EXIF from HEIC containers so "keep metadata" works on HEIC conversions.
- **TIFF input**: [UTIF.js](https://github.com/photopea/UTIF.js) (`src/lib/tiff.ts`), bundled and loaded only when a TIFF is dropped, since only Safari decodes TIFF natively. Images to PDF turns every page of a multi-page TIFF into a PDF page.
- **Zip downloads**: [fflate](https://github.com/101arrowz/fflate) in the tab when there is more than one output.
- **Offline**: a service worker generated at build time (`src/pages/sw.js.ts`) caches every tool page, and `scripts/postbuild.mjs` writes the list of hashed assets and fonts into it so all tool code (including the on-demand pdf-lib and pdf.js chunks) is cached on the first visit. Pages are network-first so deploys show up immediately; assets are cache-first because their names are content-hashed, and assets from earlier deploys are pruned on activation.
- **Security headers**: the site ships a strict Content-Security-Policy. Scripts may load only from the site itself and the self-hosted analytics host; no third-party CDN is involved. `connect-src` is limited the same way, so even a bug could not send a file elsewhere.
- **Analytics**: a self-hosted, cookie-free [Umami](https://umami.is) counter records page views and a handful of anonymous events, such as a visit starting, files being added, a tool running and a search being used, each with coarse ranges rather than exact values. It never records file names, file types, exact sizes or file contents, and nothing is recorded when the browser sends a Do Not Track or Global Privacy Control signal. [stayput.dev/privacy](https://stayput.dev/privacy) is the source of truth for exactly what is sent and what the stats server can see. In the code, every event and property is listed in `src/lib/analytics.ts`, and `tests/analytics.spec.ts` fails if an event gains a property that is not listed or names a file.
There is no backend and no cookies. See [`/privacy`](https://stayput.dev/privacy) for the full statement.
## Development
Requirements: Node 22+.
```bash
npm install
npm run dev # http://localhost:4321
npm run build # static output in dist/
npm run check-links # fail on any broken internal link in dist/ (--external also checks outbound links)
node scripts/serve.mjs # serve dist/ locally with clean URLs and the production headers
npm run check # Astro and TypeScript checks
```
### Tests
End-to-end tests drive every tool in headless Chromium with generated fixtures (JPEG with EXIF and GPS, PNG, WebP, HEIC, multi-page PDFs, plus static encrypted and offset-box PDFs under `tests/fixtures/static`), then check the downloaded outputs. One test also asserts the privacy claim directly: after files are added, no request leaves the page except the stubbed analytics call. The local server (`scripts/serve.mjs`) applies the site's production headers, so tests run under the production Content-Security-Policy and a violation shows up as a console error.
```bash
pip install pillow pillow-heif # once, for fixture generation
python3 tests/fixtures/make-fixtures.py && node tests/fixtures/make-pdf.mjs
npx playwright install chromium # once
npm run build && npm test
```
Set `PLAYWRIGHT_CHROMIUM_PATH=/path/to/chrome` to use a preinstalled browser.
### Social images
`public/og.png` and `public/og/<slug>.png` are rendered from the page copy by `node scripts/make-og.mjs` (headless Chromium). Re-run it after changing a tool's heading or tagline and commit the result.
### Sitemap dates
Every sitemap URL has a real `<lastmod>`, and tool and guide structured data carries the same `dateModified`. The dates live in `src/data/lastmod/`, one small file per page (path, date and a content hash), so two branches that add pages never touch the same file, so builds never depend on git history, which Vercel and CI clones don't have. After you change a tool, pair, preset, guide or static page, run `node scripts/lastmod.mjs`: pages whose content hash changed get today's date and new pages are added. Commit the result. `tests/lastmod.spec.ts` runs `node scripts/lastmod.mjs --check`, so CI fails if you forget. Guides use their own `updated` field as the date, and the check also fails when a guide changes without it moving forward.
After each production deploy, `scripts/indexnow.mjs` pings IndexNow with only the pages whose lastmod entry differs from the previous commit. `node scripts/indexnow.mjs --all` resubmits every page (the workflow's manual run does this); `--dry-run` lists what would be sent.
## Adding a tool
1. Add an entry to `src/data/tools.ts` (slug, SEO copy, steps, FAQ) and a paragraph to `src/data/engine.ts` saying what actually runs in the tab. These feed the home page, footer, sitemap, service worker, structured data and social image.
2. Create `src/pages/tools/<slug>.astro` using `ToolLayout` and put the option controls in the `options` slot.
3. Create `src/tools/<slug>.ts`, call `createShell({ process })` from `src/lib/shell.ts`, and return the output files.
4. Add a test to `tests/tools.spec.ts`, then run `node scripts/make-og.mjs`.
A format-pair landing page ("WebP to PNG") is just an entry in `src/data/pairs.ts`; the page, its social image and its sitemap entry are generated. A preset landing page for any other tool ("JPG to PDF", "Combine PDF") is an entry in `src/data/presets.ts` naming the base tool and its option defaults; the base tool's option controls live in `src/components/options/` so the tool page and its presets share one copy. A guide is an entry in `src/data/guides.ts` (sections, FAQ, the tools it points to); copy supports `[text](/path)` links and `**bold**`. Run `node scripts/make-og.mjs` after adding any of these.
See [CONTRIBUTING.md](CONTRIBUTING.md) for the ground rules, the main one being that no change may make a file leave the browser.
## Support
Stayput is free and needs no account, no ads. If it saved you a subscription and you want to say thanks, the repository has a GitHub Sponsors link; sponsorship helps cover running costs (domain, hosting and the stats server).
## License
MIT. See `LICENSE`. Third-party libraries keep their own licenses: heic-to (LGPL-3.0, loaded as a separate module at runtime), FFmpeg's libraries via libav.js (LGPL-2.1-or-later, loaded as a separate module at runtime), qpdf (Apache-2.0, loaded as a separate module at runtime), @jsquash/jxl and @jsquash/avif (Apache-2.0, loaded as separate modules at runtime), pdf-lib (MIT), pdf.js (Apache-2.0), fflate (MIT), UTIF.js and pako (MIT), Astro (MIT). The Caveat font in `public/fonts` is under the SIL Open Font License 1.1 (see `public/fonts/OFL-Caveat.txt`).
TDQS
Scored across 9 tools
Each tool targets a distinct file operation, and overlapping cases like split_pdf vs. extract_pdf_pages are explicitly differentiated in their descriptions. Read-only and modifying variants (pdf_info, inspect_image_metadata vs. strip_image_metadata) are clearly separated.
Most names follow a consistent snake_case verb_noun pattern (split_pdf, merge_pdfs, rotate_pdf, extract_pdf_pages). Minor deviations like pdf_info (noun-first) and images_to_pdf (noun_to_noun) are still readable and predictable.
9 tools is well within the ideal range for a PDF/image utility server. Each tool covers a unique operation, and none feels redundant or trivial.
The set covers core PDF operations (info, split, merge, extract, rotate, number) and image metadata handling. However, it lacks PDF encryption/decryption, PDF-to-image conversion, or text extraction, leaving minor gaps that agents would need to work around.