An Experiment - UUID v7 for TiddlyWiki

This is an experiment and not intended to be used for production wikis.

Giving Every Tiddler a Birth Certificate

The status quo

TiddlyWiki identifies tiddlers by title — and titles must be unique within a wiki. That’s fine and should stay that way. But titles alone can’t answer:

  • When you merge two wikis — are these two differently-titled tiddlers actually the same?

  • When you import — did this tiddler already exist under another name?

  • When you rename — how do external references (URLs, printed notes, conversations) find it again?

There’s no built-in way to say “this is the same tiddler” across wikis or across renames. A c32 field gives every tiddler a permanent identity that is independent of its title.

What this plugin does

The wikilabs/uuid7 plugin assigns every new tiddler a UUID v7 identifier — a 128-bit, globally unique, time-ordered ID defined in RFC 9562 (2024). It stores this ID in two formats:

c32 — Crockford Base32 (29 chars)

01KMP4-ND9W-E8Y8V5YM1DR0-01VG

c7 — Standard UUID hex (36 chars)

019d2c4a-b53c-723c-8d97-d40b70000770

The c32 field uses Crockford’s Base32 encoding in a 6-4-12-4 display format. Same 128 bits as c7, just more compact and a bit more human-friendly:

c7 (UUID hex) c32 (Crockford Base32) TW created
Length 36 chars 29 chars 17 chars
Sortable Yes Yes Yes
Unique Globally Globally Not guaranteed
URL-safe Yes Yes Yes
Human-readable Hard Better Readable (but not unique)

The c32 format adds only 12 characters over the existing created field — in exchange for guaranteed global uniqueness.

Mnemonic phrases

Every UUID is also encoded as 8 adjective-noun-verb triplets:

metal dog soars, clear hawk drifts, proud leaf seeks, ...

You can navigate to a tiddler by typing a few phrase words in the URL:

https://mywiki.com/#metal+dog+soars

Remembering tiddlers

You don’t need to memorize 29 characters. The 6-4-12-4 format is designed so you can remember just the first few and last few characters:

Remembered Example Safe up to
First 3 + last 4 (6 chars) 01K + 01VG ~5K tiddlers
First 5 + last 4 (8 chars) 01KMP + 01VG ~1M tiddlers
First group + last group (9 chars) 01KMP4 + 01VG millions

Navigation

c32 values work as wiki links and URL hashes — full or partial. Because the 6-4-12-4 format groups bits by meaning, partial matches act as ranged selections:


[[01KMP4]]                            → all tiddlers from the same ~17 min window
[[01KMP4-ND9W]]                       → narrows to the exact millisecond
[[E8Y8V5YM1DR0]]                     → double-click, copy, paste — 56 random bits, clash: 1 in ~72 quadrillion
[[01VG]]                              → last 4 chars — usually unique in any wiki
https://mywiki.com/#01KMP4            → URL: opens all tiddlers from that time window
https://mywiki.com/#01VG              → URL: opens the specific tiddler

The first group is a time-based range — sharing a link like #01KMP4 opens every tiddler created in that ~17-minute session. The last group is a random pinpoint#01VG almost certainly identifies one specific tiddler.

Input is case-insensitive with Crockford alias support (O0, I1, L1).

Filter operators

Both c32 and uuid7 filters have full feature parity:

[{!!c32}c32[ms]format:timestamp[YYYY-0MM-0DD]]  →  2026-03-26
[{!!c32}c32[phrase]]                             →  metal dog soars, clear hawk drifts, ...
[{!!c32}c32[check]]                              →  U  (mod 37 check symbol)
[{!!c32}c32[c7]]                                 →  019d2c4a-b53c-...  (convert to UUID)
[{!!c7}uuid7[c32]]                               →  01KMP4-ND9W-...  (convert to c32)

Backfill

Existing tiddlers get their c7 and c32 fields on import or via a startup backfill module (dry-run by default). Timestamps are derived from the existing created or modified field.

What it would need to be part of core

