Skip to content
  • There are no suggestions because the search field is empty.

Custom layout: Advanced functionality explained

A detailed guide to advanced functionality, including custom variables and audience targeting, available in custom layouts

If you’re ready to go beyond the core templating features of Welcome, this article covers the advanced tools available to custom layout developers. You’ll learn how to use dynamic metadata from SharePoint, set up audience targeting, hook into layout lifecycle events, and safely apply scoped styling using .SCOPE.

This guide is ideal for those building polished, flexible, and dynamic layouts for the Welcome app.

Jump to


Settings and custom managed metadata fields

The <settings> section allows you to define custom settings for your layout, including custom managed metadata fields that will be loaded for dynamic content.

Basic structure:

<settings>
<content>
<fields>
<field name="FieldName1" />
<field name="FieldName2" />
</fields>
</content>
</settings>

Custom managed metadata fields

Custom managed metadata fields allow you to load additional metadata from SharePoint lists or libraries where your content is stored. These fields should be mapped to a tenant-scoped managed property.

We recommend reading the following Microsoft resources before proceeding:

How to define custom fields

  1. Add a <field> element inside the <fields> section
  2. Set the name attribute to the internal name of the SharePoint managed property

Example:

<settings>
<content>
<fields>
<field name="ShowInWelcome" />
<field name="Category" />
<field name="Priority" />
</fields>
</content>
</settings>

Accessing custom fields in layouts

Custom fields can be accessed in the layout using the content[i].custom object:

