mirror of
https://github.com/BookStackApp/BookStack.git
synced 2026-02-08 11:19:36 +03:00
URL Scheme Proposal #2864
Closed
opened 2026-02-05 05:29:46 +03:00 by OVERLORD
·
17 comments
No Branch/Tag Specified
development
further_theme_development
l10n_development
release
llm_only
vectors
v25-11
docker_env
drawio_rendering
user_permissions
ldap_host_failover
svg_image
prosemirror
captcha_example
fix/video-export
v25.12.3
v25.12.2
v25.12.1
v25.12
v25.11.6
v25.11.5
v25.11.4
v24.11.4
v25.11.3
v25.11.2
v25.11.1
v25.11
v25.07.3
v25.07.2
v25.07.1
v25.07
v25.05.2
v25.05.1
v25.05
v25.02.5
v25.02.4
v25.02.3
v25.02.2
v25.02.1
v25.02
v24.12.1
v24.12
v24.10.3
v24.10.2
v24.10.1
v24.10
v24.05.4
v24.05.3
v24.05.2
v24.05.1
v24.05
v24.02.3
v24.02.2
v24.02.1
v24.02
v23.12.3
v23.12.2
v23.12.1
v23.12
v23.10.4
v23.10.3
v23.10.2
v23.10.1
v23.10
v23.08.3
v23.08.2
v23.08.1
v23.08
v23.06.2
v23.06.1
v23.06
v23.05.2
v23.05.1
v23.05
v23.02.3
v23.02.2
v23.02.1
v23.02
v23.01.1
v23.01
v22.11.1
v22.11
v22.10.2
v22.10.1
v22.10
v22.09.1
v22.09
v22.07.3
v22.07.2
v22.07.1
v22.07
v22.06.2
v22.06.1
v22.06
v22.04.2
v22.04.1
v22.04
v22.03.1
v22.03
v22.02.3
v22.02.2
v22.02.1
v22.02
v21.12.5
v21.12.4
v21.12.3
v21.12.2
v21.12.1
v21.12
v21.11.3
v21.11.2
v21.11.1
v21.11
v21.10.3
v21.10.2
v21.10.1
v21.10
v21.08.6
v21.08.5
v21.08.4
v21.08.3
v21.08.2
v21.08.1
v21.08
v21.05.4
v21.05.3
v21.05.2
v21.05.1
v21.05
v21.04.6
v21.04.5
v21.04.4
v21.04.3
v21.04.2
v21.04.1
v21.04
v0.31.8
v0.31.7
v0.31.6
v0.31.5
v0.31.4
v0.31.3
v0.31.2
v0.31.1
v0.31.0
v0.30.7
v0.30.6
v0.30.5
v0.30.4
v0.30.3
v0.30.2
v0.30.1
v0.30.0
v0.29.3
v0.29.2
v0.29.1
v0.29.0
v0.28.3
v0.28.2
v0.28.1
v0.28.0
v0.27.5
v0.27.4
v0.27.3
v0.27.2
v0.27.1
v0.27
v0.26.4
v0.26.3
v0.26.2
v0.26.1
v0.26.0
v0.25.5
v0.25.4
v0.25.3
v0.25.2
v0.25.1
v0.25.0
v0.24.3
v0.24.2
v0.24.1
v0.24.0
v0.23.2
v0.23.1
v0.23.0
v0.22.0
v0.21.0
v0.20.3
v0.20.2
v0.20.1
v0.20.0
v0.19.0
v0.18.5
v0.18.4
v0.18.3
v0.18.2
v0.18.1
v0.18.0
v0.17.4
v0.17.3
v0.17.2
v0.17.1
v0.17.0
v0.16.3
v0.16.2
v0.16.1
v0.16.0
v0.15.3
v0.15.2
v0.15.1
v0.15.0
v0.14.3
v0.14.2
v0.14.1
v0.14.0
v0.13.1
v0.13.0
v0.12.2
v0.12.1
v0.12.0
v0.11.2
v0.11.1
v0.11.0
v0.10.0
v0.9.3
v0.9.2
v0.9.1
v0.9.0
v0.8.2
v0.8.1
v0.8.0
v0.7.6
v0.7.5
v0.7.4
v0.7.3
0.7.2
v.0.7.1
v0.7.0
v0.6.3
v0.6.2
v0.6.1
v0.6.0
v0.5.0
Labels
Clear labels
🎨 Design
📖 Docs Update
🐛 Bug
🐛 Bug
:cat2:🐈 Possible duplicate
💿 Database
☕ Open to discussion
💻 Front-End
🐕 Support
🚪 Authentication
🌍 Translations
🔌 API Task
🏭 Back-End
⛲ Upstream
🔨 Feature Request
🛠️ Enhancement
🛠️ Enhancement
🛠️ Enhancement
❤️ Happy feedback
🔒 Security
🔍 Pending Validation
💆 UX
📝 WYSIWYG Editor
🌔 Out of scope
🔩 API Request
:octocat: Admin/Meta
🖌️ View Customization
❓ Question
🚀 Priority
🛡️ Blocked
🚚 Export System
♿ A11y
🔧 Maintenance
> Markdown Editor
pull-request
Mirrored from GitHub Pull Request
No Label
☕ Open to discussion
Milestone
No items
No Milestone
Projects
Clear projects
No project
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: starred/BookStack#2864
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @ssddanbrown on GitHub (Jun 22, 2022).
History & Purpose
Currently BookStack uses a fixed system for URLs of content within the system which uses "slugs" generated from the name of content. Upon this there's also a fairly hidden id-based system for pages. Examples of existing content urls:
The original idea behind using slugs is provide the user an indication of the likely destination content from the URL alone. A user observing the uri
/books/frogswould instantly know the link will likely lead to a book about frogs.Over the years since building BookStack a number of cases have arisen which has indicated this scheme is not ideal in some scenarios. This proposal/discussion puts forward a new scheme, to address these scenarios, to use as default going forward.
This is an open discussion to gain feedback, details below are not at all final. Comments are very much welcomed. This is not assured to go ahead, especially if the impact looks to be greater than actual long-term benefits.
Targets
Achieve more "Permanent" URLs by default
Right now URLs use slugs which are generated based upon the name of an item.
Changes to name can cause changes to the URL which can break URL references. We do have a system to help handle these scenarios, by referring to the revisions system, but this does not cover every change and revisions may be pruned.
We do have the
/link/<page_id>permalinks but these use the database incrementing integers which can leak detail regarding system content (Gap in ids may indicate hidden pages).There are additional ways we could improve the current URL handling on changes but I think it may be better to use a more reliable base system than apply patches.
Allow flexibility of the content URL
While the existing URLs provide good indication of content, this breaks down when other languages are used. The URL path sections, between slugs, is hard-coded to English which may not be understandable to the reader. In addition, we attempt some conversion to latin for some slugs which can completely remove the original content name for some languages.
We've also seen both requests to have a minimal length URL, and a longer, more descriptive, URL.
Proposed Scheme
The proposed new URL scheme is that shown below. This reflects what would be the new default for a "Page" item URL. The default configuration is intended to closely align with the appearance of the existing default URLs. The components of this scheme are broken down in sections below.
Examples for existing item types
UID
This acts as a unique identifier for an item within BookStack. It will be a flexible-length case-insensitive (defaulting to lower case) alpha-numeric string defaulting to minimum 5 characters. This is used instead of a UUID-like ID to keep this short and usable in URLs with little impact. It's case-insensitive for compatibility with case-insensitive systems.
The UID is prefixed with an type letter followed by a hyphen. The type letter represents the content type (
p= page) which allows type identification from the UID alone while ensuring UIDs are unique across content types. The hyphen separates the type indicator while allowing a pattern to match upon to prevent confusion with other systems URL at the same path prefix.Separator
The separator simply exists to separate the UID from the configurable trail portion of the URL.
This allows us to still use the core ID-based URL for system endpoints (For example
/b-7itu3/create-page) while having clear separation to the configurable trail to prevent conflicts.Configurable Trail
This portion of the URL would be system-admin configurable as required. It would default to match the current BookStack slug scheme, but we would provide an interface to allow per-content-type (book/page/chapter/shelf) configuration of this trail using static and dynamic-placeholder elements.
The available dynamic-placeholder elements would initially be as follows:
{{name}}- URL encoded version of the item name.{{slug}}- Name-generated slug, as we provide now.{{book_slug}}- - Name-generated slug, as we provide now. Chapters and pages only.This could be expanded on in the future, but the initial implementation goal would be to match existing options.
The trail could be made empty if desired, which would cleanly generate item URLs with no trail and no separator components.
Considerations
/booksendpoint would remain as-is, which may limit the flexibility benefits in scenarios where users want to get away from the default BookStack terms, although this isn't a core goal here.Personal Thoughts
I'm not fully sure on the propose scheme. The separator especially makes it look a little ugly, but it's the closest I could think when thinking about the technical handling and attempting to have a default aligning with the current URL scheme.
After writing this out, It's very hard to assess if such a change would be worth it. The benefits right now are very edge-case based but these may amplify long-term.
@Szwendacz99 commented on GitHub (Jun 25, 2022):
Do I understand correctly that with the new system when someone changes name of book, chapter etc, BookStack will be still able to retreive (or redirect?) to the proper address with the new trail, thanks to the unchangable ID? If so then it seems reasonable, same as replacing integer ID to more unpredictable one. But then I wonder how will be such ID generated, because It seems to be kidna too short for true pseudo randomization, unless it wil perform additional check if it is untaken.
The separator visual value is hard to asses for me, but such binary question (separator, Yay or nay?) could be investigated with some simple survey., Or even make survey with multiple possible separators
@andysh-uk commented on GitHub (Jul 8, 2022):
I personally don't see an issue with the current URL scheme itself - I actually use something very similar myself in an open-source project.
The first URL component lists the item type, followed by the path to the item - for example:
My biggest pet peeve with the system in Bookstack is that a change to a page name or book name, changes the URLs used to access it. In a system designed for documentation, where links directly to content are pretty much expected, this is a big issue.
My suggestions would be to either:
a) keep the existing scheme but generate a separate "permalink" based on an identifier (not the DB ID.)
When a page/chapter/shelf is created, generate a pseudo-random token of suitable length (e.g. ta19en2uan) and allow users to link to this permalink - e.g. /permalink/ta19en2uan - but the rest of Bookstack would still use the current scheme.
The link would be a flat structure so you don't have to worry about nested pages/chapters etc.
When a browser visits a permalink, Bookstack could retrieve the item based on the identifier (you could maybe include the item type in the link so you know what type it is - e.g. /permalink/book/ta19en2uan) and then redirects to whatever the current (non-permanent) URL of the item is, so search engines don't see it as duplicate content.
This should be a temporary redirect so browsers do not cache the non-permanent link in place of the permanent link.
E.g. 302 redirect /permalink/book/ta19en2uan --> /book/my-cool-book
b) Alternatively, keep the existing scheme but whenever a page title changes, store a history of the URLs it has been accessible under.
If accessing a URL would return a 404, check the history table to see if it was previously used and redirect to the current URL of the item it was used for.
E.g.
@tcatlas commented on GitHub (Jul 11, 2022):
Personally, I really like the proposed change. Sure, the URLs may be slightly ugly but it solves the very real problem of breaking links if and when the name of a document is updated. It's a URL, not a work of art. It doesn't have to be beautiful - I mean have you seen SharePoint URLs? I think
/p-4b72a/:/books/my-awesome-book/pages/my-cool-pageis a good balance of form and function - though perhaps the ID could be placed at the end of the URL instead of the beginning since the end is more likely to be hidden in an overflow scenario.@andysh-uk commented on GitHub (Jul 12, 2022):
Does it though? What happens to the URL “/p-4b72a/:/books/my-awesome-book/pages/my-cool-page” if you rename “My Cool Page” to “My Other Cool Page”? Yes the unique ID is still the same, but the page name is still part of the URL, so would that change to “/p-4b72a/:/books/my-awesome-book/pages/my-other-cool-page”, in which case you’ve got the same problem - the previous URL is now broken.
Or are we saying that Bookstack would only use the UID part of the URL to find the matching content? In which case, the URL “/p-4b72a/:/books/my-awesome-book/pages/my-cool-page” and “/p-4b72a/:/something-completely-random” would arrive at the same piece of content? In which case, what is the purpose/benefit of the trail?
EDIT: just seen Stack Overflow does exactly this. “/questions/1234/whatever” will get you to question ID 1234, whatever you put in place of “whatever”. However it does redirect to the correct URL of “/questions/1234/the-question-title” to avoid duplicate content issues arising with search engines.
I sure have! They’re horrendous.
@amelszg commented on GitHub (Jul 12, 2022):
Hi, i like the proposed schema as well and would prefer a page/chapter lookup via UID.
In my use case i am consuming Bookstack data exclusively via API and handling the navigation between pages myself.
That means i'm parsing the target urls from content and rewriting it with pageid. This only works as long as page is not renamed. As soon as that happens, the link gets broken and since revision data is not available via API i have no means to lookup/fix those links automatically.
For this reason i would prefer the proposed url schema and using UID per default in page content when cross linking to another page.
Not sure why the API interface needs to change though, keeping Integer based Id for all operations would be still fine for me, i.E. api/pages/{id}, as long as the new UID is returned as result with page object.
And yes, the separator feels a little bit ugly in the url, maybe it could be defined differently or removed alltogether.
Just some suggestions:
/p-4b72a/books/my-awesome-book/pages/my-cool-page
/p/4b72a/books/my-awesome-book/pages/my-cool-page
To be honest, even keeping the int based Id would be fine, the main issue for me is preventing having broken links after page being renamed, thus some kind of fixed, convention-based and parsable identifier in url is needed and for that case even current int Id's would be allright, imho.
So this example, using current {page_id} would be fine as well:
/p/15/books/my-awesome-book/pages/my-cool-page
Thanks.
@ssddanbrown commented on GitHub (Jul 12, 2022):
Thanks all for your input so far. Some feedback on the responses:
Yes, that is correct.
Yeah, We'd scan the DB to ensure uniqueness. We already do this with content slugs.
We already have these available (albeit rather unused) for pages. The main thing I don't want is non-alignment between a permalink system and actual browser/resulting URLs. If a user can't have the same benefits from copying the URL from the browser URL bar I see that as a fail, and hence why I've been hesitant to expand the current ID-based system.
Yeah, this is another approach on the table, but I wanted to explore the changes to the URL system first to see if a wider set of issues could be solved, upon just the linking aspect.
End placement gets more complex to parse out, especially where we're allowing a lot of customizability in the trail part. Not impossible, but probably not worth the move to the end for the complexity it could add upon having stable base url patterns.
Yes, BookStack would only use the UID part to identify the content. As per the existing slugs in content URLs, it allows context to be provided in the URL alone. This is mentioned in the "History & Purpose" section of the proposal. As reflected in the proposal, you could configure the trail empty to have clean UID-only urls if desired.
The change away from the current integer ids would be to help avoid current potential security considerations. Jumps in id, or lack of access to certain id, can indicate existence of hidden content. Not an issue in many environments but will be a consideration in some. In addition, these new ids could set-up for future migration to having the different content types in shared table-space in the database (Longer term thinking though).
Just to confirm though, if it helps, you can still currently use
/link/<page_id>URLs if you need a page permalink at this time.Personal Thoughts - Updated
I'm still unsure about this overall, probably now less convinced than before. I think this may be mass-optimizing for too many problems that hardly actually are problematic in reality. Additional targeted addressing of URL changes would likely solve 90% of actual problems this whole proposal addresses, without causing a painful migration.
@amelszg commented on GitHub (Jul 12, 2022):
Hi @ssddanbrown, thanks for the reply.
The problem is, that i don't have any control over the content. The editors/maintainers will simply use what's most convenient for them, meaning they will either use the built in URL picker element which doesn't produce the mentioned "permalink" or they just copy the url from adress bar, they won't bother selecting some text snippets to create permalinks.
If this would be configurable (i.E. URL Picker would create permalinks instead of slug base urls), that would mitigate my current problem, although there would be still a small issue with manually pasted urls.
@c0shea commented on GitHub (Jul 13, 2022):
I'm not a fan of the proposed change. The only issue we've faced with the current URLs is that if you move a page, the pretty URL changes. However, we mitigated that by using the page's permalink by hovering over the page title and clicking the pop-over copy button to get the permalink.
A sizeable portion of our documentation in BookStack is linked to from an external system to specific pages via their permalink. Changing the unique ID from an integer as it is today to a different value would be a breaking change that would be a monumental effort to adjust all of our links or risk them not working at some undetermined time in the future.
I tend to think that using a UID instead of the plain old database incrementing IDs is security through obscurity and not worth the added complexity here. Yes, it's possible for someone to deduce that if the page ID in their URL is 123 that they could try to access page 122, but the real effort would be ensuring the permission system is working appropriately to deny them access if they shouldn't be able to see that page in the first place. Our instance of BookStack is for internal-only access for our documentation. What could someone possibly have to gain from knowing that we have thousands of pages based on the ID? To me, it's not that useful to know. I'd be curious to know how many people are actually using BookStack in a truly public manner (not just internal company documentation as we do) and how much obscurity would really benefit them.
@milneauk commented on GitHub (Aug 4, 2022):
Would it be possible to review the permalink visibility when addressing this? Users currently have to highlight a portion of the page content to get the UID/permalink but I think there should be an option to display this on the page automatically from the settings. We find that users typically copy the URL from the browser's address bar but this breaks when pages are moved or renamed.
@c0shea commented on GitHub (Aug 5, 2022):
@cbbaaron I agree. Even if it was another button with the other actions on the right side of the page that when clicked copied the permalink to the clipboard that would be helpful.
@ghaberek commented on GitHub (Aug 9, 2022):
I think overall the URL scheme should be improved but the current proposed scheme feels awkward to me. You could use something like Hashids to generate reversible unique IDs from the database ID for each entity type, which alleviate having to run a large migration across the whole database. Then use each URL prefix (or just
'p', etc.) to encode the entity IDs uniquely by type, which helps ensure URL prefixes can't be easily swapped. You could take this a step further and combine theAPP_KEYwith the prefix to make the IDs unique to the system to help prevent leaking of IDs (unless that could expose the site's app key, I haven't dug into Hashids too deeply).Another suggestion I propose is reducing the URL prefixes for entities from plural to singular, while leaving the plural names (e.g.
/books) for all entites of that type as well as for supporting the old URL scheme until it's retired. So you would browse to/booksto see all books but those links would point you to/book/<uid>/<book-title>./shelves/<slug>/s/<uid>/shelf/<uid>/<ignored-title-slug>/books/<slug>/b/<uid>/book/<uid>/<ignored-title-slug>/books/<slug>/pages/<slug>/p/<uid>/page/<uid>/<ignored-title-slug>@ssddanbrown commented on GitHub (Sep 20, 2022):
As of v22.09 the main pain-point, that this proposal would have addressed (breaking of internal cross-links), should now be much less of an issue. Therefore I think it'd be especially not worthwhile now to move ahead with this proposal, or a variation of it, as I don't think it'd be addressed enough of a fundamental need for the cost and confusion it would require.
Thanks everyone for your input, it has been very valuable to guide my thoughts and understand the actual needs & desires at play here.
@whoamiafterall commented on GitHub (Jan 13, 2025):
Hi @ssddanbrown,
I've been reading about this problem through several issues now and i wonder if you are willing to re-consider changing the URL scheme?
The Problem still exists that when someone shares a URL to a bookstack instance and the URL changed, the link just breaks. I didnt even know about Permalinks until today.
Since your biggest concern about this change (if i understand correctly) was that existing Databases would break, i wonder if now - having the .zip import feature making Migrations super easy - this could come to the table again?
On the other hand, like @ghaberek mentioned above, you could try to stick to the existing database scheme with the db ID, and just hash it for the URL Scheme.
Could you please state your thoughts on this one more time?
@ssddanbrown commented on GitHub (Jan 13, 2025):
@whoamiafterall I would not reconsider, and I'd be more set on keeping it as-is (rather than a full overhaul) due to the relative infrequency I hear of issues around the scheme. The ZIP exports/imports don't really have an impact regarding this (they're not a backup format to be used to fix potential issues, and the concerns were with maintaining compatibility in general, not for specific database breakages).
If there are common issues, I'd be open to improving the old URL resolution (by properly tracking old slugs/paths rather than the semi-hack of using revisions like we do now) as a main focused approach to that issue.
@whoamiafterall commented on GitHub (Jan 14, 2025):
Thanks for your reply.
That makes sense, and your suggestion seems like a good solution.
In our case the issue is that many domain redirects link to certain books or pages, because our instance is used by multiple organizations. We try to be a knowledge base for activists in germany through federating content (some is fetched automatically from git repos).
So e.g. wiki.infraunited.org links to one of our books and redirects like wasser.infraunited.org to a specific chapter of that book regularly break because the slug changes.
In addition to that, a lot of people will link to the content on our instance by copying the URL from the adress bar when they share it with their group or even include it in documents.
That is why we would be very happy if this issue was adressed :)
@ssddanbrown commented on GitHub (Jan 14, 2025):
@whoamiafterall I've now opened an issue/request focused on that in #5411.
@avmaksimov commented on GitHub (Sep 14, 2025):
I like your proposed change too but passed three years and there is a bug issue around renaming book's name.
I don't see any criminal using DB ID neither. Bookstack can use rich links to show that this is the link about fogs.
But if you like your solutions and this gives compatibility with old version and solves renaming problems I think it's very good!!!
May be before you don't have time to make this ability you can add a button "Copy permanent link" for all kinds of pages (pages, chapters, books, shelves)? It would be very good for all of us and don't break any scenarions.