Skip to content

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

  1. Go to the template repository
  2. Click Fork (or use as template)
  3. Clone your forked repository
  4. 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 files

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"
    }
  ]
}

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.path directory 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"
  }
}

And this directory layout:

widgets/greeting/
├── index.html
├── app.js
└── icon.svg

Your index.html references files relative to its own directory:

html
<script type="module" src="app.js"></script>
<img src="icon.svg" alt="icon" />

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.css

File Naming

  • Scripts: {name}/script.js (always script.js)
  • Stylesheets: {name}/style.css (always style.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"
}

External (path is a URL):

json
{
  "name": "external-analytics",
  "path": "https://cdn.example.com/analytics.js"
}

How It Works

  1. You push changes to your watched branch
  2. The platform fetches extensions_registry.json from your repository
  3. If connectors_registry.json exists, connectors are validated and synced
  4. For each widget with a source block, the widget directory is fetched and published
  5. For each script or stylesheet with a relative path, the file is fetched and published
  6. External URLs for scripts/stylesheets are used as-is
  7. 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": []
}

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 type no longer appears in the widgets array
  • Script: Removed if its name no longer appears in the scripts array
  • Stylesheet: Removed if its name no longer appears in the stylesheets array

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.sh to 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/DirectoryPurpose
extensions_registry.jsonRegistry definitions (required, at repo root)
widgets/{name}/Widget directories
widgets/{name}/index.htmlWidget entry point
scripts/{name}/Script directories
scripts/{name}/script.jsScript file
stylesheets/{name}/Stylesheet directories
stylesheets/{name}/style.cssStylesheet file
Configuration FieldTypeUsed For
widgetsarrayWidget definitions
scriptsarrayScript definitions
stylesheetsarrayStylesheet definitions
source.pathstringWidget directory path
source.entrystringWidget HTML entry point
placementstringScript/stylesheet injection point
rulesarrayConditional loading for scripts/stylesheets

Best Practices

  1. Use the template: Start with the template repo to avoid common mistakes
  2. Keep organized: One folder per extension under widgets/, scripts/, or stylesheets/
  3. Use relative paths: For repository-hosted files, define paths relative to repo root
  4. Version your widgets: Update the version field when making changes
  5. Validate JSON: Ensure extensions_registry.json is valid JSON before pushing
  6. Test with empty registry first: Verify your connection works before adding extensions
  7. Name extensions uniquely: Across all connected repositories, extension names must be unique

Troubleshooting

"Widget registry file is invalid"

  • Ensure extensions_registry.json is 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.path and source.entry are 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 path ends 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:// or https://

"Build failed after push"

  • Check if extensions_registry.json passes 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

Gainsight CC Developer Portal