Appearance
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
- A connected GitHub organization — see Connect Your GitHub Account
- The Developer Studio CLI (
gsds) installed — see Get Started with the CLI - Basic understanding of JSON
Quick Start: Create a Project with the CLI
The quickest way to get the right layout is to let the CLI create it:
Create the project.
gsds initwrites the registries,gsds.json, and thewidgets/,scripts/, andstylesheets/directories, and makes a first Git commit onmain:shgsds init acme-widgets cd acme-widgets1
2Create an empty repository on GitHub — without a README, license, or
.gitignore— and push the project to it:shgit remote add origin git@github.com:your-org/acme-widgets.git git push -u origin main1
2Enable the repository in Integrations → Developer Studio → Sources, with
mainas the watched branch — see Repository & Branch Settings. If your GitHub App installation is limited to selected repositories, grant it access to the new repository first, from your GitHub organization's Settings > GitHub Apps page.
Then add extensions with gsds create (widgets), gsds script new (scripts), and gsds style new (stylesheets). Each command creates the files and adds the entry to extensions_registry.json for you. Run gsds create with no flags and it prompts for the widget name, framework (React, Vue, Angular, vanilla JavaScript, or plain HTML), and category, so you pick the technology your widget needs. See the Command Reference.
If you already have a widget repository, run gsds init . from its root instead — it adds only the files that are missing and never overwrites existing ones.
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
├── gsds.json # Created by gsds init: marks the project root for the CLI
├── widgets/
│ ├── my_widget/ # Widget with a build step (for example React or vanilla JavaScript)
│ │ ├── package.json
│ │ ├── src/ # Widget source files
│ │ ├── thumbnail.png
│ │ └── dist/ # Built by gsds build (source.path: widgets/my_widget/dist)
│ │ ├── index.html # Widget content file
│ │ └── widget.js
│ └── another_widget/ # Plain HTML widget: no build step
│ ├── thumbnail.png
│ └── dist/
│ └── content.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
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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": [ ... ],
"scripts": [ ... ],
"stylesheets": [ ... ]
}1
2
3
4
5
2
3
4
5
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 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.
Best Practices
- Scaffold with the CLI: Create the project with
gsds initand each extension withgsds create,gsds script new, orgsds style newto avoid common mistakes - Build before you push: Run
gsds buildbefore each commit. It rebuilds each widget'sdist/directory — the files the platform publishes — and validates both registries - 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 - 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
- Configurable Widgets — Let editors customize widgets
- Build & Publish — How publishing works

