SplasheryPlayground

Share links and QR codes

A Splashery link does not point at your scene. It is your scene. Open the Share tab of the More sheet, copy the link, and the whole Splash (its options, the look, the camera, any paint) is written into the web address itself. This chapter takes one apart, lets you make one, and shows how the same link fits in a QR code.

The link has four parts, from left to right:

https://splashery.app/#s=d.eJxVjkEOgzAM…

  1. The page. The ordinary address of the app. The browser asks the server for this part and nothing more.
  2. #s=, the start of the fragment. A browser never sends the fragment (everything after the #) to a server, so the scene is not in any request. The page reads it with script after it loads (parseHash in src/splashery/codec.js).
  3. A format letter and a dot: d. means the rest is compressed, and j. means it is not (the next paragraph says when).
  4. The scene. The scene as JSON text (its shape is set by normalizeScene in src/splashery/state.js), turned into bytes as UTF-8, compressed with raw deflate (the same method as a zip file, without the zip's wrapper), and written with the 64 letters, digits, - and _ that are safe in an address (base64url).

The j. form exists for a browser that cannot compress (no CompressionStream): it is the same JSON, written in base64url but not compressed. It is not readable as text, only safely spelled. Any browser reads either form if it can decompress, and every current browser can.

A link can also end with &n= and a number, the splat count, as in #s=…&n=300000. It is not one of the four parts because it is optional: a link with no n opens the Splash at its own count, and the Splats slider sets it. The first links the Playground made, such as #toy=grape&n=300000&v=yaw,pitch,zoom, still open.

It is not a hash of anything, and not a lookup. Some sites make a short link by saving your data on a server and handing you a key. Splashery cannot do that, because it has no server (the previous chapter). Nothing is stored anywhere. The link is the only copy of the scene, which is why you can send it in a message and the other person sees exactly your Splash, and why a scene you did not copy the link of is not recoverable from the site.

Try it: write a scene, get its link, read it back

This runs the real encodeSceneHash and decodeSceneHash from src/splashery/codec.js in your browser, on a tiny scene you can edit. The link below uses this page's own address as the base, so it is the same shape as a real one. Press Change one character to see how fragile the text is. Nothing leaves this page.

The share link demo: a tiny scene as JSON, its compressed link, the scene read back from the link, and the link as a QR code
Interactive in the web version: splashery.app/manual/share.html#demo-share

Why one changed character usually breaks it

Compressed text is not like ordinary text. In a sentence, a wrong letter is one wrong letter. In a deflate stream each bit tells the decoder how to read the bits after it (which code table, how far back to copy from), so one changed character changes how everything after it is read. The decoder then either stops with an error (decodeSceneHash throws, and the app shows "Could not read the shared scene"), or produces text that is no longer valid JSON, which stops at JSON.parse. Rarely, the changed character lands on a spare bit at the very end or turns one letter inside a word of the JSON into another, and the link still works with a slightly different scene. That is the "usually". Do not edit a link by hand to change a setting. Change the setting in the app and copy the new link.

When a link does decode, the app does not trust it. The JSON goes through normalizeScene (src/splashery/state.js), which drops keys it does not know, clamps every number to its allowed range, and fills in anything missing with its default. The demo shows the result of that step below the decoded text.

A link made last month must open next year. The scene has a version number, and normalizeScene reads old ones. Version 3 is the current schema. A version 2 link is a version 3 scene without the pattern, the motion, and the options on shelf Splashes; it loads, and those take their defaults. A version 1 file (the old planet painter) is refused with a message that says so, and so is a scene from a newer Splashery than the one you are running. Saving always writes the current version. The project's rules require this, and a check opens old links to make sure they still load.

The same applies to the embed player and the <splashery-toy> element, which read the same links (embed/, src/element.js).

A Splash at its defaults is a link of about 220 characters. Paint and clay are the things that grow, because every dab or stroke is written into the scene. The link has no limit of its own: writeScene (src/scene.js) writes the whole scene, however long, and leaves nothing out to shorten it. A link carries settings, not your files: a photo or splat file from your device has to be opened again by whoever opens the link.

A scene as a QR code

A QR code is a picture of bytes, and the link is just text, so any link can be one. The Share tab of the More sheet always shows a QR code of the same link as the Copy button, which you can scan with a phone camera (it has no Save button). The code holds only the link. Nothing is uploaded, and your own files stay on your device exactly as they do for a link.

There is a ceiling. A QR code at its largest (version 40, the lowest error correction) holds 2,953 bytes, and the whole link counts, address and all. A code carries extra codewords (a codeword is 8 squares) from which a scanner can rebuild the data when some of it cannot be read, and the more of them it carries the fewer bytes it holds. Level M can rebuild about 15 percent of the codewords and level L about 7 percent. That is a rate for codewords, not a promise about any 15 percent of the squares: damage that covers the three big squares that locate the code, or lands on many blocks of codewords at once, can defeat a code that lost less, while many damaged squares inside one codeword cost only that codeword. shareCode (src/splashery/qr/share-code.js) picks level M while the code stays at version 30 or smaller, and level L beyond that, for the smallest code a phone can still resolve. In measurements, every Splash at its defaults came to 211 to 228 characters, which is version 10 or 11 at level M. Paint adds much more: one dab, with its droplets and drips, takes the link to about 550 characters (version 18 at level M), five dabs to about 1,500 (version 28 at level L), ten to about 2,700 (version 39 at level L), and twelve past what any code holds. Then the Share tab shows no code, and its note says the link is too long for a QR code and gives its size in bytes; Save → Scene file (JSON) keeps such a scene. Nothing is split across several codes.

The note under the code says to scan it to open the Splash and its view on another device, and names the code's error correction level (M or L). The code is drawn from the link and not read back on your device, so it is not a promise about every phone camera. The demo above does the same sizing with the real shareCode: it shows the version and level of its own link, and draws it.

Where it lives: src/splashery/codec.js (encode, decode, parseHash), src/splashery/state.js (normalizeScene), src/scene.js (writeScene), src/splashery/qr/share-code.js (the QR code), and tools/links.mjs, which checks that old links still open.

The Splash Manual, first published September 28, 2026 and updated October 11, 2026. Part of Splashery, a playground of 3D Gaussian splats that runs in your browser. Every picture here was made with Splashery itself.