Custom scripts - from unwanted to first class citizen - a growth story

How we used the MVP approach to deliver value to our customers as quick as possible

Photo by Patrick Tomasso on Unsplash

Custom scripts - from unwanted to first class citizen - a growth story

Staffbase is a high growth tech company, providing internal communication tools to 1000+ leading enterprises. Appropriately, growth is one of our 3 core values here at Staffbase and that also applies to our ideas. We want to innovate and try new things to expand our portfolio with the coolest and newest stuff.

We have many ideas and want to try as many of them as possible to see what enterprises and their employees really benefit from. On the other hand, like probably every company, we only have limited development resources. This is why we live and breathe the MVP (minimum viable product) principle: create the most basic version of a product that can already help a customer, then extend it.

To illustrate how we do this in practice, let me tell you a story.

Quickly prototyping the search extension

Beginning in 2020, there was a new customer request rising up in the list of requested features: showing Microsoft 365 documents in our search. The Staffbase search can be used to search for news posts and articles, comments, files and people in the company. Everything that is stored in our system. Now, we wanted to extend that with content from Microsoft 365, that is SharePoint and OneDrive. Normally, we'd write a plugin for something like this, like the dozens of plugins we already had. But there were two problems:

  • Plugins are whole-page. A plugin instance is always a separate page. So, the search would have to have a button leading to that page → that's bad UX.
  • Plugins don't inherit custom styling. All of our customers have custom styling to individualize the experience for their employees. But since the plugins are embedded via iFrames, we can't inherit the site's custom CSS → bad UX again.

What we needed was a seamless integration where users wouldn't even recognize that it's third-party. We needed a first-party script that would load and do this stuff. However, the company's direction at that point was against that. Some of the reasons:

  • If the custom script is compromised, there is no security border. Since the custom script would be run first party on that page, (without any restrictions that iFrames, shadow DOM or web components would impose) a script that was hijacked by an attacker would basically be able to do everything it wants.
  • Customers would be able to change our client-side behavior. They could change any HTML and CSS and (unknowingly) block improvements we do or new features we introduce - leading to potentially a lot of work of identifying the culprit together with the customer.
  • Users would often have to re-login. With a client-only app, Microsoft issues refresh tokens with a lifetime of only 1 day. If a user came back to the search more than 24 hours after the last session, they would need to re-login.

Having these drawbacks in mind, we still decided to go with that approach in order to develop an MVP. It would give us the opportunity to quickly get this custom script developed by a partner and get feedback from customers. With that feedback, we could then develop a real, first-party feature, integrated into our backend that would also be configurable and easily extendable to load search results from other providers as well.

The script itself was then written in a couple of days. It was loaded on application start and checked whether the user is on the search page. It then triggered the Microsoft 365 search using the put-in search term and displayed the results in a new search-results container, re-using the already existing styles on that page.

Perfect!

The first real feature: the Microsoft 365 Widgets

The custom script was a total success. Having it developed so quickly and integrated so seamlessly made us re-think our opinion on custom scripts. We realized the huge potential this solution has.

In about no time, our partner was given a new project: Using the same custom script approach, they were tasked to develop a set of widgets that show content from different Microsoft 365 products. These widgets could then be added to pages by editors.

We already had widgets, but they were bundled into our frontend. We didn't have any lazy-loaded, custom ones. Since we had to extend this functionality on our side to support widgets from external scripts but also wanted to quickly prototype something, we decided to do everything in parallel.

  • Our partner would already start with the widgets. They would look for a div with a special class (e.g. ms365-files-widget) and render the widget content inside it.
  • Meanwhile, we would extend the existing widget functionality with the ability to load widgets from custom scripts:
  • Since those have to be web components with their own HTML tags, add them (as well as custom attributes) to our DOM sanitizer - otherwise they couldn't be saved.
  • Add a dialog to add such a custom widget to a page.
  • Add a page where those custom scripts can be managed by the customer. There, introduce checks on those scripts, e.g. the script has to register a web component.
  • Provide an SDK with helpful functions and types.
  • As soon as our extended widget functionality was ready, we'd adapt the widgets to use the new SDK and register themselves as web components, instead of looking for a div with a class.

For quick prototyping and to get a visual feeling of CSS side effects, the partner used Storybook and copied the Staffbase styles in there. We even put one of our designers on this project to create visually appealing designs for these widgets. The project was set up like any other of our projects, completely with CI/CD, CDN, release descriptions, public documentation, etc.

The new widget bundle went into beta testing with real customers in a few weeks. And only a few months later, we had the whole integration standing, including our brand-new Widget SDK.

Photo by Marvin Zeising

What a blast!

