Azure Workbook Tips & Tricks
If you use Azure, you most likely use Application Insights for logging. You can use a dashboard to visualize your logging and gain better insights, but dashboards come with some limitations. For more flexibility Azure has workbooks.
In this blog post I’ll share some tips & tricks that I’ve gathered over the years. As a sample, we’ll create a workbook that shows information about requests sent to an API Management instance.
- Construct a query
- Create reusable query
- Create Workbook
- Parameters
- Table
- Number of Requests Graph
- Master-detail Table
- Save Workbook
- Share Workbook
- Conclusion
Construct a query
When you want to display data from Application Insights on a dashboard or workbook, you’ll need to create a query. Azure uses the Kusto Query Language for this.
Creating such a query can be daunting if you’re unfamiliar with the syntax. I always like to start by constructing an initial query through the transaction search screen of Application Insights.
For this, open your Application Insights instance in the portal and go to Transaction Search. At the top you’ll see ‘pills’ that you can use to filter the data. By default, it will show logging from the last 24 hours for all event types. In our case, we’re interested in requests, so unselect all event types except ‘Request’.
We can add extra filters by adding new pills. You can then select the property on which to filter and the value(s).
To filter on requests from our API Management instance, first select the ‘Service ID’ property and then the name of your API Management instance.
I also want to be able to filter on requests from specific APIs. Add another pill, select ‘API Name’ as the property and select the APIs on which to filter.
The result should look similar to the image below.

By clicking on ‘View in Logs’ you’ll go to the Logs screen where the query we’ve just constructed is loaded. It should look like the query below.
union isfuzzy = true requests
| where timestamp > datetime("2026-10-18T13:05:55.344Z") and timestamp < datetime("2026-10-19T13:05:55.344Z")
| where customDimensions["Service ID"] in("apim-monitorais-frc-u7eei")
| where customDimensions["API Name"] in("user-ratings-api", "movies-api")
| order by timestamp desc
| take 100
The query has the filters specified in the Transaction Search screen as where clauses. It’s also sorting the results on the timestamp and only showing the first 100 records.
We’ll optimize this query in a bit, but first have a look at the left of the Logs screen. You can find a list of tables you can query on and the columns of each table. Here’s part of the requests table for example.

The timestamp property is a property we can directly use in our queries because it’s a column of the requests table. The Service ID and API Name are not default columns in the requests table because they are specific to API Management. These are stored in the customDimensions property and can be accessed with the syntax customDimensions["Service ID"] and customDimensions["API Name"].
When you specify your own custom properties to log from your application, you’ll find them in this customDimensions property.
Create reusable query
We’ll be reusing the same query in different parts of our workbook, so we’re going to make a function. First, we’ll clean up the previously generated query to look like this:
requests
| where customDimensions["Service ID"] == "apim-monitorais-frc-u7eei"
| extend subscription = tostring(customDimensions["Subscription Name"])
, api = tostring(customDimensions["API Name"])
We’ve made the following changes:
- The
union isfuzzy=truepart is useful when querying multiple event types. Because we’re only querying requests we’ve removed it. - The query screen provides a ‘Time range’ pill that can be used to specify a time range to filter on. We will provide a similar filter in our workbook. So, the where clause on
timestampis removed. - We’ll only query on a single API Management instance, so the ‘in’ filter has become an ‘equals’.
- We’ll be adding the filter on API name in our workbook. So, it’s removed for now.
- We extend the query results with the following properties from the custom dimensions array. We also convert them to strings, so they’re easier to work with.
- Subscription Name: the name of the subscription that was used to call an API. This will make it possible to identify who performed a request.
- API Name: this is the name of the API that was called.
- Sorting is up to the queries using the function, so
| order by timestamp desccan be removed. - Lastly, we can remove the
| take 100to show more results.
To save the query as a function, choose ‘Save > Save as function’ and give it a name like ‘ApimRequests’.
When the function is saved, you can use it in a query like this:

Because we’ve extended the results in the function with api, we don’t need to use the customDimensions["API Name"] syntax in our query.
Create Workbook
Now that we have our query, we can start creating our workbook. Open your Application Insights instance and go to Workbooks. Azure already provides several workbooks that you can use and customize, but we’ll start from scratch. Click on Empty (A completely empty workbook). A new empty workbook opens.
When you click on Add, you’ll see that you can add different items to the workbook. We’ll focus on parameters and queries in this workbook.