The plugin works today with zero core changes — it monkey-patches getCreationFields() at startup. But for first-class support, a small set of core changes would make it cleaner:

  1. boot.js — Register c32 as a built-in field type, so it’s available before plugins load (~5 lines)

  2. wiki.js getCreationFields() — Generate UUID v7 natively, store as c32 (Crockford Base32), and derive created from its timestamp. This eliminates the dual-field redundancy and makes the UUID the source of truth (~10 lines)

  3. Tiddler constructor — Enforce read-only: once a c32 is assigned, it never changes. This prevents accidental mutation through the UI (~5 lines)

Note: MWS already uses c7 (UUID hex) as a database index. The c32 format encodes the same 128 bits — conversion between c7 and c32 is lossless and trivial, so both can coexist.

That’s roughly 20 lines of core code. Everything else — display, sorting, filtering, serialization — works unchanged because tiddler.fields.created remains a Date object.

Backward compatibility

  • Existing tiddlers without UUIDs keep working — the field module falls back to parseDate
  • Code reading tiddler.fields.created gets a Date object as before
  • The UUID is an addition to the timestamp, not a replacement — nothing is lost

Try it

The plugin is available at wikilabs/uuid7 — install it, create a tiddler, and watch the c32 badge appear.

The plugin also provides a c7 field (standard UUID v7 hex format) with full feature parity to c32 — same filter operators, same navigation, same backfill. Both encode the exact same 128-bit value and are 100% compatible with the UUID v7 spec. Having both lets you explore which format you prefer — in the future only one may remain.

Links

6 Likes

This is fantastic! I really like this idea. I still want my human-readable urls, but this helps fill that gap, while adding a much-needed immutable id to tiddlers.

This was also my first exposure to Crockford’s base32, and I’m quite impressed. The check-digits are a little odd, expanding the alphabet for little gain, but otherwise wonderful.

I’ve only read the docs, but will try it out as soon as I’m done with my current time-consuming project.

Thank you very much for sharing!

Yea, I wanted to know, if I could shorten the UUIDv7 format and found ULID, which has a different usecase and is encoded using Crockford base32. My plugin only uses Crockford base32 encoding the 128 bits are still UUID v7.

c32 in the end is not much shorter. but I think a bit more memorable for “geeky” humans.

The checksum can be used if c32 values are “transferred” over telephone. It is an easy way to validate the whole string. …

I did find it funny to experiment with time “range sets”. I think a series of the last random elements could make a nice immutable link, that survives tiddler renaming.

Me too. – I know, my + separator will clash with your concept. But mine is only a concept an experiment. It would be nice if we could make it compatible.

This is great!

Could we get a b64 version that’s encoded in only 15 chars??? Or are there b64 poison characters that would make this nontrivial for TW?

With b64url, if you only encode the random elements with a version number it could be done with 14 characters or so, but we additionally need the timestamp, if it should replace the created field.

But the goal was, one field to get both, timestamp + random part. The field name: “created” also needs 7 characters. So it also adds to the tiddler size.

I did experiment with a 62 character and 64 character alphabets. But those values are not lexicographically sortable, which is the whole reason to use the new UUID v7 format in the first place.

Base64url would be 22 characters without hyphens and those values are case sensitive. Which is very hard to remember by humans. Even if you only need to remember partials.

Note: TW created encodes only 64 bits (ms-precision timestamp, no random bits), so bits per char is 64/17 ≈ 3.76 . The others all encode the full 128-bit UUID.

Encoding Chars per value Bits per char Sortable Alphabet
TW created 17 ~3.76 Yes 0-9
Hex (no hyphens) 32 4.00 Yes 0-9a-f
Crockford Base32 26 5.00 Yes 0-9A-Z (no I,L,O,U)
Base62 22 5.95 No A-Za-z0-9
Base64url 22 6.00 No A-Za-z0-9-_

Yes, understood. But if you need a check-sum, it does not have to be a check-digit. ISBN uses a check-digit, and to achieve its base-11 (first prime after 10), they need only add the symbol X to their alphabet, and it will only appear in the last place.

Crockford does a similar thing, but because 37 is five more than his 32 “digits” (0 - 9, A - Z, less I, L, O, and U), he adds five new symbols to his alphabet, all five of which can only appear in the last position: *, ~, $, =, and U/u. That seems overkill. There are lots of alternatives if you don’t insist on a single digit. One possibility, use the U, which was excluded not because it was easily confused with a digit, as were I, L, and O, but mostly because he needed to remove one more, and I guess he was worried about people saying FU too often, which, because he adds it back, can still happen at the end of the word. Here’s one possibility, which uses a U in one of the last two places when you need a check-sum.

