One Click from OneStream to SAP Fiori: Lessons from a Drill-Back Connector
What started as a routine OneStream SAP drill-back request became a thirteen-version technical journey through the connector framework, SAP Fiori URL routing, and the surprising places where enterprise software disagrees about what “open this file” means.
The OneStream SAP drill-back request was simple
Every OneStream SAP drill-back project starts with a similar user story. A finance analyst at a large utility client needs to investigate an unusual expense line on a project P&L in OneStream. They want to know: what actual invoices made up this number, and what’s the original document in SAP?
In a perfect world: right-click the cell, see the source invoices, click one, land in the SAP Fiori VIM transaction with the document already filtered. Total time: a few seconds.
In the actual world: log into SAP separately, navigate to the right transaction, manually search by document number copied across from OneStream, hope you remembered to write down the right one. Total time: minutes — and that’s before you account for the cognitive cost of context-switching between two enterprise applications mid-analysis.
This is the kind of friction every finance team learns to live with. It’s also exactly the kind of friction OneStream’s drill-back framework is designed to eliminate. So: we built the bridge.
The journey to get it working was more interesting than we expected.
The OneStream SAP drill-back plan, in theory
OneStream supports four “drill-back” display types in its connector framework, each suited to a different kind of source-system handoff:
DataGrid— return a table of contributing detail rowsWebUrl/WebUrlPopOutDefaultBrowser— open a URL inside OneStream’s embedded browser, or pop out to the user’s default browserFileShareFile— return a file that OneStream renders inline in its drill viewerTextMessage— show a dialog with copy-pasteable text
The architecture we sketched looked clean: drill from a OneStream stage row into a project line-items grid, drill from any line into an invoice summary grid, drill from any invoice row into the SAP Fiori transaction via WebUrlPopOutDefaultBrowser. Three drill operations, each with a clear purpose, terminating at the source-system document.
In code, OneStream’s drill-type registration is straightforward — the framework’s NameAndDesc constructor takes a Name (used internally to route the dispatch) and a Description (used as the display label in the picker):
If args.DrillCode = StageConstants.TransformationGeneral.DrillCodeDefaultValue Then
drillTypes.Add(New DrillBackTypeInfo(
ConnectorDrillBackDisplayTypes.DataGrid,
New NameAndDesc("ProjectDrillBack", "Project Line Items")
))
drillTypes.Add(New DrillBackTypeInfo(
ConnectorDrillBackDisplayTypes.WebUrlPopOutDefaultBrowser,
New NameAndDesc("Invoice", "Open Invoice in SAP")
))
End If
(One subtle gotcha we hit later: the Name field must be a single-word identifier. Spaces in the Name make OneStream silently fall back to a generic “Drill Back” label and the user never sees your nicely-labelled option. Use PascalCase for the Name; put the friendly label in the Description.)
We built it. It compiled. We clicked.
The URL truncated.
The cmd.exe surprise
SAP Fiori’s deep-link URL format follows their intent-navigation spec:
https://server/sap/bc/ui2/flp?sap-client=100&sap-language=EN#ZVIM_DOC-display?sap-ui-tech-hint=GUI&S_DOCID-LOW=000000123456
That hash fragment with ? and & characters is unusual but blessed by SAP — the SAPUI5 router parses parameters after the hash. The URL is valid, and pasted into a browser address bar it works perfectly.
But when OneStream’s WebUrlPopOutDefaultBrowser invokes the OS-level “open this URL” mechanism on Windows, the URL passes through cmd.exe argument parsing — and cmd.exe doesn’t know anything about URL fragments. To it, & is the shell’s command-separator. The Fiori URL gets truncated at the first & after the #, and the browser opens to the launchpad with no document filter applied.
Workarounds that didn’t work
We tried %26-encoding the post-hash ampersand. That survives cmd.exe parsing — but SAPUI5’s hash router has version-dependent behaviour for %26 decoding, and the document filter still didn’t always resolve correctly.
Switching to OneStream’s embedded WebUrl viewer didn’t help either. On OneStream Cloud’s web client, it throws a null-reference exception inside XFHtmlControl.CreateXfWpfWebBrowserAsContent — a WPF dependency that doesn’t exist in the cloud-hosted UI. So that path was dead on arrival.
The last option was TextMessage. It works — a dialog pops up with the URL, the user copy-pastes it into a browser, and Fiori opens correctly. But the UX is genuinely awful and we’d built a one-click experience that requires three deliberate user actions.
The clean solutions weren’t clean. Time to think differently.
The PDF transport pivot
If the problem is that we can’t hand a URL to the browser without it getting mangled, the answer is to stop handing URLs to the browser entirely. Hand the user a document instead — one that contains the URL as a clickable link annotation, where the browser handles the click natively rather than the OS shell.
That’s how PDF link annotations work. A PDF link annotation embeds the destination URL as an action on a rectangle within the document. When the user clicks the rectangle, the PDF viewer (Adobe Reader, Edge’s PDF viewer, whatever) hands the URL to the browser directly via the browser’s native navigation API — no cmd.exe, no shell parsing, no character-level mangling. The & survives intact because it never leaves the PDF/browser boundary.
So our drill response could be: “here’s a one-page PDF showing the invoice details, with a clickable button that takes you to SAP Fiori.” OneStream’s FileShareFile display type is designed exactly for returning files like this.
Lovely. Now we needed to actually build a PDF inside a OneStream Business Rule.
The library that wasn’t there
The standard approach to generating PDFs in .NET is to use iTextSharp or PdfSharp. Both are mature, both produce clean output, both are widely deployed.
Neither is available in this OneStream tenant’s Business Rule runtime.
Imports iTextSharp.text — compile error.
Imports PdfSharp.Pdf — compile error.
Various workarounds with reflection-loaded assemblies — all dead-end.
The OneStream BR runtime is a sandbox. It exposes a curated set of .NET assemblies, and the PDF libraries weren’t on the list. We had three options: petition OneStream to add them (slow, uncertain), give up on the PDF approach, or build the PDF by hand from raw bytes.
Building a PDF from raw bytes
The PDF specification is public. A single-page document with text and a link annotation is conceptually straightforward — a handful of objects (catalog, pages, page, content stream, font references, link annotation), a cross-reference table tracking byte offsets, a trailer. The content stream uses PostScript-style drawing operators that anyone who’s used SVG will recognise: rg for fill colour, re for rectangle, f for fill, BT/ET for text blocks. Standard Helvetica is built into every PDF reader so no font embedding required.
At the heart of the whole workaround is the clickable link annotation — just a few lines of declarative data:
7 0 obj << /Type /Annot /Subtype /Link /Rect [40 435 360 470] /Border [0 0 0] /A << /Type /Action /S /URI /URI (https://server/sap/bc/ui2/flp?...) >> >> endobj
That /Rect defines a clickable region in PDF user-space coordinates (origin bottom-left, points). The /A action says “when this region is clicked, open the URI.” The PDF viewer hands the URL to the browser’s native navigation API — no cmd.exe, no character mangling. The hash fragment and trailing & characters survive the round-trip because the URL is never serialised through a shell.
We wrote the rest of the document the same way — about 250 lines of VB.NET, zero external dependencies. The result was a clean, branded PDF rendered in the client’s corporate brand palette, the invoice metadata laid out in a clear table, and a Fiori-launch button at the bottom that opened the correct document on click.
We deployed it. We drilled.
OneStream silently did nothing.
The silent failure
No error. No dialog. No file in the user’s Downloads folder. The drill returned successfully — the log showed the PDF had been written to the file share. But the file never appeared on the client.
What made this particularly maddening: when we’d previously tried to return an HTML file via the same FileShareFile mechanism, OneStream had produced a very specific error message: “Unable to open file with path \\\\…\\file.html. File has invalid content.” The error showed us OneStream had successfully resolved the file path and only failed at the content-type validator. So why would a PDF — a perfectly valid content type for inline viewing — fail without any error at all?
The answer turned out to be a single character: a backslash.
Physical paths versus logical paths
OneStream’s FileShareFile.DocumentPath property is documented as a “path to the file.” In OneStream’s terms, that means a logical path — relative to the FileShare root, using forward slashes, starting with Applications/:
Applications/Production/DrillTemp/SAPInvoices_xxx.pdf
We had been returning the physical UNC path:
\\tenantstorage.file.core.windows.net\onestreamshare\FileShare\Applications\Production\DrillTemp\SAPInvoices_xxx.pdf
The HTML path apparently got far enough through OneStream’s validation pipeline to fail at the content-type checker (producing the visible error). The PDF path didn’t get that far — the path resolver couldn’t normalise the physical UNC against the expected logical root, returned a null silently, and OneStream’s drill handler treated the null as “no file to display.” No file delivered, no error surfaced.
The fix was a one-line change: write the file to the physical path (because File.WriteAllBytes needs that), but return the logical path in DocumentPath. OneStream’s server-side resolver does the physical→logical mapping the other way and finds the file.
' Physical write - this part was always right
Dim physicalFile As String = Path.Combine(
BRApi.Utilities.GetFileShareFolder(si, FileShareFolderTypes.ApplicationRoot, Nothing),
si.AppToken.AppName, "DrillTemp", fileName)
File.WriteAllBytes(physicalFile, pdfBytes)
' Wrong - what we returned in DocumentPath the first time
drillBackInfo.DocumentPath = physicalFile
' = "\\fileshare\share\FileShare\Applications\AppName\DrillTemp\file.pdf"
' Right - what OneStream's FileShareFile handler actually expects
drillBackInfo.DocumentPath = $"Applications/{si.AppToken.AppName}/DrillTemp/{fileName}"
' = "Applications/AppName/DrillTemp/file.pdf"
That’s the entire difference between a silent no-op and a working drill-back. The path that File.WriteAllBytes accepts is the path that FileShareFile.DocumentPath rejects, and vice versa. Both are documented in OneStream’s reference as “the path to the file.”
That was the breakthrough. The PDF opened. Inline. With the green button. Click → Fiori → correct invoice.
The polish: making it actually useful
The single-invoice PDF was a victory, but real-world usage exposed a UX wrinkle. A single GL line on a project P&L typically represents multiple underlying invoices. The original drill chain forced users into a two-step pattern: drill to the invoice grid, pick a row, drill again to get the PDF. Two clicks instead of one, with a grid step that added no real value over the PDF format.
So we collapsed it. The “Drill to Invoice” action now returns a single multi-page PDF with one card per contributing invoice — each card showing the key metadata (VIMNO, accounting doc, purchasing doc, simulation ID, amount) and each with its own clickable Fiori button pointing to that specific invoice’s transaction.
The PDF auto-paginates: six cards on page 1 (shares space with the drill-context summary showing year, period, entity, account, and aggregated total), seven cards on subsequent pages. A typical drill returns one to ten invoices on a single page. A high-activity project with thirty contributing invoices produces a clean three-page audit document. The link annotations work across pages because each is a separate object referencing its own URL.
End-to-end UX is now: right-click a cell, pick “Drill to Invoice,” see a branded PDF with every contributing invoice and a Fiori button per row, click the one you want, land in the right SAP transaction. One drill operation, one visible artefact, one click to the source.
OneStream SAP drill-back: lessons from thirteen versions
We landed on a working solution and a piece of code we’re confident in. But the journey through thirteen versions of the connector left us with some lessons worth sharing for other consultants working on similar integrations:
LESSON
01
Documentation gaps matter more than documentation quality
OneStream’s drill-back framework documentation describes FileShareFile.DocumentPath as “the path to the file.” Both physical and logical paths look like paths, both compile fine, only one works — and the failure mode for the wrong one is invisible. We spent half a day on a problem that a single example in the docs would have prevented. When you’re stuck and the framework is silent, the next thing to question is the assumption you didn’t realise you’d made.
LESSON
02
Browsers and shells handle URLs differently, and the difference matters
Anything you launch through Windows’ ShellExecute mechanism passes through cmd.exe argument parsing, which doesn’t respect URL semantics. Anything launched from within an already-rendered HTML page goes through the browser’s navigation API and handles URL fragments correctly. If your URL contains characters that have meaning to a shell (&, |, <, >), one of these paths will mangle it and the other won’t. Choose deliberately.
LESSON
03
Sandbox runtimes are not full .NET
OneStream’s Business Rule runtime is a curated subset of .NET. So is SAP’s BTP runtime. So is Azure Functions in some configurations. So is Salesforce Apex. When you can’t add the library you want, sometimes the answer is to build the thing yourself from primitives. PDFs, ZIPs, XML, JSON — most file formats have public specifications and aren’t actually that hard to construct directly. Library convenience hides this, and that’s mostly a good thing — but the option is there when you need it.
LESSON
04
The audit document is its own UX win
We started building the PDF as a workaround for URL mangling. What we ended up with is genuinely more useful than the original PopOut-to-Fiori plan would have been. Users can save the PDF, attach it to a ticket, email it to a reviewer, archive it for compliance. The drill artefact becomes evidence in its own right. Sometimes the “compromise” solution is the better one — and you only see it once you’ve been forced to look.
LESSON
05
Versions are cheap, decisions are not
This connector went through thirteen named versions over a few days, with each version capturing a deliberate decision point. When the user came back with a UX observation we hadn’t anticipated, we had v1–v12 to reference and didn’t have to rebuild context. Treat your iterations as documentation. Keep your branches. Future-you, or future-someone-else, will thank you.
The connector is live, the users are drilling, and the auditors have a clean PDF trail. It took thirteen versions to get there, and not one of them was wasted.
If your finance team is dealing with friction between OneStream and the source ERP — SAP, NetSuite, Oracle, or anything else — the same drill-back patterns apply. The framework supports it, the SAP side supports it, and the gap between them is bridgeable. Just expect the bridge to teach you a few things about the framework you thought you knew.
Need Help with OneStream Integration?
James & Monroe is a specialist OneStream and Oracle EPM consulting partner, delivering source-system integration work — drill-back connectors, REST API bridges, file-based loads, and middleware integrations — across Australia and New Zealand. Whether your project involves SAP, Oracle ERP, NetSuite, or something more bespoke, our team can help close the gap between source and CPM.