{{#each content}}
{{#if custom.ShowInWelcome}}
<div class="item">
<h3>{{title}}</h3>
{{#if custom.Category}}
<span class="category">Category: {{custom.Category}}</span>
{{/if}}
{{#if custom.Priority}}
<span class="priority priority-{{custom.Priority}}">{{custom.Priority}}</span>
{{/if}}
</div>
{{/if}}
{{/each}}

Note: custom managed metadata fields are only supported for dynamic content (news and pages) loaded from SharePoint. They are not supported for static content defined directly in the web part settings.

Common use cases

  • Filtering content: Show only items with specific metadata values
  • Styling content: Apply different styles based on metadata
  • Categorizing content: Group or organize content by metadata values
  • Adding badges or tags: Display additional visual indicators based on metadata

Advanced example

{{#each content}}
{{#if custom.ShowInWelcome}}
<div class="card {{#if custom.IsHighlighted}}highlighted{{/if}}">
<h3>{{title}}</h3>

{{#if custom.Categories}}
<div class="categories">
{{#each custom.Categories}}
<span class="tag">{{this.label}}</span>
{{/each}}
</div>
{{/if}}

{{#if custom.Expires}}
<div class="expires-on">Expires: {{formatDate custom.Expires "MM/DD/YYYY"}}</div>
{{/if}}

{{#if custom.Owner}}
<div class="owner">
<img src="{{custom.Owner.pictureUrl}}" alt="{{custom.Owner.displayName}}" />
<span>{{custom.Owner.displayName}}</span>
</div>
{{/if}}
</div>
{{/if}}
{{/each}}

Custom variables

Custom variables let you define configurable settings for your layouts. They are grouped by feature, and you can access them in your templates like this:

Path Description Example Access
custom.greeting.* Greeting feature variables {{custom.greeting.hide}}
custom.time.* Time feature variables {{custom.time.hide}}
custom.weather.* Weather feature variables {{custom.weather.hide}}
custom.search.* Search feature variables {{custom.search.hide}}
custom.content.* Content feature variables {{custom.content.heading}}, {{custom.content.cycle}}
custom.background.* Background feature variables {{custom.background.title}}

Defining custom variables

Custom variables make your layouts more flexible by letting site owners configure behavior in the property panel without editing code. They’re defined in the <variables> section of your metadata and accessed in your layout with Handlebars.

Supported types
  • string: Text input (e.g. headings, labels)
  • number: Numeric values (e.g. opacity, item count)
  • boolean: True/false toggles (e.g. show/hide)
  • choice: Dropdown list of options (e.g. theme, style)
Defining variables

Add a <variable> tag inside <variables> in your metadata:

<template id="metadata" data-name="Customizable Layout">
  <variables>
    <variable feature="content" name="sectionTitle" type="string"
              label="Section title" default="Latest News" />
    <variable feature="content" name="showIcons" type="boolean"
              label="Show icons" default="true" />
    <variable feature="content" kind="slider" name="opacity" type="number"
              default="50" min="0" max="100" label="Overlay opacity" />
    <variable feature="content" name="theme" type="choice"
              label="Theme" default="blue">
      <choice value="blue" label="Blue" />
      <choice value="green" label="Green" />
      <choice value="red" label="Red" />
    </variable>
  </variables>
</template>
Variable attributes
  • feature: Feature this variable belongs to (content, greeting, background, etc.)
  • name: Variable name (letters only, no spaces)
  • type: One of: string, number, boolean, choice
  • label: Friendly name shown in the property panel
  • default: Default value

Optional: scope (global/slide), min/max (numbers), showWhen (conditional display).

Scope
  • Global (default): applies to the entire feature.
  • Slide: applies to individual slides when used with the content feature.
<variable feature="content" scope="slide" name="backgroundColor" type="choice"
         label="Slide background color" default="white">
  <choice value="white" label="White" />
  <choice value="dark" label="Dark" />
</variable>
Using variables in layouts

Reference values using Handlebars:

<h2>{{custom.content.sectionTitle}}</h2>

{{#if custom.content.showIcons}}
  <div class="icons">...</div>
{{/if}}

<div style="opacity: {{custom.content.opacity}}%">
  <!-- content -->
</div>
Best practices
  • Use descriptive names
  • Provide sensible defaults
  • Write clear labels for end users
  • Group related variables under the same feature
  • Test different combinations
Localization

You can add labels in multiple languages:

<variable feature="content" name="cycle" type="boolean" default="false"
         label="Auto-cycle content"
         label-fr-fr="Faire défiler automatiquement le contenu"
         label-de-de="Inhalt automatisch durchlaufen"
         label-es-es="Ciclar contenido automáticamente" />

Audience targeting

To specify which audiences to load, you must list them in a settings metadata section:

<settings>
    <content>
        <fields>
            <field name="WelcomeNewsCategory" />
        </fields>
    </content>
    <!--
                    List any audiences ids you want to use in your template. These ids then can be used with {{#audience "<guid>"}} helper.
                -->
    <audiences>
        <audience id="54698842-c8c0-4c95-9832-b88a52065ab3" />
    </audiences>
</settings>

 

You can conditionally render content based on audience targeting using Handlebars helpers:

{{#audience "00000000-0000-0000-0000-000000000000"}}
<div>Content for audience 1</div>
{{else}}
{{#audience "11111111-1111-1111-1111-111111111111"}}
<div>Content for audience 2</div>
{{else}}
<div>Default content</div>
{{/audience}}
{{/audience}}

You can also use variables for dynamic audience targeting:

{{#audience custom.content.secret}}
<div>This content is only visible to the specified audience</div>
{{/audience}}

Lifecycle hooks

Three JavaScript functions can be defined to hook into the layout's lifecycle:

1. onInitialize()

Called when the layout is initialized

function onInitialize() {
console.log('Layout initialized');
// Set up resources, event listeners, etc.

var webPart = ctx.getWebPart(); // get web part context

// Example: Make a SharePoint REST API call
webPart.spHttpClient.get(
webPart.pageContext.web.absoluteUrl + '/_api/web?$select=Title',
webPart.spHttpClient.constructor.configurations.v1,
{ headers: { 'Accept': 'application/json' } }
)
.then(function(response) {
return response.json();
})
.then(function(data) {
console.log('Web Title:', data.Title);
})
.catch(function(error) {
console.error('Error:', error);
});
}

2. onError(error)

Called when an error occurs

function onError(error) {
console.error('Layout error:', error);
// Handle errors
}

3. onDispose()

Called when the layout is disposed

function onDispose() {
console.log('Layout disposed');
// Clean up resources, remove event listeners, etc.
clearInterval(interval); // example: clean up interval
}

JavaScript context

A global ctx object is available in your scripts to interact with the Welcome app:

// Get a custom variable value
var hideTime = ctx.getVariable('custom.time.hide');

// Access the web part context
var webPart = ctx.getWebPart();

// Make SharePoint API calls
webPart.spHttpClient.get(
webPart.pageContext.web.absoluteUrl + '/_api/web',
webPart.spHttpClient.constructor.configurations.v1,
{ headers: { 'Accept': 'application/json' } }
)
.then(response => response.json())
.then(data => console.log('Web Title:', data.Title))
.catch(error => console.error('Error:', error));

Understanding the .SCOPE pseudo-class

The .SCOPE pseudo-class is a special selector in Welcome app layouts that gets replaced at runtime with the actual scope of the web part instance (e.g., #acc365-Welcome-12345). This provides several benefits:

  • CSS Isolation: Ensures your styles don't affect other elements on the page
  • CSS Variables: Define CSS variables that are scoped only to your web part
  • External Targeting: Safely target elements outside your layout when necessary
  • Prevent Collisions: Avoid CSS class name collisions with other components

When the layout is rendered, .SCOPE is replaced with the actual web part container selector, turning:

.SCOPE { --primary-color: #0078d4; }

into something like:

#acc365-Welcome-12345 { --primary-color: #0078d4; }

This allows you to safely define styles that won't interfere with other elements on the page, even if they use the same class names.


⬅️Back: Custom layout: Core functionality explained