u-first u-second
UA - 0 AU - 19
UB - 1 BU - 20
UC - 2 CU - 21
UD - 3 DU - 22
UE - 4 EU - 23
UF - 5 FU - 24
UG - 6 GU - 25
UH - 7 HU - 26
UJ - 8 JU - 27
UK - 9 KU - 28
UM - 10 MU - 29
UN - 11 NU - 30
UP - 12 PU - 31
UQ - 13 QU - 32
UQ - 14 RU - 33
US - 15 SU - 34
UT - 16 TU - 35
UV - 17 VU - 36
UW - 18

I don’t know why he insisted on a check-digit.

I’m sure we could get them together. The last state of play was that Jeremy wanted to consider more general-purpose tools to carry information in the URL fragment. Mine would fit in fine, but wouldn’t be the whole story. This POC would certainly fit in as well. I didn’t have the skills at the time to make the necessary changes in the TW codebase, and it got dropped for 5.4. I think my skills have grown enough that I could reasonably consider this now. I’ll try to squeeze out some time.

I just thought I would point out you can add to a tiddlywiki url a search term, if it searches for a unique id include a unique time stamp then only that tiddler opens in the story.

There are possibly many reasons to use a static reference to a given tiddler and in tiddlywiki where the unique key is title that can but often does not change over time, is a useful feature. however there are possibly many different solutions and reasons to employ a static reference. in many cases it need not be universal. With this in mind perhaps we could document the key features and degrees of freedom along with limitations that may exist in different solutions then document each, such as the one here against those items, so it is easy to compare and contrast.

Whoa! Brilliant!
I have two different types of comment I will post seperately.

First on the UUID ideas.
Sounds and looks great.
Especially the Crockford with phrases.
I can imagine scenarios where it would definitely be helpful moving stuff between wikis.

Q: If implemented would you be able to switch it off?
Some wikis I create are only titles–these don’t need any other field.

TT

3 posts were split to a new topic: Create random titles using a shuffle algorithm

If I understand the “cut-up” mechanism right. You need a “linear text first”.

  1. You split the text into an array of sentences
  2. You shuffle the array and
  3. Get a new text that way

You are right. Shuffle is one algorithm, that is part of the library. But you need the 3 steps above. You need a definition, how to split the “input text”.

The easiest way would be new-line. But that would need some preparation of the input text.

A second possibility would be a “sentence end marker” like a dot “.” … Which can be problematic for numbers. so it may be dot-space ". " … which is problematic at line ends. … dot-space OR dot-newline. That should be reasonably save to split ordinary text into sentences.

We get an array of sentences that can now be shuffled and written to a new tiddler, with an “origin” field that points back to the original text, where it comes from. This origin field could be a c32 field :wink:

So IMO you would need to be a bit more specific about:

  • How your input text looks like
  • Can you prepare your text for splitting or not
  • What can be used to split it into sentences
  • How do you want to use it.
    • widget
    • filter operators
    • ?
      -m

I recommend trying this UUIDv7 encoding: Base62id

Efforts are underway to make this encoding the standard compact UUID encoding

Hi Sergey,
That’s very interesting. Did you design it?

One of the main advantages of the c32 format is, that it allows a somewhat “human readable” structure, by allowing to add hyphens.

Hyphens do add length, but also increase human readability and memorability.

The encode / decode algorithm seems to be straight to implement and the result seems to be sortable, according to the docs.

I have to say I do not understand yet, what the encoding algorithm exactly does and why it works.

One of the key features I wanted to have, is to select a “range” of tiddlers by using parts of the ID in a URL and open several tiddlers. Is this still possible?

Yes, I am the author. But the encoding requirements and features have been actively discussed with the community for many years. In other words, it’s a collective effort.

Human readability, memorability, and ease of pronunciation weren’t important requirements. In reality, what’s required is ease of copying with a double click.

If UUIDs were sorted in binary representation or in long canonical text representation (with hyphens), they will remain sorted in the same order after encoding with Base62id.

