Skip to content

Xojo Web 2026r3: URL Routing, New Features, and Two Important Changes

2026r3 brings a bit of everything to Xojo Web: two breaking changes you’ll want to read about before updating, a new URL router, a few small additions that were long overdue, and more than 40 bug fixes.

Breaking Changes First

I’d rather start with the uncomfortable part, so nobody gets surprised after updating.

Unhandled exceptions will now quit your app

Until now, the web framework was silently swallowing unhandled exceptions, and your app kept running as if nothing had happened. Desktop projects don’t behave like that. After going back and forth on it internally, we’ve decided to treat this difference as what it really is: a bug.

Starting with 2026r3, a web app will quit when an exception isn’t handled, unless you Return True from one of the UnhandledException events.

There are two of them. When something goes wrong inside a session context and you aren’t catching it in code, Session.UnhandledException fires first. A database call failing after the user presses a WebButton is a typical example. If you Return True there, the app keeps running. If you don’t, the exception keeps bubbling up and App.UnhandledException fires.

App.UnhandledException is your app’s last chance, and this is where the behavior changes. If you don’t Return Truethere, the app will print the stack trace, hoping it ends up logged somewhere, and quit.

If you really want things to work like before, add a Return True to App.UnhandledException and you’re done. But I’d encourage you to use this as an excuse to start paying attention to those exceptions. They are trying to tell you something. At the 2024 MBS Conference, Xojo MVP Jeremy Leroy gave a talk about tracking exceptions with Sentry, and I can’t recommend it enough:

Positioning controls by code

The second change comes from a series of bug fixes around positioning controls programmatically. The web framework had some issues here that made dynamic interfaces more painful than they should have been, and many of you ended up writing workarounds.

Web projects now place controls much closer to how Desktop projects do. The catch is that a workaround in your code may now be compensating for a problem that no longer exists. If you position controls by code, check your layouts after updating and remove the workarounds you don’t need anymore.

We try really hard to avoid breaking changes. In these two cases, we believe it’s the right call in the long run.

URL Router

On to the new stuff. The URL router is another piece of the puzzle on our way to SEO friendly web apps. Until now, if you wanted nice URLs, you had to parse them yourself in App.HandleURL, probably fighting with regular expressions along the way.

2026r3 adds a new WebApplication.AddURLRoute method. Imagine you are building a blogging system with URLs like this one:

/2026/06/25/build-a-threaded-find-in-files-solution/

You’ll need a WebPage to display a single post. Let’s call it BlogPostWebPage. With that in place, this one-liner in App.Opening is all it takes:

AddURLRoute("/<year:integer>/<month:integer>/<day:integer>/<slug:string>/", TypeOf BlogPostWebPage)

Two things are happening here. The obvious one: when a request matches the route, Xojo Web creates a new BlogPostWebPage instance and shows it. Notice the type next to each parameter name, too. A URL like /hello/06/25/some-post/ won’t match, because “hello” isn’t an integer.

The second one is more subtle, and it’s where this gets handy. Since the route defines named parameters, you can read them from the BlogPostWebPage.Opening event using Session.URLNamedParameter:

Self.Title = Session.URLNamedParameter("slug")

From there, it’s just a matter of using the slug to query your database and fill the page with the right post.

This is the first version of the router, and we’re looking forward to your feedback. If you find something we missed, please open a Bug Report or a Feature Request.

WebCanvas.DrawControlInLayoutEditor

Xojo targets keep borrowing ideas from each other. In the previous release, DesktopCanvas got a new DrawControlInLayoutEditor event, heavily inspired by WebSDKUIControl.DrawControlInLayoutEditor. Now WebCanvas supports it as well, so your canvas-based controls can show a preview of themselves in the Layout Editor instead of an empty box.

WebFileUploader Improvements

Until now, if a user picked the wrong file, the only thing you could offer was clearing the whole uploader and asking them to select everything again. That’s not great when they’ve picked ten files and only one of them was wrong.

WebFileUploader in 2026r3 adds the FileNameAt and RemoveFileAt methods, plus the FileCount and LastFileIndex read-only computed properties. With them, you can let your users change their mind and remove individual files before uploading.

WebRectangle.Pressed Event

Last, but not least, WebRectangle finally has a Pressed event. WebRectangles are commonly used to group other controls, like a card with a title, a picture and a couple of labels, so making the whole group clickable made a lot of sense. Child controls can still have their own Pressed handlers. If none of them handles the press, it keeps bubbling up until it reaches the parent WebRectangle.

Bug Fixes & Wrapping Up

The rest of the web side of this release is mostly bug fixes, more than 40 of them. As boring as that might sound, if just one of them makes your day to day a bit easier, it was worth it.

As usual, make sure to read the full release notes, because there’s much more in there than fits in a blog post. And thank you for taking the time to read this, test the betas, report bugs, send feature requests and renew your license. It all helps more than you might think.

Happy coding!

Ricardo has always been curious about how things work. Growing up surrounded by computers he became interested in web technologies in the dial-up connections era. Xojo has been his secret weapon and language of preference since 2018. When he’s not online, chances are he will be scuba diving … or crocheting amigurumis. Find Ricardo on Twitter @piradoiv.