• Last edited on: 11 August 2022

Create a Panel

Learn how to create Panel Apps for specific pages in Coupa.

Introduction

Panel Apps allow customers to display data from external sources within a UI panel on a given Coupa page. This data can be context-specific and can be automatically or manually refreshed. For example, when a Supplier page loads in Coupa, a Panel App on that page can automatically get data from an external source via API that pertains to that particular Supplier and display the data in Coupa.

Create a Panel App by going to Setup > Platform > IFrames and Panels and click the Create button and then the option: Panel. The key to creating and configuring a Panel App is through it's Descriptor, a JSON format set of parameters for your App.

Here's an example Panel App on the Coupa Homepage.

Example panel app on the Coupa homepage

You can create a Panel App by going to Setup > Platform > Installed Apps . Click the Create button and select Create New Panel App .

Requirements

Starting in R29, Panel Apps require at least TLS v1.2 to make any outbound connections to 3rd party APIs.

Panel App basics

Panel Apps are built using an app descriptor that uses JSON format. The descriptor consists of two main properties: data and blocks. Data is processed using JQ v1.6 .

Property Description
slot

Specifies the page type where the panel will be rendered. Current options are:

  • user.home (Coupa homepage: / and /user/home )
  • suppliers.show (supplier details page: /suppliers/:id/record )
  • supplier_order_headers.show
  • items.show (item details page: /items/{id} )
  • contracts.show (contract details page: /contracts/show/:id )
  • requisition_headers.edit (Coupa shopping cart: /requisition_headers/{id}/edit )
  • invoices.show (invoice details pages, including editing invoices on the next gen invoicing UI: /invoices/{id} and /invoices/{id}/show_for_approval )
  • receipts.full_receipt (Detailed receipt page: /receipts/{id}/full_receipt )
  • expenses.index (Expenses landing page: /expenses )
  • projects.show (project details page: /projects/{id} )
  • quotes/requests.show (sourcing event details page: /quotes/requests/{id} )
  • asn/headers.show (ASN details page: /asn/headers/{id} )
  • asn/supplier_headers.show
  • budget_lines.show (budget lines page: /budget_lines/{id})
{
	 "slot": "user.home"
 }

Only one slot can be specified per panel app. To have the app appear on multiple pages, create the app again with the desired slot value.

data

The data attribute describes how to fetch the desired data that is rendered in the panel.  A JSON object that describes what data to fetch to render the panel. The values in this object will be URLs that Coupa will send a request to in order to retrieve data. The responses from all of these data requests will be merged into one Hash, aliased under the key name given to them in this object.

"data": {
		"study_queue": "https://www.wanikani.com/api/user/{user_ID}/study-queue",
		"level_progression": "https://www.wanikani.com/api/user/{user_ID}/level-progression"
	}
}
context


Each slot provides contextual detail about the page on which it is displayed. For example a panel app being displayed on a supplier show page will have access to contextual data about that specific supplier. You can use this contextual information in your data objects, but not in blocks . So when a panel app renders on the show page for “ACME Supplier” the API call can be customized based on the supplier’s name, coupa ID, custom field values, and other properties of that supplier.

An API call to the NY Times article search for recent articles about a supplier would look like this:

"data": {
		"nyt_data": {
			"uri": "https://api.nytimes.com/svc/search/v2/articlesearch.json?
			q={context.supplier.name}&api-key={properties.api_key}"
	}
}			

Contact Coupa for an updated list of what contextual data is available for each slot.

Parameterize api calls (properties)

An array of properties that a user/administrator has to furnish after enabling your panel app. If your app/API requires a unique API key, or login/password for each Coupa customer that enables it, here's how you provide that option.

An example block would be:

"properties": {
		"username": {
			"type": "string"
		},
		"password": {
			"type": "string"
		}
	}

You then reference your properties in the data blocks like this:

"data": {
		"my_data": {
			"method": "post",
			"uri": "https://someurl.com/EVToken",
			"body": "grant_type=password&username={{ properties.username }}&
			password={{ properties.password }}"
		}
	}

When a Coupa admin enables a new panel app, they will be prompted to fill in all “properties” fields specified in your app descriptor.

launch