Connecting it to the backend

Meanwhile, another team had added the real integration of the search extension (as promised at the end of "Quickly prototyping the search extension").

They had implemented a Third Party Connections page where different providers (like Microsoft 365) could be connected. The customers are even able to use their own Azure App Registration with their custom branding. And they're able to toggle the new Microsoft search feature on and off, as well as decide which user group is able to see it. Since it's integrated into the backend, we're able to store client credentials for the app registration and use another login flow. This enables us to receive a refresh token with a lifetime of 30 days. Users don't have to re-login anymore (as long as they're not on their well-deserved 4+ weeks vacation).

But of course, this didn't apply to our new Microsoft 365 widgets. They still used their own authentication mechanism and employees had to separately log in to the search and to the widgets. Also, the widget bundle would always request all the permissions for all widgets upon logging in. But many of our customers wanted to restrict this to only the widgets/permissions they needed/wanted to grant.

To fix these inconveniences for our customers, we decided on connecting the widgets to our new backend authentication flow.

  • On the Third Party Connections page, we added toggles for each individual widget.
  • Toggling a widget removes it from the widget catalogue, so it can't be added anymore. If it was added to a page before, we show a banner informing the user that it's been turned off.
  • A toggled-off widget's permission scopes won't be requested anymore when a user logs in.
  • The widget SDK received new functions to login users using one of the (in the backend) supported providers that deliver valid access tokens for each widget.
  • A feature flag toggles the old flow to the new flow, so we can test it with some selected customers first.

Photo by Marvin Zeising

With this new architecture, customers are even able to develop their own widgets connecting other providers (other than Microsoft). They don't even have to care about the whole OAuth 2.0 process of receiving a token - we do it for them.

As of writing this article, the change is currently rolled out and activated for the selected customers. We'll start activating it for all customers in the next weeks/months.

Lessons learned

What went well

It's always worth it to take a break and look back to see what one achieved and how it all came together (or broke apart in the worst case).

  • Develop it iteratively. Starting with an easy interim solution was a great idea. We could already gather intel from customers and see what works best for them and what's really important for them. Also, our customers really get a sense that we're constantly improving our products.
  • Don't have devs stuck in meetings all the time. Communication is key. And that even more with external contractors. The weeklys with status updates and separate meetings for technical planning already go a long way. But probably the most valuable thing was that our engineers had enough time to quickly hop on a call with other devs if they had problems.
  • Eat your own dogfood. We developed the Widget SDK with our own widgets as the first primary citizen. That way, we're sure that customers can painlessly write their own widgets with this SDK.

What still bugs us

At the same time though, there were a couple of things along the way that proved to be quite struggling.

  • Release process: To help our customers use the Widget SDK, we released a typings library. Unfortunately, this makes adding new functionality a time-consuming process:

    • First, we update the typings and release a private alpha version for us.
    • We then use this alpha version in our main frontend and implement the new functionality.
    • And we update the widget generator project as well as the custom widget examples to use the new alpha version and its functionality.
    • If that works well, we release a public beta version and let our contractors implement the changes for their widgets. And we do it internally for our widgets.
    • If all works well and the version is stable, we release a new public version.

    So yeah, we're still brainstorming over what could be a better way to simplify this flow...

  • Hybrid app support: We build Staffbase into customer-specific hybrid apps via Capacitor. Luckily, we have good foundations teams that wrote a GitHub Action. Using that, we can build hybrid apps with specific configurations for every pull request just by commenting on it. But some pieces of software just don't want to work easily in hybrid apps (try supporting OAuth2 Login for Microsoft → edge cases over edge cases!)

  • Missing tests: Two of the widgets load their data from Microsoft endpoints that don't align to the way the widgets are designed. Example: to show all teams sorted by creation date (the newest team first), we have to fetch all groups because the API doesn't support sorting by creation date. And since the API pages the results, we can only get batches of teams and load their next-links. With a customer like DHL that has thousands of teams, that already triggers a lot(!) of subsequent requests. To organize this and all the data these two widgets need, a quite smart logic was created to batch requests and run them in parallel to be able to render something as fast as possible. BUT, it was developed without tests (don't let your Product Managers contract work without letting a tech lead have a look over it). Now, Microsoft has new endpoints that would simplify our data loading flow. But we fear breaking something when changing the current implementation to use these new endpoints... Long story short: Write tests, they help you in the long run!

What a ride...

That was it! You got a glimpse of what it's like for us at Staffbase to rethink our strategies, to deliver new features as fast as possible, and iteratively improve them based on customer feedback.

I hope this has been interesting to you. If so, feel free to check out our jobs board and who knows - maybe we'll develop the next big thing together ツ