Appearance
Are you an LLM? You can read better optimized documentation at /docs/custom-widgets/v2/project-setup.md for this page in Markdown format
Repository Layout
This guide explains how to structure your GitHub repository for publishing extensions — widgets, scripts, and stylesheets. Build any combination of the three; every shape shares the same layout.
Prerequisites
- A GitHub account
- Basic understanding of JSON
Quick Start: Use the Template
The easiest way to get started is to fork the official template repository:
https://github.com/gainsight-hub/widgets-repository-template
- Go to the template repository
- Click Fork (or use as template)
- Clone your forked repository
- Customize the extensions for your needs
Repository Structure
Your repository should follow this structure:
your-repo/
├── extensions_registry.json # Required: Widget, script, and stylesheet definitions
├── connectors_registry.json # Optional: Connector definitions
├── widgets/
│ ├── my_widget/
│ │ └── index.html # Widget content file
│ └── another_widget/
│ └── index.html
├── scripts/
│ └── analytics/
│ └── script.js # Global script files
└── stylesheets/
└── theme/
└── style.css # Global stylesheet files1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Required File: extensions_registry.json
This file must be at the root of your repository. It contains the definitions for all your widgets, scripts, and stylesheets. For the root object, see Registry Reference. For per-type entry fields, see Widget Definition Reference, Script Definition Reference, and Stylesheet Definition Reference.
Migration from `widget_registry.json`
If your repository uses the old widget_registry.json filename, it still works during the migration window. Rename it to extensions_registry.json at your earliest convenience — the old name is deprecated and will be removed in a future release. No other schema changes are required.
json
{
"widgets": [
{
"version": "1.0.0",
"title": "My Widget",
"type": "my_company_my_widget",
"description": "A description of what this widget does",
"category": "custom",
"containers": ["Full width", "Sidebar"],
"widgetsLibrary": true,
"settings": {
"configurable": true,
"editable": true,
"removable": true,
"shared": false,
"movable": true
},
"source": {
"path": "widgets/my_widget",
"entry": "index.html"
}
}
],
"scripts": [
{
"name": "analytics",
"path": "scripts/analytics/script.js",
"description": "Tracks page interactions",
"placement": "head"
}
],
"stylesheets": [
{
"name": "theme",
"path": "stylesheets/theme/style.css",
"description": "Shared design tokens and component styles"
}
]
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
See Script Definition Reference and Stylesheet Definition Reference for complete documentation on those entry types.
Widget Content Files
Each widget's source block points to the widget content in your repository. The source.path specifies the directory (relative to repository root) and source.entry specifies the HTML entry point within that directory.
Supported content types:
- HTML files with optional CSS, JavaScript, and image assets
How content is served:
- The entire
source.pathdirectory is published to the platform - Asset references in the entry HTML file are automatically transformed to hosted URLs
Asset Paths in Widget HTML
Paths in your widget HTML (src, href) are relative to the widget's directory — the directory specified in source.path. The platform publishes everything inside that directory and rewrites relative paths to hosted URLs automatically.
For example, given this registry entry:
json
{
"source": {
"path": "widgets/greeting",
"entry": "index.html"
}
}1
2
3
4
5
6
2
3
4
5
6
And this directory layout:
widgets/greeting/
├── index.html
├── app.js
└── icon.svg1
2
3
4
2
3
4
Your index.html references files relative to its own directory:
html
<script type="module" src="app.js"></script>
<img src="icon.svg" alt="icon" />1
2
2
Use relative paths — they are the intended form and resolve against the widget directory. Leading slashes are stripped during publishing (so /app.js resolves the same as app.js), but relative paths make the intent clearer and match how the files are laid out on disk.
Script and Stylesheet Files
Directory Conventions
Place each script and stylesheet in its own directory for clarity:
scripts/
├── analytics/
│ └── script.js
├── hero-tracking/
│ └── script.js
└── external-lib/
└── script.js
stylesheets/
├── theme/
│ └── style.css
├── components/
│ └── style.css
└── responsive/
└── style.css1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
File Naming
- Scripts:
{name}/script.js(alwaysscript.js) - Stylesheets:
{name}/style.css(alwaysstyle.css) - The directory name becomes the identifier in your registry
External vs Repository-Hosted
Repository-hosted (path is relative):
json
{
"name": "analytics",
"path": "scripts/analytics/script.js"
}1
2
3
4
2
3
4
External (path is a URL):
json
{
"name": "external-analytics",
"path": "https://cdn.example.com/analytics.js"
}1
2
3
4
2
3
4
How It Works
- You push changes to your watched branch
- The platform fetches
extensions_registry.jsonfrom your repository - If
connectors_registry.jsonexists, connectors are validated and synced - For each widget with a
sourceblock, the widget directory is fetched and published - For each script or stylesheet with a relative path, the file is fetched and published
- External URLs for scripts/stylesheets are used as-is
- Widgets, scripts, and stylesheets become available in the No-Code Builder and on community pages
Empty Registries
An empty widgets array is valid:
json
{
"widgets": [],
"scripts": [],
"stylesheets": []
}1
2
3
4
5
2
3
4
5
This will:
- Pass validation successfully
- Show "Completed" build status
- Publish 0 extensions (no content uploaded)
This is useful when you want to:
- Temporarily remove all content without disabling the repository
- Start with a clean slate before adding extensions
- Test that your repository connection works
Removing Extensions
When you remove a definition from the Widget Registry and push to the watched branch, that extension is removed from your Community.
- Widget: Removed if its
typeno longer appears in thewidgetsarray - Script: Removed if its
nameno longer appears in thescriptsarray - Stylesheet: Removed if its
nameno longer appears in thestylesheetsarray
WARNING
Removing extensions is a destructive action. If a widget is already placed on pages in the No-Code Builder, those pages will show a missing widget. Make sure the extension is no longer in use before removing it.
Template Repository Features
The template repository includes:
- Example widgets: Ready-to-use demo widgets
- Example scripts and stylesheets: Sample global assets with default configurations
- Build script:
bin/build-registry.shto generate registry from individual configs - Documentation: Detailed setup guide in
widgets/WIDGET_SETUP.md - Default config: Global settings in
config/defaults.json
Quick Reference
| File/Directory | Purpose |
|---|---|
extensions_registry.json | Registry definitions (required, at repo root) |
widgets/{name}/ | Widget directories |
widgets/{name}/index.html | Widget entry point |
scripts/{name}/ | Script directories |
scripts/{name}/script.js | Script file |
stylesheets/{name}/ | Stylesheet directories |
stylesheets/{name}/style.css | Stylesheet file |
| Configuration Field | Type | Used For |
|---|---|---|
widgets | array | Widget definitions |
scripts | array | Script definitions |
stylesheets | array | Stylesheet definitions |
source.path | string | Widget directory path |
source.entry | string | Widget HTML entry point |
placement | string | Script/stylesheet injection point |
rules | array | Conditional loading for scripts/stylesheets |
Best Practices
- Use the template: Start with the template repo to avoid common mistakes
- Keep organized: One folder per extension under
widgets/,scripts/, orstylesheets/ - Use relative paths: For repository-hosted files, define paths relative to repo root
- Version your widgets: Update the
versionfield when making changes - Validate JSON: Ensure
extensions_registry.jsonis valid JSON before pushing - Test with empty registry first: Verify your connection works before adding extensions
- Name extensions uniquely: Across all connected repositories, extension names must be unique
Troubleshooting
"Widget registry file is invalid"
- Ensure
extensions_registry.jsonis at the repository root - Validate the JSON syntax (use a JSON validator online)
- Check that all required fields are present
"Content file not found"
- Verify that
source.pathandsource.entryare correct for widgets - Verify that script/stylesheet paths are correct
- Paths are relative to the repository root
- Check that the entry file exists in the watched branch
"Script path error" or "Stylesheet path error"
- Ensure
pathends in.js(scripts) or.css(stylesheets) - For repository files, use paths like
scripts/analytics/script.js - For external files, use full URLs starting with
http://orhttps://
"Build failed after push"
- Check if
extensions_registry.jsonpasses schema validation - Ensure all referenced files exist in the watched branch
- Review build error details by hovering over the status icon
- See Common Issues for specific error codes
Next Steps
- Registry Reference — the root of
extensions_registry.json - Widget Definition Reference — widget entry fields
- Script Definition Reference — script entry fields
- Stylesheet Definition Reference — stylesheet entry fields
- Widget Configuration — Let editors customize widgets
- Build & Publish — How publishing works