Adds a Launch Button that can be used when a Panel App does not need to be displayed immediately. Rather than the Panel App automatically reaching out to the 3rd party and rendering the data, it only reaches out if the end user manually clicks the Launch Button.

    This JSON object includes description , button_text and help_text . For example:

    "launch": {
    		"description": "PretendCo Tax Calculator Service",
    		"button_text": "Launch Calculator",
    		"help_text": "Tax Calculator integrates with Coupa to help you calculate international 
    		taxes, right in the cart."
    	}

    How to use launch :

    • Use if the data in the Panel App is only relevant for specific business cases within the page
    • Add at the same level as slot , data , etc, but not in blocks
    allow_refresh

    Adds a Refresh Button that can be used when data on the page changes and affects the data rendered in the Panel App. This is handy for your Cart Panel App when a Req line is updated. The user can click Refresh Button to update the data in the Panel App.

    This is boolean option is used to display a refresh button on the Panel App in the app descriptor:

    "allow_refresh": true
    

    How to use allow_ refresh :

    • Use if the data required by the app can be updated by the user and the app needs the new data

    When clicked, the button refreshes the contents on that app. This field should be added on the same level as “slot”, “data”, “launch” etc, but not in blocks. The refresh button is shown at the top of panel application.

    blocks

    The blocks attribute describes how to render data in the panel. It is an array of JSON objects ("blocks") of various types that each describe a separate visualization. Blocks have access to the data that was previously sent and use it to create visualizations.

    error_blocks The error blocks attribute works similarly to the blocks attribute but is used to create error messages when there's a problem getting or rendering the data.

    Fetching Data

    We will allow apps to make up to 5 separate HTTP calls in order to fetch data for the app. These will be specified using the data attribute in the descriptor. API calls should return either JSON (preferred) or XML.

    Panel App block types

    Panel App renders that data on an existing Coupa page using different types of UI elements known as blocks . Coupa supports the following block types:

    • Rich text (including images)
    • Fields (essentially key-value pairs)
    • Number
    • Money
    • Pie chart
    • Line/Bar/Column graph
    • Launch
    • Refresh
    • Row
    • Dynamic Table

    App configuration

    Attribute Description

    type

    Identifies the type of block. For example, fields , text , or bars . This will determine how the block is rendered in the panel on the UI, and also the required format for the data.

    data

    This JSON object will have different properties depending on strategy.

    JQ

    • type must equal "jq"

    • jq is a string that contains a JQ script. The JQ script is fed the merged data structure from the data attribute as its input and must produce a JSON structure that fits the required format for the block type.

    Processing data with JQ

    For example, the following JQ script could be used to extract several specific pieces of data and repackage them in another JSON object:

    {
    	 "username": ".study_queue.user_information.username",
    	 "radicals": ".level_progression.requested_information.radicals_progress",
    	 "kanji": ".level_progression.requested_information.kanji_progress"
    }

    This would result in the following JSON object being produced:

    {
    	 "username": "example_user",
    	 "radicals": 0,
    	 "kanji": 0
    }

    More information:

    other properties

    Different blocks types may have other properties specific to that block type.

    Panel App block types

    Text block

    Attribute Description

    type

    Must equal text

    template

    The text block type uses a Liquid Markdown template. The template is first parsed as Liquid to interpolate the data that's returned from the requests. Then, the template is parsed again as Markdown to generate the HTML.

    Markdown provides a safe method of generating HTML, and for security reasons, Coupa blocks inline HTML in the template. There's no character limit other than the max limit of the data column that stores the block's configuration.

    Markdown allows for headers, inline emphasis formatting, lists, images, links, and blockquotes. Coupa also supports syntax highlighting and tables.

    For detailed info about working with Liquid and Markdown, see Liquid template language and Mastering Markdown .

    data

    The data strategy must produce a single JSON object. All of the keys in the object will be available to the Liquid template as variables.

    {
    	 "foo": "bar",
    	 "fizz": {
    			"buzz": "buzz"
    	 }
    }
    Tip

    Currently a text block is the best/only way to display data in a table using the kramdown table format.

    Fields block

    Property Description

    type

    Must equal field

    data

    The data strategy must produce an array of JSON objects with label and value attributes. When rendering, the label will be used as the field label and the value will be used as the field value.

    [
    	 {
    			"label": "Foobar",
    			"value": "fizzbuzz"
    	 },
    	 {
    			"label": "Lorem Ipsum",
    			"value": 42
    	 }
    ]
    title An optional title that will appear above the list of fields.

    Bar/Line block

    Property Description

    type

    Must equal bar or line

    data

    The data strategy must produce an array of arrays. Unlike the Table Block, the data for bar/line graphs will be column-oriented.

    Each array represents a single series of data that will be displayed. The first element is the name of the series, then the remaining elements are the points in the series.

    [{
    		"name": "Year 1800",
    		"data": [107, 31, 635, 203, 2]
     }, {
    		"name": "Year 1900",
    		"data": [133, 156, 947, 408, 6]
     }, {
    		"name": "Year 2000",
    		"data": [814, 841, 3714, 727, 31]
     }, {
    		"name": "Year 2016",
    		"data": [1216, 1001, 4436, 738, 40]
    }]
    axis Allows configuration of the labels on the x and y axes.
    title An optional title that will appear above the graph.
    description An options description that will appear below the graph in smaller text.

    Pie block

    Property Description

    type

    Must equal pie

    data

    The data strategy must produce a column-oriented array of arrays as described in the Bar/Line Block section.

    [{
    		name: 'Chrome',
    		y: 20.41,
    		sliced: true,
    		selected: true
    	}, {
    		name: 'Internet Explorer',
    		y: 11.84
    	}, {
    		name: 'Firefox',
    		y: 10.85
    	}, {
    		name: 'Edge',
    		y: 4.67
    	}, {
    		name: 'Safari',
    		y: 4.18
    	}, {
    		name: 'Sogou Explorer',
    		y: 1.64
    	}, {
    		name: 'Opera',
    		y: 2.8
    	}, {
    		name: 'Other',
    		y: 2.61
    }]
    title An optional title that will appear above the chart.
    description An options description that will appear below the chart in smaller text.

    Money block

    Property Description

    type

    Must equal money

    data The data strategy must produce a JSON object with the following structure:
    • Amount a JSON Float to determine the amount of currency.
    • Currency a string containing the ISO 4217 currency code of the currency.
    {
    	"amount": 42.0,
    	"currency": "USD"
    }
    Title An optional title that will appear above the number.
    Description An optional description that will appear below the number in smaller text.

    Numbers block

    Property Description

    Type

    Must equal numbers

    Data

    The data strategy must produce a single JSON integer or Float.

    42.0
    Decimal The number of decimals to display after the decimal point. Defaults to 0.
    Title An optional title that will appear above the number.
    Description An optional description that will appear below the number in smaller text.Example Panel Apps

    Custom fields

    Accessing custom fields on an in-context object follows this general format:
    context.<object>.custom_fields.<field_name>

    Where <field_name> is the Field Name prompt when creating a custom field:

    cf_fields.png

    Since we're looking at the Supplier object and have a field, Phone number, you'd reference it like this:
    context.supplier.custom_fields.phone_number

    Slot Custom field accessor
    contracts.show context.contract.custom_fields.<field_name>
    projects.show context.project.custom_fields.<field_name>
    quotes/requests.show context.quote_request.custom_fields.<field_name>
    requisition_headers.edit context.requisition_header.custom_fields.<field_name>
    suppliers.show context.supplier.custom_fields.<field_name>

    Custom fields aren’t applicable for slots that don’t reference a specific object, like:

    • user.home
    • expenses.index

    API details

    Authentication

    Coupa supports the following authentication types:

    Method Example
    Standard API key
    "data": {
    		"nyt_data": {
    			"uri": "https://api.nytimes.com/svc/search/v2/articlesearch.json?q={context.supplier.name}
    						&api-key={properties.api_key}"
    		}
    	}
    Basic Auth
    "data": {
    		"trip_data": {
    			"uri": "https://someurl.net/trips",
    			"headers": {"Authorization":"basic YOUR_BASE64_ENCODED_TOKEN_HERE"}
    		}
    	}
    Request for short lived token using “before_data” request
    "before_data": {
    		"auth": {
    			"method": "post",
    			"uri": "https://someurl.com/MYToken",
    			"body": "grant_type=password&username={{ properties.username }}&password={{ properties.password }}"
    		}
    	},
    	"data": {
    		"risk_data": {
    			"uri": "https://someurl.com/MYData?integration_id=%22ID{context.supplier.id}%22",
    			"headers": {
    				"Authorization": "bearer {{ before_data.auth.access_token }}"
    			}
    		}
    	}

    API URLs

    • URLs must be HTTPS
    • URLs must be accessible by Coupa servers (i.e. a HEAD request must succeed)
    • URLs must successfully parse as valid Liquid templates. This is to enable interpolation

    API request details

    • When interpolating data into the URLs using Liquid, the result must be a valid URL
    • Response code must be 200 or 204
    • Remote server must respond within a timeout period of 5 seconds
    • Response Content-Type must be either application/json or application/xml

    Buttons

    You can configure a Launch Button ( launch )and a Refresh Button ( allow_refresh ) for your panel apps. While the functionality looks and feels like a block, technically buttons are not blocks. You can find details on buttons in the Panel App basics section of this article. for more info.

    Panel App context

    You can build Panel Apps for the following pages:

    • Homepage: / and /user/home
    • Suppliers page: /suppliers/:id/record
    • Contracts page: /contracts/show/:id
    • Cart: /requisition_headers/{id}/edit
    • Projects home page: projects.show
    • Sourcing event Settings page: quotes/requests.show

    Each Panel App type has its own respective context payload. Below are the fields that will be provided in the Panel Apps API payload, which are used as context to match and find data.

    Page Available fields
    All panel apps user_instance (i.e. [domain name].coupahost.com) is included in the payload.
    Homepage id , email , fullname , login , employee_number
    Invoice show/edit

    invoice_id , invoice_date , invoice_number , supplier_name , supplier_number , supplier_id

    Supplier page

    id , name , display_name , duns , number , tax_id , website , status

    custom_fields: object.supplier.custom_field_values_hash

    Contract page id , parent_contract_id , name , number , version , supplier_id , start_date , status
    Cart The Cart Panel App payload will contain the same fields as the Requisition header API payload
    Projects All Project system fields and custom fields
    Sourcing event

    id , event_name , event_commodity , project_id , supplier_id , and any custom fields and tags

    Panel App examples

    New York Times article search

    This app adds the recent news about the supplier from the New York Times on the supplier's page. You'll need to get an NYT API key to make this one work.

    nyt-article-search.png

    This is the code that drives the app.

    {
    	"properties": {
    		"api_key": {
    			"type": "string"
    		}
    	},
    	"slot": "suppliers.show",
    	"before_data": {},
    	"data": {
    		"nyt_data": {
    			"uri": "https://api.nytimes.com/svc/search/v2/articlesearch.json?q={context.supplier.name}&api-key={properties.api_key}"
    		}
    	},
    	"blocks": [
    		{
    			"type": "text",
    			"data": {
    				"type": "jq",
    				"jq": "{ \"docs\": .nyt_data | .response | .docs }"
    			},
    			"text": "![nyt logo](http://www.transervice.com/wp-content/uploads/2018/06/the-new-york-times-4.png){:height=\"100px\"}"
    		},
    		{
    			"type": "text",
    			"data": {
    				"type": "jq",
    				"jq": "{ \"docs\": .nyt_data | .response | .docs }"
    			},
    			"text": "1. [{{ docs[0].headline.main }}]({{ docs[0].web_url }}) \n2. [{{ docs[1].headline.main }}]({{ docs[1].web_url }})\n3. [{{ docs[2].headline.main }}]({{ docs[2].web_url }})\n4. [{{ docs[3].headline.main }}]({{ docs[2].web_url }})\n5. [{{ docs[2].headline.main }}]({{ docs[4].web_url }})\n"
    		}
    	]
    }

    Weather Panel App

    This example app shows the graphical representation of temperature aspects of a location. You'll need to get an API key to make this one work.

    weather-app-example.png

    This is the code that drives the app.

    {
    	"properties": {},
    	"slot": "suppliers.show",
    	"data": {
    		"chicago_data": {
    			"method": "get",
    			"uri": "https://api.openweathermap.org/data/2.5/weather?q=Chicago&appid=155057bc8e3dde82a6482e76bddf9c10&units=imperial"
    		}
    	},
    	"blocks": [
    		{
    			"type": "bar",
    			"title": "Here's the weather in Chicago",
    			"data": {
    				"type": "jq",
    				"jq": "[{name: \"Temp\", data: [.chicago_data.main.temp]}, {name: \"Wind Speed\", data: [.chicago_data.wind.speed]}, {name: \"Humidity\", data: [.chicago_data.main.humidity]}]"
    			},
    			"axes": {
    				"xAxis": {
    					"labels": {
    						"enabled": false
    					}
    				},
    				"yAxis": {
    					"title": {
    						"text": ""
    					},
    					"max": 100
    				}
    			}
    		},
    		{
    			"type": "line",
    			"title": "Here's the weather in Chicago",
    			"data": {
    				"type": "jq",
    				"jq": "[{name: \"Minimum, Current, max temp for Chicago\", data: [[.chicago_data.main.temp_min], [.chicago_data.main.temp], [.chicago_data.main.temp_max]] }, {name: \"Fake data not from API\", data: [44, 52, 130, 87, 74, 88] }]"
    			},
    			"axes": {
    				"xAxis": {
    					"labels": {
    						"enabled": true
    					},
    					"categories": [
    						"Jan",
    						"Feb",
    						"Mar",
    						"Apr",
    						"May",
    						"June"
    					]
    				},
    				"yAxis": {
    					"title": {
    						"text": ""
    					},
    					"max": 200
    				}
    			}
    		}
    	]
    }

    Panel App API example

    Below is the App descriptor used for an automated test. We stub out the API response to return exactly this:

    [	
    	{	
    		"data_title":"Title",
    		"data_number":42,
    		"second_score":88
    	}
    ]
     
    {
    	# Here is where you'd setup client specific fields. Like login/password. The customer would be prompted to fill in
    	# those values once, when activating the application
    	"properties": {},
    	# This is the page where the app gets displayed. See the "slot" property for possible values.	
    	"slot": "suppliers.show",	 
    	# Some APIs require a bearer token, or a 2-step process, this is where you handle that.
    	"before_data": {}, 
    	"data": {
    		"coupa_data": {
    			# You can add context specific values to this API call, {{context.contract.supplier_name}} for example
    			"uri": "http://fake.test", 
    			# This works intuitively, just specify your API headers here, Bearer token, params, etc.
    			"headers": {} 
    		}
    	},
    	"blocks": [
    		{
    			"type": "number",
    			# This is hard-coded title for this block, cant use api data, or contextual data here	
    			"title": "Title for number block",
    			# "coupa_data" was the API response above, .[0] returns the stuff in the {}, ".data_number" gets the value, 
    			# in this case "42"
    			"data": {
    				"type": "jq",
    				"jq": ".coupa_data | .[0] | .data_number"	
    			}
    		}, 
    			# So this block renders a specially formatted number block, with the number "42" in large bold font, 
    			# and a text label/title of "Title for number block"
    		{
    			# Fields block has some of the strictest requirements for data, It displays all data in key -> value pairs
    			"type": "fields",
    			"data": {
    				"type": "jq",
    				"jq": ".coupa_data | .[] | to_entries | map({ label: .key, value: .value })" 
    			}
    		},
    		{
    			# For this block, we are just going to pull the hash out from the [] and use the values inside it
    			"type": "text",
    			"data": {
    				"type": "jq",
    				"jq": " .coupa_data | .[0]"
    			},
    			# Text blocks gives you a lot of flexibility for displaying data from the API response in context.
    			# The text can be formatted just like markup, so you can create links, tables, render/resize images, etc.	 
    			"text": "{{ data_number }}	is the data_number" 
    		},
    		{
    			# Setting up the bar graph is pretty tricky, maybe work with us if you want to go this route.
    			# But this example creates a bar graph with values/labels bar sizes based on the values in the API response
    			"type": "bar",
    			"title": "Bar graph with fake data",
    			"data": {
    				"type": "jq",
    				"jq": "[{name: \"Overall Score\", data: [.coupa_data | .[0].data_number]}, 
    								{name: \"Environment score\", data: [.coupa_data | .[0].second_score]}]" 
    			},
    			"axes": {"xAxis": {"labels": { "enabled": false}}, "yAxis": {"title": { "text": ""}, "max": 100} }
    		}
    	]
    }

    Test in the partner's instance

    To test this in Coupa, you will need to be on version R25.0 or higher.  In the end, your App will be a part of the App Directory and not a custom Panel App, but using these steps you can test on your own before Coupa officially adds the App to the App Directory.

    1. Go to Setup
    2. Under Platform, click 'Installed Apps'
    3. Click the 'Create' button, then select 'Create New Panel App'
    4. Create the App
    5. Add a name
    6. In the descriptor section, include the code listed above

    Once complete, if no errors a Panel App will be displayed on each Supplier page.

    Download an app from the Coupa App Marketplace

    Visit the Coupa App Marketplace to download a Panel and follow the installation instructions provided. For more information about viewing installed apps, see Coupa's App Marketplace

    Related Items


    Create an IFrame

    Create an IFrame to embed read-only third-party data into your Coupa instance.

    Platform Plus

    The Coupa Platform comes in two different variants: Platform Standard and Platform Plus. Platform Plus contains all the functionality of Standard, plus some unique capabilities that help customers maximize value from Coupa.

    Create a Process Using Process Automator

    From the Processes page, you can view, edit, and delete existing processes, or create a new process.

    Create a Process to Export Real-Time Data Updates

    You can use Process Automator with Call Outs to send data to third-party systems in real time. You can also process the Call Out response and set field values based on response variables.