Parameters
The first thing we’ll do is add a couple of parameters. These will allow us to filter on the data that will be displayed.
Click on ‘Add > Add parameters’ to add a parameters section to the top of the workbook.
Time Range Parameter
We’ll want to filter on a specific time range, so click on the ‘Add Parameter’ button. The New Parameter screen opens to the side. Enter the parameter name ‘Time’, select ‘Time range picker’ as the parameter type and make it required.

Click Save to add the parameter.
Subscription Parameter (drop down from logs)
When calling API Management, we need to use a subscription for authentication. I want to be able filter on this subscription to see who performed which requests.
Click on the ‘Add Parameter’ button. Enter the parameter name ‘Subscription’, select ‘Drop down’ as the parameter type, check the ‘Allow multiple selections’ box and select ‘Query’ as the source of the data.
Enter the following query. As you can see, we’re using the function that we’ve created earlier.
ApimRequests
| distinct subscription
| sort by subscription asc
If you want to filter the results in your parameter based on the selected time in the Time parameter, select Time in the Time Range drop down.
To test the query, click the Run Query button.
You might have to select a time range in the Time parameter first for the query to work.
The New Parameter screen should look like this.

NOTE: if you scroll down in the New Parameter window, you’ll see how you can use this parameter in a query.
Click Save to add the parameter.
Api Parameter (drop down from logs)
As mentioned before, we also want to filter on the API that was called. As an extra requirement, I want to filter the list of APIs based on the selected subscription(s) from Subscription parameter.
Click on the ‘Add Parameter’ button. Enter the parameter name ‘Api’, select ‘Drop down’ as the parameter type, check the ‘Allow multiple selections’ box and select ‘Query’ as the source of the data.
Enter the following query and select Time as the Time Range.
let subscriptionFilter = dynamic([{Subscription}]);
ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| distinct api
| sort by api asc
As you can see, the query is a bit more complicated.
- The
let subscriptionFilter = dynamic([{Subscription}]);line will create an array of selected subscriptions based on the Subscription parameter. If no subscription is selected, the array is empty. - The filter
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)will show all APIs if no subscription is selected or APIs with requests that have a matching subscription if one or more are selected in the Subscription filter.
The New Parameter screen should look like this.

Run the query to test if it works. Click Save to add the parameter.
Success parameter (drop down from JSON)
You can also use a static list to populate a drop down filter. We’ll add another parameter to filter on successful and/or failed requests.
Click on the ‘Add Parameter’ button. Enter the parameter name ‘Success’, select ‘Drop down’ as the parameter type. The ‘Allow multiple selections’ checkbox can be left unchecked.
Select ‘JSON’ as the source of the data. In the JSON Input we need to add an array of values and labels. See the example below.
[
{ "value": "true", "label": "yes"},
{ "value": "false", "label": "no"}
]
We’ll see the items ‘yes’ and ‘no’ in the drop down list. These correspond to true and false respectively.
The New Parameter screen should look like this.

Click Save to add the parameter.
Now that we’ve added our parameters, click the ‘Done Editing’ button in the ‘Editing parameters item’ section. The result should look something like this.

Table
The next step is to add a table that shows the requests. Choose ‘Add > Add query’.
To filter the results in the table based on the selected time range of the previously created Time parameter, we can select the Time parameter in the Time Range drop down. Set the Visualization to Grid to display the results as a table.

Now add the following query.
let subscriptionFilter = dynamic([{Subscription}]);
let apiFilter = dynamic([{Api}]);
let successFilter = '{Success}';
ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| where array_length(apiFilter) == 0 or api in (apiFilter)
| where isempty(successFilter) or success == tobool(successFilter)
| project status = iif(success==true, "success", "error")
, timestamp
, subscription
, api
, name
, resultCode
, duration
, details = itemId
, transaction = itemId
, operation_Id
| order by timestamp desc
The subscription and api filters are like the one in the query of the Api parameter.
The Success parameter was not multiselect, so let successFilter = '{Success}'; will be empty if nothing is selected, true if yes is selected and false if no is selected. With the filter | where isempty(successFilter) or success == tobool(successFilter) we either show all requests or the requests that were (un)successful.
With the project keyword we specify a list of columns to display in the table. The status column uses iif to display success when the request succeeded and error otherwise. We’ll use this to display an icon further on. The itemId is displayed twice in both the details and transaction column. We’ll use these to create links to the request details.
The query is executed when you click the Run Query button. You can filter the data by changing the values of the parameters. For example, select ‘no’ in the Success parameter to show all failed requests.
You can configure more settings in the Advanced Settings tab. I’ve set the chart title to ‘Requests’. I also like to check the following options:
- Show open external query button when not editing: this will add a button above the table on the right that opens the executed query in a new screen.
- Show refresh icon when not editing: this will add a button above the table on the right to refresh the results.
- Show Export to CSV button when not editing: this will add a button above the table on the right to export the results to a CSV.
- Show filter field above grid or tiles: this will show a filter input field above the table. It can be used to further filter the results as shown below.