The algorithm can’t be explained in a few words. It’s more complex than for 16, 32, and 64-character encodings. But programmers understand it easily and can code it quickly without asking questions.

Since encoded UUIDs are sorted, a range can also be selected by a few characters on the left side of the encoded identifier, where the timestamp is contained.

Yea, I did test it already. It is short, but not nice to read. Is there a full spec? RFC draft

Is it allowed to add hyphens?

Yes, this is a complete spec. It will be the basis for a separate RFC or a modification to RFC 9562. Hyphens are not allowed, as they prevent the entire identifier from being selected by double-clicking.

For readability, I recommend adding identicons generated from the UUID value.

I would recommend hiding the UUID in any format from users’ view, leaving only the identicon instead, with the ability to copy the UUID by clicking on the identicon. The UUID is intended for the information system, not for humans.

This encoding is a great idea, a very nice improvement on most existing encodings, and the click-to-copy is very useful.

But I don’t think it really meets the design goals here. While I can’t speak for @pmario, I think our notions overlap a great deal, and the reasons for his design include things that Base62id can’t really cover:

  • This should be as easily read and transferred as possible. Some of the multiple English word systems would cover this better, but that’s hard to internationalize, and much more complex to implement. If the encoding is difficult to read over the phone, it’s a problem.

  • Case insensitivity. This makes user mistakes much less common.

  • Hyphens and ranges. These work together to help us identify tiddlers written within short time-frames of one another.

  • Short unique-enough identifiers that are somewhat memorizable – 01K + 01VG in the examples.

And as much as I appreciate identicons, I don’t think TW has any place to put them by default, although they could be useful for some multi-user wikis, I’m sure.

I appreciate the work you’re putting into standardizing this. And I do like the tool. But it doesn’t feel like a good fit here to me.

I did experiment with base62 (no id) encoding for IDs for quite some time already. I do like, that it is much shorter than UUIDs. They are much shorter, but have the disadvantages Scott described.

Due to the underlaying UUID v7 origin we still can use some characters from the start and end of c62.

  • FdAvKdAg-yeCllbTI4qzZpA
  • FdAvKdDr-mzZ9EAqrvuP87I

As we can see they start with the same characters. And the F will be there for quite a long time. It contains binary 10 at the MSB (most significant bits) - Then there are the 48 bit timestamp.

  • FdAvKdD still selects a range
  • FdAvKdD + P887i … Should be unique within a wiki. Since every character represents almost 6 bits, less chars will be more specific, than with all other formats.

WikiLab edition is updated

In today’s era of instant messaging, copy-paste, and seamless integration of information systems, it’s hard to imagine anyone dictating thirty-character gibberish over the phone, much less trying to remember it. It’s easier to send a message or find a document by the words in its title. But it would be a shame if the document identifier format in this wiki turned out to be incompatible with other information systems that adhere to the standard.

Currently TW has no UUID at all. So this experiment should allow us to “play” with different possibilities. In a somewhat funny way (for geeks :wink:

TiddlyWiki is special, since our target group are non-techie users. While we still need to enable power users, that are willing to follow the “rabbit hole”.

Link rot is a real problem, especially with TW. Finding “stable” tiddler titles is difficult. Refactoring is a main workflow in a wiki.

I did create the uni-link plugin, which allows me to use aliases to create wikilinks [[alias|?]] that do not break if tiddler titles change. A tiddler can have several aliases. Those aliases have the same problem as tiddler titles, if content from different wikis are merged. They can clash, so links are not unique anymore.

An other goal of these experiments is to evaluate if the created field could be used to cover several usecases:

  • Be a timestamp
  • Be a UUID
  • Be manageable by non techy users
  • Use it as an alias-link
  • Use it to create “unbreakable” URLs
  • Use it to create TW pretty-links [[link text|FdAvT+vs]] that can be remembered if you need to

The last example is much more convenient to type, because it’s less characters. Those links should not break, if tiddler titles change. The main problem is case sensitivity, which is hard to remember.

[[link text|01KMZY-GD3G]] c32 encoded is less uniqe than c62, but case does not matter and still won’t brake on title change. It’s less collision resistant then c62 …

So there are pros and cons for every encoding type. As I said – It’s an experiment – There is no guarantee if it ever would land in the core at all.