Status Icon
To make it easier to differentiate successful from failed requests, we can use icons in our table.
Follow these steps:
- Click on the ‘Column Settings’ button in the ‘Settings’ tab and select the status column
- Select Icon in the ‘Column renderer’ drop down

Choose ‘Save and Close’ to see the results.
The status column will display a green/red icon depending on the value:
![]()
See the Rendering options page for more supported icon values.
Duration Formatting
By default, the duration is displayed in milliseconds. Making it difficult to quickly see how long a request took. We can configure a different renderer and custom number formatting to improve readability.
Follow these steps:
- Open the column settings again and select the duration column
- Select ‘Bar (underneath)’ in the ‘Column renderer’ drop down
- Check the ‘Custom number formatting’ checkbox
- Select Milliseconds as the unit

Choose ‘Save and Close’ to see the results.
Now, the duration column will display the value in milliseconds or seconds, depending on the value. It also renders a bar to indicate its relative value in relation to the slowest request.

Request Details in Context Pane
To show more information about a request, we can change the details column to show a link that opens the request details to the side.
Follow these steps:
- Open the column settings again and select the details column
- Select Link in the ‘Column renderer’ drop down
- Enter ‘11ch’ as the ‘Custom Column Width’
- Select ‘Request Details’ in the ‘View to open’ drop down
- Enter ‘details’ as the ‘Link label’
- Check the ‘Open link in Context pane’ box
- Select Workbook in the ‘Resource Id comes from’ drop down

Choose ‘Save and Close’ to see the results.
When you click on a details link, a context pane opens to the right showing the request properties. See the example below.

End-to-end Transaction Details
To show the end-to-end transaction details of a request, we can change the transaction column to show a link that opens the end-to-end-transaction details.
Follow these steps:
- Open the column settings again and select the transaction column
- Select Link in the ‘Column renderer’ drop down
- Enter ‘15ch’ as the ‘Custom Column Width’
- Select ‘Request Details’ in the ‘View to open’ drop down
- Enter ‘transaction’ as the ‘Link label’
- Keep the ‘Open link in Context pane’ unchecked
- Select Workbook in the ‘Resource Id comes from’ drop down

Choose ‘Save and Close’ to see the results.
When you click on a transaction link, the end-to-end transaction screen is opened. See the example below.

Hide Operation ID
The query needs to include operation_Id so we can use it later to load dependencies for the selected request. However, I don’t want to display this column in the table.
Follow these steps:
- Open the column settings again and select the operation_Id column
- Select Hidden in the ‘Column renderer’ drop down

Choose ‘Save and Close’ to see the results.
Column Labels
The current column headings look fairly technical because some are using camelCasing. We can improve these by configuring column labels.
Follow these steps:
- Open the column settings again
- Click on the Labels tab
- Specify the labels for the columns

Choose ‘Save and Close’ to see the results.
Number of Requests Graph
Besides tables you can also use other visualizations to display your query results. A graph that shows the number of requests over time gives a quick impression of the traffic and when errors occur. See the example below.

Start by adding another query to the workbook with ‘Add > Add query’. Select Time as the Time Range and Bar chart as the Visualization.
Add the following query:
let subscriptionFilter = dynamic([{Subscription}]);
let apiFilter = dynamic([{Api}]);
let successFilter = '{Success}';
ApimRequests
| where array_length(subscriptionFilter) == 0 or subscription in (subscriptionFilter)
| where array_length(apiFilter) == 0 or api in (apiFilter)
| where isempty(successFilter) or success == tobool(successFilter)
| make-series numberOfRequests=count() default=0 on timestamp from todatetime("{Time:startISO}") to todatetime("{Time:endISO}") step {Time:grain} by success
The filters are the same as in the table query. The query then uses make-series instead of summarize with bin, because a summarize only returns time slots that have data. With make-series, the graph always shows the entire selected time period, even if there are no requests at the start or end of the period.
The make-series operator is configured as follows:
fromandtouse the{Time:startISO}and{Time:endISO}properties of the Time parameter, so the series covers the selected time range.stepuses{Time:grain}. This way, the granularity is automatically adjusted depending on the time range you’ve selected in the Time parameter.default=0makes sure time slots without requests get the value 0 instead of null.by successsplits the results, so you get a separate series for successful and failed requests.
To configure the color and labels, follow these steps:
- Click on the ‘Chart Settings’ button in the ‘Settings’ tab
- Open the ‘Series Settings’ tab
- Specify a display label and color for each series

Choose ‘Save and Close’ to see the results.
I usually display a graph like this above a table. You can move it up by choosing ‘Move > Move up’.
Time Brushing
The graph supports time brushing, which means you can select a time period in the graph and all data in the workbook will be filtered accordingly.

To configure time brushing, you first enable it on the graph:
- Open the ‘Advanced Settings’ of the graph.
- Check ‘Enable time range brushing’.
- Use
Timeas the parameter name. This is the same name as the time range parameter at the top of the workbook, which all queries use to filter on time. - Check ‘Only export the parameter when a range is brushed’.

Next, edit the Time parameter:
- Go to ‘Advanced Settings’.
- Check ‘Treat this parameter as a global’.

With this configuration, the time range selected in the drop-down is used by default. When you select a different time range in the graph, the Time parameter is updated and all data is filtered using the brushed range instead.
Master-detail Table
Tables and other items provide the option to select data. We can use that selection as a filter in other items.
As an example, we’ll create a master-detail table. When a request in the master table is selected, all dependencies that have the same operation id will be displayed in the details table. See the example below.

Export Selection from Master Table
To start, Edit the current Requests table. Go to Advanced Settings and check the ‘When items are selected, export parameters’ box.
Click on Add parameter. Enter operation_Id as the field to export. Enter SelectedOperationId as the parameter name.

Choose Save. It should look like this.

To reduce the amount of scrolling, I’ve changed the table size from medium to small in the settings.
Choose Done Editing on the editing query item.
Add Detail Table
Choose ‘Add > Add query’ to add another table. Select Time as the Time Range and Grid as Visualization.
Add the following query.
dependencies
| where operation_Id == '{SelectedOperationId}'
| project status = iif(success==true, "success", "error")
, timestamp
, target
, name
, type
, resultCode
, duration
, details = itemId
, transaction = itemId
| order by timestamp desc
This query looks similar to the query for requests. A few columns are different and it only filters on the operation_Id column using the exported parameter of the master table.
You can customize the styling of the columns again, similar to the master table. I also like to add a chart title in which I display the selected value. You can update the chart title in the Advanced Settings with: Dependencies of operation: {SelectedOperationId}.
Show Detail Table on Selection
If you don’t select an item in the master table. You’ll see the message below in place of the details table.

If you don’t like this, you can make the detail table hide when no row is selected in the master table.
Open the Advanced Settings and check the ‘Make this item conditionally visible’. Choose Add Condition. Enter SelectedOperationId as the Parameter name and select ‘is not equal to’ in the Comparison drop down. Leave the Parameter value input empty.

With this the details table is only shown when the SelectedOperationId has a value, which it will have if a row is selected in the master table.
Save Workbook
Once you’re done editing the workbook you can choose ‘Done Editing’. Then click the Save button. Enter a title in the Save As window and select the correct subscription, resource group & location. Choose Apply and the workbook is saved.
Note that parameter selections are also saved. Meaning, if you’re filtering on for example success == no and choose Save, this selection will be saved. Whenever you or another user opens the workbook, this selection will be preselected.
Share Workbook
To share the workbook with different parameters preselected, configure the parameters and click the Share button:
.
Then, in the share workbook screen:
- Specify whether people who open the workbook from the link can edit it or only view it.
- Check the ‘Open with selected parameter values’ checkbox.
- Specify which parameter values should be preselected.
- Copy the link to share.

I use this for parameter configurations that I frequently use. For example all failed API Management requests in the last 24 hours.
Conclusion
As you can see, there’s a lot you can do with workbooks, and we’ve only scratched the surface. See these links for more information.
Here are links to the exported workbook and shared function:
In my blog post Deploy Azure Workbook and App Insights Function I show you how to deploy this workbook using Bicep and the Azure CLI.